Merge pull request #3359 from ethereum/engine-versioned-hashes

Use `engine_newPayloadV3` to pass `versioned_hashes` to EL for validation
This commit is contained in:
Hsiao-Wei Wang
2023-05-24 15:34:00 +08:00
committed by GitHub
18 changed files with 399 additions and 339 deletions

View File

@@ -336,6 +336,10 @@ class SpecBuilder(ABC):
"""
raise NotImplementedError()
@classmethod
def execution_engine_cls(cls) -> str:
raise NotImplementedError()
@classmethod
@abstractmethod
def hardcoded_ssz_dep_constants(cls) -> Dict[str, str]:
@@ -469,6 +473,12 @@ get_attesting_indices = cache_this(
),
_get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3)'''
@classmethod
def execution_engine_cls(cls) -> str:
return ""
@classmethod
def hardcoded_ssz_dep_constants(cls) -> Dict[str, str]:
return {}
@@ -573,9 +583,11 @@ def get_execution_state(_execution_state_root: Bytes32) -> ExecutionState:
def get_pow_chain_head() -> PowBlock:
pass
pass"""
@classmethod
def execution_engine_cls(cls) -> str:
return "\n\n" + """
class NoopExecutionEngine(ExecutionEngine):
def notify_new_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
@@ -592,6 +604,13 @@ class NoopExecutionEngine(ExecutionEngine):
# pylint: disable=unused-argument
raise NotImplementedError("no default block production")
def is_valid_block_hash(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
return True
def verify_and_notify_new_payload(self: ExecutionEngine,
new_payload_request: NewPayloadRequest) -> bool:
return True
EXECUTION_ENGINE = NoopExecutionEngine()"""
@@ -658,6 +677,39 @@ def retrieve_blobs_and_proofs(beacon_block_root: Root) -> PyUnion[Tuple[Blob, KZ
# pylint: disable=unused-argument
return ("TEST", "TEST")'''
@classmethod
def execution_engine_cls(cls) -> str:
return "\n\n" + """
class NoopExecutionEngine(ExecutionEngine):
def notify_new_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
return True
def notify_forkchoice_updated(self: ExecutionEngine,
head_block_hash: Hash32,
safe_block_hash: Hash32,
finalized_block_hash: Hash32,
payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]:
pass
def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse:
# pylint: disable=unused-argument
raise NotImplementedError("no default block production")
def is_valid_block_hash(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
return True
def is_valid_versioned_hashes(self: ExecutionEngine, new_payload_request: NewPayloadRequest) -> bool:
return True
def verify_and_notify_new_payload(self: ExecutionEngine,
new_payload_request: NewPayloadRequest) -> bool:
return True
EXECUTION_ENGINE = NoopExecutionEngine()"""
@classmethod
def hardcoded_custom_type_dep_constants(cls, spec_object) -> str:
constants = {
@@ -691,6 +743,11 @@ def is_byte_vector(value: str) -> bool:
return value.startswith(('ByteVector'))
def make_function_abstract(protocol_def: ProtocolDefinition, key: str):
function = protocol_def.functions[key].split('"""')
protocol_def.functions[key] = function[0] + "..."
def objects_to_spec(preset_name: str,
spec_object: SpecObject,
builder: SpecBuilder,
@@ -708,6 +765,11 @@ def objects_to_spec(preset_name: str,
)
def format_protocol(protocol_name: str, protocol_def: ProtocolDefinition) -> str:
abstract_functions = ["verify_and_notify_new_payload"]
for key in protocol_def.functions.keys():
if key in abstract_functions:
make_function_abstract(protocol_def, key)
protocol = f"class {protocol_name}(Protocol):"
for fn_source in protocol_def.functions.values():
fn_source = fn_source.replace("self: "+protocol_name, "self")
@@ -783,6 +845,7 @@ def objects_to_spec(preset_name: str,
+ ('\n\n\n' + protocols_spec if protocols_spec != '' else '')
+ '\n\n\n' + functions_spec
+ '\n\n' + builder.sundry_functions()
+ builder.execution_engine_cls()
# Since some constants are hardcoded in setup.py, the following assertions verify that the hardcoded constants are
# as same as the spec definition.
+ ('\n\n\n' + ssz_dep_constants_verification if ssz_dep_constants_verification != '' else '')

View File

@@ -177,12 +177,11 @@ class BeaconState(Container):
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
process_withdrawals(state, block.body.execution_payload)
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [Modified in EIP6110]
process_execution_payload(state, block.body, EXECUTION_ENGINE) # [Modified in EIP6110]
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body) # [Modified in EIP6110]
process_sync_aggregate(state, block.body.sync_aggregate)
process_blob_kzg_commitments(block.body)
```
#### Modified `process_operations`
@@ -236,7 +235,9 @@ def process_deposit_receipt(state: BeaconState, deposit_receipt: DepositReceipt)
*Note*: The function `process_execution_payload` is modified to use the new `ExecutionPayloadHeader` type.
```python
def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None:
def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None:
payload = body.execution_payload
# Verify consistency of the parent hash with respect to the previous execution payload header
assert payload.parent_hash == state.latest_execution_payload_header.block_hash
# Verify prev_randao
@@ -244,7 +245,10 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe
# Verify timestamp
assert payload.timestamp == compute_timestamp_at_slot(state, state.slot)
# Verify the execution payload is valid
assert execution_engine.notify_new_payload(payload)
versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments]
assert execution_engine.verify_and_notify_new_payload(
NewPayloadRequest(execution_payload=payload, versioned_hashes=versioned_hashes)
)
# Cache execution payload header
state.latest_execution_payload_header = ExecutionPayloadHeader(
parent_hash=payload.parent_hash,

View File

@@ -33,7 +33,12 @@
- [Modified `slash_validator`](#modified-slash_validator)
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
- [Execution engine](#execution-engine)
- [Request data](#request-data)
- [`NewPayloadRequest`](#newpayloadrequest)
- [Engine APIs](#engine-apis)
- [`notify_new_payload`](#notify_new_payload)
- [`is_valid_block_hash`](#is_valid_block_hash)
- [`verify_and_notify_new_payload`](#verify_and_notify_new_payload)
- [Block processing](#block-processing)
- [Execution payload](#execution-payload)
- [`process_execution_payload`](#process_execution_payload)
@@ -300,18 +305,30 @@ def slash_validator(state: BeaconState,
### Execution engine
#### Request data
##### `NewPayloadRequest`
```python
@dataclass
class NewPayloadRequest(object):
execution_payload: ExecutionPayload
```
#### Engine APIs
The implementation-dependent `ExecutionEngine` protocol encapsulates the execution sub-system logic via:
* a state object `self.execution_state` of type `ExecutionState`
* a notification function `self.notify_new_payload` which may apply changes to the `self.execution_state`
*Note*: `notify_new_payload` is a function accessed through the `EXECUTION_ENGINE` module which instantiates the `ExecutionEngine` protocol.
The body of this function is implementation dependent.
The body of these functions are implementation dependent.
The Engine API may be used to implement this and similarly defined functions via an external execution engine.
#### `notify_new_payload`
`notify_new_payload` is a function accessed through the `EXECUTION_ENGINE` module which instantiates the `ExecutionEngine` protocol.
```python
def notify_new_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
"""
@@ -320,6 +337,31 @@ def notify_new_payload(self: ExecutionEngine, execution_payload: ExecutionPayloa
...
```
#### `is_valid_block_hash`
```python
def is_valid_block_hash(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
"""
Return ``True`` if and only if ``execution_payload.block_hash`` is computed correctly.
"""
...
```
#### `verify_and_notify_new_payload`
```python
def verify_and_notify_new_payload(self: ExecutionEngine,
new_payload_request: NewPayloadRequest) -> bool:
"""
Return ``True`` if and only if ``new_payload_request`` is valid with respect to ``self.execution_state``.
"""
if not self.is_valid_block_hash(new_payload_request.execution_payload):
return False
if not self.notify_new_payload(new_payload_request.execution_payload):
return False
return True
```
### Block processing
*Note*: The call to the `process_execution_payload` must happen before the call to the `process_randao` as the former depends on the `randao_mix` computed with the reveal of the previous block.
@@ -328,7 +370,7 @@ def notify_new_payload(self: ExecutionEngine, execution_payload: ExecutionPayloa
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
if is_execution_enabled(state, block.body):
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Bellatrix]
process_execution_payload(state, block.body, EXECUTION_ENGINE) # [New in Bellatrix]
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body)
@@ -340,7 +382,9 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
##### `process_execution_payload`
```python
def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None:
def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None:
payload = body.execution_payload
# Verify consistency of the parent hash with respect to the previous execution payload header
if is_merge_transition_complete(state):
assert payload.parent_hash == state.latest_execution_payload_header.block_hash
@@ -349,7 +393,7 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe
# Verify timestamp
assert payload.timestamp == compute_timestamp_at_slot(state, state.slot)
# Verify the execution payload is valid
assert execution_engine.notify_new_payload(payload)
assert execution_engine.verify_and_notify_new_payload(NewPayloadRequest(execution_payload=payload))
# Cache execution payload header
state.latest_execution_payload_header = ExecutionPayloadHeader(
parent_hash=payload.parent_hash,

View File

@@ -333,7 +333,7 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
# [Modified in Capella] Removed `is_execution_enabled` check in Capella
process_withdrawals(state, block.body.execution_payload) # [New in Capella]
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [Modified in Capella]
process_execution_payload(state, block.body, EXECUTION_ENGINE) # [Modified in Capella]
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body) # [Modified in Capella]
@@ -404,10 +404,12 @@ def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
#### Modified `process_execution_payload`
*Note*: The function `process_execution_payload` is modified to use the new `ExecutionPayloadHeader` type.
*Note*: The function `process_execution_payload` is modified to use the new `ExecutionPayloadHeader` type
and removed the `is_merge_transition_complete` check.
```python
def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None:
def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None:
payload = body.execution_payload
# [Modified in Capella] Removed `is_merge_transition_complete` check in Capella
# Verify consistency of the parent hash with respect to the previous execution payload header
assert payload.parent_hash == state.latest_execution_payload_header.block_hash
@@ -416,7 +418,7 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe
# Verify timestamp
assert payload.timestamp == compute_timestamp_at_slot(state, state.slot)
# Verify the execution payload is valid
assert execution_engine.notify_new_payload(payload)
assert execution_engine.verify_and_notify_new_payload(NewPayloadRequest(execution_payload=payload))
# Cache execution payload header
state.latest_execution_payload_header = ExecutionPayloadHeader(
parent_hash=payload.parent_hash,

View File

@@ -24,13 +24,16 @@
- [Helper functions](#helper-functions)
- [Misc](#misc)
- [`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)
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
- [Execution engine](#execution-engine)
- [Request data](#request-data)
- [Modified `NewPayloadRequest`](#modified-newpayloadrequest)
- [Engine APIs](#engine-apis)
- [`is_valid_versioned_hashes`](#is_valid_versioned_hashes)
- [Modified `verify_and_notify_new_payload`](#modified-verify_and_notify_new_payload)
- [Block processing](#block-processing)
- [Execution payload](#execution-payload)
- [`process_execution_payload`](#process_execution_payload)
- [Blob KZG commitments](#blob-kzg-commitments)
- [Testing](#testing)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -158,63 +161,65 @@ def kzg_commitment_to_versioned_hash(kzg_commitment: KZGCommitment) -> Versioned
return VERSIONED_HASH_VERSION_KZG + hash(kzg_commitment)[1:]
```
#### `tx_peek_blob_versioned_hashes`
This function retrieves the hashes from the `SignedBlobTransaction` as defined in Deneb, using SSZ offsets.
Offsets are little-endian `uint32` values, as defined in the [SSZ specification](../../ssz/simple-serialize.md).
See [the full details of `blob_versioned_hashes` offset calculation](https://gist.github.com/protolambda/23bd106b66f6d4bb854ce46044aa3ca3).
```python
def tx_peek_blob_versioned_hashes(opaque_tx: Transaction) -> Sequence[VersionedHash]:
assert opaque_tx[0] == BLOB_TX_TYPE
message_offset = 1 + uint32.decode_bytes(opaque_tx[1:5])
# field offset: 32 + 8 + 32 + 32 + 8 + 4 + 32 + 4 + 4 + 32 = 188
blob_versioned_hashes_offset = (
message_offset
+ uint32.decode_bytes(opaque_tx[(message_offset + 188):(message_offset + 192)])
)
# `VersionedHash` is a 32-byte object
assert (len(opaque_tx) - blob_versioned_hashes_offset) % 32 == 0
return [
VersionedHash(opaque_tx[x:(x + 32)])
for x in range(blob_versioned_hashes_offset, len(opaque_tx), 32)
]
```
#### `verify_kzg_commitments_against_transactions`
```python
def verify_kzg_commitments_against_transactions(transactions: Sequence[Transaction],
kzg_commitments: Sequence[KZGCommitment]) -> bool:
all_versioned_hashes: List[VersionedHash] = []
for tx in transactions:
if tx[0] == BLOB_TX_TYPE:
all_versioned_hashes += tx_peek_blob_versioned_hashes(tx)
return all_versioned_hashes == [kzg_commitment_to_versioned_hash(commitment) for commitment in kzg_commitments]
```
## Beacon chain state transition function
### Block processing
### Execution engine
#### Request data
##### Modified `NewPayloadRequest`
```python
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
process_withdrawals(state, block.body.execution_payload)
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [Modified in Deneb]
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body)
process_sync_aggregate(state, block.body.sync_aggregate)
process_blob_kzg_commitments(block.body) # [New in Deneb]
@dataclass
class NewPayloadRequest(object):
execution_payload: ExecutionPayload
versioned_hashes: Sequence[VersionedHash]
```
#### Engine APIs
##### `is_valid_versioned_hashes`
```python
def is_valid_versioned_hashes(self: ExecutionEngine, new_payload_request: NewPayloadRequest) -> bool:
"""
Return ``True`` if and only if the version hashes computed by the blob transactions of
``new_payload_request.execution_payload`` matches ``new_payload_request.version_hashes``.
"""
...
```
##### Modified `verify_and_notify_new_payload`
```python
def verify_and_notify_new_payload(self: ExecutionEngine,
new_payload_request: NewPayloadRequest) -> bool:
"""
Return ``True`` if and only if ``new_payload_request`` is valid with respect to ``self.execution_state``.
"""
if not self.is_valid_block_hash(new_payload_request.execution_payload):
return False
# [New in Deneb]
if not self.is_valid_versioned_hashes(new_payload_request):
return False
if not self.notify_new_payload(new_payload_request.execution_payload):
return False
return True
```
### Block processing
#### Execution payload
##### `process_execution_payload`
```python
def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None:
def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None:
payload = body.execution_payload
# Verify consistency of the parent hash with respect to the previous execution payload header
assert payload.parent_hash == state.latest_execution_payload_header.block_hash
# Verify prev_randao
@@ -222,7 +227,11 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe
# Verify timestamp
assert payload.timestamp == compute_timestamp_at_slot(state, state.slot)
# Verify the execution payload is valid
assert execution_engine.notify_new_payload(payload)
# [Modified in Deneb]
versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments]
assert execution_engine.verify_and_notify_new_payload(
NewPayloadRequest(execution_payload=payload, versioned_hashes=versioned_hashes)
)
# Cache execution payload header
state.latest_execution_payload_header = ExecutionPayloadHeader(
@@ -245,13 +254,6 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe
)
```
#### Blob KZG commitments
```python
def process_blob_kzg_commitments(body: BeaconBlockBody) -> None:
assert verify_kzg_commitments_against_transactions(body.execution_payload.transactions, body.blob_kzg_commitments)
```
## Testing
*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Deneb testing only.

View File

@@ -101,22 +101,7 @@ All validator responsibilities remain unchanged other than those noted below.
1. After retrieving the execution payload from the execution engine as specified in Capella,
use the `payload_id` to retrieve `blobs`, `blob_kzg_commitments`, and `blob_kzg_proofs`
via `get_payload(payload_id).blobs_bundle`.
2. Validate `blobs` and `blob_kzg_commitments`:
```python
def validate_blobs_and_kzg_commitments(execution_payload: ExecutionPayload,
blobs: Sequence[Blob],
blob_kzg_commitments: Sequence[KZGCommitment],
blob_kzg_proofs: Sequence[KZGProof]) -> None:
# Optionally sanity-check that the KZG commitments match the versioned hashes in the transactions
assert verify_kzg_commitments_against_transactions(execution_payload.transactions, blob_kzg_commitments)
# Optionally sanity-check that the KZG commitments match the blobs (as produced by the execution engine)
assert len(blob_kzg_commitments) == len(blobs) == len(blob_kzg_proofs)
assert verify_blob_kzg_proof_batch(blobs, blob_kzg_commitments, blob_kzg_proofs)
```
3. If valid, set `block.body.blob_kzg_commitments = blob_kzg_commitments`.
2. Set `block.body.blob_kzg_commitments = blob_kzg_commitments`.
#### Constructing the `SignedBlobSidecar`s

View File

@@ -159,7 +159,7 @@ these conditions.*
To optimistically import a block:
- The [`notify_new_payload`](../specs/bellatrix/beacon-chain.md#notify_new_payload) function MUST return `True` if the execution
- The [`verify_and_notify_new_payload`](../specs/bellatrix/beacon-chain.md#verify_and_notify_new_payload) function MUST return `True` if the execution
engine returns `NOT_VALIDATED` or `VALID`. An `INVALIDATED` response MUST return `False`.
- The [`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block)
function MUST NOT raise an assertion if both the
@@ -172,7 +172,7 @@ In addition to this change in validation, the consensus engine MUST track which
blocks returned `NOT_VALIDATED` and which returned `VALID` for subsequent processing.
Optimistically imported blocks MUST pass all verifications included in
`process_block` (withstanding the modifications to `notify_new_payload`).
`process_block` (withstanding the modifications to `verify_and_notify_new_payload`).
A consensus engine MUST be able to retrospectively (i.e., after import) modify
the status of `NOT_VALIDATED` blocks to be either `VALID` or `INVALIDATED` based upon responses

View File

@@ -27,33 +27,35 @@ def run_execution_payload_processing(spec, state, execution_payload, valid=True,
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
# Before Deneb, only `body.execution_payload` matters. `BeaconBlockBody` is just a wrapper.
body = spec.BeaconBlockBody(execution_payload=execution_payload)
yield 'pre', state
yield 'execution', {'execution_valid': execution_valid}
yield 'execution_payload', execution_payload
yield 'body', body
called_new_block = False
class TestEngine(spec.NoopExecutionEngine):
def notify_new_payload(self, payload) -> bool:
def verify_and_notify_new_payload(self, new_payload_request) -> bool:
nonlocal called_new_block, execution_valid
called_new_block = True
assert payload == execution_payload
assert new_payload_request.execution_payload == body.execution_payload
return execution_valid
if not valid:
expect_assertion_error(lambda: spec.process_execution_payload(state, execution_payload, TestEngine()))
expect_assertion_error(lambda: spec.process_execution_payload(state, body, TestEngine()))
yield 'post', None
return
spec.process_execution_payload(state, execution_payload, TestEngine())
spec.process_execution_payload(state, body, TestEngine())
# Make sure we called the engine
assert called_new_block
yield 'post', state
assert state.latest_execution_payload_header == get_execution_payload_header(spec, execution_payload)
assert state.latest_execution_payload_header == get_execution_payload_header(spec, body.execution_payload)
def run_success_test(spec, state):

View File

@@ -56,7 +56,7 @@ def verify_post_state(state, spec, expected_withdrawals,
def run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=None,
fully_withdrawable_indices=None, partial_withdrawals_indices=None, valid=True):
"""
Run ``process_execution_payload``, yielding:
Run ``process_withdrawals``, yielding:
- pre-state ('pre')
- execution payload ('execution_payload')
- post-state ('post').

View File

@@ -0,0 +1,192 @@
from random import Random
from eth2spec.test.helpers.execution_payload import (
build_empty_execution_payload,
compute_el_block_hash,
get_execution_payload_header,
)
from eth2spec.test.context import (
spec_state_test,
expect_assertion_error,
with_deneb_and_later
)
from eth2spec.test.helpers.sharding import (
get_sample_opaque_tx,
)
def run_execution_payload_processing(spec, state, execution_payload, blob_kzg_commitments,
valid=True, execution_valid=True):
"""
Run ``process_execution_payload``, yielding:
- pre-state ('pre')
- execution payload ('execution_payload')
- execution details, to mock EVM execution ('execution.yml', a dict with 'execution_valid' key and boolean value)
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
# Before Deneb, only `body.execution_payload` matters. `BeaconBlockBody` is just a wrapper.
body = spec.BeaconBlockBody(
blob_kzg_commitments=blob_kzg_commitments,
execution_payload=execution_payload
)
yield 'pre', state
yield 'execution', {'execution_valid': execution_valid}
yield 'body', body
called_new_block = False
class TestEngine(spec.NoopExecutionEngine):
def verify_and_notify_new_payload(self, new_payload_request) -> bool:
nonlocal called_new_block, execution_valid
called_new_block = True
assert new_payload_request.execution_payload == body.execution_payload
return execution_valid
if not valid:
expect_assertion_error(lambda: spec.process_execution_payload(state, body, TestEngine()))
yield 'post', None
return
spec.process_execution_payload(state, body, TestEngine())
# Make sure we called the engine
assert called_new_block
yield 'post', state
assert state.latest_execution_payload_header == get_execution_payload_header(spec, body.execution_payload)
@with_deneb_and_later
@spec_state_test
def test_incorrect_blob_tx_type(spec, state):
"""
The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default.
"""
execution_payload = build_empty_execution_payload(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec)
opaque_tx = b'\x04' + opaque_tx[1:] # incorrect tx type
execution_payload.transactions = [opaque_tx]
execution_payload.block_hash = compute_el_block_hash(spec, execution_payload)
yield from run_execution_payload_processing(spec, state, execution_payload, blob_kzg_commitments)
@with_deneb_and_later
@spec_state_test
def test_incorrect_transaction_length_1_byte(spec, state):
"""
The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default.
"""
execution_payload = build_empty_execution_payload(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec)
opaque_tx = opaque_tx + b'\x12' # incorrect tx length
execution_payload.transactions = [opaque_tx]
execution_payload.block_hash = compute_el_block_hash(spec, execution_payload)
yield from run_execution_payload_processing(spec, state, execution_payload, blob_kzg_commitments)
@with_deneb_and_later
@spec_state_test
def test_incorrect_transaction_length_32_bytes(spec, state):
"""
The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default.
"""
execution_payload = build_empty_execution_payload(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec)
opaque_tx = opaque_tx + b'\x12' * 32 # incorrect tx length
execution_payload.transactions = [opaque_tx]
execution_payload.block_hash = compute_el_block_hash(spec, execution_payload)
yield from run_execution_payload_processing(spec, state, execution_payload, blob_kzg_commitments)
@with_deneb_and_later
@spec_state_test
def test_incorrect_commitment(spec, state):
"""
The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default.
"""
execution_payload = build_empty_execution_payload(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec)
blob_kzg_commitments[0] = b'\x12' * 48 # incorrect commitment
execution_payload.transactions = [opaque_tx]
execution_payload.block_hash = compute_el_block_hash(spec, execution_payload)
yield from run_execution_payload_processing(spec, state, execution_payload, blob_kzg_commitments)
@with_deneb_and_later
@spec_state_test
def test_incorrect_commitments_order(spec, state):
"""
The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default.
"""
execution_payload = build_empty_execution_payload(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec, blob_count=2, rng=Random(1111))
blob_kzg_commitments = [blob_kzg_commitments[1], blob_kzg_commitments[0]] # incorrect order
execution_payload.transactions = [opaque_tx]
execution_payload.block_hash = compute_el_block_hash(spec, execution_payload)
yield from run_execution_payload_processing(spec, state, execution_payload, blob_kzg_commitments)
@with_deneb_and_later
@spec_state_test
def test_incorrect_block_hash(spec, state):
execution_payload = build_empty_execution_payload(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec)
execution_payload.transactions = [opaque_tx]
execution_payload.block_hash = b'\x12' * 32 # incorrect block hash
# CL itself doesn't verify EL block hash
yield from run_execution_payload_processing(spec, state, execution_payload, blob_kzg_commitments)
@with_deneb_and_later
@spec_state_test
def test_zeroed_commitment(spec, state):
"""
The blob is invalid, but the commitment is in correct form.
"""
execution_payload = build_empty_execution_payload(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec, blob_count=1, is_valid_blob=False)
assert all(commitment == b'\x00' * 48 for commitment in blob_kzg_commitments)
execution_payload.transactions = [opaque_tx]
execution_payload.block_hash = compute_el_block_hash(spec, execution_payload)
yield from run_execution_payload_processing(spec, state, execution_payload, blob_kzg_commitments)
@with_deneb_and_later
@spec_state_test
def test_invalid_correct_input__execution_invalid(spec, state):
"""
The versioned hashes are wrong, but the testing ExecutionEngine returns VALID by default.
"""
execution_payload = build_empty_execution_payload(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec)
execution_payload.transactions = [opaque_tx]
execution_payload.block_hash = compute_el_block_hash(spec, execution_payload)
yield from run_execution_payload_processing(spec, state, execution_payload, blob_kzg_commitments,
valid=False, execution_valid=False)

View File

@@ -1,5 +1,3 @@
import random
from eth2spec.test.helpers.state import (
state_transition_and_sign_block
)
@@ -49,124 +47,3 @@ def test_one_blob(spec, state):
@spec_state_test
def test_max_blobs(spec, state):
yield from run_block_with_blobs(spec, state, blob_count=spec.MAX_BLOBS_PER_BLOCK)
@with_deneb_and_later
@spec_state_test
def test_invalid_incorrect_blob_tx_type(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec)
block.body.blob_kzg_commitments = blob_kzg_commitments
opaque_tx = b'\x04' + opaque_tx[1:] # incorrect tx type
block.body.execution_payload.transactions = [opaque_tx]
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload)
signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True)
yield 'blocks', [signed_block]
yield 'post', None
@with_deneb_and_later
@spec_state_test
def test_invalid_incorrect_transaction_length_1_byte(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec)
block.body.blob_kzg_commitments = blob_kzg_commitments
opaque_tx = opaque_tx + b'\x12' # incorrect tx length
block.body.execution_payload.transactions = [opaque_tx]
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload)
signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True)
yield 'blocks', [signed_block]
yield 'post', None
@with_deneb_and_later
@spec_state_test
def test_invalid_incorrect_transaction_length_32_bytes(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec)
block.body.blob_kzg_commitments = blob_kzg_commitments
opaque_tx = opaque_tx + b'\x12' * 32 # incorrect tx length
block.body.execution_payload.transactions = [opaque_tx]
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload)
signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True)
yield 'blocks', [signed_block]
yield 'post', None
@with_deneb_and_later
@spec_state_test
def test_invalid_incorrect_commitment(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec)
blob_kzg_commitments[0] = b'\x12' * 48 # incorrect commitment
block.body.blob_kzg_commitments = blob_kzg_commitments
block.body.execution_payload.transactions = [opaque_tx]
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload)
signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True)
yield 'blocks', [signed_block]
yield 'post', None
@with_deneb_and_later
@spec_state_test
def test_invalid_incorrect_commitments_order(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec, blob_count=2, rng=random.Random(1111))
block.body.blob_kzg_commitments = [blob_kzg_commitments[1], blob_kzg_commitments[0]] # incorrect order
block.body.execution_payload.transactions = [opaque_tx]
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload)
signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True)
yield 'blocks', [signed_block]
yield 'post', None
@with_deneb_and_later
@spec_state_test
def test_incorrect_block_hash(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec)
block.body.blob_kzg_commitments = blob_kzg_commitments
block.body.execution_payload.transactions = [opaque_tx]
block.body.execution_payload.block_hash = b'\x12' * 32 # incorrect block hash
# CL itself doesn't verify EL block hash
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'blocks', [signed_block]
yield 'post', state
@with_deneb_and_later
@spec_state_test
def test_zeroed_commitment(spec, state):
"""
The blob is invalid, but the commitment is in correct form.
"""
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec, blob_count=1, is_valid_blob=False)
assert all(commitment == b'\x00' * 48 for commitment in blob_kzg_commitments)
block.body.blob_kzg_commitments = blob_kzg_commitments
block.body.execution_payload.transactions = [opaque_tx]
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'blocks', [signed_block]
yield 'post', state

View File

@@ -1,23 +0,0 @@
from eth2spec.test.helpers.constants import (
DENEB,
MINIMAL,
)
from eth2spec.test.helpers.sharding import (
get_sample_opaque_tx,
)
from eth2spec.test.context import (
with_phases,
spec_state_test,
with_presets,
)
@with_phases([DENEB])
@spec_state_test
@with_presets([MINIMAL])
def test_tx_peek_blob_versioned_hashes(spec, state):
otx, _, commitments, _ = get_sample_opaque_tx(spec)
data_hashes = spec.tx_peek_blob_versioned_hashes(otx)
expected = [spec.kzg_commitment_to_versioned_hash(blob_commitment) for blob_commitment in commitments]
assert expected == data_hashes

View File

@@ -2,7 +2,6 @@ from eth2spec.test.context import (
always_bls,
spec_state_test,
with_deneb_and_later,
expect_assertion_error
)
from eth2spec.test.helpers.execution_payload import (
compute_el_block_hash,
@@ -18,96 +17,6 @@ from eth2spec.test.helpers.keys import (
)
@with_deneb_and_later
@spec_state_test
def test_validate_blobs_and_kzg_commitments(spec, state):
"""
Test `validate_blobs_and_kzg_commitments`
"""
blob_count = 4
block = build_empty_block_for_next_slot(spec, state)
opaque_tx, blobs, blob_kzg_commitments, proofs = get_sample_opaque_tx(spec, blob_count=blob_count)
block.body.blob_kzg_commitments = blob_kzg_commitments
block.body.execution_payload.transactions = [opaque_tx]
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload)
spec.validate_blobs_and_kzg_commitments(block.body.execution_payload,
blobs,
blob_kzg_commitments,
proofs)
@with_deneb_and_later
@spec_state_test
def test_validate_blobs_and_kzg_commitments_missing_blob(spec, state):
"""
Test `validate_blobs_and_kzg_commitments`
"""
blob_count = 4
block = build_empty_block_for_next_slot(spec, state)
opaque_tx, blobs, blob_kzg_commitments, proofs = get_sample_opaque_tx(spec, blob_count=blob_count)
block.body.blob_kzg_commitments = blob_kzg_commitments
block.body.execution_payload.transactions = [opaque_tx]
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload)
expect_assertion_error(
lambda: spec.validate_blobs_and_kzg_commitments(
block.body.execution_payload,
blobs[:-1],
blob_kzg_commitments,
proofs
)
)
@with_deneb_and_later
@spec_state_test
def test_validate_blobs_and_kzg_commitments_missing_proof(spec, state):
"""
Test `validate_blobs_and_kzg_commitments`
"""
blob_count = 4
block = build_empty_block_for_next_slot(spec, state)
opaque_tx, blobs, blob_kzg_commitments, proofs = get_sample_opaque_tx(spec, blob_count=blob_count)
block.body.blob_kzg_commitments = blob_kzg_commitments
block.body.execution_payload.transactions = [opaque_tx]
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload)
expect_assertion_error(
lambda: spec.validate_blobs_and_kzg_commitments(
block.body.execution_payload,
blobs,
blob_kzg_commitments,
proofs[:-1]
)
)
@with_deneb_and_later
@spec_state_test
def test_validate_blobs_and_kzg_commitments_incorrect_blob(spec, state):
"""
Test `validate_blobs_and_kzg_commitments`
"""
blob_count = 4
block = build_empty_block_for_next_slot(spec, state)
opaque_tx, blobs, blob_kzg_commitments, proofs = get_sample_opaque_tx(spec, blob_count=blob_count)
block.body.blob_kzg_commitments = blob_kzg_commitments
block.body.execution_payload.transactions = [opaque_tx]
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload)
blobs[1] = spec.Blob(blobs[1][:13] + bytes([(blobs[1][13] + 1) % 256]) + blobs[1][14:])
expect_assertion_error(
lambda: spec.validate_blobs_and_kzg_commitments(
block.body.execution_payload,
blobs,
blob_kzg_commitments,
proofs
)
)
@with_deneb_and_later
@spec_state_test
def test_blob_sidecar_signature(spec, state):

View File

@@ -91,7 +91,7 @@ def add_optimistic_block(spec, mega_store, signed_block, test_steps,
Add a block with optimistic sync logic
``valid`` indicates if the given ``signed_block.message.body.execution_payload`` is valid/invalid
from ``notify_new_payload`` method response.
from ``verify_and_notify_new_payload`` method response.
"""
block = signed_block.message
block_root = block.hash_tree_root()

View File

@@ -143,7 +143,7 @@ def process_and_sign_block_without_header_validations(spec, state, block):
)
if is_post_bellatrix(spec):
if spec.is_execution_enabled(state, block.body):
spec.process_execution_payload(state, block.body.execution_payload, spec.EXECUTION_ENGINE)
spec.process_execution_payload(state, block.body, spec.EXECUTION_ENGINE)
# Perform rest of process_block transitions
spec.process_randao(state, block.body)

View File

@@ -42,7 +42,7 @@ Operations:
| `proposer_slashing` | `ProposerSlashing` | `proposer_slashing` | `process_proposer_slashing(state, proposer_slashing)` |
| `voluntary_exit` | `SignedVoluntaryExit` | `voluntary_exit` | `process_voluntary_exit(state, voluntary_exit)` |
| `sync_aggregate` | `SyncAggregate` | `sync_aggregate` | `process_sync_aggregate(state, sync_aggregate)` (new in Altair) |
| `execution_payload` | `ExecutionPayload` | `execution_payload` | `process_execution_payload(state, execution_payload)` (new in Bellatrix) |
| `execution_payload` | `BeaconBlockBody` | **`body`** | `process_execution_payload(state, body)` (new in Bellatrix) |
| `withdrawals` | `ExecutionPayload` | `execution_payload` | `process_withdrawals(state, execution_payload)` (new in Capella) |
| `bls_to_execution_change` | `SignedBLSToExecutionChange` | `address_change` | `process_bls_to_execution_change(state, address_change)` (new in Capella) |
| `deposit_receipt` | `DepositReceipt` | `deposit_receipt` | `process_deposit_receipt(state, deposit_receipt)` (new in EIP6110) |

View File

@@ -37,7 +37,10 @@ if __name__ == "__main__":
]}
capella_mods = combine_mods(_new_capella_mods, bellatrix_mods)
deneb_mods = capella_mods
_new_deneb_mods = {key: 'eth2spec.test.deneb.block_processing.test_process_' + key for key in [
'execution_payload',
]}
deneb_mods = combine_mods(_new_deneb_mods, capella_mods)
_new_eip6110_mods = {key: 'eth2spec.test.eip6110.block_processing.test_process_' + key for key in [
'deposit_receipt',