Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pure consensus upgrade version of the merge #2257

Merged
merged 24 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0dec828
Add initial merge spec
mkalinin Mar 11, 2021
ee16163
Polish beacon chain spec and validator guide
mkalinin Mar 11, 2021
f6f3687
Index from GENESIS_SLOT in compute_time_at_slot
mkalinin Mar 17, 2021
3fb5f2e
Use Vector struct for recent_block_roots field
mkalinin Mar 17, 2021
5435324
Add a line break in get_recent_beacon_block_roots
mkalinin Mar 17, 2021
3c9cd85
Fix spelling
mkalinin Mar 17, 2021
a368f5d
Lable Added/Remove notes with Merge explicitly
mkalinin Mar 17, 2021
b8e16c1
Remove min(..., ...) in get_evm_beacon_block_roots
mkalinin Mar 17, 2021
bf15164
Add rebase-to-Altair warning
mkalinin Mar 17, 2021
46fc8a1
Strip down the merge to the pure consensus upgrade
mkalinin Mar 20, 2021
3420e51
Verify transition block to be assembled correctly
mkalinin Mar 20, 2021
24dc8a2
Fix block_body variable in is_transition_block
mkalinin Mar 22, 2021
38a455c
Verify that ApplicationPayload is zeroed before the transition
mkalinin Mar 22, 2021
83453d2
Simplify merge.BeaconState definition
mkalinin Mar 22, 2021
7e6ac4e
Boolean -> boolean
mkalinin Mar 22, 2021
96de910
Distinguish invalid and not processed transition block
mkalinin Mar 22, 2021
ea5f606
Address various cleanups and formatting suggestions
mkalinin Mar 24, 2021
63ae9f2
Standardise PowBlock between fork-choice and validator
mkalinin Mar 24, 2021
ee5ecf8
Address a new portion of comments and fixes
mkalinin Mar 25, 2021
a23bde3
Bytes1 to byte in ApplicationPayload.logs_bloom
mkalinin Mar 25, 2021
260a0a5
Polish merge/fork-choice.md
mkalinin Mar 25, 2021
81a2c2c
Use ByteList[N] and ByteVector[N] types
mkalinin Mar 25, 2021
41a087a
minor edits from code review
djrtwo Mar 26, 2021
223aba3
byte-list for opaque transaction payload
djrtwo Mar 26, 2021
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
185 changes: 185 additions & 0 deletions specs/merge/beacon-chain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# Ethereum 2.0 The Merge

**Warning:** This document is currently based on [Phase 0](../phase0/beacon-chain.md) but will be rebased to [Altair](../altair/beacon-chain.md) once the latter is shipped.

**Notice**: This document is a work-in-progress for researchers and implementers.

## Table of contents

<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Introduction](#introduction)
- [Constants](#constants)
- [Transition](#transition)
- [Execution](#execution)
- [Containers](#containers)
- [Extended containers](#extended-containers)
- [`BeaconBlockBody`](#beaconblockbody)
- [`BeaconState`](#beaconstate)
- [New containers](#new-containers)
- [`Transaction`](#transaction)
- [`ApplicationPayload`](#applicationpayload)
- [Helper functions](#helper-functions)
- [Misc](#misc)
- [`is_transition_completed`](#is_transition_completed)
- [`is_transition_block`](#is_transition_block)
- [Block processing](#block-processing)
- [Application payload processing](#application-payload-processing)
- [`get_application_state`](#get_application_state)
- [`application_state_transition`](#application_state_transition)
- [`process_application_payload`](#process_application_payload)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->

## Introduction

This is a patch implementing the executable beacon chain proposal.
It enshrines application-layer execution and validity as a first class citizen at the core of the beacon chain.

## Constants

### Transition

| Name | Value |
mkalinin marked this conversation as resolved.
Show resolved Hide resolved
| - | - |
| `TRANSITION_TOTAL_DIFFICULTY` | **TBD** |

### Execution

| Name | Value |
| - | - |
| `MAX_BYTES_PER_TRANSACTION_PAYLOAD` | `uint64(2**20)` (= 1,048,576) |
| `MAX_APPLICATION_TRANSACTIONS` | `uint64(2**14)` (= 16,384) |
| `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) |


## Containers

### Extended containers

*Note*: Extended SSZ containers inherit all fields from the parent in the original
order and append any additional fields to the end.

#### `BeaconBlockBody`

*Note*: `BeaconBlockBody` fields remain unchanged other than the addition of `application_payload`.

```python
class BeaconBlockBody(phase0.BeaconBlockBody):
application_payload: ApplicationPayload # [New in Merge] application payload
```

#### `BeaconState`

*Note*: `BeaconState` fields remain unchanged other than addition of `application_state_root` and `application_block_hash`.

```python
class BeaconState(phase0.BeaconState):
# Application-layer
application_state_root: Bytes32 # [New in Merge]
application_block_hash: Bytes32 # [New in Merge]
```

### New containers

#### `Transaction`

Application transaction fields structured as an SSZ object for inclusion in an `ApplicationPayload` contained within a `BeaconBlock`.
djrtwo marked this conversation as resolved.
Show resolved Hide resolved

```python
class Transaction(Container):
nonce: uint64
gas_price: uint256
gas_limit: uint64
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
recipient: Bytes20
value: uint256
data: List[byte, MAX_BYTES_PER_TRANSACTION_PAYLOAD]
v: uint256
r: uint256
s: uint256
```

#### `ApplicationPayload`

The application payload included in a `BeaconBlock`.
djrtwo marked this conversation as resolved.
Show resolved Hide resolved

```python
class ApplicationPayload(Container):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post-London, we'll want to add the BASE FEE here.

block_hash: Bytes32 # Hash of application block
coinbase: Bytes20
state_root: Bytes32
gas_limit: uint64
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
gas_used: uint64
receipt_root: Bytes32
logs_bloom: Vector[byte, BYTES_PER_LOGS_BLOOM]
transactions: List[Transaction, MAX_APPLICATION_TRANSACTIONS]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe define this as transactions: List[Union[Transaction], MAX_APPLICATION_TRANSACTIONS] to add a 4 byte selector to enable different transaction types in the future?

Optimization note: the Union type in SSZ is unused in Eth2 so far. We can parametrize the selector length, to shorten it to a 1 byte selector.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you referring to this EIP https://eips.ethereum.org/EIPS/eip-2718? If yes then I would wait until the EIP is approved and shipped on the Mainnet. The intention is to replicate the application layer structures from those that will be on the Mainnet at the point of merge.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also see #2270, to support Union with a 1-byte selector

```

## Helper functions

### Misc

#### `is_transition_completed`

```python
def is_transition_completed(state: BeaconState) -> boolean:
return state.application_block_hash != Bytes32()
```

#### `is_transition_block`

```python
def is_transition_block(state: BeaconState, block_body: BeaconBlockBody) -> boolean:
return state.application_block_hash == Bytes32() and block_body.application_payload.block_hash != Bytes32()
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
```

### Block processing

```python
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
process_randao(state, block.body)
process_eth1_data(state, block.body)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
process_eth1_data(state, block.body)
process_application_data(state, block.body)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that Eth1Data name will look odd after the merge but we might want to keep it as it this time. The follow up cleanups are going to reduce the follow distance of Eth1Data and it would be a good time to make the renaming too.

Copy link
Contributor

@djrtwo djrtwo Mar 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more of DepositContractData if we are going to rename it.
It is more specific than application layer data

process_operations(state, block.body)
process_application_payload(state, block.body) # [New in Merge]
```

#### Application payload processing

##### `get_application_state`

*Note*: `ApplicationState` class is an abstract class representing ethereum application state.

Let `get_application_state(application_state_root: Bytes32) -> ApplicationState` be the function that given the root hash returns a copy of ethereum application state.
The body of the function is implementation dependent.

##### `application_state_transition`

Let `application_state_transition(application_state: ApplicationState, application_payload: ApplicationPayload) -> None` be the transition function of ethereum application state.
The body of the function is implementation dependent.

*Note*: `application_state_transition` must throw `AssertionError` if either the transition itself or one of the post-transition verifications has failed.
Comment on lines +142 to +154
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need get_application_state() at all? (also see other comment below)

Suggested change
##### `get_application_state`
*Note*: `ApplicationState` class is an abstract class representing ethereum application state.
Let `get_application_state(application_state_root: Bytes32) -> ApplicationState` be the function that given the root hash returns a copy of ethereum application state.
The body of the function is implementation dependent.
##### `application_state_transition`
Let `application_state_transition(application_state: ApplicationState, application_payload: ApplicationPayload) -> None` be the transition function of ethereum application state.
The body of the function is implementation dependent.
*Note*: `application_state_transition` must throw `AssertionError` if either the transition itself or one of the post-transition verifications has failed.
##### `application_state_transition`
Let `application_state_transition(application_state_root: Bytes32, application_payload: ApplicationPayload) -> Bytes32` be the transition function of ethereum application state. This function takes the pre-state associated with `application_state_root`, applies the `application_payload`, and returns the post-state root.
The body of the function is implementation dependent.
*Note*: `application_state_transition` must throw `AssertionError` if either the transition itself or one of the post-transition verifications has failed.


##### `process_application_payload`

```python
def process_application_payload(state: BeaconState, body: BeaconBlockBody) -> None:
"""
Note: This function is designed to be able to be run in parallel with the other `process_block` sub-functions
"""

if is_transition_completed(state):
application_state = get_application_state(state.application_state_root)
application_state_transition(application_state, body.application_payload)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to pass in the randao or some other seed for DIFFICULTY/BLOCKHASH here?

obviously, easiest to just stick them on the application_payload so the eth1 engine doesn't even have to know about this new logic.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I think it would make sense to prepare a consensus bundle that will be passed onto this function with randao mix and further extended by other bits. I'd add it later once we made the decision about difficulty and whether to use randao or not.


djrtwo marked this conversation as resolved.
Show resolved Hide resolved
state.application_state_root = body.application_payload.state_root
Comment on lines +165 to +168
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a need for the Eth2 node to access application_state?
A cleaner approach would be to outsource all the work to application_state_transition(), so that it operates using the application_state_root. (see suggestion for the application_state_transition function)
Also, need to check that whatever is returned by application_state_transition() actually matches with body.application_payload.state_root.

Suggested change
application_state = get_application_state(state.application_state_root)
application_state_transition(application_state, body.application_payload)
state.application_state_root = body.application_payload.state_root
state.application_state_root = application_state_transition(state.application_state_root, body.application_payload)
assert state.application_state_root == body.application_payload.state_root

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely see the value in simplifying this part. But then it would not read like the beacon state and the application state are tightly coupled.

For the root check we can do the following:

application_state = get_application_state(state.application_state_root)
application_state_transition(application_state, body.application_payload)

assert body.application_payload.state_root == get_application_state_root(application_state)

state.application_state_root = body.application_payload.state_root
state.application_block_hash = body.application_payload.block_hash

But that would require yet another abstract function. So, it might really be better to have the following (according to your suggestion):

application_state_root = application_state_transition(state.application_state_root, body.application_payload)

assert application_state_root == body.application_payload.state_root

state.application_state_root = body.application_payload.state_root
state.application_block_hash = body.application_payload.block_hash

The latter form also fits stateless verification.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicking: application_state_root = application_state_transition(...) requires application_state_transition to return Bytes32, but it doesn't align with the naming pattern as beacon state transition function state_transition(...) -> None. So having a get_application_state_root(application_state) makes sense to me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making this call conformant with state_transition(...) was the original intention put behind this extra function call and using application state object rather than the state root.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may also define ApplicationState in a following way

class ApplicationState(Container):
    root: Bytes32

And then add an explicit check of the state root after application state transition

application_state = get_application_state(state.application_state_root)
application_state_transition(application_state, body.application_payload)

assert application_state.root == body.application_payload.state_root

state.application_state_root = body.application_payload.state_root
state.application_block_hash = body.application_payload.block_hash

state.application_block_hash = body.application_payload.block_hash
elif is_transition_block(state, body):
assert body.application_payload == ApplicationPayload(block_hash=body.application_payload.block_hash)
state.application_block_hash = body.application_payload.block_hash
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
else:
assert body.application_payload == ApplicationPayload()
```
116 changes: 116 additions & 0 deletions specs/merge/fork-choice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Ethereum 2.0 The Merge

**Notice**: This document is a work-in-progress for researchers and implementers.

## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Introduction](#introduction)
- [Helpers](#helpers)
- [`PowBlock`](#powblock)
- [`get_pow_block`](#get_pow_block)
- [`is_valid_transition_block`](#is_valid_transition_block)
- [Updated fork-choice handlers](#updated-fork-choice-handlers)
- [`on_block`](#on_block)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->

## Introduction

This is the modification of the fork choice according to the executable beacon chain proposal.

*Note*: It introduces the process of transition from the last PoW block to the first PoS block.

### Helpers

#### `PowBlock`

```python
class PowBlock(Container):
block_hash: Bytes32
is_processed: boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is is_processed just a super set of is_valid?

Do eth1 clients remember if an invalid block has already been processed? or does it just drop it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current behaviour is to return error if either invalid or not yet processed block is requested meaning that these two statuses are indistinguishable. So, if we want this level of granularity then JSON-RPC implementation will have to be adjusted but we probably don't want it. It's worth discussing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do eth1 clients remember if an invalid block has already been processed? or does it just drop it

I think with some configuration it stores invalid blocks to be able to serve debug_getBadBlock

is_valid: boolean
total_difficulty: uint256
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this granularity required?

uint's larger than 64 have been entirely avoided in the consensus-layer so far

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

total difficulty is on the order of 10**22 so it doesn't fit in uint64...

We could reduce precision to avoid uint256. Need to consider our options here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, total difficulty falls into > 2**74 interval today. The other potential way of handling this is to return an offset wrt some absolute total difficulty value. Current block's difficulty is around 2**64, so we indeed may divide total difficulty by 2**20 without loss of generality.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it worth noting that uint256 type is used by Transaction data structure to define several fields.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, right. I don't thnk we'll be able to get around value with 256 granularity

I suppose beacon clients don't actually have to support arithmetic on these TX values because all arithmetic and validations happen in application layer so as long as they can serialize and deserialize, that's enough support

```

#### `get_pow_block`

Let `get_pow_block(hash: Bytes32) -> PowBlock` be the function that given the hash of the PoW block returns its data.

*Note*: The `eth_getBlockByHash` JSON-RPC method does not distinguish invalid blocks from blocks that haven't been processed yet. Either extending this existing method or implementing a new one is required.

#### `is_valid_transition_block`

Used by fork-choice handler, `on_block`.

```python
def is_valid_transition_block(block: PowBlock) -> boolean:
is_total_difficulty_reached = block.total_difficulty >= TRANSITION_TOTAL_DIFFICULTY
return block.is_valid and is_total_difficulty_reached
```

### Updated fork-choice handlers

#### `on_block`

*Note*: The only modification is the addition of the verification of transition block conditions.

```python
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
block = signed_block.message
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably move some of his logic into sub-functions on phase0 so we can have better code reuse. Can do that in a separate pr

# Parent block must be known
assert block.parent_root in store.block_states
# Make a copy of the state to avoid mutability issues
pre_state = copy(store.block_states[block.parent_root])
# Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past.
assert get_current_slot(store) >= block.slot

# Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor)
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
assert block.slot > finalized_slot
# Check block is a descendant of the finalized block at the checkpoint finalized slot
assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root

# [New in Merge]
if is_transition_block(pre_state, block.body):
# Delay consideration of block until PoW block is processed by the PoW node
pow_block = get_pow_block(block.body.application_payload.block_hash)
assert pow_block.is_processed
assert is_valid_transition_block(pow_block)
djrtwo marked this conversation as resolved.
Show resolved Hide resolved

# Check the block is valid and compute the post-state
state = pre_state.copy()
state_transition(state, signed_block, True)
# Add new block to the store
store.blocks[hash_tree_root(block)] = block
# Add new state for this block to the store
store.block_states[hash_tree_root(block)] = state

# Update justified checkpoint
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch:
store.best_justified_checkpoint = state.current_justified_checkpoint
if should_update_justified_checkpoint(store, state.current_justified_checkpoint):
store.justified_checkpoint = state.current_justified_checkpoint

# Update finalized checkpoint
if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
store.finalized_checkpoint = state.finalized_checkpoint

# Potentially update justified if different from store
if store.justified_checkpoint != state.current_justified_checkpoint:
# Update justified if new justified is later than store justified
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
store.justified_checkpoint = state.current_justified_checkpoint
return

# Update justified if store justified is not in chain with finalized checkpoint
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
ancestor_at_finalized_slot = get_ancestor(store, store.justified_checkpoint.root, finalized_slot)
if ancestor_at_finalized_slot != store.finalized_checkpoint.root:
store.justified_checkpoint = state.current_justified_checkpoint
```

70 changes: 70 additions & 0 deletions specs/merge/validator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Ethereum 2.0 The Merge

**Warning:** This document is currently based on [Phase 0](../phase0/validator.md) but will be rebased to [Altair](../altair/validator.md) once the latter is shipped.

**Notice**: This document is a work-in-progress for researchers and implementers.

## Table of contents

<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
- [Block proposal](#block-proposal)
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [Application Payload](#application-payload)
- [`get_pow_chain_head`](#get_pow_chain_head)
- [`produce_application_payload`](#produce_application_payload)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->

## Introduction

This document represents the changes to be made in the code of an "honest validator" to implement executable beacon chain proposal.

## Prerequisites

This document is an extension of the [Phase 0 -- Validator](../phase0/validator.md). All behaviors and definitions defined in the Phase 0 doc carry over unless explicitly noted or overridden.

All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of [The Merge](./beacon-chain.md) are requisite for this document and used throughout. Please see related Beacon Chain doc before continuing and use them as a reference throughout.

## Beacon chain responsibilities

All validator responsibilities remain unchanged other than those noted below. Namely, the transition block handling and the addition of `ApplicationPayload`.

### Block proposal

#### Constructing the `BeaconBlockBody`

##### Application Payload

###### `get_pow_chain_head`

Let `get_pow_chain_head() -> PowBlock` be the function that returns the head of the PoW chain. The body of the function is implementation specific.

###### `produce_application_payload`

Let `produce_application_payload(parent_hash: Bytes32) -> ApplicationPayload` be the function that produces new instance of application payload.
The body of this function is implementation dependent.

* Set `block.body.application_payload = get_application_payload(state)` where:

```python
def get_application_payload(state: BeaconState) -> ApplicationPayload:
if not is_transition_completed(state):
pow_block = get_pow_chain_head()
if pow_block.total_difficulty < TRANSITION_TOTAL_DIFFICULTY:
# Pre-merge, empty payload
return ApplicationPayload()
else:
# Signify merge via last PoW block_hash and an otherwise empty payload
return ApplicationPayload(block_hash=pow_block.block_hash)

# Post-merge, normal payload
application_parent_hash = state.application_block_hash
return produce_application_payload(state.application_block_hash)
```