Note: This document is a work-in-progress for researchers and implementers.
- Introduction
- Types
- Modified
AggregationBits - Modified
Attestations - Modified
AttesterSlashings - Modified
AttestingIndices - Modified
Balances - Modified
BlobKZGCommitments - Modified
BLSToExecutionChanges - Modified
ConsolidationRequests - Modified
DepositRequests - Modified
Deposits - Modified
EpochParticipation - Modified
InactivityScores - Modified
PendingConsolidations - Modified
PendingDeposits - Modified
PendingPartialWithdrawals - Modified
ProposerSlashings - Modified
Transaction - Modified
Transactions - Modified
Validators - Modified
VoluntaryExits - Modified
WithdrawalRequests - Modified
Withdrawals - New
BlockAccessList - New
BuilderDepositRequests - New
BuilderExitRequests - New
BuilderIndex - New
BuilderPendingPayments - New
BuilderPendingWithdrawals - New
Builders - New
ExecutionPayloadAvailability - New
PayloadAttestations - New
PayloadTimelinessCommittee - New
PayloadTimelinessCommitteeIndices - New
PayloadTimelinessCommitteeBits - New
PayloadTimelinessCommitteeWindow
- Modified
- Constants
- Presets
- Configs
- Containers
- Dataclasses
- Helpers
- Predicates
- Misc
- Beacon state accessors
- Modified
get_beacon_proposer_indices - Modified
get_next_sync_committee_indices - Modified
get_attestation_participation_flag_indices - New
get_ptc - New
get_indexed_payload_attestation - New
get_builder_payment_quorum_threshold - New
get_activation_churn_limit - New
get_exit_churn_limit - Modified
get_consolidation_churn_limit - Modified
compute_exit_epoch_and_update_churn
- Modified
- Beacon state mutators
- Beacon chain state transition function
- Modified
process_slot - Epoch processing
- Block processing
- Parent execution payload
- Withdrawals
- New
get_builder_withdrawals - New
get_builder_balance_after_withdrawals - New
get_builders_sweep_withdrawals - Modified
get_expected_withdrawals - Modified
apply_withdrawals - New
update_payload_expected_withdrawals - New
update_builder_pending_withdrawals - New
update_next_withdrawal_builder_index - Modified
process_withdrawals
- New
- Execution payload
- Execution payload bid
- Operations
- Modified
Gloas is a consensus-layer upgrade containing a number of features. Including:
- EIP-7688: Forward compatible consensus data structures
- EIP-7732: Enshrined Proposer-Builder Separation
- EIP-7843: SLOTNUM opcode
- EIP-8070: eth/72 - Sparse Blobpool
- EIP-8045: Exclude slashed validators from proposing
- EIP-8061: Increase exit and consolidation churn
- EIP-8261: Gas Limit Schedule
- EIP-8282: Builder Execution Requests
Note: These EIPs are in draft and may change or be removed. Each link above points to the specific version targeted by this specification, which may differ from the latest published version of the EIPs.
# [Modified in Gloas:EIP7688]
class AggregationBits(ProgressiveBitList):
"""
The participation bits of all committees participating in an attestation,
concatenated in committee order.
"""# [Modified in Gloas:EIP7688]
class Attestations(ProgressiveList[Attestation]):
"""
The attestations included in a beacon block.
"""# [Modified in Gloas:EIP7688]
class AttesterSlashings(ProgressiveList[AttesterSlashing]):
"""
The attester slashings included in a beacon block.
"""# [Modified in Gloas:EIP7688]
class AttestingIndices(ProgressiveList[ValidatorIndex]):
"""
The indices of the validators participating in an attestation.
"""# [Modified in Gloas:EIP7688]
class Balances(ProgressiveList[Gwei]):
"""
The balances of all validators.
"""# [Modified in Gloas:EIP7688]
class BlobKZGCommitments(ProgressiveList[KZGCommitment]):
"""
The KZG commitments to the blobs of a beacon block.
"""# [Modified in Gloas:EIP7688]
class BLSToExecutionChanges(ProgressiveList[SignedBLSToExecutionChange]):
"""
The signed BLS-to-execution credential changes included in a beacon
block.
"""# [Modified in Gloas:EIP7688]
class ConsolidationRequests(ProgressiveList[ConsolidationRequest]):
"""
The consolidation requests pertaining to a single execution payload.
"""# [Modified in Gloas:EIP7688]
class DepositRequests(ProgressiveList[DepositRequest]):
"""
The deposit requests pertaining to a single execution payload.
"""# [Modified in Gloas:EIP7688]
class Deposits(ProgressiveList[Deposit]):
"""
The deposits included in a beacon block.
"""# [Modified in Gloas:EIP7688]
class EpochParticipation(ProgressiveList[ParticipationFlags]):
"""
The participation flags of each validator for an epoch.
"""# [Modified in Gloas:EIP7688]
class InactivityScores(ProgressiveList[Uint64]):
"""
Each validator's inactivity score, tracking missed timely target votes.
"""# [Modified in Gloas:EIP7688]
class PendingConsolidations(ProgressiveList[PendingConsolidation]):
"""
The queue of consolidations awaiting processing.
"""# [Modified in Gloas:EIP7688]
class PendingDeposits(ProgressiveList[PendingDeposit]):
"""
The queue of deposits awaiting processing.
"""# [Modified in Gloas:EIP7688]
class PendingPartialWithdrawals(ProgressiveList[PendingPartialWithdrawal]):
"""
The queue of partial withdrawals awaiting processing.
"""# [Modified in Gloas:EIP7688]
class ProposerSlashings(ProgressiveList[ProposerSlashing]):
"""
The proposer slashings included in a beacon block.
"""# [Modified in Gloas:EIP7688]
class Transaction(ProgressiveList[Byte]):
"""
An opaque execution-layer transaction, either a typed transaction
envelope or a legacy RLP-encoded transaction.
"""# [Modified in Gloas:EIP7688]
class Transactions(ProgressiveList[Transaction]):
"""
A list of execution-layer transactions.
"""# [Modified in Gloas:EIP7688]
class Validators(ProgressiveList[Validator]):
"""
The validator registry.
"""# [Modified in Gloas:EIP7688]
class VoluntaryExits(ProgressiveList[SignedVoluntaryExit]):
"""
The signed voluntary exits included in a beacon block.
"""# [Modified in Gloas:EIP7688]
class WithdrawalRequests(ProgressiveList[WithdrawalRequest]):
"""
The withdrawal requests pertaining to a single execution payload.
"""# [Modified in Gloas:EIP7688]
class Withdrawals(ProgressiveList[Withdrawal]):
"""
A list of withdrawals.
"""class BlockAccessList(ProgressiveList[Byte]):
"""
The serialized block access list of an execution payload.
"""class BuilderDepositRequests(ProgressiveList[BuilderDepositRequest]):
"""
The builder deposit requests pertaining to a single execution payload.
"""class BuilderExitRequests(ProgressiveList[BuilderExitRequest]):
"""
The builder exit requests pertaining to a single execution payload.
"""class BuilderIndex(Uint64):
"""
The index of a builder in the builder registry.
"""class BuilderPendingPayments(Vector[BuilderPendingPayment]):
"""
The pending builder payments of the previous and current epoch.
"""
LENGTH = 2 * SLOTS_PER_EPOCHclass BuilderPendingWithdrawals(ProgressiveList[BuilderPendingWithdrawal]):
"""
The queue of builder withdrawals awaiting processing.
"""class Builders(ProgressiveList[Builder]):
"""
The builder registry.
"""class ExecutionPayloadAvailability(BitVector):
"""
Bits tracking payload availability for recent slots, indexed by slot
modulo ``SLOTS_PER_HISTORICAL_ROOT``.
"""
LENGTH = SLOTS_PER_HISTORICAL_ROOTclass PayloadAttestations(ProgressiveList[PayloadAttestation]):
"""
The payload attestations included in a beacon block.
"""class PayloadTimelinessCommittee(Vector[ValidatorIndex]):
"""
The payload timeliness committee of a slot.
"""
LENGTH = PTC_SIZEclass PayloadTimelinessCommitteeIndices(List[ValidatorIndex]):
"""
The indices of payload timeliness committee members, as a list limited
by the size of the committee.
"""
LIMIT = PTC_SIZEclass PayloadTimelinessCommitteeBits(BitVector):
"""
The participation bits of the payload timeliness committee, one bit per
member in committee order.
"""
LENGTH = PTC_SIZEclass PayloadTimelinessCommitteeWindow(Vector[PayloadTimelinessCommittee]):
"""
A rolling window of payload timeliness committees for the previous,
current, and lookahead epochs.
"""
LENGTH = Uint64(MIN_SEED_LOOKAHEAD + 2) * Uint64(SLOTS_PER_EPOCH)Note: The BUILDER_INDEX_FLAG is a bitwise flag which indicates that a
ValidatorIndex should be treated as a BuilderIndex. This exists so that the
same Withdrawal container can be used for validators and builders.
| Name | Value |
|---|---|
BUILDER_INDEX_FLAG |
Uint64(2**40) |
| Name | Value |
|---|---|
DOMAIN_BEACON_BUILDER |
DomainType("0x0B000000") |
DOMAIN_PTC_ATTESTER |
DomainType("0x0C000000") |
DOMAIN_PROPOSER_PREFERENCES |
DomainType("0x0D000000") |
DOMAIN_BUILDER_DEPOSIT |
DomainType("0x0E000000") |
| Name | Value |
|---|---|
BUILDER_INDEX_SELF_BUILD |
BuilderIndex(UINT64_MAX) |
BUILDER_PAYMENT_THRESHOLD_NUMERATOR |
Uint64(6) |
BUILDER_PAYMENT_THRESHOLD_DENOMINATOR |
Uint64(10) |
| Name | Value |
|---|---|
BUILDER_WITHDRAWAL_PREFIX |
Bytes1("0xB0") |
| Name | Value |
|---|---|
PAYLOAD_BUILDER_VERSION |
Uint8(0) |
| Name | Value |
|---|---|
BUILDER_DEPOSIT_REQUEST_TYPE |
Bytes1("0x03") |
BUILDER_EXIT_REQUEST_TYPE |
Bytes1("0x04") |
| Name | Value |
|---|---|
PTC_SIZE |
Uint64(2**9) (= 512) |
| Name | Value |
|---|---|
MAX_PAYLOAD_ATTESTATIONS |
Uint64(2**2) (= 4) |
| Name | Value |
|---|---|
MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD |
Uint64(2**6) (= 64) |
MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD |
Uint64(2**4) (= 16) |
| Name | Value |
|---|---|
MAX_BUILDERS_PER_WITHDRAWALS_SWEEP |
Uint64(2**14) (= 16,384) |
| Name | Value |
|---|---|
CHURN_LIMIT_QUOTIENT_GLOAS |
Uint64(2**15) (= 32,768) |
CONSOLIDATION_CHURN_LIMIT_QUOTIENT |
Uint64(2**16) (= 65,536) |
MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GLOAS |
Gwei(2**8 * 10**9) (= 256,000,000,000) |
| Name | Value |
|---|---|
MIN_BUILDER_WITHDRAWABILITY_DELAY |
Epoch(2**6) (= 64) |
[New in Gloas:EIP8261] This schedule defines the default and recommended maximum gas limit for a given epoch. The field is optional: clients that do not support it ignore it, and it introduces no new validity rules.
There MUST NOT exist multiple gas limit schedule entries with the same epoch
value. The epoch value in each entry MUST be greater than or equal to
GLOAS_FORK_EPOCH. The gas limit schedule entries SHOULD be sorted by epoch in
ascending order. The gas limit schedule MAY be empty.
| Epoch | Gas Limit | Date |
|---|
class Builder(Container):
pubkey: BLSPubkey
version: Uint8
execution_address: ExecutionAddress
balance: Gwei
deposit_epoch: Epoch
withdrawable_epoch: Epochclass BuilderPendingPayment(Container):
weight: Gwei
withdrawal: BuilderPendingWithdrawal
proposer_index: ValidatorIndexclass BuilderPendingWithdrawal(Container):
fee_recipient: ExecutionAddress
amount: Gwei
builder_index: BuilderIndexclass BuilderDepositRequest(Container):
pubkey: BLSPubkey
withdrawal_credentials: Bytes32
amount: Gwei
signature: BLSSignatureclass BuilderExitRequest(Container):
source_address: ExecutionAddress
pubkey: BLSPubkeyclass PayloadAttestationData(Container):
beacon_block_root: Root
slot: Slot
payload_present: Boolean
blob_data_available: Booleanclass PayloadAttestation(ProgressiveContainer):
ACTIVE_FIELDS = active_fields(width=3)
aggregation_bits: PayloadTimelinessCommitteeBits
data: PayloadAttestationData
signature: BLSSignatureclass PayloadAttestationMessage(Container):
validator_index: ValidatorIndex
data: PayloadAttestationData
signature: BLSSignatureclass IndexedPayloadAttestation(ProgressiveContainer):
ACTIVE_FIELDS = active_fields(width=3)
attesting_indices: PayloadTimelinessCommitteeIndices
data: PayloadAttestationData
signature: BLSSignatureclass ExecutionPayloadBid(ProgressiveContainer):
ACTIVE_FIELDS = active_fields(width=12)
parent_block_hash: Hash32
parent_block_root: Root
block_hash: Hash32
prev_randao: Bytes32
fee_recipient: ExecutionAddress
gas_limit: Uint64
builder_index: BuilderIndex
slot: Slot
value: Gwei
execution_payment: Gwei
blob_kzg_commitments: BlobKZGCommitments
execution_requests_root: Rootclass SignedExecutionPayloadBid(Container):
message: ExecutionPayloadBid
signature: BLSSignatureclass ExecutionPayloadEnvelope(ProgressiveContainer):
ACTIVE_FIELDS = active_fields(width=5)
payload: ExecutionPayload
execution_requests: ExecutionRequests
builder_index: BuilderIndex
beacon_block_root: Root
parent_beacon_block_root: Rootclass SignedExecutionPayloadEnvelope(Container):
message: ExecutionPayloadEnvelope
signature: BLSSignature# [Modified in Gloas:EIP7688]
class Attestation(ProgressiveContainer):
ACTIVE_FIELDS = active_fields(width=4)
aggregation_bits: AggregationBits
data: AttestationData
signature: BLSSignature
committee_bits: CommitteeBits# [Modified in Gloas:EIP7688]
class IndexedAttestation(ProgressiveContainer):
ACTIVE_FIELDS = active_fields(width=3)
attesting_indices: AttestingIndices
data: AttestationData
signature: BLSSignatureNote: The removed fields (execution_payload, blob_kzg_commitments, and
execution_requests) now exist in ExecutionPayloadEnvelope.
# [Modified in Gloas:EIP7688]
class BeaconBlockBody(ProgressiveContainer):
ACTIVE_FIELDS = active_fields(width=13)
randao_reveal: BLSSignature
eth1_data: Eth1Data
graffiti: Bytes32
# [Modified in Gloas:EIP7688]
proposer_slashings: ProposerSlashings
# [Modified in Gloas:EIP7688]
attester_slashings: AttesterSlashings
# [Modified in Gloas:EIP7688]
attestations: Attestations
# [Modified in Gloas:EIP7688]
deposits: Deposits
# [Modified in Gloas:EIP7688]
voluntary_exits: VoluntaryExits
sync_aggregate: SyncAggregate
# [Modified in Gloas:EIP7732]
# Removed `execution_payload`
# [Modified in Gloas:EIP7688]
bls_to_execution_changes: BLSToExecutionChanges
# [Modified in Gloas:EIP7732]
# Removed `blob_kzg_commitments`
# [Modified in Gloas:EIP7732]
# Removed `execution_requests`
# [New in Gloas:EIP7732]
signed_execution_payload_bid: SignedExecutionPayloadBid
# [New in Gloas:EIP7732]
payload_attestations: PayloadAttestations
# [New in Gloas:EIP7732]
parent_execution_requests: ExecutionRequests# [Modified in Gloas:EIP7688]
class BeaconState(ProgressiveContainer):
ACTIVE_FIELDS = active_fields(width=46)
genesis_time: Uint64
genesis_validators_root: Root
slot: Slot
fork: Fork
latest_block_header: BeaconBlockHeader
block_roots: BlockRoots
state_roots: StateRoots
historical_roots: HistoricalRoots
eth1_data: Eth1Data
eth1_data_votes: Eth1DataVotes
eth1_deposit_index: Uint64
# [Modified in Gloas:EIP7688]
validators: Validators
# [Modified in Gloas:EIP7688]
balances: Balances
randao_mixes: RandaoMixes
slashings: Slashings
# [Modified in Gloas:EIP7688]
previous_epoch_participation: EpochParticipation
# [Modified in Gloas:EIP7688]
current_epoch_participation: EpochParticipation
justification_bits: JustificationBits
previous_justified_checkpoint: Checkpoint
current_justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
# [Modified in Gloas:EIP7688]
inactivity_scores: InactivityScores
current_sync_committee: SyncCommittee
next_sync_committee: SyncCommittee
# [Modified in Gloas:EIP7732]
# Removed `latest_execution_payload_header`
# [New in Gloas:EIP7732]
latest_block_hash: Hash32
next_withdrawal_index: WithdrawalIndex
next_withdrawal_validator_index: ValidatorIndex
historical_summaries: HistoricalSummaries
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
# [Modified in Gloas:EIP7688]
pending_deposits: PendingDeposits
# [Modified in Gloas:EIP7688]
pending_partial_withdrawals: PendingPartialWithdrawals
# [Modified in Gloas:EIP7688]
pending_consolidations: PendingConsolidations
proposer_lookahead: ProposerLookahead
# [New in Gloas:EIP7732]
builders: Builders
# [New in Gloas:EIP7732]
next_withdrawal_builder_index: BuilderIndex
# [New in Gloas:EIP7732]
execution_payload_availability: ExecutionPayloadAvailability
# [New in Gloas:EIP7732]
builder_pending_payments: BuilderPendingPayments
# [New in Gloas:EIP7732]
builder_pending_withdrawals: BuilderPendingWithdrawals
# [New in Gloas:EIP7732]
latest_execution_payload_bid: ExecutionPayloadBid
# [New in Gloas:EIP7732]
payload_expected_withdrawals: Withdrawals
# [New in Gloas:EIP7732]
ptc_window: PayloadTimelinessCommitteeWindow# [Modified in Gloas:EIP7688]
class ExecutionPayload(ProgressiveContainer):
ACTIVE_FIELDS = active_fields(width=19)
parent_hash: Hash32
fee_recipient: ExecutionAddress
state_root: Bytes32
receipts_root: Bytes32
logs_bloom: LogsBloom
prev_randao: Bytes32
block_number: Uint64
gas_limit: Uint64
gas_used: Uint64
timestamp: Uint64
extra_data: ExtraData
base_fee_per_gas: Uint256
block_hash: Hash32
# [Modified in Gloas:EIP7688]
transactions: Transactions
# [Modified in Gloas:EIP7688]
withdrawals: Withdrawals
blob_gas_used: Uint64
excess_blob_gas: Uint64
# [New in Gloas:EIP7928]
block_access_list: BlockAccessList
# [New in Gloas:EIP7843]
slot_number: Uint64# [Modified in Gloas:EIP7688]
class ExecutionRequests(ProgressiveContainer):
ACTIVE_FIELDS = active_fields(width=5)
deposits: DepositRequests
withdrawals: WithdrawalRequests
consolidations: ConsolidationRequests
# [New in Gloas:EIP8282]
builder_deposits: BuilderDepositRequests
# [New in Gloas:EIP8282]
builder_exits: BuilderExitRequests# [Modified in Gloas:EIP7688]
class NewPayloadRequest(ProgressiveContainer):
ACTIVE_FIELDS = active_fields(width=4)
execution_payload: ExecutionPayload
versioned_hashes: VersionedHashes
parent_beacon_block_root: Root
execution_requests: ExecutionRequests@dataclass
class ExpectedWithdrawals:
withdrawals: Sequence[Withdrawal]
# [New in Gloas:EIP7732]
processed_builder_withdrawals_count: Uint64
processed_partial_withdrawals_count: Uint64
# [New in Gloas:EIP7732]
processed_builders_sweep_count: Uint64
processed_sweep_withdrawals_count: Uint64def is_valid_indexed_attestation(
state: BeaconState, indexed_attestation: IndexedAttestation
) -> bool:
"""
Check if ``indexed_attestation`` is not empty, has sorted and unique indices and has a valid aggregate signature.
"""
# Verify indices are sorted and unique
indices = indexed_attestation.attesting_indices
if (
len(indices) == 0
# [New in Gloas:EIP7688]
or len(indices) > MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT
or list(indices) != sorted(set(indices))
):
return False
# Verify aggregate signature
pubkeys = [state.validators[index].pubkey for index in indices]
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch)
signing_root = compute_signing_root(indexed_attestation.data, domain)
return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature)def is_builder_index(validator_index: ValidatorIndex) -> bool:
return (validator_index & BUILDER_INDEX_FLAG) != 0def is_active_builder(state: BeaconState, builder_index: BuilderIndex) -> bool:
"""
Check if the builder at ``builder_index`` is active for the given ``state``.
"""
builder = state.builders[builder_index]
return (
# Placement in builder list is finalized
builder.deposit_epoch < state.finalized_checkpoint.epoch
# Has not initiated exit
and builder.withdrawable_epoch == FAR_FUTURE_EPOCH
)def is_builder_withdrawal_credential(withdrawal_credentials: Bytes32) -> bool:
return withdrawal_credentials[:1] == BUILDER_WITHDRAWAL_PREFIXdef is_attestation_same_slot(state: BeaconState, data: AttestationData) -> bool:
"""
Check if the attestation is for the block proposed at the attestation slot.
"""
if data.slot == 0:
return True
blockroot = data.beacon_block_root
slot_blockroot = get_block_root_at_slot(state, data.slot)
prev_blockroot = get_block_root_at_slot(state, data.slot - 1)
return blockroot == slot_blockroot and blockroot != prev_blockrootdef is_valid_indexed_payload_attestation(
state: BeaconState, attestation: IndexedPayloadAttestation
) -> bool:
"""
Check if ``attestation`` is non-empty, has sorted indices, and has
a valid aggregate signature.
"""
# Verify indices are non-empty and sorted
indices = attestation.attesting_indices
if len(indices) == 0 or list(indices) != sorted(indices):
return False
# Verify aggregate signature
pubkeys = [state.validators[index].pubkey for index in indices]
domain = get_domain(state, DOMAIN_PTC_ATTESTER, compute_epoch_at_slot(attestation.data.slot))
signing_root = compute_signing_root(attestation.data, domain)
return bls.FastAggregateVerify(pubkeys, signing_root, attestation.signature)Note: This function naively revalidates deposit signatures on every call. Implementations SHOULD cache verification results to avoid repeated work.
def is_pending_validator(pending_deposits: Sequence[PendingDeposit], pubkey: BLSPubkey) -> bool:
"""
Check if a pending deposit with a valid signature is in the queue for the given pubkey.
"""
for pending_deposit in pending_deposits:
if pending_deposit.pubkey != pubkey:
continue
if is_valid_deposit_signature(
pending_deposit.pubkey,
pending_deposit.withdrawal_credentials,
pending_deposit.amount,
pending_deposit.signature,
):
return True
return Falsedef convert_builder_index_to_validator_index(builder_index: BuilderIndex) -> ValidatorIndex:
return ValidatorIndex(builder_index | BUILDER_INDEX_FLAG)def convert_validator_index_to_builder_index(validator_index: ValidatorIndex) -> BuilderIndex:
return BuilderIndex(validator_index & ~BUILDER_INDEX_FLAG)def get_scheduled_gas_limit(epoch: Epoch) -> Uint64 | None:
"""
Return the scheduled gas limit at a given epoch, if any.
"""
for entry in sorted(GAS_LIMIT_SCHEDULE, key=lambda e: e["EPOCH"], reverse=True):
if epoch >= entry["EPOCH"]:
return entry["GAS_LIMIT"]
return Nonedef get_pending_balance_to_withdraw_for_builder(
state: BeaconState, builder_index: BuilderIndex
) -> Gwei:
balance = Gwei(0)
for withdrawal in state.builder_pending_withdrawals:
if withdrawal.builder_index == builder_index:
balance += withdrawal.amount
for payment in state.builder_pending_payments:
if payment.withdrawal.builder_index == builder_index:
balance += payment.withdrawal.amount
return balancedef can_builder_cover_bid(
state: BeaconState, builder_index: BuilderIndex, bid_amount: Gwei
) -> bool:
builder_balance = state.builders[builder_index].balance
pending_withdrawals_amount = get_pending_balance_to_withdraw_for_builder(state, builder_index)
min_balance = MIN_DEPOSIT_AMOUNT + pending_withdrawals_amount
if builder_balance < min_balance:
return False
return builder_balance - min_balance >= bid_amountdef compute_balance_weighted_selection(
state: BeaconState,
indices: Sequence[ValidatorIndex],
seed: Bytes32,
size: Uint64,
shuffle_indices: bool,
) -> Sequence[ValidatorIndex]:
"""
Return ``size`` indices sampled by effective balance, using ``indices``
as candidates. If ``shuffle_indices`` is ``True``, candidate indices
are themselves sampled from ``indices`` by shuffling it, otherwise
``indices`` is traversed in order. The returned list can contain duplicates.
"""
MAX_RANDOM_VALUE = 2**16 - 1
total = Uint64(len(indices))
assert total > 0
effective_balances = [state.validators[index].effective_balance for index in indices]
selected: list[ValidatorIndex] = []
i = Uint64(0)
while len(selected) < size:
offset = i % 16 * 2
if offset == 0:
random_bytes = sha256(seed + uint_to_bytes(i // 16))
next_index = i % total
if shuffle_indices:
next_index = compute_shuffled_index(next_index, total, seed)
weight = effective_balances[next_index] * MAX_RANDOM_VALUE
random_value = bytes_to_uint64(random_bytes[offset : offset + 2])
threshold = MAX_EFFECTIVE_BALANCE_ELECTRA * random_value
if weight >= threshold:
selected.append(indices[next_index])
i += 1
return selectedNote: compute_proposer_indices is modified to use
compute_balance_weighted_selection as a helper for the balance-weighted
sampling process.
def compute_proposer_indices(
state: BeaconState, epoch: Epoch, seed: Bytes32, indices: Sequence[ValidatorIndex]
) -> ProposerIndices:
"""
Return the proposer indices for the given ``epoch``.
"""
start_slot = compute_start_slot_at_epoch(epoch)
seeds = [sha256(seed + uint_to_bytes(start_slot + index)) for index in range(SLOTS_PER_EPOCH)]
# [Modified in Gloas:EIP7732]
return ProposerIndices(
data=[
compute_balance_weighted_selection(
state,
indices,
seed,
size=Uint64(1),
shuffle_indices=True,
)[0]
for seed in seeds
]
)def compute_ptc(state: BeaconState, slot: Slot) -> PayloadTimelinessCommittee:
"""
Get the payload timeliness committee, with possible duplicates, for the given ``slot``.
"""
epoch = compute_epoch_at_slot(slot)
seed = sha256(get_seed(state, epoch, DOMAIN_PTC_ATTESTER) + uint_to_bytes(slot))
indices: list[ValidatorIndex] = []
# Concatenate all committees for this slot in order
committees_per_slot = get_committee_count_per_slot(state, epoch)
for i in range(committees_per_slot):
committee = get_beacon_committee(state, slot, CommitteeIndex(i))
indices.extend(committee)
return PayloadTimelinessCommittee(
data=compute_balance_weighted_selection(
state, indices, seed, size=PTC_SIZE, shuffle_indices=False
)
)Note: get_beacon_proposer_indices is modified to exclude slashed validators
from the candidate pool before invoking compute_proposer_indices, so the newly
computed proposer indices only contain active and unslashed validators.
def get_beacon_proposer_indices(state: BeaconState, epoch: Epoch) -> ProposerIndices:
"""
Return the proposer indices for the given ``epoch``.
"""
# [Modified in Gloas:EIP8045]
indices = [
index
for index in get_active_validator_indices(state, epoch)
if not state.validators[index].slashed
]
seed = get_seed(state, epoch, DOMAIN_BEACON_PROPOSER)
return compute_proposer_indices(state, epoch, seed, indices)Note: get_next_sync_committee_indices is modified to use
compute_balance_weighted_selection as a helper for the balance-weighted
sampling process.
def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]:
"""
Return the sync committee indices, with possible duplicates, for the next sync committee.
"""
epoch = get_current_epoch(state) + 1
seed = get_seed(state, epoch, DOMAIN_SYNC_COMMITTEE)
indices = get_active_validator_indices(state, epoch)
return compute_balance_weighted_selection(
state, indices, seed, size=SYNC_COMMITTEE_SIZE, shuffle_indices=True
)Note: The function get_attestation_participation_flag_indices is modified to
include a new payload matching constraint to is_matching_head. The new
parent_slot parameter is the slot of the parent block of the block being
processed. Since the timely head flag requires both the minimum inclusion delay
and a head root that matches the block root at the attestation slot, an
attestation can only receive the flag if it attests to the parent block. The
payload availability of the attested block is therefore tracked at
parent_slot, even when data.slot is a skipped slot.
def get_attestation_participation_flag_indices(
state: BeaconState,
data: AttestationData,
inclusion_delay: Uint64,
# [New in Gloas:EIP7732]
parent_slot: Slot,
) -> Sequence[int]:
"""
Return the flag indices that are satisfied by an attestation.
"""
# Matching source
if data.target.epoch == get_current_epoch(state):
justified_checkpoint = state.current_justified_checkpoint
else:
justified_checkpoint = state.previous_justified_checkpoint
is_matching_source = data.source == justified_checkpoint
# Matching target
target_root = get_block_root(state, data.target.epoch)
target_root_matches = data.target.root == target_root
is_matching_target = is_matching_source and target_root_matches
# [New in Gloas:EIP7732]
if is_attestation_same_slot(state, data):
assert data.index == 0
payload_matches = True
else:
slot_index = parent_slot % SLOTS_PER_HISTORICAL_ROOT
payload_index = state.execution_payload_availability[slot_index]
payload_matches = Uint64(data.index) == Uint64(payload_index)
# Matching head
head_root = get_block_root_at_slot(state, data.slot)
head_root_matches = data.beacon_block_root == head_root
# [Modified in Gloas:EIP7732]
is_matching_head = is_matching_target and head_root_matches and payload_matches
assert is_matching_source
participation_flag_indices = []
if is_matching_source and inclusion_delay <= integer_squareroot(SLOTS_PER_EPOCH):
participation_flag_indices.append(TIMELY_SOURCE_FLAG_INDEX)
if is_matching_target:
participation_flag_indices.append(TIMELY_TARGET_FLAG_INDEX)
if is_matching_head and inclusion_delay == MIN_ATTESTATION_INCLUSION_DELAY:
participation_flag_indices.append(TIMELY_HEAD_FLAG_INDEX)
return participation_flag_indicesdef get_ptc(state: BeaconState, slot: Slot) -> PayloadTimelinessCommittee:
"""
Get the payload timeliness committee for the given ``slot``.
"""
epoch = compute_epoch_at_slot(slot)
assert epoch >= GLOAS_FORK_EPOCH
state_epoch = get_current_epoch(state)
if epoch < state_epoch:
assert epoch + 1 == state_epoch
return state.ptc_window[slot % SLOTS_PER_EPOCH]
assert epoch <= state_epoch + MIN_SEED_LOOKAHEAD
offset = Uint64(epoch - state_epoch + 1) * SLOTS_PER_EPOCH
return state.ptc_window[offset + slot % SLOTS_PER_EPOCH]def get_indexed_payload_attestation(
state: BeaconState, payload_attestation: PayloadAttestation
) -> IndexedPayloadAttestation:
"""
Return the indexed payload attestation corresponding to ``payload_attestation``.
"""
slot = payload_attestation.data.slot
ptc = get_ptc(state, slot)
bits = payload_attestation.aggregation_bits
attesting_indices = [
validator_index for index, validator_index in enumerate(ptc) if bits[index]
]
return IndexedPayloadAttestation(
attesting_indices=PayloadTimelinessCommitteeIndices(data=sorted(attesting_indices)),
data=payload_attestation.data,
signature=payload_attestation.signature,
)def get_builder_payment_quorum_threshold(state: BeaconState) -> Gwei:
"""
Calculate the quorum threshold for builder payments.
"""
per_slot_balance = get_total_active_balance(state) // Uint64(SLOTS_PER_EPOCH)
quorum = per_slot_balance * BUILDER_PAYMENT_THRESHOLD_NUMERATOR
return quorum // BUILDER_PAYMENT_THRESHOLD_DENOMINATORdef get_activation_churn_limit(state: BeaconState) -> Gwei:
"""
Per-epoch churn limit for activations, rounded to
``EFFECTIVE_BALANCE_INCREMENT``.
"""
churn = max(
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA,
get_total_active_balance(state) // CHURN_LIMIT_QUOTIENT_GLOAS,
)
churn = churn - churn % EFFECTIVE_BALANCE_INCREMENT
return min(MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GLOAS, churn)def get_exit_churn_limit(state: BeaconState) -> Gwei:
"""
Per-epoch churn limit for exits, rounded to
``EFFECTIVE_BALANCE_INCREMENT``.
"""
churn = max(
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA,
get_total_active_balance(state) // CHURN_LIMIT_QUOTIENT_GLOAS,
)
return churn - churn % EFFECTIVE_BALANCE_INCREMENTNote: Consolidation churn is now independently derived from the total active
balance using CONSOLIDATION_CHURN_LIMIT_QUOTIENT.
def get_consolidation_churn_limit(state: BeaconState) -> Gwei:
"""
Per-epoch churn limit reserved for consolidations (EIP-7521).
Derived from total active balance and rounded to
``EFFECTIVE_BALANCE_INCREMENT``.
"""
churn = get_total_active_balance(state) // CONSOLIDATION_CHURN_LIMIT_QUOTIENT
return churn - churn % EFFECTIVE_BALANCE_INCREMENTNote: Exit processing now uses the uncapped exit churn, while deposit
processing remains capped by get_activation_churn_limit.
def compute_exit_epoch_and_update_churn(state: BeaconState, exit_balance: Gwei) -> Epoch:
earliest_exit_epoch = max(
state.earliest_exit_epoch, compute_activation_exit_epoch(get_current_epoch(state))
)
# [Modified in Gloas:EIP8061]
per_epoch_churn = get_exit_churn_limit(state)
# New epoch for exits.
if state.earliest_exit_epoch < earliest_exit_epoch:
exit_balance_to_consume = per_epoch_churn
else:
exit_balance_to_consume = state.exit_balance_to_consume
# Exit doesn't fit in the current earliest epoch.
if exit_balance > exit_balance_to_consume:
balance_to_process = exit_balance - exit_balance_to_consume
additional_epochs = (balance_to_process - 1) // per_epoch_churn + 1
earliest_exit_epoch += Epoch(additional_epochs)
exit_balance_to_consume += additional_epochs * per_epoch_churn
# Consume the balance and update state variables.
state.exit_balance_to_consume = exit_balance_to_consume - exit_balance
state.earliest_exit_epoch = earliest_exit_epoch
return state.earliest_exit_epochdef initiate_builder_exit(state: BeaconState, builder_index: BuilderIndex) -> None:
"""
Initiate the exit of the builder with index ``index``.
"""
# Set builder exit epoch
builder = state.builders[builder_index]
builder.withdrawable_epoch = get_current_epoch(state) + MIN_BUILDER_WITHDRAWABILITY_DELAYdef settle_builder_payment(state: BeaconState, payment_index: Uint64) -> None:
assert payment_index < len(state.builder_pending_payments)
payment = state.builder_pending_payments[payment_index]
if payment.withdrawal.amount > 0:
state.builder_pending_withdrawals.append(payment.withdrawal)
state.builder_pending_payments[payment_index] = BuilderPendingPayment.empty()def slash_validator(
state: BeaconState,
slashed_index: ValidatorIndex,
whistleblower_index: ValidatorIndex | None = None,
) -> None:
"""
Slash the validator with index ``slashed_index``.
"""
epoch = get_current_epoch(state)
initiate_validator_exit(state, slashed_index)
validator = state.validators[slashed_index]
validator.slashed = Boolean(True)
validator.withdrawable_epoch = max(
validator.withdrawable_epoch, epoch + EPOCHS_PER_SLASHINGS_VECTOR
)
state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance
slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA
decrease_balance(state, slashed_index, slashing_penalty)
# Apply proposer and whistleblower rewards
proposer_index = get_beacon_proposer_index(state)
if whistleblower_index is None:
whistleblower_index = proposer_index
whistleblower_reward = validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA
proposer_reward = whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR
increase_balance(state, proposer_index, proposer_reward)
increase_balance(state, whistleblower_index, whistleblower_reward - proposer_reward)
# [New in Gloas:EIP7732]
# Remove pending builder payments for blocks proposed by the slashed validator
for payment_index, payment in enumerate(state.builder_pending_payments):
if payment.proposer_index == slashed_index:
state.builder_pending_payments[payment_index] = BuilderPendingPayment.empty()State transition is fundamentally modified in Gloas. 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 validity of a signed execution payload envelope signed_envelope against a
pre-state state is checked by
verify_execution_payload_envelope(state, signed_envelope, execution_engine).
Payload processing is deferred to the next beacon block via
process_parent_execution_payload. Payloads that trigger an unhandled exception
(e.g. a failed assert or an out-of-range list access) are considered invalid.
Payloads that cause a Uint64 overflow or underflow are also considered
invalid.
def process_slot(state: BeaconState) -> None:
slot_index = state.slot % SLOTS_PER_HISTORICAL_ROOT
# Cache state root
previous_state_root = hash_tree_root(state)
state.state_roots[slot_index] = previous_state_root
# Cache latest block header state root
if state.latest_block_header.state_root == Bytes32():
state.latest_block_header.state_root = previous_state_root
# Cache block root
previous_block_root = hash_tree_root(state.latest_block_header)
state.block_roots[slot_index] = previous_block_root
# [New in Gloas:EIP7732]
# Unset the next payload availability
next_slot_index = (state.slot + 1) % SLOTS_PER_HISTORICAL_ROOT
state.execution_payload_availability[next_slot_index] = Boolean(False)Note: The function process_epoch is modified in Gloas to call the new
helpers process_builder_pending_payments and process_ptc_window.
def process_epoch(state: BeaconState) -> None:
process_justification_and_finalization(state)
process_inactivity_updates(state)
process_rewards_and_penalties(state)
process_registry_updates(state)
process_slashings(state)
process_eth1_data_reset(state)
# [Modified in Gloas:EIP8061]
process_pending_deposits(state)
process_pending_consolidations(state)
# [New in Gloas:EIP7732]
process_builder_pending_payments(state)
process_effective_balance_updates(state)
process_slashings_reset(state)
process_randao_mixes_reset(state)
process_historical_summaries_update(state)
process_participation_flag_updates(state)
process_sync_committee_updates(state)
process_proposer_lookahead(state)
# [New in Gloas:EIP7732]
process_ptc_window(state)def process_pending_deposits(state: BeaconState) -> None:
next_epoch = get_current_epoch(state) + 1
# [Modified in Gloas:EIP8061]
# Deposits still consume the activation-only churn budget in Gloas.
available_for_processing = state.deposit_balance_to_consume + get_activation_churn_limit(state)
processed_amount = 0
next_deposit_index = 0
deposits_to_postpone = []
is_churn_limit_reached = False
finalized_slot = compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
for deposit in state.pending_deposits:
# Check if deposit has been finalized, otherwise, stop processing.
if deposit.slot > finalized_slot:
break
# Check if number of processed deposits has not reached the limit, otherwise, stop processing.
if next_deposit_index >= MAX_PENDING_DEPOSITS_PER_EPOCH:
break
# Read validator state
is_validator_exited = False
is_validator_withdrawn = False
validator_pubkeys = [validator.pubkey for validator in state.validators]
if deposit.pubkey in validator_pubkeys:
validator = state.validators[ValidatorIndex(validator_pubkeys.index(deposit.pubkey))]
is_validator_exited = validator.exit_epoch < FAR_FUTURE_EPOCH
is_validator_withdrawn = validator.withdrawable_epoch < next_epoch
if is_validator_withdrawn:
# Deposited balance will never become active. Increase balance but do not consume churn
apply_pending_deposit(state, deposit)
elif is_validator_exited:
# Validator is exiting, postpone the deposit until after withdrawable epoch
deposits_to_postpone.append(deposit)
else:
# Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch.
is_churn_limit_reached = processed_amount + deposit.amount > available_for_processing
if is_churn_limit_reached:
break
# Consume churn and apply deposit.
processed_amount += deposit.amount
apply_pending_deposit(state, deposit)
# Regardless of how the deposit was handled, we move on in the queue.
next_deposit_index += 1
state.pending_deposits = state.pending_deposits[next_deposit_index:] + deposits_to_postpone
# Accumulate churn only if the churn limit has been hit.
if is_churn_limit_reached:
state.deposit_balance_to_consume = available_for_processing - processed_amount
else:
state.deposit_balance_to_consume = Gwei(0)def process_builder_pending_payments(state: BeaconState) -> None:
"""
Processes the builder pending payments from the previous epoch.
"""
quorum = get_builder_payment_quorum_threshold(state)
for payment in state.builder_pending_payments[:SLOTS_PER_EPOCH]:
if payment.weight >= quorum:
state.builder_pending_withdrawals.append(payment.withdrawal)
old_payments = state.builder_pending_payments[SLOTS_PER_EPOCH:]
state.builder_pending_payments[:SLOTS_PER_EPOCH] = old_payments
new_payments = [BuilderPendingPayment.empty() for _ in range(SLOTS_PER_EPOCH)]
state.builder_pending_payments[SLOTS_PER_EPOCH:] = new_paymentsdef process_ptc_window(state: BeaconState) -> None:
"""
Update the cached PTC window.
"""
# Shift all epochs forward by one
state.ptc_window[: len(state.ptc_window) - SLOTS_PER_EPOCH] = state.ptc_window[SLOTS_PER_EPOCH:]
# Fill in the last epoch
next_epoch = get_current_epoch(state) + MIN_SEED_LOOKAHEAD + 1
start_slot = compute_start_slot_at_epoch(next_epoch)
state.ptc_window[len(state.ptc_window) - SLOTS_PER_EPOCH :] = [
compute_ptc(state, Slot(slot)) for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH)
]def process_block(state: BeaconState, block: BeaconBlock) -> None:
# [New in Gloas:EIP7732]
parent_slot = state.latest_block_header.slot
# [New in Gloas:EIP7732]
process_parent_execution_payload(state, block)
process_block_header(state, block)
# [Modified in Gloas:EIP7732]
process_withdrawals(state)
# [Modified in Gloas:EIP7732]
# Removed `process_execution_payload`
# [New in Gloas:EIP7732]
process_execution_payload_bid(state, block.body.signed_execution_payload_bid)
process_randao(state, block.body)
process_eth1_data(state, block.body)
# [Modified in Gloas:EIP7732]
process_operations(state, block.body, parent_slot)
process_sync_aggregate(state, block.body.sync_aggregate)Note: This function queues the builder payment, processes the parent's
execution requests, updates payload availability, and updates the latest block
hash. It is called by process_parent_execution_payload during block processing
and by the validator during block production before computing withdrawals.
def apply_parent_execution_payload(
state: BeaconState,
requests: ExecutionRequests,
) -> None:
parent_bid = state.latest_execution_payload_bid
parent_slot = state.latest_block_header.slot
parent_epoch = compute_epoch_at_slot(parent_slot)
assert len(requests.withdrawals) <= MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD
assert len(requests.consolidations) <= MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD
assert len(requests.builder_deposits) <= MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD
assert len(requests.builder_exits) <= MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD
# Settle the builder payment before the requests so that a builder exit
# request is rejected while the payment is pending
if parent_epoch == get_current_epoch(state):
payment_index = SLOTS_PER_EPOCH + parent_slot % SLOTS_PER_EPOCH
settle_builder_payment(state, payment_index)
elif parent_epoch == get_previous_epoch(state):
payment_index = parent_slot % SLOTS_PER_EPOCH
settle_builder_payment(state, payment_index)
elif parent_bid.value > 0:
# Parent is older than the previous epoch, its payment entry has been
# evicted from builder_pending_payments. Append the withdrawal directly.
state.builder_pending_withdrawals.append(
BuilderPendingWithdrawal(
fee_recipient=parent_bid.fee_recipient,
amount=parent_bid.value,
builder_index=parent_bid.builder_index,
)
)
# Process execution requests from parent's payload. The execution
# requests are processed at state.slot (child's slot), not the parent's slot.
def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
for operation in operations:
fn(state, operation)
for_ops(requests.deposits, process_deposit_request)
for_ops(requests.withdrawals, process_withdrawal_request)
for_ops(requests.consolidations, process_consolidation_request)
for_ops(requests.builder_deposits, process_builder_deposit_request)
for_ops(requests.builder_exits, process_builder_exit_request)
# Update parent payload availability and latest block hash
state.execution_payload_availability[parent_slot % SLOTS_PER_HISTORICAL_ROOT] = Boolean(True)
state.latest_block_hash = parent_bid.block_hashNote: This function validates and processes the parent's execution payload.
process_parent_execution_payload must be called before
process_execution_payload_bid (which overwrites
state.latest_execution_payload_bid).
def process_parent_execution_payload(state: BeaconState, block: BeaconBlock) -> None:
bid = block.body.signed_execution_payload_bid.message
parent_bid = state.latest_execution_payload_bid
requests = block.body.parent_execution_requests
if bid.parent_block_hash != parent_bid.block_hash:
# Parent was EMPTY -- no execution requests expected
assert requests == ExecutionRequests.empty()
return
# Parent was FULL -- verify the bid commitment and apply the payload
assert hash_tree_root(requests) == parent_bid.execution_requests_root
apply_parent_execution_payload(state, requests)def get_builder_withdrawals(
state: BeaconState,
withdrawal_index: WithdrawalIndex,
prior_withdrawals: Sequence[Withdrawal],
) -> tuple[Sequence[Withdrawal], WithdrawalIndex, Uint64]:
withdrawals_limit = MAX_WITHDRAWALS_PER_PAYLOAD - 1
assert len(prior_withdrawals) <= withdrawals_limit
processed_count = Uint64(0)
withdrawals: list[Withdrawal] = []
for withdrawal in state.builder_pending_withdrawals:
all_withdrawals = list(prior_withdrawals) + withdrawals
has_reached_limit = len(all_withdrawals) >= withdrawals_limit
if has_reached_limit:
break
builder_index = withdrawal.builder_index
withdrawals.append(
Withdrawal(
index=withdrawal_index,
validator_index=convert_builder_index_to_validator_index(builder_index),
address=withdrawal.fee_recipient,
amount=withdrawal.amount,
)
)
withdrawal_index += 1
processed_count += 1
return withdrawals, withdrawal_index, processed_countdef get_builder_balance_after_withdrawals(
state: BeaconState, builder_index: BuilderIndex, withdrawals: Sequence[Withdrawal]
) -> Gwei:
validator_index = convert_builder_index_to_validator_index(builder_index)
withdrawn = Gwei(
sum(
withdrawal.amount
for withdrawal in withdrawals
if withdrawal.validator_index == validator_index
)
)
return saturating_sub(state.builders[builder_index].balance, withdrawn)def get_builders_sweep_withdrawals(
state: BeaconState,
withdrawal_index: WithdrawalIndex,
prior_withdrawals: Sequence[Withdrawal],
) -> tuple[Sequence[Withdrawal], WithdrawalIndex, Uint64]:
epoch = get_current_epoch(state)
builders_limit = min(len(state.builders), MAX_BUILDERS_PER_WITHDRAWALS_SWEEP)
withdrawals_limit = MAX_WITHDRAWALS_PER_PAYLOAD - 1
assert len(prior_withdrawals) <= withdrawals_limit
processed_count = Uint64(0)
withdrawals: list[Withdrawal] = []
builder_index = state.next_withdrawal_builder_index
for _ in range(builders_limit):
all_withdrawals = list(prior_withdrawals) + withdrawals
has_reached_limit = len(all_withdrawals) >= withdrawals_limit
if has_reached_limit:
break
builder = state.builders[builder_index]
balance = get_builder_balance_after_withdrawals(state, builder_index, all_withdrawals)
if builder.withdrawable_epoch <= epoch and balance > 0:
withdrawals.append(
Withdrawal(
index=withdrawal_index,
validator_index=convert_builder_index_to_validator_index(builder_index),
address=builder.execution_address,
amount=balance,
)
)
withdrawal_index += 1
builder_index = (builder_index + 1) % len(state.builders)
processed_count += 1
return withdrawals, withdrawal_index, processed_countdef get_expected_withdrawals(state: BeaconState) -> ExpectedWithdrawals:
withdrawal_index = state.next_withdrawal_index
withdrawals: list[Withdrawal] = []
# [New in Gloas:EIP7732]
# Get builder withdrawals
builder_withdrawals, withdrawal_index, processed_builder_withdrawals_count = (
get_builder_withdrawals(state, withdrawal_index, withdrawals)
)
withdrawals.extend(builder_withdrawals)
# Get partial withdrawals
partial_withdrawals, withdrawal_index, processed_partial_withdrawals_count = (
get_pending_partial_withdrawals(state, withdrawal_index, withdrawals)
)
withdrawals.extend(partial_withdrawals)
# [New in Gloas:EIP7732]
# Get builders sweep withdrawals
builders_sweep_withdrawals, withdrawal_index, processed_builders_sweep_count = (
get_builders_sweep_withdrawals(state, withdrawal_index, withdrawals)
)
withdrawals.extend(builders_sweep_withdrawals)
# Get validators sweep withdrawals
validators_sweep_withdrawals, withdrawal_index, processed_validators_sweep_count = (
get_validators_sweep_withdrawals(state, withdrawal_index, withdrawals)
)
withdrawals.extend(validators_sweep_withdrawals)
return ExpectedWithdrawals(
withdrawals,
# [New in Gloas:EIP7732]
processed_builder_withdrawals_count,
processed_partial_withdrawals_count,
# [New in Gloas:EIP7732]
processed_builders_sweep_count,
processed_validators_sweep_count,
)def apply_withdrawals(state: BeaconState, withdrawals: Sequence[Withdrawal]) -> None:
for withdrawal in withdrawals:
# [Modified in Gloas:EIP7732]
if is_builder_index(withdrawal.validator_index):
builder_index = convert_validator_index_to_builder_index(withdrawal.validator_index)
state.builders[builder_index].balance = saturating_sub(
state.builders[builder_index].balance, withdrawal.amount
)
else:
decrease_balance(state, withdrawal.validator_index, withdrawal.amount)def update_payload_expected_withdrawals(
state: BeaconState, withdrawals: Sequence[Withdrawal]
) -> None:
state.payload_expected_withdrawals = Withdrawals(data=withdrawals)def update_builder_pending_withdrawals(
state: BeaconState, processed_builder_withdrawals_count: Uint64
) -> None:
state.builder_pending_withdrawals = state.builder_pending_withdrawals[
processed_builder_withdrawals_count:
]def update_next_withdrawal_builder_index(
state: BeaconState, processed_builders_sweep_count: Uint64
) -> None:
if len(state.builders) > 0:
# Update the next builder index to start the next withdrawal sweep
next_index = state.next_withdrawal_builder_index + processed_builders_sweep_count
next_builder_index = next_index % len(state.builders)
state.next_withdrawal_builder_index = next_builder_indexNote: This is modified to only take 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. process_withdrawals must be called after
process_parent_execution_payload (which updates state.latest_block_hash) and
before process_execution_payload_bid as the latter function affects validator
balances.
Note: Unlike deposits (which are applied at the child's slot via
apply_parent_execution_payload), withdrawal balance deductions are applied
immediately via apply_withdrawals. Deferring the deduction to the child's slot
would break the total supply invariant: state transitions between the commitment
slot and the deduction slot (e.g., process_pending_consolidations at an epoch
boundary) can reduce a validator's balance below the committed withdrawal
amount, causing decrease_balance to saturate at zero. Since the execution
layer mints the full committed amount regardless, any CL-side saturation creates
a net supply inflation. As a consequence, state.balances reflects the
withdrawal deduction before the corresponding execution payload is confirmed,
creating a transient asymmetry with the EL state at state.latest_block_hash.
Off-chain consumers that require CL/EL balance consistency can reconstruct
pre-deduction balances by adding back state.payload_expected_withdrawals.
def process_withdrawals(
state: BeaconState,
# [Modified in Gloas:EIP7732]
# Removed `payload`
) -> None:
# [New in Gloas:EIP7732]
# Return early if the parent block is empty
if state.latest_block_hash != state.latest_execution_payload_bid.block_hash:
return
# Get expected withdrawals
expected = get_expected_withdrawals(state)
# Apply expected withdrawals
apply_withdrawals(state, expected.withdrawals)
# Update withdrawals fields in the state
update_next_withdrawal_index(state, expected.withdrawals)
# [New in Gloas:EIP7732]
update_payload_expected_withdrawals(state, expected.withdrawals)
# [New in Gloas:EIP7732]
update_builder_pending_withdrawals(state, expected.processed_builder_withdrawals_count)
update_pending_partial_withdrawals(state, expected.processed_partial_withdrawals_count)
# [New in Gloas:EIP7732]
update_next_withdrawal_builder_index(state, expected.processed_builders_sweep_count)
update_next_withdrawal_validator_index(state, expected.withdrawals)process_execution_payload has been replaced by
verify_execution_payload_envelope, a pure verification helper called from
on_execution_payload_envelope. Payload processing is deferred to the next
beacon block via process_parent_execution_payload.
def verify_execution_payload_envelope_signature(
state: BeaconState, signed_envelope: SignedExecutionPayloadEnvelope
) -> bool:
builder_index = signed_envelope.message.builder_index
if builder_index == BUILDER_INDEX_SELF_BUILD:
validator_index = state.latest_block_header.proposer_index
pubkey = state.validators[validator_index].pubkey
else:
pubkey = state.builders[builder_index].pubkey
signing_root = compute_signing_root(
signed_envelope.message, get_domain(state, DOMAIN_BEACON_BUILDER)
)
return bls.Verify(pubkey, signing_root, signed_envelope.signature)def get_execution_requests_list(execution_requests: ExecutionRequests) -> Sequence[bytes]:
requests: Sequence[tuple[Bytes1, ProgressiveList]] = [
(DEPOSIT_REQUEST_TYPE, execution_requests.deposits),
(WITHDRAWAL_REQUEST_TYPE, execution_requests.withdrawals),
(CONSOLIDATION_REQUEST_TYPE, execution_requests.consolidations),
# [New in Gloas:EIP8282]
(BUILDER_DEPOSIT_REQUEST_TYPE, execution_requests.builder_deposits),
# [New in Gloas:EIP8282]
(BUILDER_EXIT_REQUEST_TYPE, execution_requests.builder_exits),
]
return [
request_type + ssz_serialize(request_data)
for request_type, request_data in requests
if len(request_data) != 0
]def verify_execution_payload_bid_signature(
state: BeaconState, signed_bid: SignedExecutionPayloadBid
) -> bool:
builder = state.builders[signed_bid.message.builder_index]
signing_root = compute_signing_root(
signed_bid.message, get_domain(state, DOMAIN_BEACON_BUILDER)
)
return bls.Verify(builder.pubkey, signing_root, signed_bid.signature)def process_execution_payload_bid(
state: BeaconState, signed_bid: SignedExecutionPayloadBid
) -> None:
bid = signed_bid.message
builder_index = bid.builder_index
amount = bid.value
# For self-builds, amount must be zero regardless of withdrawal credential prefix
if builder_index == BUILDER_INDEX_SELF_BUILD:
assert amount == 0
assert signed_bid.signature == bls.G2_POINT_AT_INFINITY
else:
# Verify that the builder is active
assert is_active_builder(state, builder_index)
# Verify that the builder is a payload builder
assert state.builders[builder_index].version == PAYLOAD_BUILDER_VERSION
# Verify that the builder has funds to cover the bid
assert can_builder_cover_bid(state, builder_index, amount)
# Verify that the bid signature is valid
assert verify_execution_payload_bid_signature(state, signed_bid)
# Verify commitments are under limit
assert (
len(bid.blob_kzg_commitments)
<= get_blob_parameters(get_current_epoch(state)).max_blobs_per_block
)
# Verify that the bid is for the current slot
assert bid.slot == state.slot
assert state.slot > GENESIS_SLOT
# Verify that the bid is for the right parent block
assert bid.parent_block_hash == state.latest_block_hash
# Verify that the bid's block hash differs from its parent block hash
assert bid.block_hash != bid.parent_block_hash
assert bid.parent_block_root == get_block_root_at_slot(state, state.slot - 1)
assert bid.prev_randao == get_randao_mix(state, get_current_epoch(state))
# Record the pending payment if there is some payment
if amount > 0:
pending_payment = BuilderPendingPayment(
weight=Gwei(0),
withdrawal=BuilderPendingWithdrawal(
fee_recipient=bid.fee_recipient,
amount=amount,
builder_index=builder_index,
),
proposer_index=get_beacon_proposer_index(state),
)
state.builder_pending_payments[SLOTS_PER_EPOCH + bid.slot % SLOTS_PER_EPOCH] = (
pending_payment
)
# Cache the signed execution payload bid
state.latest_execution_payload_bid = bidNote: process_operations is modified to process PTC attestations, to pass
the parent block's slot to process_attestation, and removes calls to
process_deposit_request, process_withdrawal_request, and
process_consolidation_request.
def process_operations(
state: BeaconState,
body: BeaconBlockBody,
# [New in Gloas:EIP7732]
parent_slot: Slot,
) -> None:
assert len(body.deposits) == 0
# [Modified in Gloas:EIP7732]
def for_ops(operations: Sequence[Any], fn: Callable[..., None], *args: Any) -> None:
for operation in operations:
fn(state, operation, *args)
# [New in Gloas:EIP7688]
assert len(body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS
assert len(body.attester_slashings) <= MAX_ATTESTER_SLASHINGS_ELECTRA
assert len(body.attestations) <= MAX_ATTESTATIONS_ELECTRA
assert len(body.voluntary_exits) <= MAX_VOLUNTARY_EXITS
assert len(body.bls_to_execution_changes) <= MAX_BLS_TO_EXECUTION_CHANGES
assert len(body.payload_attestations) <= MAX_PAYLOAD_ATTESTATIONS
# [Modified in Gloas:EIP7732]
for_ops(body.proposer_slashings, process_proposer_slashing)
for_ops(body.attester_slashings, process_attester_slashing)
# [Modified in Gloas:EIP7732]
for_ops(body.attestations, process_attestation, parent_slot)
for_ops(body.voluntary_exits, process_voluntary_exit)
for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
# [Modified in Gloas:EIP7732]
# Removed `process_deposit_request`
# [Modified in Gloas:EIP7732]
# Removed `process_withdrawal_request`
# [Modified in Gloas:EIP7732]
# Removed `process_consolidation_request`
# [New in Gloas:EIP7732]
for_ops(body.payload_attestations, process_payload_attestation)Note: Builder deposits are signed over the DepositMessage under
DOMAIN_BUILDER_DEPOSIT. The dedicated domain ensures that validator deposit
signatures and builder deposit signatures cannot be replayed against the other
deposit contract.
def is_valid_builder_deposit_signature(request: BuilderDepositRequest) -> bool:
deposit_message = DepositMessage(
pubkey=request.pubkey,
withdrawal_credentials=request.withdrawal_credentials,
amount=request.amount,
)
domain = compute_domain(DOMAIN_BUILDER_DEPOSIT)
signing_root = compute_signing_root(deposit_message, domain)
return bls.Verify(request.pubkey, signing_root, request.signature)def get_index_for_new_builder(state: BeaconState) -> BuilderIndex:
for index, builder in enumerate(state.builders):
if builder.withdrawable_epoch <= get_current_epoch(state) and builder.balance == 0:
return BuilderIndex(index)
return BuilderIndex(len(state.builders))def add_builder_to_registry(
state: BeaconState,
pubkey: BLSPubkey,
version: Uint8,
execution_address: ExecutionAddress,
amount: Gwei,
slot: Slot,
) -> None:
set_or_append_list(
state.builders,
get_index_for_new_builder(state),
Builder(
pubkey=pubkey,
version=version,
execution_address=execution_address,
balance=amount,
deposit_epoch=compute_epoch_at_slot(slot),
withdrawable_epoch=FAR_FUTURE_EPOCH,
),
)Note: Builder indices are reusable. When a builder exits, its index may later be reassigned to a different builder with a new public key. A deposit for an exited builder that is still in the registry will be withdrawn by the builders sweep to the builder’s execution address. If its index has been reassigned, the deposit is instead processed as a new builder registration, which requires a valid signature. Exited builders cannot be reactivated, although a newly registered builder’s public key may have previously appeared in the builder set. Implementations that rely on caching should account for this behavior.
def process_builder_deposit_request(state: BeaconState, request: BuilderDepositRequest) -> None:
# Ignore deposits with unexpected withdrawal credential prefixes
if not is_builder_withdrawal_credential(request.withdrawal_credentials):
return
builder_pubkeys = [builder.pubkey for builder in state.builders]
if request.pubkey not in builder_pubkeys:
if is_valid_builder_deposit_signature(request):
add_builder_to_registry(
state,
request.pubkey,
PAYLOAD_BUILDER_VERSION,
ExecutionAddress(request.withdrawal_credentials[12:]),
request.amount,
state.slot,
)
else:
builder_index = BuilderIndex(builder_pubkeys.index(request.pubkey))
builder = state.builders[builder_index]
# If exited and swept, reset the withdrawable epoch
if builder.withdrawable_epoch != FAR_FUTURE_EPOCH and builder.balance == 0:
epoch = get_current_epoch(state)
builder.withdrawable_epoch = epoch + MIN_BUILDER_WITHDRAWABILITY_DELAY
# Increase balance by deposit amount
builder.balance += request.amountdef process_builder_exit_request(state: BeaconState, request: BuilderExitRequest) -> None:
builder_pubkeys = [builder.pubkey for builder in state.builders]
if request.pubkey not in builder_pubkeys:
return
builder_index = BuilderIndex(builder_pubkeys.index(request.pubkey))
builder = state.builders[builder_index]
if not is_active_builder(state, builder_index):
return
if builder.execution_address != request.source_address:
return
if get_pending_balance_to_withdraw_for_builder(state, builder_index) != 0:
return
initiate_builder_exit(state, builder_index)Note: The function is modified to track the weight for pending builder
payments and to use the index field in the AttestationData to signal the
payload availability. The new parent_slot parameter, the slot of the parent
block, is where the payload availability of the attested block is looked up.
def process_attestation(
state: BeaconState,
attestation: Attestation,
# [New in Gloas:EIP7732]
parent_slot: Slot,
) -> None:
data = attestation.data
assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state))
assert data.target.epoch == compute_epoch_at_slot(data.slot)
assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot
# [Modified in Gloas:EIP7732]
assert data.index < 2
committee_indices = get_committee_indices(attestation.committee_bits)
committee_offset = 0
for committee_index in committee_indices:
assert committee_index < get_committee_count_per_slot(state, data.target.epoch)
committee = get_beacon_committee(state, data.slot, committee_index)
committee_attesters = {
attester_index
for index, attester_index in enumerate(committee)
if attestation.aggregation_bits[committee_offset + index]
}
assert len(committee_attesters) > 0
committee_offset += len(committee)
# Bitfield length matches total number of participants
assert len(attestation.aggregation_bits) == committee_offset
# Participation flag indices
# [Modified in Gloas:EIP7732]
participation_flag_indices = get_attestation_participation_flag_indices(
state, data, state.slot - data.slot, parent_slot
)
# Verify signature
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
# [Modified in Gloas:EIP7732]
if data.target.epoch == get_current_epoch(state):
current_epoch_target = True
epoch_participation = state.current_epoch_participation
payment = state.builder_pending_payments[SLOTS_PER_EPOCH + data.slot % SLOTS_PER_EPOCH]
else:
current_epoch_target = False
epoch_participation = state.previous_epoch_participation
payment = state.builder_pending_payments[data.slot % SLOTS_PER_EPOCH]
proposer_reward_numerator = 0
for index in get_attesting_indices(state, attestation):
# [New in Gloas:EIP7732]
had_no_participation = epoch_participation[index] == 0b0000_0000
will_set_new_flag = False
for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS):
if flag_index in participation_flag_indices and 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
# [New in Gloas:EIP7732]
will_set_new_flag = True
# [New in Gloas:EIP7732]
if (
will_set_new_flag
and had_no_participation
and is_attestation_same_slot(state, data)
and payment.withdrawal.amount > 0
):
payment.weight += state.validators[index].effective_balance
# Reward proposer
proposer_reward_denominator = (
(WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT
)
proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator)
increase_balance(state, get_beacon_proposer_index(state), proposer_reward)
# [New in Gloas:EIP7732]
# Update builder payment weight
if current_epoch_target:
state.builder_pending_payments[SLOTS_PER_EPOCH + data.slot % SLOTS_PER_EPOCH] = payment
else:
state.builder_pending_payments[data.slot % SLOTS_PER_EPOCH] = paymentdef process_payload_attestation(
state: BeaconState, payload_attestation: PayloadAttestation
) -> None:
data = payload_attestation.data
# Check that the attestation is for the parent beacon block
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, payload_attestation)
assert is_valid_indexed_payload_attestation(state, indexed_payload_attestation)