Merge pull request #3192 from ethereum/dev

release for jan testnets
This commit is contained in:
Danny Ryan 2023-01-06 15:16:21 -05:00 committed by GitHub
commit 5f1b88f6fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 716 additions and 534 deletions

133
.github/workflows/run-tests.yml vendored Normal file
View File

@ -0,0 +1,133 @@
name: Run spec tests and linter
defaults:
run:
shell: zsh {0}
env:
TEST_PRESET_TYPE: "minimal"
DEFAULT_BRANCH: "dev"
# Run tests on workflow_Dispatch
on:
push:
branches:
- dev
- master
pull_request:
workflow_dispatch:
inputs:
test_preset_type:
default: minimal
description: Type of test to run, either mainnet or minimal
type: string
required: true
commitRef:
description: The branch, tag or SHA to checkout and build from
default: dev
required: true
schedule:
- cron: '0 0 * * *'
jobs:
precleanup:
runs-on: self-hosted
if: always()
steps:
- name: 'Cleanup build folder'
run: |
ls -la ./
rm -rf ./* || true
rm -rf ./.??* || true
ls -la ./
setup-env:
runs-on: self-hosted
needs: precleanup
steps:
- name: Checkout this repo
uses: actions/checkout@v3.2.0
with:
ref: ${{ github.event.inputs.commitRef || env.DEFAULT_BRANCH }}
- uses: actions/cache@v3.2.2
id: cache-git
with:
path: ./*
key: ${{ github.sha }}
table_of_contents:
runs-on: self-hosted
needs: setup-env
steps:
- uses: actions/cache@v3.2.2
id: restore-build
with:
path: ./*
key: ${{ github.sha }}
- name: Check table of contents
run: sudo npm install -g doctoc@2 && make check_toc
codespell:
runs-on: self-hosted
needs: setup-env
steps:
- name: Check codespell
run: pip install 'codespell<3.0.0,>=2.0.0' --user && make codespell
lint:
runs-on: self-hosted
needs: setup-env
steps:
- name: Run linter for pyspec
run: make lint
- name: Run linter for test generators
run: make lint_generators
pyspec-tests:
runs-on: self-hosted
needs: [setup-env,lint,codespell,table_of_contents]
strategy:
matrix:
version: ["phase0", "altair", "bellatrix", "capella", "eip4844"]
steps:
- uses: actions/cache@v3.2.2
id: restore-build
with:
path: ./*
key: ${{ github.sha }}
- name: set TEST_PRESET_TYPE
if: github.event.inputs.test_preset_type != ''
run: |
echo "spec_test_preset_type=${{ github.event.inputs.test_preset_type || env.TEST_PRESET_TYPE }}" >> $GITHUB_ENV
- name: set TEST_PRESET_TYPE
if: ${{ (github.event_name == 'push' && github.ref_name != 'master') || github.event_name == 'pull_request' }}
run: |
echo "spec_test_preset_type=${{ env.TEST_PRESET_TYPE}}" >> $GITHUB_ENV
- name: set TEST_PRESET_TYPE
if: ${{ github.event_name == 'push' && github.ref_name == 'master' }}
run: |
echo "spec_test_preset_type=mainnet" >> $GITHUB_ENV
- name: set TEST_PRESET_TYPE
if: github.event.schedule=='0 0 * * *'
run: |
echo "spec_test_preset_type=mainnet" >> $GITHUB_ENV
- name: Install pyspec requirements
run: make install_test
- name: test-${{ matrix.version }}
run: make citest fork=${{ matrix.version }} TEST_PRESET_TYPE=${{env.spec_test_preset_type}}
- uses: actions/upload-artifact@v3
if: always()
with:
name: test-${{ matrix.version }}
path: tests/core/pyspec/test-reports
cleanup:
runs-on: self-hosted
needs: [setup-env,pyspec-tests,codespell,lint,table_of_contents]
if: always()
steps:
- name: 'Cleanup build folder'
run: |
ls -la ./
rm -rf ./* || true
rm -rf ./.??* || true
ls -la ./

View File

@ -13,7 +13,7 @@ SOLIDITY_DEPOSIT_CONTRACT_SOURCE = ${SOLIDITY_DEPOSIT_CONTRACT_DIR}/deposit_cont
SOLIDITY_FILE_NAME = deposit_contract.json
DEPOSIT_CONTRACT_TESTER_DIR = ${SOLIDITY_DEPOSIT_CONTRACT_DIR}/web3_tester
CONFIGS_DIR = ./configs
TEST_PRESET_TYPE ?= minimal
# Collect a list of generator names
GENERATORS = $(sort $(dir $(wildcard $(GENERATOR_DIR)/*/.)))
# Map this list of generator paths to "gen_{generator name}" entries
@ -96,30 +96,30 @@ generate_tests: $(GENERATOR_TARGETS)
# "make pyspec" to create the pyspec for all phases.
pyspec:
. venv/bin/activate; python3 setup.py pyspecdev
python3 -m venv venv; . 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
# Testing against `minimal` or `mainnet` 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=eth2spec.bellatrix.minimal --cov=eth2spec.capella.minimal --cov=eth2spec.eip4844.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
python3 -m pytest -n 4 --disable-bls --cov=eth2spec.phase0.$(TEST_PRESET_TYPE) --cov=eth2spec.altair.$(TEST_PRESET_TYPE) --cov=eth2spec.bellatrix.$(TEST_PRESET_TYPE) --cov=eth2spec.capella.$(TEST_PRESET_TYPE) --cov=eth2spec.eip4844.$(TEST_PRESET_TYPE) --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
# Testing against `minimal` config by default
# Testing against `minimal` or `mainnet` config by default
find_test: pyspec
. venv/bin/activate; cd $(PY_SPEC_DIR); \
python3 -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov=eth2spec.bellatrix.minimal --cov=eth2spec.capella.minimal --cov=eth2spec.eip4844.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
python3 -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.$(TEST_PRESET_TYPE) --cov=eth2spec.altair.$(TEST_PRESET_TYPE) --cov=eth2spec.bellatrix.$(TEST_PRESET_TYPE) --cov=eth2spec.capella.$(TEST_PRESET_TYPE) --cov=eth2spec.eip4844.$(TEST_PRESET_TYPE) --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
citest: pyspec
mkdir -p $(TEST_REPORT_DIR);
ifdef fork
. venv/bin/activate; cd $(PY_SPEC_DIR); \
python3 -m pytest -n 4 --bls-type=milagro --fork=$(fork) --junitxml=test-reports/test_results.xml eth2spec
python3 -m pytest -n 16 --bls-type=milagro --preset=$(TEST_PRESET_TYPE) --fork=$(fork) --junitxml=test-reports/test_results.xml eth2spec
else
. venv/bin/activate; cd $(PY_SPEC_DIR); \
python3 -m pytest -n 4 --bls-type=milagro --junitxml=test-reports/test_results.xml eth2spec
python3 -m pytest -n 16 --bls-type=milagro --preset=$(TEST_PRESET_TYPE) --junitxml=test-reports/test_results.xml eth2spec
endif
@ -135,7 +135,7 @@ check_toc: $(MARKDOWN_FILES:=.toc)
rm $*.tmp
codespell:
codespell . --skip ./.git -I .codespell-whitelist
codespell . --skip "./.git,./venv,$(PY_SPEC_DIR)/.mypy_cache" -I .codespell-whitelist
# TODO: add future protocol upgrade patch packages to linting.
# NOTE: we use `pylint` just for catching unused arguments in spec code

View File

@ -25,7 +25,7 @@ Features are researched and developed in parallel, and then consolidated into se
| Code Name or Topic | Specs | Notes |
| - | - | - |
| Capella (tentative) | <ul><li>Core</li><ul><li>[Beacon chain changes](specs/capella/beacon-chain.md)</li><li>[Capella fork](specs/capella/fork.md)</li></ul><li>Additions</li><ul><li>[Validator additions](specs/capella/validator.md)</li><li>[P2P networking](specs/capella/p2p-interface.md)</li></ul></ul> |
| EIP4844 (tentative) | <ul><li>Core</li><ul><li>[Beacon Chain changes](specs/eip4844/beacon-chain.md)</li><li>[EIP-4844 fork](specs/eip4844/fork.md)</li><li>[Polynomial commitments](specs/eip4844/polynomial-commitments.md)</li></ul><li>Additions</li><ul><li>[Honest validator guide changes](specs/eip4844/validator.md)</li><li>[P2P networking](specs/eip4844/p2p-interface.md)</li></ul></ul> |
| EIP4844 (tentative) | <ul><li>Core</li><ul><li>[Beacon Chain changes](specs/eip4844/beacon-chain.md)</li><li>[EIP-4844 fork](specs/eip4844/fork.md)</li><li>[Polynomial commitments](specs/eip4844/polynomial-commitments.md)</li><li>[Fork choice changes](specs/eip4844/fork-choice.md)</li></ul><li>Additions</li><ul><li>[Honest validator guide changes](specs/eip4844/validator.md)</li><li>[P2P networking](specs/eip4844/p2p-interface.md)</li></ul></ul> |
| Sharding (outdated) | <ul><li>Core</li><ul><li>[Beacon Chain changes](specs/sharding/beacon-chain.md)</li></ul><li>Additions</li><ul><li>[P2P networking](specs/sharding/p2p-interface.md)</li></ul></ul> |
| Custody Game (outdated) | <ul><li>Core</li><ul><li>[Beacon Chain changes](specs/custody_game/beacon-chain.md)</li></ul><li>Additions</li><ul><li>[Honest validator guide changes](specs/custody_game/validator.md)</li></ul></ul> | Dependent on sharding |
| Data Availability Sampling (outdated) | <ul><li>Core</li><ul><li>[Core types and functions](specs/das/das-core.md)</li><li>[Fork choice changes](specs/das/fork-choice.md)</li></ul><li>Additions</li><ul><li>[P2P Networking](specs/das/p2p-interface.md)</li><li>[Sampling process](specs/das/sampling.md)</li></ul></ul> | <ul><li> Dependent on sharding</li><li>[Technical explainer](https://hackmd.io/@HWeNw8hNRimMm2m2GH56Cw/B1YJPGkpD)</li></ul> |

View File

@ -638,34 +638,6 @@ T = TypeVar('T') # For generic function
@classmethod
def sundry_functions(cls) -> str:
return super().sundry_functions() + '\n\n' + '''
#
# Temporarily disable Withdrawals functions for EIP4844 testnets
#
def no_op(fn): # type: ignore
# pylint: disable=unused-argument
def wrapper(*args, **kw): # type: ignore
return None
return wrapper
def get_empty_list_result(fn): # type: ignore
# pylint: disable=unused-argument
def wrapper(*args, **kw): # type: ignore
return []
return wrapper
process_withdrawals = no_op(process_withdrawals)
process_bls_to_execution_change = no_op(process_bls_to_execution_change)
get_expected_withdrawals = get_empty_list_result(get_expected_withdrawals)
#
# End
#
def retrieve_blobs_sidecar(slot: Slot, beacon_block_root: Root) -> PyUnion[BlobsSidecar, str]:
# pylint: disable=unused-argument
return "TEST"'''
@ -1020,6 +992,7 @@ class PySpecCommand(Command):
self.md_doc_paths += """
specs/eip4844/beacon-chain.md
specs/eip4844/fork.md
specs/eip4844/fork-choice.md
specs/eip4844/polynomial-commitments.md
specs/eip4844/p2p-interface.md
specs/eip4844/validator.md

View File

@ -11,6 +11,7 @@
- [Introduction](#introduction)
- [Helper functions](#helper-functions)
- [`compute_merkle_proof_for_state`](#compute_merkle_proof_for_state)
- [`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)
@ -34,6 +35,19 @@ def compute_merkle_proof_for_state(state: BeaconState,
...
```
### `block_to_light_client_header`
```python
def block_to_light_client_header(block: SignedBeaconBlock) -> BeaconBlockHeader:
return 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.
@ -55,13 +69,7 @@ def create_light_client_bootstrap(state: BeaconState,
assert hash_tree_root(header) == hash_tree_root(block.message)
return LightClientBootstrap(
header=BeaconBlockHeader(
slot=state.latest_block_header.slot,
proposer_index=state.latest_block_header.proposer_index,
parent_root=state.latest_block_header.parent_root,
state_root=hash_tree_root(state),
body_root=state.latest_block_header.body_root,
),
header=block_to_light_client_header(block),
current_sync_committee=state.current_sync_committee,
current_sync_committee_branch=compute_merkle_proof_for_state(state, CURRENT_SYNC_COMMITTEE_INDEX),
)
@ -103,42 +111,30 @@ def create_light_client_update(state: BeaconState,
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:
next_sync_committee = attested_state.next_sync_committee
next_sync_committee_branch = compute_merkle_proof_for_state(attested_state, NEXT_SYNC_COMMITTEE_INDEX)
else:
next_sync_committee = SyncCommittee()
next_sync_committee_branch = [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))]
update.next_sync_committee = attested_state.next_sync_committee
update.next_sync_committee_branch = compute_merkle_proof_for_state(
attested_state, NEXT_SYNC_COMMITTEE_INDEX)
# Indicate finality whenever possible
if finalized_block is not None:
if finalized_block.message.slot != GENESIS_SLOT:
finalized_header = BeaconBlockHeader(
slot=finalized_block.message.slot,
proposer_index=finalized_block.message.proposer_index,
parent_root=finalized_block.message.parent_root,
state_root=finalized_block.message.state_root,
body_root=hash_tree_root(finalized_block.message.body),
)
assert hash_tree_root(finalized_header) == attested_state.finalized_checkpoint.root
update.finalized_header = block_to_light_client_header(finalized_block)
assert hash_tree_root(update.finalized_header) == attested_state.finalized_checkpoint.root
else:
assert attested_state.finalized_checkpoint.root == Bytes32()
finalized_header = BeaconBlockHeader()
finality_branch = compute_merkle_proof_for_state(attested_state, FINALIZED_ROOT_INDEX)
else:
finalized_header = BeaconBlockHeader()
finality_branch = [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))]
update.finality_branch = compute_merkle_proof_for_state(
attested_state, FINALIZED_ROOT_INDEX)
return LightClientUpdate(
attested_header=attested_header,
next_sync_committee=next_sync_committee,
next_sync_committee_branch=next_sync_committee_branch,
finalized_header=finalized_header,
finality_branch=finality_branch,
sync_aggregate=block.message.body.sync_aggregate,
signature_slot=block.message.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.

View File

@ -18,6 +18,7 @@
- [`Withdrawal`](#withdrawal)
- [`BLSToExecutionChange`](#blstoexecutionchange)
- [`SignedBLSToExecutionChange`](#signedblstoexecutionchange)
- [`HistoricalSummary`](#historicalsummary)
- [Extended Containers](#extended-containers)
- [`ExecutionPayload`](#executionpayload)
- [`ExecutionPayloadHeader`](#executionpayloadheader)
@ -29,6 +30,8 @@
- [`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)
@ -50,6 +53,11 @@ to validator withdrawals. Including:
* 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:
@ -115,6 +123,18 @@ class SignedBLSToExecutionChange(Container):
signature: BLSSignature
```
#### `HistoricalSummary`
```python
class HistoricalSummary(Container):
"""
`HistoricalSummary` matches the components of the phase0 `HistoricalBatch`
making the two hash_tree_root-compatible.
"""
block_summary_root: Root
state_summary_root: Root
```
### Extended Containers
#### `ExecutionPayload`
@ -196,7 +216,7 @@ class BeaconState(Container):
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]
historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] # Frozen in Capella, replaced by historical_summaries
# Eth1
eth1_data: Eth1Data
eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]
@ -226,6 +246,8 @@ class BeaconState(Container):
# Withdrawals
next_withdrawal_index: WithdrawalIndex # [New in Capella]
next_withdrawal_validator_index: ValidatorIndex # [New in Capella]
# Deep history valid from Capella onwards
historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] # [New in Capella]
```
## Helpers
@ -270,6 +292,40 @@ def is_partially_withdrawable_validator(validator: Validator, balance: Gwei) ->
## Beacon chain state transition function
### Epoch processing
*Note*: The function `process_historical_summaries_update` replaces `process_historical_roots_update` in Bellatrix.
```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

View File

@ -22,8 +22,6 @@
- [`ExecutionPayloadHeader`](#executionpayloadheader)
- [Helper functions](#helper-functions)
- [Misc](#misc)
- [`validate_blobs_sidecar`](#validate_blobs_sidecar)
- [`is_data_available`](#is_data_available)
- [`kzg_commitment_to_versioned_hash`](#kzg_commitment_to_versioned_hash)
- [`tx_peek_blob_versioned_hashes`](#tx_peek_blob_versioned_hashes)
- [`verify_kzg_commitments_against_transactions`](#verify_kzg_commitments_against_transactions)
@ -33,7 +31,6 @@
- [`process_execution_payload`](#process_execution_payload)
- [Blob KZG commitments](#blob-kzg-commitments)
- [Testing](#testing)
- [Disabling Withdrawals](#disabling-withdrawals)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
@ -146,45 +143,6 @@ class ExecutionPayloadHeader(Container):
### Misc
#### `validate_blobs_sidecar`
```python
def validate_blobs_sidecar(slot: Slot,
beacon_block_root: Root,
expected_kzg_commitments: Sequence[KZGCommitment],
blobs_sidecar: BlobsSidecar) -> None:
assert slot == blobs_sidecar.beacon_block_slot
assert beacon_block_root == blobs_sidecar.beacon_block_root
blobs = blobs_sidecar.blobs
kzg_aggregated_proof = blobs_sidecar.kzg_aggregated_proof
assert len(expected_kzg_commitments) == len(blobs)
assert verify_aggregate_kzg_proof(blobs, expected_kzg_commitments, kzg_aggregated_proof)
```
#### `is_data_available`
The implementation of `is_data_available` is meant to change with later sharding upgrades.
Initially, it requires every verifying actor to retrieve the matching `BlobsSidecar`,
and validate the sidecar with `validate_blobs_sidecar`.
The block MUST NOT be considered valid until a valid `BlobsSidecar` has been downloaded.
```python
def is_data_available(slot: Slot, beacon_block_root: Root, blob_kzg_commitments: Sequence[KZGCommitment]) -> bool:
# `retrieve_blobs_sidecar` is implementation dependent, raises an exception if not available.
sidecar = retrieve_blobs_sidecar(slot, beacon_block_root)
# For testing, `retrieve_blobs_sidecar` returns "TEST".
# TODO: Remove it once we have a way to inject `BlobsSidecar` into tests.
if isinstance(sidecar, str):
return True
validate_blobs_sidecar(slot, beacon_block_root, blob_kzg_commitments, sidecar)
return True
```
#### `kzg_commitment_to_versioned_hash`
```python
@ -240,9 +198,6 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_operations(state, block.body)
process_sync_aggregate(state, block.body.sync_aggregate)
process_blob_kzg_commitments(state, block.body) # [New in EIP-4844]
# New in EIP-4844
assert is_data_available(block.slot, hash_tree_root(block), block.body.blob_kzg_commitments)
```
#### Execution payload
@ -345,11 +300,3 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32,
return state
```
### Disabling Withdrawals
During testing we avoid Capella-specific updates to the state transition. We do this by replacing the following functions with a no-op implementation:
- `process_withdrawals`
- `process_bls_to_execution_change`
The `get_expected_withdrawals` function is also modified to return an empty withdrawals list. As such, the `PayloadAttributes` used to update forkchoice does not contain withdrawals.

View File

@ -0,0 +1,137 @@
# EIP-4844 -- Fork Choice
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Containers](#containers)
- [`BlobsSidecar`](#blobssidecar)
- [Helpers](#helpers)
- [`validate_blobs_sidecar`](#validate_blobs_sidecar)
- [`is_data_available`](#is_data_available)
- [Updated fork-choice handlers](#updated-fork-choice-handlers)
- [`on_block`](#on_block)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This is the modification of the fork choice accompanying the EIP-4844 upgrade.
## Containers
### `BlobsSidecar`
```python
class BlobsSidecar(Container):
beacon_block_root: Root
beacon_block_slot: Slot
blobs: List[Blob, MAX_BLOBS_PER_BLOCK]
kzg_aggregated_proof: KZGProof
```
## Helpers
#### `validate_blobs_sidecar`
```python
def validate_blobs_sidecar(slot: Slot,
beacon_block_root: Root,
expected_kzg_commitments: Sequence[KZGCommitment],
blobs_sidecar: BlobsSidecar) -> None:
assert slot == blobs_sidecar.beacon_block_slot
assert beacon_block_root == blobs_sidecar.beacon_block_root
blobs = blobs_sidecar.blobs
kzg_aggregated_proof = blobs_sidecar.kzg_aggregated_proof
assert len(expected_kzg_commitments) == len(blobs)
assert verify_aggregate_kzg_proof(blobs, expected_kzg_commitments, kzg_aggregated_proof)
```
#### `is_data_available`
The implementation of `is_data_available` will become more sophisticated during later scaling upgrades.
Initially, verification requires every verifying actor to retrieve the matching `BlobsSidecar`,
and validate the sidecar with `validate_blobs_sidecar`.
The block MUST NOT be considered valid until a valid `BlobsSidecar` has been downloaded. Blocks that have been previously validated as available SHOULD be considered available even if the associated `BlobsSidecar` has subsequently been pruned.
```python
def is_data_available(slot: Slot, beacon_block_root: Root, blob_kzg_commitments: Sequence[KZGCommitment]) -> bool:
# `retrieve_blobs_sidecar` is implementation and context dependent, raises an exception if not available.
# Note: the p2p network does not guarantee sidecar retrieval outside of `MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS`
sidecar = retrieve_blobs_sidecar(slot, beacon_block_root)
# For testing, `retrieve_blobs_sidecar` returns "TEST".
# TODO: Remove it once we have a way to inject `BlobsSidecar` into tests.
if isinstance(sidecar, str):
return True
validate_blobs_sidecar(slot, beacon_block_root, blob_kzg_commitments, sidecar)
return True
```
## 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.
"""
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
assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root
# [New in EIP-4844]
# Check if blob data is available
# If not, this block MAY be queued and subsequently considered when blob data becomes available
assert is_data_available(block.slot, hash_tree_root(block), block.body.blob_kzg_commitments)
# Check the block is valid and compute the post-state
state = pre_state.copy()
state_transition(state, signed_block, True)
# Check the merge transition
if is_merge_transition_block(pre_state, block.body):
validate_merge_block(block)
# 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
# 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
if get_current_slot(store) == block.slot and is_before_attesting_interval:
store.proposer_boost_root = hash_tree_root(block)
# 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
store.justified_checkpoint = state.current_justified_checkpoint
```

View File

@ -12,7 +12,6 @@ The specification of these changes continues in the same format as the network s
- [Configuration](#configuration)
- [Containers](#containers)
- [`BlobsSidecar`](#blobssidecar)
- [`SignedBeaconBlockAndBlobsSidecar`](#signedbeaconblockandblobssidecar)
- [The gossip domain: gossipsub](#the-gossip-domain-gossipsub)
- [Topics and messages](#topics-and-messages)
@ -41,16 +40,6 @@ The specification of these changes continues in the same format as the network s
## Containers
### `BlobsSidecar`
```python
class BlobsSidecar(Container):
beacon_block_root: Root
beacon_block_slot: Slot
blobs: List[Blob, MAX_BLOBS_PER_BLOCK]
kzg_aggregated_proof: KZGProof
```
### `SignedBeaconBlockAndBlobsSidecar`
```python
@ -141,8 +130,8 @@ Per `context = compute_fork_digest(fork_version, genesis_validators_root)`:
**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/`
After `EIP4844_FORK_EPOCH`, `BeaconBlocksByRootV2` is replaced by `BeaconBlockAndBlobsSidecarByRootV1`
clients MUST support requesting blocks by root for pre-fork-epoch blocks.
After `EIP4844_FORK_EPOCH`, `BeaconBlocksByRootV2` is replaced by `BeaconBlockAndBlobsSidecarByRootV1`.
Clients MUST support requesting blocks by root for pre-fork-epoch blocks.
Per `context = compute_fork_digest(fork_version, genesis_validators_root)`:
@ -186,7 +175,7 @@ No more than `MAX_REQUEST_BLOCKS` may be requested at a time.
The response MUST consist of zero or more `response_chunk`.
Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlockAndBlobsSidecar` payload.
Clients MUST support requesting blocks and sidecars since the latest finalized epoch.
Clients MUST support requesting blocks and sidecars since `minimum_request_epoch`, where `minimum_request_epoch = max(finalized_epoch, current_epoch - MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS, EIP4844_FORK_EPOCH)`. If any root in the request content references a block earlier than `minimum_request_epoch`, peers SHOULD respond with error code `3: ResourceUnavailable`.
Clients MUST respond with at least one block and sidecar, if they have it.
Clients MAY limit the number of blocks and sidecars in the response.
@ -219,7 +208,7 @@ may not be available beyond the initial distribution via gossip.
Before consuming the next response chunk, the response reader SHOULD verify the blobs sidecar is well-formatted and
correct w.r.t. the expected KZG commitments through `validate_blobs_sidecar`.
`BlobsSidecarsByRange` is primarily used to sync blobs that may have been missed on gossip.
`BlobsSidecarsByRange` is primarily used to sync blobs that may have been missed on gossip and to sync within the `MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS` window.
The request MUST be encoded as an SSZ-container.
@ -227,7 +216,7 @@ The response MUST consist of zero or more `response_chunk`.
Each _successful_ `response_chunk` MUST contain a single `BlobsSidecar` payload.
Clients MUST keep a record of signed blobs sidecars seen on the epoch range
`[max(GENESIS_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS), current_epoch]`
`[max(current_epoch - MIN_EPOCHS_FOR_BLOBS_SIDECARS_REQUESTS, EIP4844_FORK_EPOCH), current_epoch]`
where `current_epoch` is defined by the current wall-clock time,
and clients MUST support serving requests of blocks on this range.

View File

@ -370,8 +370,9 @@ def verify_kzg_proof_impl(polynomial_kzg: KZGCommitment,
```python
def compute_kzg_proof(polynomial: Polynomial, z: BLSFieldElement) -> KZGProof:
"""
Compute KZG proof at point `z` with `polynomial` being in evaluation form
Do this by computing the quotient polynomial in evaluation form: q(x) = (p(x) - p(z)) / (x - z)
Compute KZG proof at point `z` with `polynomial` being in evaluation form.
Do this by computing the quotient polynomial in evaluation form: q(x) = (p(x) - p(z)) / (x - z).
Public method.
"""
y = evaluate_polynomial_in_evaluation_form(polynomial, z)
polynomial_shifted = [BLSFieldElement((int(p) - int(y)) % BLS_MODULUS) for p in polynomial]

View File

@ -1 +1 @@
1.3.0-alpha.2
1.3.0-rc.0

View File

@ -10,8 +10,8 @@ from eth2spec.test.context import (
@spec_state_test
def test_current_sync_committee_merkle_proof(spec, state):
yield "object", state
current_sync_committee_branch = \
spec.compute_merkle_proof_for_state(state, spec.CURRENT_SYNC_COMMITTEE_INDEX)
current_sync_committee_branch = spec.compute_merkle_proof_for_state(
state, spec.CURRENT_SYNC_COMMITTEE_INDEX)
yield "proof", {
"leaf": "0x" + state.current_sync_committee.hash_tree_root().hex(),
"leaf_index": spec.CURRENT_SYNC_COMMITTEE_INDEX,
@ -31,8 +31,8 @@ def test_current_sync_committee_merkle_proof(spec, state):
@spec_state_test
def test_next_sync_committee_merkle_proof(spec, state):
yield "object", state
next_sync_committee_branch = \
spec.compute_merkle_proof_for_state(state, spec.NEXT_SYNC_COMMITTEE_INDEX)
next_sync_committee_branch = spec.compute_merkle_proof_for_state(
state, spec.NEXT_SYNC_COMMITTEE_INDEX)
yield "proof", {
"leaf": "0x" + state.next_sync_committee.hash_tree_root().hex(),
"leaf_index": spec.NEXT_SYNC_COMMITTEE_INDEX,
@ -52,8 +52,8 @@ def test_next_sync_committee_merkle_proof(spec, state):
@spec_state_test
def test_finality_root_merkle_proof(spec, state):
yield "object", state
finality_branch = \
spec.compute_merkle_proof_for_state(state, spec.FINALIZED_ROOT_INDEX)
finality_branch = spec.compute_merkle_proof_for_state(
state, spec.FINALIZED_ROOT_INDEX)
yield "proof", {
"leaf": "0x" + state.finalized_checkpoint.root.hex(),
"leaf_index": spec.FINALIZED_ROOT_INDEX,

View File

@ -9,45 +9,23 @@ from eth2spec.test.helpers.attestations import (
)
from eth2spec.test.helpers.constants import MINIMAL
from eth2spec.test.helpers.light_client import (
get_sync_aggregate,
signed_block_to_header,
create_update,
)
from eth2spec.test.helpers.state import (
next_slots,
)
from math import floor
def create_update(spec, test, with_next, with_finality, participation_rate):
def create_test_update(spec, test, with_next, with_finality, participation_rate):
attested_state, attested_block, finalized_block = test
num_participants = floor(spec.SYNC_COMMITTEE_SIZE * participation_rate)
attested_header = signed_block_to_header(spec, attested_block)
if with_next:
next_sync_committee = attested_state.next_sync_committee
next_sync_committee_branch = spec.compute_merkle_proof_for_state(attested_state, spec.NEXT_SYNC_COMMITTEE_INDEX)
else:
next_sync_committee = spec.SyncCommittee()
next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))]
if with_finality:
finalized_header = signed_block_to_header(spec, finalized_block)
finality_branch = spec.compute_merkle_proof_for_state(attested_state, spec.FINALIZED_ROOT_INDEX)
else:
finalized_header = spec.BeaconBlockHeader()
finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))]
sync_aggregate, signature_slot = get_sync_aggregate(spec, attested_state, num_participants)
return spec.LightClientUpdate(
attested_header=attested_header,
next_sync_committee=next_sync_committee,
next_sync_committee_branch=next_sync_committee_branch,
finalized_header=finalized_header,
finality_branch=finality_branch,
sync_aggregate=sync_aggregate,
signature_slot=signature_slot,
return create_update(
spec,
attested_state,
attested_block,
finalized_block,
with_next,
with_finality,
participation_rate,
)
@ -84,76 +62,76 @@ def test_update_ranking(spec, state):
# Create updates (in descending order of quality)
updates = [
# Updates with sync committee finality
create_update(spec, fin, with_next=1, with_finality=1, participation_rate=1.0),
create_update(spec, lat, with_next=1, with_finality=1, participation_rate=1.0),
create_update(spec, fin, with_next=1, with_finality=1, participation_rate=0.8),
create_update(spec, lat, with_next=1, with_finality=1, participation_rate=0.8),
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_update(spec, att, with_next=1, with_finality=1, participation_rate=1.0),
create_update(spec, att, with_next=1, with_finality=1, participation_rate=0.8),
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_update(spec, att, with_next=1, with_finality=0, participation_rate=1.0),
create_update(spec, fin, with_next=1, with_finality=0, participation_rate=1.0),
create_update(spec, lat, with_next=1, with_finality=0, participation_rate=1.0),
create_update(spec, att, with_next=1, with_finality=0, participation_rate=0.8),
create_update(spec, fin, with_next=1, with_finality=0, participation_rate=0.8),
create_update(spec, lat, with_next=1, with_finality=0, participation_rate=0.8),
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_update(spec, sig, with_next=0, with_finality=1, participation_rate=1.0),
create_update(spec, fin, with_next=0, with_finality=1, participation_rate=1.0),
create_update(spec, lat, with_next=0, with_finality=1, participation_rate=1.0),
create_update(spec, sig, with_next=0, with_finality=1, participation_rate=0.8),
create_update(spec, fin, with_next=0, with_finality=1, participation_rate=0.8),
create_update(spec, lat, with_next=0, with_finality=1, participation_rate=0.8),
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_update(spec, att, with_next=0, with_finality=1, participation_rate=1.0),
create_update(spec, att, with_next=0, with_finality=1, participation_rate=0.8),
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_update(spec, sig, with_next=0, with_finality=0, participation_rate=1.0),
create_update(spec, att, with_next=0, with_finality=0, participation_rate=1.0),
create_update(spec, fin, with_next=0, with_finality=0, participation_rate=1.0),
create_update(spec, lat, with_next=0, with_finality=0, participation_rate=1.0),
create_update(spec, sig, with_next=0, with_finality=0, participation_rate=0.8),
create_update(spec, att, with_next=0, with_finality=0, participation_rate=0.8),
create_update(spec, fin, with_next=0, with_finality=0, participation_rate=0.8),
create_update(spec, lat, with_next=0, with_finality=0, participation_rate=0.8),
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_update(spec, fin, with_next=1, with_finality=1, participation_rate=0.4),
create_update(spec, lat, with_next=1, with_finality=1, participation_rate=0.4),
create_update(spec, att, with_next=1, with_finality=1, participation_rate=0.4),
create_update(spec, att, with_next=1, with_finality=0, participation_rate=0.4),
create_update(spec, fin, with_next=1, with_finality=0, participation_rate=0.4),
create_update(spec, lat, with_next=1, with_finality=0, participation_rate=0.4),
create_update(spec, sig, with_next=0, with_finality=1, participation_rate=0.4),
create_update(spec, fin, with_next=0, with_finality=1, participation_rate=0.4),
create_update(spec, lat, with_next=0, with_finality=1, participation_rate=0.4),
create_update(spec, att, with_next=0, with_finality=1, participation_rate=0.4),
create_update(spec, sig, with_next=0, with_finality=0, participation_rate=0.4),
create_update(spec, att, with_next=0, with_finality=0, participation_rate=0.4),
create_update(spec, fin, with_next=0, with_finality=0, participation_rate=0.4),
create_update(spec, lat, with_next=0, with_finality=0, participation_rate=0.4),
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_update(spec, fin, with_next=1, with_finality=1, participation_rate=0.2),
create_update(spec, lat, with_next=1, with_finality=1, participation_rate=0.2),
create_update(spec, att, with_next=1, with_finality=1, participation_rate=0.2),
create_update(spec, att, with_next=1, with_finality=0, participation_rate=0.2),
create_update(spec, fin, with_next=1, with_finality=0, participation_rate=0.2),
create_update(spec, lat, with_next=1, with_finality=0, participation_rate=0.2),
create_update(spec, sig, with_next=0, with_finality=1, participation_rate=0.2),
create_update(spec, fin, with_next=0, with_finality=1, participation_rate=0.2),
create_update(spec, lat, with_next=0, with_finality=1, participation_rate=0.2),
create_update(spec, att, with_next=0, with_finality=1, participation_rate=0.2),
create_update(spec, sig, with_next=0, with_finality=0, participation_rate=0.2),
create_update(spec, att, with_next=0, with_finality=0, participation_rate=0.2),
create_update(spec, fin, with_next=0, with_finality=0, participation_rate=0.2),
create_update(spec, lat, with_next=0, with_finality=0, participation_rate=0.2),
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),
]
yield "updates", updates

View File

@ -11,43 +11,44 @@ from eth2spec.test.helpers.attestations import (
)
from eth2spec.test.helpers.constants import MINIMAL
from eth2spec.test.helpers.light_client import (
get_sync_aggregate,
initialize_light_client_store,
signed_block_to_header,
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_altair_and_later
@spec_state_test_with_matching_config
def test_process_light_client_update_not_timeout(spec, state):
store = initialize_light_client_store(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)
attested_header = signed_block_to_header(spec, attested_block)
# Sync committee signing the attested_header
sync_aggregate, signature_slot = get_sync_aggregate(spec, state)
next_sync_committee = spec.SyncCommittee()
next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))]
signature_slot = state.slot + 1
# Ensure that finality checkpoint is genesis
assert state.finalized_checkpoint.epoch == 0
# Finality is unchanged
finalized_header = spec.BeaconBlockHeader()
finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))]
update = spec.LightClientUpdate(
attested_header=attested_header,
next_sync_committee=next_sync_committee,
next_sync_committee_branch=next_sync_committee_branch,
finalized_header=finalized_header,
finality_branch=finality_branch,
sync_aggregate=sync_aggregate,
signature_slot=signature_slot,
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)
@ -64,7 +65,7 @@ def test_process_light_client_update_not_timeout(spec, state):
@spec_state_test_with_matching_config
@with_presets([MINIMAL], reason="too slow")
def test_process_light_client_update_at_period_boundary(spec, state):
store = initialize_light_client_store(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)
@ -73,25 +74,16 @@ def test_process_light_client_update_at_period_boundary(spec, state):
assert store_period == update_period
attested_block = state_transition_with_full_block(spec, state, False, False)
attested_header = signed_block_to_header(spec, attested_block)
signature_slot = state.slot + 1
# Sync committee signing the attested_header
sync_aggregate, signature_slot = get_sync_aggregate(spec, state)
next_sync_committee = spec.SyncCommittee()
next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))]
# Finality is unchanged
finalized_header = spec.BeaconBlockHeader()
finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))]
update = spec.LightClientUpdate(
attested_header=attested_header,
next_sync_committee=next_sync_committee,
next_sync_committee_branch=next_sync_committee_branch,
finalized_header=finalized_header,
finality_branch=finality_branch,
sync_aggregate=sync_aggregate,
signature_slot=signature_slot,
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)
@ -108,7 +100,7 @@ def test_process_light_client_update_at_period_boundary(spec, state):
@spec_state_test_with_matching_config
@with_presets([MINIMAL], reason="too slow")
def test_process_light_client_update_timeout(spec, state):
store = initialize_light_client_store(spec, state)
genesis_block, store = setup_test(spec, state)
# Forward to next sync committee period
next_slots(spec, state, spec.UPDATE_TIMEOUT)
@ -117,26 +109,16 @@ def test_process_light_client_update_timeout(spec, state):
assert store_period + 1 == update_period
attested_block = state_transition_with_full_block(spec, state, False, False)
attested_header = signed_block_to_header(spec, attested_block)
signature_slot = state.slot + 1
# Sync committee signing the attested_header
sync_aggregate, signature_slot = get_sync_aggregate(spec, state)
# Sync committee is updated
next_sync_committee = state.next_sync_committee
next_sync_committee_branch = spec.compute_merkle_proof_for_state(state, spec.NEXT_SYNC_COMMITTEE_INDEX)
# Finality is unchanged
finalized_header = spec.BeaconBlockHeader()
finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))]
update = spec.LightClientUpdate(
attested_header=attested_header,
next_sync_committee=next_sync_committee,
next_sync_committee_branch=next_sync_committee_branch,
finalized_header=finalized_header,
finality_branch=finality_branch,
sync_aggregate=sync_aggregate,
signature_slot=signature_slot,
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)
@ -153,7 +135,7 @@ def test_process_light_client_update_timeout(spec, state):
@spec_state_test_with_matching_config
@with_presets([MINIMAL], reason="too slow")
def test_process_light_client_update_finality_updated(spec, state):
store = initialize_light_client_store(spec, state)
_, store = setup_test(spec, state)
# Change finality
blocks = []
@ -169,28 +151,21 @@ def test_process_light_client_update_finality_updated(spec, state):
assert store_period == update_period
attested_block = blocks[-1]
attested_header = signed_block_to_header(spec, attested_block)
signature_slot = state.slot + 1
# Sync committee signing the attested_header
sync_aggregate, signature_slot = get_sync_aggregate(spec, state)
# Updated sync_committee and finality
next_sync_committee = spec.SyncCommittee()
next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))]
# Updated finality
finalized_block = blocks[spec.SLOTS_PER_EPOCH - 1]
finalized_header = signed_block_to_header(spec, finalized_block)
assert finalized_header.slot == spec.compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
assert finalized_header.hash_tree_root() == state.finalized_checkpoint.root
finality_branch = spec.compute_merkle_proof_for_state(state, spec.FINALIZED_ROOT_INDEX)
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 = spec.LightClientUpdate(
attested_header=attested_header,
next_sync_committee=next_sync_committee,
next_sync_committee_branch=next_sync_committee_branch,
finalized_header=finalized_header,
finality_branch=finality_branch,
sync_aggregate=sync_aggregate,
signature_slot=signature_slot,
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)

View File

@ -64,6 +64,7 @@ def test_from_syncing_to_invalid(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)
block_hashes[f'chain_a_{i}'] = block.body.execution_payload.block_hash
@ -80,6 +81,7 @@ def test_from_syncing_to_invalid(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)
block_hashes[f'chain_b_{i}'] = block.body.execution_payload.block_hash
@ -92,9 +94,13 @@ def test_from_syncing_to_invalid(spec, state):
# 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)
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,

View File

@ -1,8 +1,7 @@
from eth2spec.test.helpers.constants import CAPELLA
from eth2spec.test.helpers.keys import pubkeys
from eth2spec.test.helpers.bls_to_execution_changes import get_signed_address_change
from eth2spec.test.context import spec_state_test, expect_assertion_error, with_phases, always_bls
from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later, always_bls
def run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=True):
@ -38,14 +37,14 @@ def run_bls_to_execution_change_processing(spec, state, signed_address_change, v
yield 'post', state
@with_phases([CAPELLA])
@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_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_not_activated(spec, state):
validator_index = 3
@ -63,7 +62,7 @@ def test_success_not_activated(spec, state):
assert not spec.is_fully_withdrawable_validator(validator, balance, spec.get_current_epoch(state))
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_in_activation_queue(spec, state):
validator_index = 3
@ -81,7 +80,7 @@ def test_success_in_activation_queue(spec, state):
assert not spec.is_fully_withdrawable_validator(validator, balance, spec.get_current_epoch(state))
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_in_exit_queue(spec, state):
validator_index = 3
@ -94,7 +93,7 @@ def test_success_in_exit_queue(spec, state):
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_exited(spec, state):
validator_index = 4
@ -111,7 +110,7 @@ def test_success_exited(spec, state):
assert not spec.is_fully_withdrawable_validator(validator, balance, spec.get_current_epoch(state))
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_withdrawable(spec, state):
validator_index = 4
@ -129,7 +128,7 @@ def test_success_withdrawable(spec, state):
assert spec.is_fully_withdrawable_validator(validator, balance, spec.get_current_epoch(state))
@with_phases([CAPELLA])
@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
@ -138,7 +137,7 @@ def test_invalid_val_index_out_of_range(spec, state):
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_already_0x01(spec, state):
# Create for one validator beyond the validator list length
@ -150,7 +149,7 @@ def test_invalid_already_0x01(spec, state):
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False)
@with_phases([CAPELLA])
@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
@ -164,7 +163,7 @@ def test_invalid_incorrect_from_bls_pubkey(spec, state):
yield from run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
@always_bls
def test_invalid_bad_signature(spec, state):

View File

@ -1,8 +1,7 @@
from eth2spec.test.context import (
spec_state_test,
with_phases,
with_capella_and_later,
)
from eth2spec.test.helpers.constants import CAPELLA
from eth2spec.test.helpers.state import next_epoch_via_block
from eth2spec.test.helpers.deposits import (
prepare_state_and_deposit,
@ -11,7 +10,7 @@ from eth2spec.test.helpers.deposits import (
from eth2spec.test.helpers.withdrawals import set_validator_fully_withdrawable
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_top_up_to_withdrawn_validator(spec, state):
validator_index = 0

View File

@ -4,9 +4,9 @@ from eth2spec.test.context import (
spec_state_test,
expect_assertion_error,
with_presets,
with_phases,
with_capella_and_later,
)
from eth2spec.test.helpers.constants import MAINNET, MINIMAL, CAPELLA
from eth2spec.test.helpers.constants import MAINNET, MINIMAL
from eth2spec.test.helpers.execution_payload import (
build_empty_execution_payload,
compute_el_block_hash,
@ -97,7 +97,7 @@ def run_withdrawals_processing(spec, state, execution_payload, num_expected_with
return expected_withdrawals
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_zero_expected_withdrawals(spec, state):
assert len(spec.get_expected_withdrawals(state)) == 0
@ -108,7 +108,7 @@ def test_success_zero_expected_withdrawals(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_one_full_withdrawal(spec, state):
fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals(
@ -125,7 +125,7 @@ def test_success_one_full_withdrawal(spec, state):
partial_withdrawals_indices=partial_withdrawals_indices)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_one_partial_withdrawal(spec, state):
fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals(
@ -145,7 +145,7 @@ def test_success_one_partial_withdrawal(spec, state):
)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_max_per_slot(spec, state):
num_full_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 2
@ -163,7 +163,7 @@ def test_success_max_per_slot(spec, state):
partial_withdrawals_indices=partial_withdrawals_indices)
@with_phases([CAPELLA])
@with_capella_and_later
@with_presets([MAINNET], reason="too few validators with minimal config")
@spec_state_test
def test_success_all_fully_withdrawable_in_one_sweep(spec, state):
@ -182,7 +182,7 @@ def test_success_all_fully_withdrawable_in_one_sweep(spec, state):
partial_withdrawals_indices=partial_withdrawals_indices)
@with_phases([CAPELLA])
@with_capella_and_later
@with_presets([MINIMAL], reason="too many validators with mainnet config")
@spec_state_test
def test_success_all_fully_withdrawable(spec, state):
@ -201,7 +201,7 @@ def test_success_all_fully_withdrawable(spec, state):
partial_withdrawals_indices=partial_withdrawals_indices)
@with_phases([CAPELLA])
@with_capella_and_later
@with_presets([MAINNET], reason="too few validators with minimal config")
@spec_state_test
def test_success_all_partially_withdrawable_in_one_sweep(spec, state):
@ -220,7 +220,7 @@ def test_success_all_partially_withdrawable_in_one_sweep(spec, state):
partial_withdrawals_indices=partial_withdrawals_indices)
@with_phases([CAPELLA])
@with_capella_and_later
@with_presets([MINIMAL], reason="too many validators with mainnet config")
@spec_state_test
def test_success_all_partially_withdrawable(spec, state):
@ -243,7 +243,7 @@ def test_success_all_partially_withdrawable(spec, state):
# Failure cases in which the number of withdrawals in the execution_payload is incorrect
#
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_non_withdrawable_non_empty_withdrawals(spec, state):
next_slot(spec, state)
@ -260,7 +260,7 @@ def test_invalid_non_withdrawable_non_empty_withdrawals(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_one_expected_full_withdrawal_and_none_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=1)
@ -273,7 +273,7 @@ def test_invalid_one_expected_full_withdrawal_and_none_in_withdrawals(spec, stat
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_one_expected_partial_withdrawal_and_none_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=1)
@ -286,7 +286,7 @@ def test_invalid_one_expected_partial_withdrawal_and_none_in_withdrawals(spec, s
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_one_expected_full_withdrawal_and_duplicate_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=2)
@ -299,7 +299,7 @@ def test_invalid_one_expected_full_withdrawal_and_duplicate_in_withdrawals(spec,
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_two_expected_partial_withdrawal_and_duplicate_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=2)
@ -312,7 +312,7 @@ def test_invalid_two_expected_partial_withdrawal_and_duplicate_in_withdrawals(sp
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_max_per_slot_full_withdrawals_and_one_less_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD)
@ -325,7 +325,7 @@ def test_invalid_max_per_slot_full_withdrawals_and_one_less_in_withdrawals(spec,
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_max_per_slot_partial_withdrawals_and_one_less_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD)
@ -338,7 +338,7 @@ def test_invalid_max_per_slot_partial_withdrawals_and_one_less_in_withdrawals(sp
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_a_lot_fully_withdrawable_too_few_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
@ -351,7 +351,7 @@ def test_invalid_a_lot_fully_withdrawable_too_few_in_withdrawals(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_a_lot_partially_withdrawable_too_few_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
@ -364,7 +364,7 @@ def test_invalid_a_lot_partially_withdrawable_too_few_in_withdrawals(spec, state
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_a_lot_mixed_withdrawable_in_queue_too_few_in_withdrawals(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD,
@ -382,7 +382,7 @@ def test_invalid_a_lot_mixed_withdrawable_in_queue_too_few_in_withdrawals(spec,
# Failure cases in which the withdrawals in the execution_payload are incorrect
#
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_incorrect_withdrawal_index(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=1)
@ -395,7 +395,7 @@ def test_invalid_incorrect_withdrawal_index(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_incorrect_address_full(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=1)
@ -408,7 +408,7 @@ def test_invalid_incorrect_address_full(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_incorrect_address_partial(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=1)
@ -421,7 +421,7 @@ def test_invalid_incorrect_address_partial(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_incorrect_amount_full(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=1)
@ -434,7 +434,7 @@ def test_invalid_incorrect_amount_full(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_incorrect_amount_partial(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=1)
@ -447,7 +447,7 @@ def test_invalid_incorrect_amount_partial(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_one_of_many_incorrectly_full(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
@ -466,7 +466,7 @@ def test_invalid_one_of_many_incorrectly_full(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_one_of_many_incorrectly_partial(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
@ -485,7 +485,7 @@ def test_invalid_one_of_many_incorrectly_partial(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_many_incorrectly_full(spec, state):
prepare_expected_withdrawals(spec, state, num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
@ -504,7 +504,7 @@ def test_invalid_many_incorrectly_full(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, valid=False)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_many_incorrectly_partial(spec, state):
prepare_expected_withdrawals(spec, state, num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4)
@ -527,7 +527,7 @@ def test_invalid_many_incorrectly_partial(spec, state):
# More full withdrawal cases
#
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_withdrawable_epoch_but_0_balance(spec, state):
current_epoch = spec.get_current_epoch(state)
@ -541,7 +541,7 @@ def test_withdrawable_epoch_but_0_balance(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_withdrawable_epoch_but_0_effective_balance_0_balance(spec, state):
current_epoch = spec.get_current_epoch(state)
@ -555,7 +555,7 @@ def test_withdrawable_epoch_but_0_effective_balance_0_balance(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_withdrawable_epoch_but_0_effective_balance_nonzero_balance(spec, state):
current_epoch = spec.get_current_epoch(state)
@ -569,7 +569,7 @@ def test_withdrawable_epoch_but_0_effective_balance_nonzero_balance(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=1)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_no_withdrawals_but_some_next_epoch(spec, state):
current_epoch = spec.get_current_epoch(state)
@ -583,7 +583,7 @@ def test_no_withdrawals_but_some_next_epoch(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_all_withdrawal(spec, state):
# Make all validators withdrawable
@ -619,25 +619,25 @@ def run_random_full_withdrawals_test(spec, state, rng):
yield from run_withdrawals_processing(spec, state, execution_payload)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_random_full_withdrawals_0(spec, state):
yield from run_random_full_withdrawals_test(spec, state, random.Random(444))
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_random_full_withdrawals_1(spec, state):
yield from run_random_full_withdrawals_test(spec, state, random.Random(420))
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_random_full_withdrawals_2(spec, state):
yield from run_random_full_withdrawals_test(spec, state, random.Random(200))
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_random_full_withdrawals_3(spec, state):
yield from run_random_full_withdrawals_test(spec, state, random.Random(2000000))
@ -647,7 +647,7 @@ def test_random_full_withdrawals_3(spec, state):
# More partial withdrawal cases
#
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_no_max_effective_balance(spec, state):
validator_index = len(state.validators) // 2
@ -663,7 +663,7 @@ def test_success_no_max_effective_balance(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_no_excess_balance(spec, state):
validator_index = len(state.validators) // 2
@ -679,7 +679,7 @@ def test_success_no_excess_balance(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0)
@with_phases([CAPELLA])
@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
@ -696,7 +696,7 @@ def test_success_excess_balance_but_no_max_effective_balance(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0)
@with_phases([CAPELLA])
@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)
@ -710,7 +710,7 @@ def test_success_one_partial_withdrawable_not_yet_active(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=1)
@with_phases([CAPELLA])
@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)
@ -725,7 +725,7 @@ def test_success_one_partial_withdrawable_in_exit_queue(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=1)
@with_phases([CAPELLA])
@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)
@ -739,7 +739,7 @@ def test_success_one_partial_withdrawable_exited(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=1)
@with_phases([CAPELLA])
@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)
@ -753,7 +753,7 @@ def test_success_one_partial_withdrawable_active_and_slashed(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=1)
@with_phases([CAPELLA])
@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)
@ -768,7 +768,7 @@ def test_success_one_partial_withdrawable_exited_and_slashed(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=1)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_two_partial_withdrawable(spec, state):
set_validator_partially_withdrawable(spec, state, 0)
@ -779,7 +779,7 @@ def test_success_two_partial_withdrawable(spec, state):
yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=2)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_max_partial_withdrawable(spec, state):
# Sanity check that this test works for this state
@ -794,7 +794,7 @@ def test_success_max_partial_withdrawable(spec, state):
spec, state, execution_payload, num_expected_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD)
@with_phases([CAPELLA])
@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):
@ -833,37 +833,37 @@ def run_random_partial_withdrawals_test(spec, state, rng):
yield from run_withdrawals_processing(spec, state, execution_payload)
@with_phases([CAPELLA])
@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_phases([CAPELLA])
@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_phases([CAPELLA])
@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_phases([CAPELLA])
@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_phases([CAPELLA])
@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_phases([CAPELLA])
@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))

View File

@ -0,0 +1,26 @@
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()

View File

@ -1,7 +1,6 @@
from eth2spec.test.context import (
with_phases, spec_state_test
with_capella_and_later, spec_state_test
)
from eth2spec.test.helpers.constants import CAPELLA
from eth2spec.test.helpers.state import (
state_transition_and_sign_block,
)
@ -25,7 +24,7 @@ from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits
# BLSToExecutionChange
#
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_bls_change(spec, state):
index = 0
@ -48,7 +47,7 @@ def test_success_bls_change(spec, state):
assert post_credentials[12:] == signed_address_change.message.to_execution_address
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_success_exit_and_bls_change(spec, state):
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
@ -77,7 +76,7 @@ def test_success_exit_and_bls_change(spec, state):
assert spec.is_fully_withdrawable_validator(validator, balance, validator.withdrawable_epoch)
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_duplicate_bls_changes_same_block(spec, state):
index = 0
@ -96,7 +95,7 @@ def test_invalid_duplicate_bls_changes_same_block(spec, state):
yield 'post', None
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_two_bls_changes_of_different_addresses_same_validator_same_block(spec, state):
index = 0
@ -124,7 +123,7 @@ def test_invalid_two_bls_changes_of_different_addresses_same_validator_same_bloc
# Withdrawals
#
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_full_withdrawal_in_epoch_transition(spec, state):
index = 0
@ -145,7 +144,7 @@ def test_full_withdrawal_in_epoch_transition(spec, state):
assert len(spec.get_expected_withdrawals(state)) == 0
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_partial_withdrawal_in_epoch_transition(spec, state):
index = state.next_withdrawal_index
@ -169,7 +168,7 @@ def test_partial_withdrawal_in_epoch_transition(spec, state):
assert len(spec.get_expected_withdrawals(state)) == 0
@with_phases([CAPELLA])
@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
@ -221,7 +220,7 @@ def _perform_valid_withdrawal(spec, state):
return pre_state, signed_block_1, pre_next_withdrawal_index
@with_phases([CAPELLA])
@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)
@ -238,7 +237,7 @@ def test_withdrawal_success_two_blocks(spec, state):
yield 'post', state
@with_phases([CAPELLA])
@with_capella_and_later
@spec_state_test
def test_invalid_withdrawal_fail_second_block_payload_isnt_compatible(spec, state):
_perform_valid_withdrawal(spec, state)

View File

@ -1,40 +0,0 @@
from eth2spec.test.helpers.bls_to_execution_changes import get_signed_address_change
from eth2spec.test.context import spec_state_test, expect_assertion_error, with_eip4844_and_later
def run_bls_to_execution_change_processing_no_op(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``
"""
pre_state = state.copy()
# 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)
# yield post-state
yield 'post', state
# Make sure state has NOT been changed
assert state == pre_state
@with_eip4844_and_later
@spec_state_test
def test_no_op(spec, state):
signed_address_change = get_signed_address_change(spec, state)
yield from run_bls_to_execution_change_processing_no_op(spec, state, signed_address_change)

View File

@ -1,41 +0,0 @@
from eth2spec.test.context import spec_state_test, expect_assertion_error, with_eip4844_and_later
from eth2spec.test.helpers.execution_payload import (
build_empty_execution_payload,
)
from eth2spec.test.helpers.state import next_slot
def run_withdrawals_processing(spec, state, execution_payload, valid=True):
"""
Run ``process_execution_payload``, yielding:
- pre-state ('pre')
- execution payload ('execution_payload')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
pre_state = state.copy()
yield 'pre', state
yield 'execution_payload', execution_payload
if not valid:
expect_assertion_error(lambda: spec.process_withdrawals(state, execution_payload))
yield 'post', None
return
spec.process_withdrawals(state, execution_payload)
yield 'post', state
# Make sure state has NOT been changed
assert state == pre_state
@with_eip4844_and_later
@spec_state_test
def test_no_op(spec, state):
next_slot(spec, state)
execution_payload = build_empty_execution_payload(spec, state)
yield from run_withdrawals_processing(spec, state, execution_payload)

View File

@ -1,5 +1,8 @@
from eth2spec.test.helpers.forks import is_post_altair
from eth2spec.test.helpers.forks import (
is_post_altair,
is_post_capella,
)
def get_process_calls(spec):
@ -22,7 +25,10 @@ def get_process_calls(spec):
'process_effective_balance_updates',
'process_slashings_reset',
'process_randao_mixes_reset',
'process_historical_roots_update',
# 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'

View File

@ -5,28 +5,7 @@ from eth2spec.test.helpers.sync_committee import (
compute_aggregate_sync_committee_signature,
compute_committee_indices,
)
def signed_block_to_header(spec, block):
return spec.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=block.message.body.hash_tree_root(),
)
def initialize_light_client_store(spec, state):
return spec.LightClientStore(
finalized_header=spec.BeaconBlockHeader(),
current_sync_committee=state.current_sync_committee,
next_sync_committee=state.next_sync_committee,
best_valid_update=None,
optimistic_header=spec.BeaconBlockHeader(),
previous_max_active_participants=0,
current_max_active_participants=0,
)
from math import floor
def get_sync_aggregate(spec, state, num_participants=None, signature_slot=None):
@ -60,3 +39,32 @@ def get_sync_aggregate(spec, state, num_participants=None, signature_slot=None):
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):
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_for_state(
attested_state, spec.NEXT_SYNC_COMMITTEE_INDEX)
if with_finality:
update.finalized_header = spec.block_to_light_client_header(finalized_block)
update.finality_branch = spec.compute_merkle_proof_for_state(
attested_state, spec.FINALIZED_ROOT_INDEX)
update.sync_aggregate, update.signature_slot = get_sync_aggregate(
spec, attested_state, num_participants)
return update

View File

@ -1,4 +1,8 @@
from eth2spec.test.context import spec_state_test, with_all_phases
from eth2spec.test.context import (
PHASE0, ALTAIR, BELLATRIX,
spec_state_test,
with_phases,
)
from eth2spec.test.helpers.epoch_processing import (
run_epoch_processing_with
)
@ -8,7 +12,7 @@ def run_process_historical_roots_update(spec, state):
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)

View File

@ -30,7 +30,7 @@ from eth2spec.test.helpers.sync_committee import (
compute_sync_committee_participant_reward_and_penalty,
)
from eth2spec.test.helpers.constants import PHASE0, MINIMAL
from eth2spec.test.helpers.forks import is_post_altair, is_post_bellatrix
from eth2spec.test.helpers.forks import is_post_altair, is_post_bellatrix, is_post_capella
from eth2spec.test.context import (
spec_test, spec_state_test, dump_skipping_message,
with_phases, with_all_phases, single_phase,
@ -1026,7 +1026,10 @@ 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
@ -1038,7 +1041,14 @@ def test_historical_batch(spec, 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
# 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

View File

@ -1,3 +1,6 @@
from eth2spec.test.helpers.forks import (
is_post_capella,
)
from eth2spec.test.helpers.state import get_state_root
from eth2spec.test.context import (
spec_state_test,
@ -61,3 +64,26 @@ def test_over_epoch_boundary(spec, state):
yield 'slots', int(slots)
spec.process_slots(state, state.slot + slots)
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

View File

@ -41,11 +41,10 @@ Sub-transitions:
- `effective_balance_updates`
- `slashings_reset`
- `randao_mixes_reset`
- `historical_roots_update`
- `historical_roots_update` (Phase0, Altair, Bellatrix only)
- `historical_summaries_update` (Capella)
- `participation_record_updates` (Phase 0 only)
- `participation_flag_updates` (Altair)
- `sync_committee_updates` (Altair)
- `full_withdrawals` (Capella)
- `partial_withdrawals` (Capella)
The resulting state should match the expected `post` state.

View File

@ -34,7 +34,7 @@ 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)` |
@ -43,7 +43,8 @@ Operations:
| `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 Bellatrix) |
| `bls_to_execution_change` | `SignedBLSToExecutionChange` | `signed_address_change` | `process_bls_to_execution_change(state, signed_address_change)` (new in Capella) |
| `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) |
Note that `block_header` is not strictly an operation (and is a full `Block`), but processed in the same manner, and hence included here.

View File

@ -27,12 +27,11 @@ if __name__ == "__main__":
# so no additional tests required.
bellatrix_mods = altair_mods
# No epoch-processing changes in Capella and previous testing repeats with new types,
# so no additional tests required.
capella_mods = bellatrix_mods
_new_capella_mods = {key: 'eth2spec.test.capella.epoch_processing.test_process_' + key for key in [
'historical_summaries_update',
]}
capella_mods = combine_mods(_new_capella_mods, bellatrix_mods)
# No epoch-processing changes in EIP4844 and previous testing repeats with new types,
# so no additional tests required.
eip4844_mods = capella_mods
# TODO Custody Game testgen is disabled for now

View File

@ -36,11 +36,7 @@ if __name__ == "__main__":
]}
capella_mods = combine_mods(_new_capella_mods, bellatrix_mods)
_new_eip4844_mods = {key: 'eth2spec.test.eip4844.block_processing.test_process_' + key for key in [
'bls_to_execution_change',
'withdrawals',
]}
eip4844_mods = combine_mods(_new_eip4844_mods, capella_mods)
eip4844_mods = capella_mods
# TODO Custody Game testgen is disabled for now
# _new_custody_game_mods = {key: 'eth2spec.test.custody_game.block_processing.test_process_' + key for key in [