diff --git a/setup.py b/setup.py index b2316ed95..8ef66df23 100644 --- a/setup.py +++ b/setup.py @@ -578,7 +578,7 @@ def get_pow_chain_head() -> PowBlock: class NoopExecutionEngine(ExecutionEngine): - def notify_new_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: + def notify_new_payload(self: ExecutionEngine, new_payload_request: NewPayloadRequest) -> bool: return True def notify_forkchoice_updated(self: ExecutionEngine, diff --git a/specs/_features/eip6110/beacon-chain.md b/specs/_features/eip6110/beacon-chain.md index 708418e1c..c9dccbdeb 100644 --- a/specs/_features/eip6110/beacon-chain.md +++ b/specs/_features/eip6110/beacon-chain.md @@ -178,12 +178,11 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_block_header(state, block) if is_execution_enabled(state, block.body): 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` @@ -238,7 +237,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 if is_merge_transition_complete(state): assert payload.parent_hash == state.latest_execution_payload_header.block_hash @@ -247,7 +248,8 @@ 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.notify_new_payload(NewPayloadRequest(execution_payload=payload)) # Cache execution payload header state.latest_execution_payload_header = ExecutionPayloadHeader( parent_hash=payload.parent_hash, diff --git a/specs/bellatrix/beacon-chain.md b/specs/bellatrix/beacon-chain.md index 1133cba06..a4c7be16e 100644 --- a/specs/bellatrix/beacon-chain.md +++ b/specs/bellatrix/beacon-chain.md @@ -33,6 +33,9 @@ - [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) - [Block processing](#block-processing) - [Execution payload](#execution-payload) @@ -300,6 +303,18 @@ 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` @@ -313,7 +328,7 @@ The Engine API may be used to implement this and similarly defined functions via #### `notify_new_payload` ```python -def notify_new_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: +def notify_new_payload(self: ExecutionEngine, new_payload_request: NewPayloadRequest) -> bool: """ Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``. """ @@ -328,7 +343,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 +355,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 +366,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.notify_new_payload(NewPayloadRequest(execution_payload=payload)) # Cache execution payload header state.latest_execution_payload_header = ExecutionPayloadHeader( parent_hash=payload.parent_hash, diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 1df617daa..1799f4e6e 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -333,7 +333,7 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_block_header(state, block) if is_execution_enabled(state, block.body): 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] @@ -407,7 +407,9 @@ def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None: *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 if is_merge_transition_complete(state): 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.notify_new_payload(NewPayloadRequest(execution_payload=payload)) # Cache execution payload header state.latest_execution_payload_header = ExecutionPayloadHeader( parent_hash=payload.parent_hash, diff --git a/specs/deneb/beacon-chain.md b/specs/deneb/beacon-chain.md index 359c7fc95..52719dcf6 100644 --- a/specs/deneb/beacon-chain.md +++ b/specs/deneb/beacon-chain.md @@ -24,13 +24,15 @@ - [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) + - [Modified `notify_new_payload`](#modified-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) @@ -158,43 +160,33 @@ 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 +### Execution engine + +#### Request data + +##### Modified `NewPayloadRequest` + +```python +@dataclass +class NewPayloadRequest(object): + execution_payload: ExecutionPayload + versioned_hashes: Sequence[VersionedHash] +``` + +#### Engine APIs + +#### Modified `notify_new_payload` + +```python +def notify_new_payload(self: ExecutionEngine, new_payload_request: NewPayloadRequest) -> bool: + """ + Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``. + """ + ... +``` + ### Block processing ```python @@ -202,12 +194,11 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_block_header(state, block) if is_execution_enabled(state, block.body): process_withdrawals(state, block.body.execution_payload) - process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [Modified in Deneb] + process_execution_payload(state, block.body, 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] ``` #### Execution payload @@ -215,7 +206,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 @@ -224,7 +217,8 @@ 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.notify_new_payload(NewPayloadRequest(execution_payload=payload, versioned_hashes=versioned_hashes)) # Cache execution payload header state.latest_execution_payload_header = ExecutionPayloadHeader( @@ -247,13 +241,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. diff --git a/specs/deneb/validator.md b/specs/deneb/validator.md index 6562c91dd..e52ce9eaa 100644 --- a/specs/deneb/validator.md +++ b/specs/deneb/validator.md @@ -98,8 +98,8 @@ 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) + # TODO: can we just remove it? + # 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) diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_execution_payload.py index 3ec58b31e..05e16f1d1 100644 --- a/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_execution_payload.py @@ -29,10 +29,10 @@ def run_execution_payload_processing(spec, state, execution_payload, valid=True, called_new_block = False class TestEngine(spec.NoopExecutionEngine): - def notify_new_payload(self, payload) -> bool: + def 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 == execution_payload return execution_valid if not valid: diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index 2e1a2a369..d31955e82 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -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)