diff --git a/Makefile b/Makefile
index c38d39a6d..73450562b 100644
--- a/Makefile
+++ b/Makefile
@@ -105,12 +105,12 @@ install_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=eth2spec.bellatrix.minimal --cov=eth2spec.capella.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
+ 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
# 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=eth2spec.bellatrix.minimal --cov=eth2spec.capella.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
+ 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
citest: pyspec
mkdir -p $(TEST_REPORT_DIR);
@@ -142,8 +142,8 @@ codespell:
lint: pyspec
. venv/bin/activate; cd $(PY_SPEC_DIR); \
flake8 --config $(LINTER_CONFIG_FILE) ./eth2spec \
- && pylint --disable=all --enable unused-argument ./eth2spec/phase0 ./eth2spec/altair ./eth2spec/bellatrix ./eth2spec/capella \
- && mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.altair -p eth2spec.bellatrix -p eth2spec.capella
+ && pylint --rcfile $(LINTER_CONFIG_FILE) ./eth2spec/phase0 ./eth2spec/altair ./eth2spec/bellatrix ./eth2spec/capella ./eth2spec/eip4844 \
+ && mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.altair -p eth2spec.bellatrix -p eth2spec.capella -p eth2spec.eip4844
lint_generators: pyspec
. venv/bin/activate; cd $(TEST_GENERATORS_DIR); \
diff --git a/README.md b/README.md
index aa22e05c5..07b78c0d5 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ Features are researched and developed in parallel, and then consolidated into se
### In-development Specifications
| Code Name or Topic | Specs | Notes |
| - | - | - |
-| Capella (tentative) |
- Core
- [Beacon chain changes](specs/capella/beacon-chain.md)
- [Capella fork](specs/capella/fork.md)
- Additions
- [Validator additions](specs/capella/validator.md)
|
+| Capella (tentative) | - Core
- [Beacon chain changes](specs/capella/beacon-chain.md)
- [Capella fork](specs/capella/fork.md)
- Additions
- [Validator additions](specs/capella/validator.md)
- [P2P networking](specs/capella/p2p-interface.md)
|
| EIP4844 (tentative) | - Core
- [Beacon Chain changes](specs/eip4844/beacon-chain.md)
- [EIP-4844 fork](specs/eip4844/fork.md)
- [Polynomial commitments](specs/eip4844/polynomial-commitments.md)
- Additions
- [Honest validator guide changes](specs/eip4844/validator.md)
- [P2P networking](specs/eip4844/p2p-interface.md)
|
| Sharding (outdated) | - Core
- [Beacon Chain changes](specs/sharding/beacon-chain.md)
- Additions
- [P2P networking](specs/sharding/p2p-interface.md)
|
| Custody Game (outdated) | - Core
- [Beacon Chain changes](specs/custody_game/beacon-chain.md)
- Additions
- [Honest validator guide changes](specs/custody_game/validator.md)
| Dependent on sharding |
diff --git a/linter.ini b/linter.ini
index 6575642f1..52a3aec0e 100644
--- a/linter.ini
+++ b/linter.ini
@@ -11,3 +11,8 @@ warn_unused_configs = True
warn_redundant_casts = True
ignore_missing_imports = True
+
+# pylint
+[MESSAGES CONTROL]
+disable = all
+enable = unused-argument
diff --git a/setup.py b/setup.py
index de446bb1c..432a41fe4 100644
--- a/setup.py
+++ b/setup.py
@@ -588,6 +588,7 @@ class NoopExecutionEngine(ExecutionEngine):
pass
def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload:
+ # pylint: disable=unused-argument
raise NotImplementedError("no default block production")
@@ -643,12 +644,14 @@ T = TypeVar('T') # For generic function
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
@@ -663,7 +666,8 @@ get_expected_withdrawals = get_empty_list_result(get_expected_withdrawals)
# End
#
-def retrieve_blobs_sidecar(slot: Slot, beacon_block_root: Root) -> Optional[BlobsSidecar]:
+def retrieve_blobs_sidecar(slot: Slot, beacon_block_root: Root) -> PyUnion[BlobsSidecar, str]:
+ # pylint: disable=unused-argument
return "TEST"'''
@classmethod
@@ -682,8 +686,8 @@ spec_builders = {
}
-def is_spec_defined_type(value: str) -> bool:
- return value.startswith(('ByteList', 'Union', 'Vector', 'List'))
+def is_byte_vector(value: str) -> bool:
+ return value.startswith(('ByteVector'))
def objects_to_spec(preset_name: str,
@@ -696,17 +700,8 @@ def objects_to_spec(preset_name: str,
new_type_definitions = (
'\n\n'.join(
[
- f"class {key}({value}):\n pass\n"
+ f"class {key}({value}):\n pass\n" if not is_byte_vector(value) else f"class {key}({value}): # type: ignore\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)
]
)
)
diff --git a/specs/capella/validator.md b/specs/capella/validator.md
index 32192a6ec..644ee476f 100644
--- a/specs/capella/validator.md
+++ b/specs/capella/validator.md
@@ -112,10 +112,10 @@ Up to `MAX_BLS_TO_EXECUTION_CHANGES`, [`BLSToExecutionChange`](./beacon-chain.md
## Enabling validator withdrawals
-Validator balances are fully or partially withdrawn via an automatic process.
+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.
-For validators, there is one prerequisite for this automated process:
-withdrawal credentials pointing to an execution layer address, i.e. having an `ETH1_ADDRESS_WITHDRAWAL_PREFIX`.
+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
diff --git a/specs/eip4844/beacon-chain.md b/specs/eip4844/beacon-chain.md
index afab21dca..63a6cd987 100644
--- a/specs/eip4844/beacon-chain.md
+++ b/specs/eip4844/beacon-chain.md
@@ -55,7 +55,7 @@ This upgrade adds blobs to the beacon chain as part of EIP-4844. This is an exte
| Name | Value |
| - | - |
| `BLOB_TX_TYPE` | `uint8(0x05)` |
-| `VERSIONED_HASH_VERSION_KZG` | `Bytes1(0x01)` |
+| `VERSIONED_HASH_VERSION_KZG` | `Bytes1('0x01')` |
## Preset
@@ -175,10 +175,13 @@ but MUST NOT be considered valid until a valid `BlobsSidecar` has been downloade
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)
- if sidecar == "TEST":
- return True # For testing; remove once we have a way to inject `BlobsSidecar` into tests
- validate_blobs_sidecar(slot, beacon_block_root, blob_kzg_commitments, sidecar)
+ # 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
```
@@ -216,7 +219,7 @@ def tx_peek_blob_versioned_hashes(opaque_tx: Transaction) -> Sequence[VersionedH
```python
def verify_kzg_commitments_against_transactions(transactions: Sequence[Transaction],
kzg_commitments: Sequence[KZGCommitment]) -> bool:
- all_versioned_hashes = []
+ all_versioned_hashes: List[VersionedHash] = []
for tx in transactions:
if tx[0] == BLOB_TX_TYPE:
all_versioned_hashes += tx_peek_blob_versioned_hashes(tx)
@@ -283,7 +286,8 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe
#### Blob KZG commitments
```python
-def process_blob_kzg_commitments(state: BeaconState, body: BeaconBlockBody):
+def process_blob_kzg_commitments(state: BeaconState, body: BeaconBlockBody) -> None:
+ # pylint: disable=unused-argument
assert verify_kzg_commitments_against_transactions(body.execution_payload.transactions, body.blob_kzg_commitments)
```
diff --git a/specs/eip4844/polynomial-commitments.md b/specs/eip4844/polynomial-commitments.md
index b945d317a..6e1c00ccd 100644
--- a/specs/eip4844/polynomial-commitments.md
+++ b/specs/eip4844/polynomial-commitments.md
@@ -19,9 +19,10 @@
- [`reverse_bits`](#reverse_bits)
- [`bit_reversal_permutation`](#bit_reversal_permutation)
- [BLS12-381 helpers](#bls12-381-helpers)
+ - [`hash_to_bls_field`](#hash_to_bls_field)
- [`bytes_to_bls_field`](#bytes_to_bls_field)
- [`blob_to_polynomial`](#blob_to_polynomial)
- - [`hash_to_bls_field`](#hash_to_bls_field)
+ - [`compute_challenges`](#compute_challenges)
- [`bls_modular_inverse`](#bls_modular_inverse)
- [`div`](#div)
- [`g1_lincomb`](#g1_lincomb)
@@ -41,7 +42,6 @@
-
## Introduction
This document specifies basic polynomial operations and KZG polynomial commitment operations as they are needed for the EIP-4844 specification. The implementations are not optimized for performance, but readability. All practical implementations should optimize the polynomial operations.
@@ -138,14 +138,29 @@ def bit_reversal_permutation(sequence: Sequence[T]) -> Sequence[T]:
### BLS12-381 helpers
+#### `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, ENDIANNESS) % BLS_MODULUS)
+```
+
#### `bytes_to_bls_field`
```python
def bytes_to_bls_field(b: Bytes32) -> BLSFieldElement:
"""
- Convert 32-byte value to a BLS field scalar. The output is not uniform over the BLS field.
+ Convert 32-byte value to a BLS scalar field element.
+ This function does not accept inputs greater than the BLS modulus.
"""
- return int.from_bytes(b, ENDIANNESS) % BLS_MODULUS
+ field_element = int.from_bytes(b, ENDIANNESS)
+ assert field_element < BLS_MODULUS
+ return BLSFieldElement(field_element)
```
#### `blob_to_polynomial`
@@ -157,37 +172,49 @@ def blob_to_polynomial(blob: Blob) -> Polynomial:
"""
polynomial = Polynomial()
for i in range(FIELD_ELEMENTS_PER_BLOB):
- value = int.from_bytes(blob[i * BYTES_PER_FIELD_ELEMENT: (i + 1) * BYTES_PER_FIELD_ELEMENT], ENDIANNESS)
- assert value < BLS_MODULUS
+ value = bytes_to_bls_field(blob[i * BYTES_PER_FIELD_ELEMENT: (i + 1) * BYTES_PER_FIELD_ELEMENT])
polynomial[i] = value
return polynomial
```
-#### `hash_to_bls_field`
+#### `compute_challenges`
```python
-def hash_to_bls_field(polys: Sequence[Polynomial],
- comms: Sequence[KZGCommitment]) -> BLSFieldElement:
+def compute_challenges(polynomials: Sequence[Polynomial],
+ commitments: Sequence[KZGCommitment]) -> Tuple[Sequence[BLSFieldElement], BLSFieldElement]:
"""
- Compute 32-byte hash of serialized polynomials and commitments concatenated.
- This hash is then converted to a BLS field element, where the result is not uniform over the BLS field.
- Return the BLS field element.
+ Return the Fiat-Shamir challenges required by the rest of the protocol.
+ The Fiat-Shamir logic works as per the following pseudocode:
+
+ hashed_data = hash(DOMAIN_SEPARATOR, polynomials, commitments)
+ r = hash(hashed_data, 0)
+ r_powers = [1, r, r**2, r**3, ...]
+ eval_challenge = hash(hashed_data, 1)
+
+ Then return `r_powers` and `eval_challenge` after converting them to BLS field elements.
+ The resulting field elements are not uniform over the BLS field.
"""
# Append the number of polynomials and the degree of each polynomial as a domain separator
- num_polys = int.to_bytes(len(polys), 8, ENDIANNESS)
+ num_polynomials = int.to_bytes(len(polynomials), 8, ENDIANNESS)
degree_poly = int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 8, ENDIANNESS)
- data = FIAT_SHAMIR_PROTOCOL_DOMAIN + degree_poly + num_polys
+ data = FIAT_SHAMIR_PROTOCOL_DOMAIN + degree_poly + num_polynomials
# Append each polynomial which is composed by field elements
- for poly in polys:
+ for poly in polynomials:
for field_element in poly:
data += int.to_bytes(field_element, BYTES_PER_FIELD_ELEMENT, ENDIANNESS)
# Append serialized G1 points
- for commitment in comms:
+ for commitment in commitments:
data += commitment
- return bytes_to_bls_field(hash(data))
+ # Transcript has been prepared: time to create the challenges
+ hashed_data = hash(data)
+ r = hash_to_bls_field(hashed_data + b'\x00')
+ r_powers = compute_powers(r, len(commitments))
+ eval_challenge = hash_to_bls_field(hashed_data + b'\x01')
+
+ return r_powers, eval_challenge
```
#### `bls_modular_inverse`
@@ -198,7 +225,7 @@ def bls_modular_inverse(x: BLSFieldElement) -> BLSFieldElement:
Compute the modular inverse of x
i.e. return y such that x * y % BLS_MODULUS == 1 and return 0 for x == 0
"""
- return pow(x, -1, BLS_MODULUS) if x != 0 else 0
+ return BLSFieldElement(pow(x, -1, BLS_MODULUS)) if x != 0 else BLSFieldElement(0)
```
#### `div`
@@ -208,7 +235,7 @@ def div(x: BLSFieldElement, y: BLSFieldElement) -> BLSFieldElement:
"""
Divide two field elements: ``x`` by `y``.
"""
- return (int(x) * int(bls_modular_inverse(y))) % BLS_MODULUS
+ return BLSFieldElement((int(x) * int(bls_modular_inverse(y))) % BLS_MODULUS)
```
#### `g1_lincomb`
@@ -234,11 +261,12 @@ def poly_lincomb(polys: Sequence[Polynomial],
Given a list of ``polynomials``, interpret it as a 2D matrix and compute the linear combination
of each column with `scalars`: return the resulting polynomials.
"""
- result = [0] * len(polys[0])
+ assert len(polys) == len(scalars)
+ result = [0] * FIELD_ELEMENTS_PER_BLOB
for v, s in zip(polys, scalars):
for i, x in enumerate(v):
result[i] = (result[i] + int(s) * int(x)) % BLS_MODULUS
- return [BLSFieldElement(x) for x in result]
+ return Polynomial([BLSFieldElement(x) for x in result])
```
#### `compute_powers`
@@ -246,7 +274,7 @@ def poly_lincomb(polys: Sequence[Polynomial],
```python
def compute_powers(x: BLSFieldElement, n: uint64) -> Sequence[BLSFieldElement]:
"""
- Return ``x`` to power of [0, n-1].
+ Return ``x`` to power of [0, n-1], if n > 0. When n==0, an empty array is returned.
"""
current_power = 1
powers = []
@@ -256,6 +284,7 @@ def compute_powers(x: BLSFieldElement, n: uint64) -> Sequence[BLSFieldElement]:
return powers
```
+
### Polynomials
#### `evaluate_polynomial_in_evaluation_form`
@@ -270,7 +299,7 @@ def evaluate_polynomial_in_evaluation_form(polynomial: Polynomial,
"""
width = len(polynomial)
assert width == FIELD_ELEMENTS_PER_BLOB
- inverse_width = bls_modular_inverse(width)
+ inverse_width = bls_modular_inverse(BLSFieldElement(width))
# Make sure we won't divide by zero during division
assert z not in ROOTS_OF_UNITY
@@ -279,9 +308,11 @@ def evaluate_polynomial_in_evaluation_form(polynomial: Polynomial,
result = 0
for i in range(width):
- result += div(int(polynomial[i]) * int(roots_of_unity_brp[i]), (int(z) - int(roots_of_unity_brp[i])))
- result = result * (pow(z, width, BLS_MODULUS) - 1) * inverse_width % BLS_MODULUS
- return result
+ a = BLSFieldElement(int(polynomial[i]) * int(roots_of_unity_brp[i]) % BLS_MODULUS)
+ b = BLSFieldElement((int(BLS_MODULUS) + int(z) - int(roots_of_unity_brp[i])) % BLS_MODULUS)
+ result += int(div(a, b) % BLS_MODULUS)
+ result = result * int(pow(z, width, BLS_MODULUS) - 1) * int(inverse_width)
+ return BLSFieldElement(result % BLS_MODULUS)
```
### KZG
@@ -341,17 +372,13 @@ 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)
"""
-
- # To avoid SSZ overflow/underflow, convert element into int
- polynomial = [int(i) for i in polynomial]
- z = int(z)
-
y = evaluate_polynomial_in_evaluation_form(polynomial, z)
- polynomial_shifted = [(p - int(y)) % BLS_MODULUS for p in polynomial]
+ polynomial_shifted = [BLSFieldElement((int(p) - int(y)) % BLS_MODULUS) for p in polynomial]
# Make sure we won't divide by zero during division
assert z not in ROOTS_OF_UNITY
- denominator_poly = [(int(x) - z) % BLS_MODULUS for x in bit_reversal_permutation(ROOTS_OF_UNITY)]
+ denominator_poly = [BLSFieldElement((int(x) - int(z)) % BLS_MODULUS)
+ for x in bit_reversal_permutation(ROOTS_OF_UNITY)]
# Calculate quotient polynomial by doing point-by-point division
quotient_polynomial = [div(a, b) for a, b in zip(polynomial_shifted, denominator_poly)]
@@ -367,17 +394,18 @@ def compute_aggregated_poly_and_commitment(
"""
Return (1) the aggregated polynomial, (2) the aggregated KZG commitment,
and (3) the polynomial evaluation random challenge.
+ This function should also work with blobs == [] and kzg_commitments == []
"""
+ assert len(blobs) == len(kzg_commitments)
+
# Convert blobs to polynomials
polynomials = [blob_to_polynomial(blob) for blob in blobs]
- # Generate random linear combination challenges
- r = hash_to_bls_field(polynomials, kzg_commitments)
- r_powers = compute_powers(r, len(kzg_commitments))
- evaluation_challenge = int(r_powers[-1]) * r % BLS_MODULUS
+ # Generate random linear combination and evaluation challenges
+ r_powers, evaluation_challenge = compute_challenges(polynomials, kzg_commitments)
# Create aggregated polynomial in evaluation form
- aggregated_poly = Polynomial(poly_lincomb(polynomials, r_powers))
+ aggregated_poly = poly_lincomb(polynomials, r_powers)
# Compute commitment to aggregated polynomial
aggregated_poly_commitment = KZGCommitment(g1_lincomb(kzg_commitments, r_powers))
@@ -390,6 +418,7 @@ def compute_aggregated_poly_and_commitment(
```python
def compute_aggregate_kzg_proof(blobs: Sequence[Blob]) -> KZGProof:
"""
+ Given a list of blobs, return the aggregated KZG proof that is used to verify them against their commitments.
Public method.
"""
commitments = [blob_to_kzg_commitment(blob) for blob in blobs]
@@ -405,8 +434,10 @@ def compute_aggregate_kzg_proof(blobs: Sequence[Blob]) -> KZGProof:
```python
def verify_aggregate_kzg_proof(blobs: Sequence[Blob],
expected_kzg_commitments: Sequence[KZGCommitment],
- kzg_aggregated_proof: KZGCommitment) -> bool:
+ kzg_aggregated_proof: KZGProof) -> bool:
"""
+ Given a list of blobs and an aggregated KZG proof, verify that they correspond to the provided commitments.
+
Public method.
"""
aggregated_poly, aggregated_poly_commitment, evaluation_challenge = compute_aggregated_poly_and_commitment(
diff --git a/specs/eip4844/validator.md b/specs/eip4844/validator.md
index 57a5610ce..1258358a2 100644
--- a/specs/eip4844/validator.md
+++ b/specs/eip4844/validator.md
@@ -46,6 +46,7 @@ Implementers may also retrieve blobs individually per transaction.
```python
def get_blobs_and_kzg_commitments(payload_id: PayloadId) -> Tuple[Sequence[BLSFieldElement], Sequence[KZGCommitment]]:
+ # pylint: disable=unused-argument
...
```
diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md
index 6277bee9f..f52752931 100644
--- a/specs/phase0/p2p-interface.md
+++ b/specs/phase0/p2p-interface.md
@@ -745,6 +745,8 @@ For example, if slot 4 were empty in the previous example, the returned array wo
`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.
@@ -831,6 +833,8 @@ 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.
+`/eth2/beacon_chain/req/beacon_blocks_by_root/1/` is deprecated. Clients MAY respond with an empty list during the deprecation transition period.
+
#### Ping
**Protocol ID:** `/eth2/beacon_chain/req/ping/1/`
diff --git a/tests/core/pyspec/eth2spec/test/eip4844/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/eip4844/unittests/validator/test_validator.py
index 634daca2d..d9e3877c3 100644
--- a/tests/core/pyspec/eth2spec/test/eip4844/unittests/validator/test_validator.py
+++ b/tests/core/pyspec/eth2spec/test/eip4844/unittests/validator/test_validator.py
@@ -25,6 +25,12 @@ def _run_validate_blobs_sidecar_test(spec, state, blob_count):
spec.validate_blobs_sidecar(block.slot, block.hash_tree_root(), expected_commitments, blobs_sidecar)
+@with_eip4844_and_later
+@spec_state_test
+def test_validate_blobs_sidecar_zero_blobs(spec, state):
+ _run_validate_blobs_sidecar_test(spec, state, blob_count=0)
+
+
@with_eip4844_and_later
@spec_state_test
def test_validate_blobs_sidecar_one_blob(spec, state):
diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py
index 0280bc7fb..1e3374a64 100644
--- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py
+++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py
@@ -165,6 +165,9 @@ def do_fork(state, spec, post_spec, fork_epoch, with_block=True, operation_dict=
elif post_spec.fork == CAPELLA:
assert state.fork.previous_version == post_spec.config.BELLATRIX_FORK_VERSION
assert state.fork.current_version == post_spec.config.CAPELLA_FORK_VERSION
+ elif post_spec.fork == EIP4844:
+ assert state.fork.previous_version == post_spec.config.CAPELLA_FORK_VERSION
+ assert state.fork.current_version == post_spec.config.EIP4844_FORK_VERSION
if with_block:
return state, _state_transition_and_sign_block_at_slot(post_spec, state, operation_dict=operation_dict)
diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py
index ee3068788..d758936e9 100644
--- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py
+++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py
@@ -60,7 +60,7 @@ def create_genesis_state(spec, validator_balances, activation_threshold):
previous_version = spec.config.BELLATRIX_FORK_VERSION
current_version = spec.config.CAPELLA_FORK_VERSION
elif spec.fork == EIP4844:
- previous_version = spec.config.BELLATRIX_FORK_VERSION
+ previous_version = spec.config.CAPELLA_FORK_VERSION
current_version = spec.config.EIP4844_FORK_VERSION
state = spec.BeaconState(