Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ ethereum-spec-sync = "ethereum_spec_tools.sync:main"
ethereum-spec-new-fork = "ethereum_spec_tools.new_fork.cli:main"
ethereum-spec-patch = "ethereum_spec_tools.patch_tool:main"
ethereum-spec-evm = "ethereum_spec_tools.evm_tools:main"
ethereum-spec-engine = "ethereum_spec_tools.engine_server:main"
whitelist = "ethereum_spec_tools.whitelist:main"

[project.entry-points."docc.plugins"]
Expand Down
52 changes: 52 additions & 0 deletions src/ethereum/forks/amsterdam/execution_engine/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
Interface between the consensus layer and the execution layer.

The consensus layer drives the execution layer through a small set of
methods defined as the `ExecutionEngine` abstraction in the
consensus-specs, carried between clients by the [Engine API]. Each new
beacon block carries an [`ExecutionPayload`] that the execution layer
validates and applies to its state with
[`verify_and_notify_new_payload`].

[Engine API]: https://github.com/ethereum/execution-apis/blob/main/src/engine/amsterdam.md
[`ExecutionPayload`]:
ref:ethereum.forks.amsterdam.execution_engine.types.ExecutionPayload
[`verify_and_notify_new_payload`]:
ref:ethereum.forks.amsterdam.execution_engine.new_payload.verify_and_notify_new_payload
""" # noqa: E501

from .forkchoice_update import notify_forkchoice_updated
from .get_payload import get_payload
from .new_payload import (
is_valid_block_hash,
is_valid_versioned_hashes,
notify_new_payload,
verify_and_notify_new_payload,
)
from .types import (
BlobsBundle,
ExecutionEngine,
ExecutionPayload,
ExecutionRequests,
GetPayloadResponse,
NewPayloadRequest,
PayloadAttributes,
PayloadId,
)

__all__ = [
"BlobsBundle",
"ExecutionEngine",
"ExecutionPayload",
"ExecutionRequests",
"GetPayloadResponse",
"NewPayloadRequest",
"PayloadAttributes",
"PayloadId",
"get_payload",
"is_valid_block_hash",
"is_valid_versioned_hashes",
"notify_forkchoice_updated",
"notify_new_payload",
"verify_and_notify_new_payload",
]
26 changes: 26 additions & 0 deletions src/ethereum/forks/amsterdam/execution_engine/forkchoice_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Forkchoice update and payload build signal.
"""

from typing import Optional

from ethereum.crypto.hash import Hash32

from .types import (
ExecutionEngine,
PayloadAttributes,
PayloadId,
)


def notify_forkchoice_updated(
_chain: ExecutionEngine,
_head_block_hash: Hash32,
_safe_block_hash: Hash32,
_finalized_block_hash: Hash32,
_payload_attributes: Optional[PayloadAttributes],
) -> Optional[PayloadId]:
"""
Notify the execution engine about the latest fork-choice state.
"""
raise NotImplementedError
16 changes: 16 additions & 0 deletions src/ethereum/forks/amsterdam/execution_engine/get_payload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
Payload build storage and retrieval helpers.
"""

from .types import (
GetPayloadResponse,
PayloadId,
)


def get_payload(_payload_id: PayloadId) -> GetPayloadResponse:
"""
Return a prepared payload response for a previously returned
``PayloadId``.
"""
raise NotImplementedError
124 changes: 124 additions & 0 deletions src/ethereum/forks/amsterdam/execution_engine/new_payload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""
Payload verification and execution.
"""

from ethereum_rlp import rlp

from ethereum.crypto.hash import keccak256
from ethereum.exceptions import EthereumException
from ethereum.state import Root

from ..fork import state_transition
from ..fork_types import VersionedHash
from ..transactions import BlobTransaction, decode_transaction
from .requests import ExecutionRequests
from .types import ExecutionEngine, ExecutionPayload, NewPayloadRequest
from .validation_helpers import _payload_block, _payload_header


def is_valid_block_hash(
execution_payload: ExecutionPayload,
parent_beacon_block_root: Root,
execution_requests: ExecutionRequests,
) -> bool:
"""
Return `True` if and only if `execution_payload.block_hash` is
computed correctly.
"""
try:
header = _payload_header(
execution_payload,
parent_beacon_block_root,
execution_requests,
)
except Exception:
# Any decoding or conversion failure means the payload
# cannot produce a valid header.
return False
return keccak256(rlp.encode(header)) == execution_payload.block_hash


def is_valid_versioned_hashes(
new_payload_request: NewPayloadRequest,
) -> bool:
"""
Return `True` if and only if the versioned hashes computed by blob
transactions in `new_payload_request.execution_payload` match
`new_payload_request.versioned_hashes`.
"""
computed_versioned_hashes: list[VersionedHash] = []

try:
for encoded_tx in new_payload_request.execution_payload.transactions:
tx = decode_transaction(encoded_tx)
if isinstance(tx, BlobTransaction):
computed_versioned_hashes.extend(tx.blob_versioned_hashes)
except Exception:
# Any decoding failure means versioned hashes cannot be
# verified.
return False

return tuple(computed_versioned_hashes) == (
new_payload_request.versioned_hashes
)


def notify_new_payload(
chain: ExecutionEngine,
new_payload_request: NewPayloadRequest,
) -> bool:
"""
Execute the payload against the chain head and return `True` if and
only if it forms a valid block.

The payload is converted into a [`Block`] and applied with
[`state_transition`], which appends it to the chain on success.

[`Block`]: ref:ethereum.forks.amsterdam.blocks.Block
[`state_transition`]: ref:ethereum.forks.amsterdam.fork.state_transition
"""
block = _payload_block(
new_payload_request.execution_payload,
new_payload_request.parent_beacon_block_root,
new_payload_request.execution_requests,
)

try:
state_transition(chain, block)
except EthereumException:
return False

return True


def verify_and_notify_new_payload(
chain: ExecutionEngine,
new_payload_request: NewPayloadRequest,
) -> bool:
"""
Validate the payload and, if valid, apply it to the chain.

Mirrors the consensus-layer `verify_and_notify_new_payload` method
of the `ExecutionEngine`: the payload must carry a correctly
computed `block_hash` and matching blob versioned hashes before it
is executed by [`notify_new_payload`].

[`notify_new_payload`]:
ref:ethereum.forks.amsterdam.execution_engine.new_payload.notify_new_payload
""" # noqa: E501
payload = new_payload_request.execution_payload

if b"" in payload.transactions:
return False

if not is_valid_block_hash(
payload,
new_payload_request.parent_beacon_block_root,
new_payload_request.execution_requests,
):
return False

if not is_valid_versioned_hashes(new_payload_request):
return False

return notify_new_payload(chain, new_payload_request)
Loading
Loading