EIP-7732 -- The Beacon Chain
Table of contents
Introduction
This is the beacon chain specification of the enshrined proposer builder separation feature.
Note: This specification is built upon Electra and is under active development.
This feature adds new staked consensus participants called Builders and new honest validators duties called payload timeliness attestations. The slot is divided in four intervals. Honest validators gather signed bids (a SignedExecutionPayloadHeader
) from builders and submit their consensus blocks (a SignedBeaconBlock
) including these bids at the beginning of the slot. At the start of the second interval, honest validators submit attestations just as they do previous to this feature). At the start of the third interval, aggregators aggregate these attestations and the builder broadcasts either a full payload or a message indicating that they are withholding the payload (a SignedExecutionPayloadEnvelope
). At the start of the fourth interval, some validators selected to be members of the new Payload Timeliness Committee (PTC) attest to the presence and timeliness of the builder's payload.
At any given slot, the status of the blockchain's head may be either
- A block from a previous slot (e.g. the current slot's proposer did not submit its block).
- An empty block from the current slot (e.g. the proposer submitted a timely block, but the builder did not reveal the payload on time).
- A full block for the current slot (both the proposer and the builder revealed on time).
Constants
Payload status
Name |
Value |
PAYLOAD_ABSENT |
uint8(0) |
PAYLOAD_PRESENT |
uint8(1) |
PAYLOAD_WITHHELD |
uint8(2) |
PAYLOAD_INVALID_STATUS |
uint8(3) |
Preset
Misc
Name |
Value |
PTC_SIZE |
uint64(2**9) (=512) # (New in EIP-7732) |
Domain types
Name |
Value |
DOMAIN_BEACON_BUILDER |
DomainType('0x1B000000') # (New in EIP-7732) |
DOMAIN_PTC_ATTESTER |
DomainType('0x0C000000') # (New in EIP-7732) |
Max operations per block
Name |
Value |
MAX_PAYLOAD_ATTESTATIONS |
2**2 (= 4) # (New in EIP-7732) |
Containers
New containers
PayloadAttestationData
| class PayloadAttestationData(Container):
beacon_block_root: Root
slot: Slot
payload_status: uint8
|
PayloadAttestation
| class PayloadAttestation(Container):
aggregation_bits: Bitvector[PTC_SIZE]
data: PayloadAttestationData
signature: BLSSignature
|
PayloadAttestationMessage
| class PayloadAttestationMessage(Container):
validator_index: ValidatorIndex
data: PayloadAttestationData
signature: BLSSignature
|
IndexedPayloadAttestation
| class IndexedPayloadAttestation(Container):
attesting_indices: List[ValidatorIndex, PTC_SIZE]
data: PayloadAttestationData
signature: BLSSignature
|
| class SignedExecutionPayloadHeader(Container):
message: ExecutionPayloadHeader
signature: BLSSignature
|
ExecutionPayloadEnvelope
| class ExecutionPayloadEnvelope(Container):
payload: ExecutionPayload
execution_requests: ExecutionRequests
builder_index: ValidatorIndex
beacon_block_root: Root
blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
payload_withheld: boolean
state_root: Root
|
SignedExecutionPayloadEnvelope
| class SignedExecutionPayloadEnvelope(Container):
message: ExecutionPayloadEnvelope
signature: BLSSignature
|
Modified containers
BeaconBlockBody
Note: The Beacon Block body is modified to contain a Signed ExecutionPayloadHeader
. The containers BeaconBlock
and SignedBeaconBlock
are modified indirectly. The field execution_requests
is removed from the beacon block body and moved into the signed execution payload envelope.
| class BeaconBlockBody(Container):
randao_reveal: BLSSignature
eth1_data: Eth1Data # Eth1 data vote
graffiti: Bytes32 # Arbitrary data
# Operations
proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS]
attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS_ELECTRA]
attestations: List[Attestation, MAX_ATTESTATIONS_ELECTRA]
deposits: List[Deposit, MAX_DEPOSITS]
voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
sync_aggregate: SyncAggregate
# Execution
# Removed execution_payload [Removed in EIP-7732]
bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES]
# Removed blob_kzg_commitments [Removed in EIP-7732]
# Removed execution_requests [Removed in EIP-7732]
# PBS
signed_execution_payload_header: SignedExecutionPayloadHeader # [New in EIP-7732]
payload_attestations: List[PayloadAttestation, MAX_PAYLOAD_ATTESTATIONS] # [New in EIP-7732]
|
Note: The ExecutionPayloadHeader
is modified to only contain the block hash of the committed ExecutionPayload
in addition to the builder's payment information, gas limit and KZG commitments root to verify the inclusion proofs.
| class ExecutionPayloadHeader(Container):
parent_block_hash: Hash32
parent_block_root: Root
block_hash: Hash32
gas_limit: uint64
builder_index: ValidatorIndex
slot: Slot
value: Gwei
blob_kzg_commitments_root: Root
|
BeaconState
Note: The BeaconState
is modified to track the last withdrawals honored in the CL. The latest_execution_payload_header
is modified semantically to refer not to a past committed ExecutionPayload
but instead it corresponds to the state's slot builder's bid. Another addition is to track the last committed block hash and the last slot that was full, that is in which there were both consensus and execution blocks included.
| class BeaconState(Container):
# Versioning
genesis_time: uint64
genesis_validators_root: Root
slot: Slot
fork: Fork
# History
latest_block_header: BeaconBlockHeader
block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] # Frozen in Capella, replaced by historical_summaries
# Eth1
eth1_data: Eth1Data
eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]
eth1_deposit_index: uint64
# Registry
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
# Randomness
randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR]
# Slashings
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
# Participation
previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]
current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT]
# Finality
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
previous_justified_checkpoint: Checkpoint
current_justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
# Inactivity
inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT]
# Sync
current_sync_committee: SyncCommittee
next_sync_committee: SyncCommittee
# Execution
latest_execution_payload_header: ExecutionPayloadHeader
# Withdrawals
next_withdrawal_index: WithdrawalIndex
next_withdrawal_validator_index: ValidatorIndex
# Deep history valid from Capella onwards
historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT]
deposit_requests_start_index: uint64
deposit_balance_to_consume: Gwei
exit_balance_to_consume: Gwei
earliest_exit_epoch: Epoch
consolidation_balance_to_consume: Gwei
earliest_consolidation_epoch: Epoch
pending_deposits: List[PendingDeposit, PENDING_DEPOSITS_LIMIT]
pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT]
pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT]
# PBS
latest_block_hash: Hash32 # [New in EIP-7732]
latest_full_slot: Slot # [New in EIP-7732]
latest_withdrawals_root: Root # [New in EIP-7732]
|
Helper functions
Math
bit_floor
| def bit_floor(n: uint64) -> uint64:
"""
if ``n`` is not zero, returns the largest power of `2` that is not greater than `n`.
"""
if n == 0:
return 0
return uint64(1) << (n.bit_length() - 1)
|
Misc
remove_flag
| def remove_flag(flags: ParticipationFlags, flag_index: int) -> ParticipationFlags:
flag = ParticipationFlags(2**flag_index)
return flags & ~flag
|
Predicates
is_valid_indexed_payload_attestation
| def is_valid_indexed_payload_attestation(
state: BeaconState,
indexed_payload_attestation: IndexedPayloadAttestation) -> bool:
"""
Check if ``indexed_payload_attestation`` is not empty, has sorted and unique indices and has
a valid aggregate signature.
"""
# Verify the data is valid
if indexed_payload_attestation.data.payload_status >= PAYLOAD_INVALID_STATUS:
return False
# Verify indices are sorted and unique
indices = indexed_payload_attestation.attesting_indices
if len(indices) == 0 or not indices == sorted(set(indices)):
return False
# Verify aggregate signature
pubkeys = [state.validators[i].pubkey for i in indices]
domain = get_domain(state, DOMAIN_PTC_ATTESTER, None)
signing_root = compute_signing_root(indexed_payload_attestation.data, domain)
return bls.FastAggregateVerify(pubkeys, signing_root, indexed_payload_attestation.signature)
|
is_parent_block_full
This function returns true if the last committed payload header was fulfilled with a payload, this can only happen when both beacon block and payload were present. This function must be called on a beacon state before processing the execution payload header in the block.
| def is_parent_block_full(state: BeaconState) -> bool:
return state.latest_execution_payload_header.block_hash == state.latest_block_hash
|
Beacon State accessors
get_ptc
| def get_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]:
"""
Get the payload timeliness committee for the given ``slot``
"""
epoch = compute_epoch_at_slot(slot)
committees_per_slot = bit_floor(min(get_committee_count_per_slot(state, epoch), PTC_SIZE))
members_per_committee = PTC_SIZE // committees_per_slot
validator_indices: List[ValidatorIndex] = []
for idx in range(committees_per_slot):
beacon_committee = get_beacon_committee(state, slot, CommitteeIndex(idx))
validator_indices += beacon_committee[:members_per_committee]
return validator_indices
|
Modified get_attesting_indices
get_attesting_indices
is modified to ignore PTC votes
| def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]:
"""
Return the set of attesting indices corresponding to ``aggregation_bits`` and ``committee_bits``.
"""
output: Set[ValidatorIndex] = set()
committee_indices = get_committee_indices(attestation.committee_bits)
committee_offset = 0
for index in committee_indices:
committee = get_beacon_committee(state, attestation.data.slot, index)
committee_attesters = set(
index for i, index in enumerate(committee) if attestation.aggregation_bits[committee_offset + i])
output = output.union(committee_attesters)
committee_offset += len(committee)
if compute_epoch_at_slot(attestation.data.slot) < EIP7732_FORK_EPOCH:
return output
ptc = get_ptc(state, attestation.data.slot)
return set(i for i in output if i not in ptc)
|
get_payload_attesting_indices
| def get_payload_attesting_indices(state: BeaconState, slot: Slot,
payload_attestation: PayloadAttestation) -> Set[ValidatorIndex]:
"""
Return the set of attesting indices corresponding to ``payload_attestation``.
"""
ptc = get_ptc(state, slot)
return set(index for i, index in enumerate(ptc) if payload_attestation.aggregation_bits[i])
|
get_indexed_payload_attestation
| def get_indexed_payload_attestation(state: BeaconState, slot: Slot,
payload_attestation: PayloadAttestation) -> IndexedPayloadAttestation:
"""
Return the indexed payload attestation corresponding to ``payload_attestation``.
"""
attesting_indices = get_payload_attesting_indices(state, slot, payload_attestation)
return IndexedPayloadAttestation(
attesting_indices=sorted(attesting_indices),
data=payload_attestation.data,
signature=payload_attestation.signature,
)
|
Beacon chain state transition function
Note: state transition is fundamentally modified in EIP-7732. The full state transition is broken in two parts, first importing a signed block and then importing an execution payload.
The post-state corresponding to a pre-state state
and a signed beacon block signed_block
is defined as state_transition(state, signed_block)
. State transitions that trigger an unhandled exception (e.g. a failed assert
or an out-of-range list access) are considered invalid. State transitions that cause a uint64
overflow or underflow are also considered invalid.
The post-state corresponding to a pre-state state
and a signed execution payload envelope signed_envelope
is defined as process_execution_payload(state, signed_envelope)
. State transitions that trigger an unhandled exception (e.g. a failed assert
or an out-of-range list access) are considered invalid. State transitions that cause an uint64
overflow or underflow are also considered invalid.
Block processing
| def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
process_withdrawals(state) # [Modified in EIP-7732]
# Removed `process_execution_payload` in EIP-7732
process_execution_payload_header(state, block) # [New in EIP-7732]
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body) # [Modified in EIP-7732]
process_sync_aggregate(state, block.body.sync_aggregate)
|
Withdrawals
Modified process_withdrawals
Note: This is modified to take only the state
as parameter. Withdrawals are deterministic given the beacon state, any execution payload that has the corresponding block as parent beacon block is required to honor these withdrawals in the execution layer. This function must be called before process_execution_payload_header
as this latter function affects validator balances.
| def process_withdrawals(state: BeaconState) -> None:
# return early if the parent block was empty
if not is_parent_block_full(state):
return
withdrawals, partial_withdrawals_count = get_expected_withdrawals(state)
withdrawals_list = List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD](withdrawals)
state.latest_withdrawals_root = hash_tree_root(withdrawals_list)
for withdrawal in withdrawals:
decrease_balance(state, withdrawal.validator_index, withdrawal.amount)
# Update pending partial withdrawals
state.pending_partial_withdrawals = state.pending_partial_withdrawals[partial_withdrawals_count:]
# Update the next withdrawal index if this block contained withdrawals
if len(withdrawals) != 0:
latest_withdrawal = withdrawals[-1]
state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1)
# Update the next validator index to start the next withdrawal sweep
if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
# Next sweep starts after the latest withdrawal's validator index
next_validator_index = ValidatorIndex((withdrawals[-1].validator_index + 1) % len(state.validators))
state.next_withdrawal_validator_index = next_validator_index
else:
# Advance sweep by the max length of the sweep if there was not a full set of withdrawals
next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP
next_validator_index = ValidatorIndex(next_index % len(state.validators))
state.next_withdrawal_validator_index = next_validator_index
|
| def verify_execution_payload_header_signature(state: BeaconState,
signed_header: SignedExecutionPayloadHeader) -> bool:
# Check the signature
builder = state.validators[signed_header.message.builder_index]
signing_root = compute_signing_root(signed_header.message, get_domain(state, DOMAIN_BEACON_BUILDER))
return bls.Verify(builder.pubkey, signing_root, signed_header.signature)
|
| def process_execution_payload_header(state: BeaconState, block: BeaconBlock) -> None:
# Verify the header signature
signed_header = block.body.signed_execution_payload_header
assert verify_execution_payload_header_signature(state, signed_header)
# Check that the builder is active non-slashed has funds to cover the bid
header = signed_header.message
builder_index = header.builder_index
builder = state.validators[builder_index]
assert is_active_validator(builder, get_current_epoch(state))
assert not builder.slashed
amount = header.value
assert state.balances[builder_index] >= amount
# Verify that the bid is for the current slot
assert header.slot == block.slot
# Verify that the bid is for the right parent block
assert header.parent_block_hash == state.latest_block_hash
assert header.parent_block_root == block.parent_root
# Transfer the funds from the builder to the proposer
decrease_balance(state, builder_index, amount)
increase_balance(state, block.proposer_index, amount)
# Cache the signed execution payload header
state.latest_execution_payload_header = header
|
Operations
Modified process_operations
Note: process_operations
is modified to process PTC attestations
| def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
# Verify that outstanding deposits are processed up to the maximum number of deposits
assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index)
def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
for operation in operations:
fn(state, operation)
for_ops(body.proposer_slashings, process_proposer_slashing)
for_ops(body.attester_slashings, process_attester_slashing)
for_ops(body.attestations, process_attestation)
for_ops(body.deposits, process_deposit)
for_ops(body.voluntary_exits, process_voluntary_exit)
for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
# Removed `process_deposit_request` in EIP-7732
# Removed `process_withdrawal_request` in EIP-7732
# Removed `process_consolidation_request` in EIP-7732
for_ops(body.payload_attestations, process_payload_attestation) # [New in EIP-7732]
|
Payload Attestations
process_payload_attestation
| def process_payload_attestation(state: BeaconState, payload_attestation: PayloadAttestation) -> None:
# Check that the attestation is for the parent beacon block
data = payload_attestation.data
assert data.beacon_block_root == state.latest_block_header.parent_root
# Check that the attestation is for the previous slot
assert data.slot + 1 == state.slot
# Verify signature
indexed_payload_attestation = get_indexed_payload_attestation(state, data.slot, payload_attestation)
assert is_valid_indexed_payload_attestation(state, indexed_payload_attestation)
if state.slot % SLOTS_PER_EPOCH == 0:
epoch_participation = state.previous_epoch_participation
else:
epoch_participation = state.current_epoch_participation
# Return early if the attestation is for the wrong payload status
payload_was_present = data.slot == state.latest_full_slot
voted_present = data.payload_status == PAYLOAD_PRESENT
proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT
proposer_index = get_beacon_proposer_index(state)
if voted_present != payload_was_present:
# Unset the flags in case they were set by an equivocating ptc attestation
proposer_penalty_numerator = 0
for index in indexed_payload_attestation.attesting_indices:
for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS):
if has_flag(epoch_participation[index], flag_index):
epoch_participation[index] = remove_flag(epoch_participation[index], flag_index)
proposer_penalty_numerator += get_base_reward(state, index) * weight
# Penalize the proposer
proposer_penalty = Gwei(2 * proposer_penalty_numerator // proposer_reward_denominator)
decrease_balance(state, proposer_index, proposer_penalty)
return
# Reward the proposer and set all the participation flags in case of correct attestations
proposer_reward_numerator = 0
for index in indexed_payload_attestation.attesting_indices:
for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS):
if not has_flag(epoch_participation[index], flag_index):
epoch_participation[index] = add_flag(epoch_participation[index], flag_index)
proposer_reward_numerator += get_base_reward(state, index) * weight
# Reward proposer
proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator)
increase_balance(state, proposer_index, proposer_reward)
|
Modified is_merge_transition_complete
is_merge_transition_complete
is modified only for testing purposes to add the blob kzg commitments root for an empty list
| def is_merge_transition_complete(state: BeaconState) -> bool:
header = ExecutionPayloadHeader()
kzgs = List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]()
header.blob_kzg_commitments_root = kzgs.hash_tree_root()
return state.latest_execution_payload_header != header
|
Modified validate_merge_block
validate_merge_block
is modified to use the new signed_execution_payload_header
message in the Beacon Block Body
| def validate_merge_block(block: BeaconBlock) -> None:
"""
Check the parent PoW block of execution payload is a valid terminal PoW block.
Note: Unavailable PoW block(s) may later become available,
and a client software MAY delay a call to ``validate_merge_block``
until the PoW block(s) become available.
"""
if TERMINAL_BLOCK_HASH != Hash32():
# If `TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached.
assert compute_epoch_at_slot(block.slot) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH
assert block.body.signed_execution_payload_header.message.parent_block_hash == TERMINAL_BLOCK_HASH
return
# Modified in EIP-7732
pow_block = get_pow_block(block.body.signed_execution_payload_header.message.parent_block_hash)
# Check if `pow_block` is available
assert pow_block is not None
pow_parent = get_pow_block(pow_block.parent_hash)
# Check if `pow_parent` is available
assert pow_parent is not None
# Check if `pow_block` is a valid terminal PoW block
assert is_valid_terminal_pow_block(pow_block, pow_parent)
|
Execution payload processing
New verify_execution_payload_envelope_signature
| def verify_execution_payload_envelope_signature(
state: BeaconState, signed_envelope: SignedExecutionPayloadEnvelope) -> bool:
builder = state.validators[signed_envelope.message.builder_index]
signing_root = compute_signing_root(signed_envelope.message, get_domain(state, DOMAIN_BEACON_BUILDER))
return bls.Verify(builder.pubkey, signing_root, signed_envelope.signature)
|
New process_execution_payload
Note: process_execution_payload
is now an independent check in state transition. It is called when importing a signed execution payload proposed by the builder of the current slot.
| def process_execution_payload(state: BeaconState,
signed_envelope: SignedExecutionPayloadEnvelope,
execution_engine: ExecutionEngine, verify: bool = True) -> None:
# Verify signature
if verify:
assert verify_execution_payload_envelope_signature(state, signed_envelope)
envelope = signed_envelope.message
payload = envelope.payload
# Cache latest block header state root
previous_state_root = hash_tree_root(state)
if state.latest_block_header.state_root == Root():
state.latest_block_header.state_root = previous_state_root
# Verify consistency with the beacon block
assert envelope.beacon_block_root == hash_tree_root(state.latest_block_header)
# Verify consistency with the committed header
committed_header = state.latest_execution_payload_header
assert envelope.builder_index == committed_header.builder_index
assert committed_header.blob_kzg_commitments_root == hash_tree_root(envelope.blob_kzg_commitments)
if not envelope.payload_withheld:
# Verify the withdrawals root
assert hash_tree_root(payload.withdrawals) == state.latest_withdrawals_root
# Verify the gas_limit
assert committed_header.gas_limit == payload.gas_limit
assert committed_header.block_hash == payload.block_hash
# Verify consistency of the parent hash with respect to the previous execution payload
assert payload.parent_hash == state.latest_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 commitments are under limit
assert len(envelope.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
# Verify the execution payload is valid
versioned_hashes = [kzg_commitment_to_versioned_hash(commitment)
for commitment in envelope.blob_kzg_commitments]
requests = envelope.execution_requests
assert execution_engine.verify_and_notify_new_payload(
NewPayloadRequest(
execution_payload=payload,
versioned_hashes=versioned_hashes,
parent_beacon_block_root=state.latest_block_header.parent_root,
execution_requests=requests,
)
)
# Process Electra operations
def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
for operation in operations:
fn(state, operation)
for_ops(requests.deposit_requests, process_deposit_request)
for_ops(requests.withdrawal_requests, process_withdrawal_request)
for_ops(requests.consolidation_requests, process_consolidation_request)
# Cache the execution payload header and proposer
state.latest_block_hash = payload.block_hash
state.latest_full_slot = state.slot
# Verify the state root
if verify:
assert envelope.state_root == hash_tree_root(state)
|