Skip to content

Commit

Permalink
Merge pull request #3359 from ethereum/engine-versioned-hashes
Browse files Browse the repository at this point in the history
Use `engine_newPayloadV3` to pass `versioned_hashes` to EL for validation
  • Loading branch information
hwwhww committed May 24, 2023
2 parents 738b981 + 7a82763 commit 2225fd3
Show file tree
Hide file tree
Showing 18 changed files with 392 additions and 332 deletions.
67 changes: 65 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,10 @@ def sundry_functions(cls) -> str:
"""
raise NotImplementedError()

@classmethod
def execution_engine_cls(cls) -> str:
raise NotImplementedError()

@classmethod
@abstractmethod
def hardcoded_ssz_dep_constants(cls) -> Dict[str, str]:
Expand Down Expand Up @@ -469,6 +473,12 @@ def wrapper(*args, **kw): # type: ignore
),
_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 {}
Expand Down Expand Up @@ -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:
Expand All @@ -592,6 +604,13 @@ def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadRespo
# 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()"""

Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
Expand All @@ -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")
Expand Down Expand Up @@ -783,6 +845,7 @@ def format_constant(name: str, vardef: VariableDefinition) -> 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 '')
Expand Down
12 changes: 8 additions & 4 deletions specs/_features/eip6110/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -236,15 +235,20 @@ 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
assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state))
# 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,
Expand Down
56 changes: 50 additions & 6 deletions specs/bellatrix/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:
"""
Expand All @@ -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.
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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,
Expand Down
10 changes: 6 additions & 4 deletions specs/capella/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
Loading

0 comments on commit 2225fd3

Please sign in to comment.