Skip to content

Gloas -- Fork Logic

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

Introduction

This document describes the process of the Gloas upgrade.

Configs

Warning: this configuration is not definitive.

Name Value
GLOAS_FORK_VERSION Version("0x07000000")
GLOAS_FORK_EPOCH Epoch(18446744073709551615) TBD

Helpers

New initialize_ptc_window

def initialize_ptc_window(
    state: BeaconState,
) -> PayloadTimelinessCommitteeWindow:
    """
    Return the cached PTC window starting from the current epoch.
    Used to initialize the ``ptc_window`` field in the beacon state at genesis and after forks.
    """
    empty_previous_epoch = [
        PayloadTimelinessCommittee(data=[ValidatorIndex(0) for _ in range(PTC_SIZE)])
        for _ in range(SLOTS_PER_EPOCH)
    ]

    ptcs = []
    current_epoch = get_current_epoch(state)
    for e in range(1 + MIN_SEED_LOOKAHEAD):
        epoch = current_epoch + e
        start_slot = compute_start_slot_at_epoch(epoch)
        ptcs += [compute_ptc(state, start_slot + index) for index in range(SLOTS_PER_EPOCH)]

    return PayloadTimelinessCommitteeWindow(data=empty_previous_epoch + ptcs)

New onboard_builders_from_pending_deposits

Note: This one-time onboarding is the only path through the validator deposit contract that creates builders. From the fork onward, builders are created and topped up only via BuilderDepositRequest.

During this pass:

  • Only deposits with builder withdrawal credentials can fund builders.
  • Other deposits stay queued, even if a builder uses the same public key.
  • A valid builder registration can use a validator's public key.

Note: In the slots leading up to the fork, implementations SHOULD validate pending deposit signatures and cache the results. The pending deposit queue might be large and verifying many signatures at the fork could be slow.

def onboard_builders_from_pending_deposits(state: BeaconState) -> None:
    """
    Applies any pending deposit for builders, effectively
    onboarding builders at the fork.
    """
    validator_pubkeys = [validator.pubkey for validator in state.validators]

    pending_deposits = PendingDeposits()
    for deposit in state.pending_deposits:
        # Only builder-credential deposits can fund builders at the fork.
        if not is_builder_withdrawal_credential(deposit.withdrawal_credentials):
            pending_deposits.append(deposit)
            continue

        # Note that applying a deposit below can mutate the state and
        # may add a builder to the registry. For this reason, the list
        # of builder pubkeys must be recomputed each iteration.
        builder_pubkeys = [builder.pubkey for builder in state.builders]

        if deposit.pubkey not in builder_pubkeys:
            if not is_valid_deposit_signature(
                deposit.pubkey,
                deposit.withdrawal_credentials,
                deposit.amount,
                deposit.signature,
            ):
                # Preserve validator top-ups when no builder can receive them.
                if deposit.pubkey in validator_pubkeys or is_pending_validator(
                    pending_deposits, deposit.pubkey
                ):
                    pending_deposits.append(deposit)
                continue

            add_builder_to_registry(
                state,
                deposit.pubkey,
                PAYLOAD_BUILDER_VERSION,
                ExecutionAddress(deposit.withdrawal_credentials[12:]),
                deposit.amount,
                deposit.slot,
            )
        else:
            builder_index = BuilderIndex(builder_pubkeys.index(deposit.pubkey))
            state.builders[builder_index].balance += deposit.amount

    state.pending_deposits = pending_deposits

Fork to Gloas

Fork trigger

The fork is triggered at epoch GLOAS_FORK_EPOCH.

Upgrading the state

If state.slot % SLOTS_PER_EPOCH == 0 and compute_epoch_at_slot(state.slot) == GLOAS_FORK_EPOCH, an irregular state change is made to upgrade to Gloas.

def upgrade_to_gloas(pre: fulu.BeaconState) -> BeaconState:
    epoch = fulu.get_current_epoch(pre)

    post = BeaconState(
        genesis_time=pre.genesis_time,
        genesis_validators_root=pre.genesis_validators_root,
        slot=pre.slot,
        fork=Fork(
            previous_version=pre.fork.current_version,
            # [Modified in Gloas]
            current_version=GLOAS_FORK_VERSION,
            epoch=epoch,
        ),
        latest_block_header=pre.latest_block_header,
        block_roots=pre.block_roots,
        state_roots=pre.state_roots,
        historical_roots=pre.historical_roots,
        eth1_data=pre.eth1_data,
        eth1_data_votes=pre.eth1_data_votes,
        eth1_deposit_index=pre.eth1_deposit_index,
        # [Modified in Gloas:EIP7688]
        validators=Validators(data=pre.validators),
        # [Modified in Gloas:EIP7688]
        balances=Balances(data=pre.balances),
        randao_mixes=pre.randao_mixes,
        slashings=pre.slashings,
        # [Modified in Gloas:EIP7688]
        previous_epoch_participation=EpochParticipation(data=pre.previous_epoch_participation),
        # [Modified in Gloas:EIP7688]
        current_epoch_participation=EpochParticipation(data=pre.current_epoch_participation),
        justification_bits=pre.justification_bits,
        previous_justified_checkpoint=pre.previous_justified_checkpoint,
        current_justified_checkpoint=pre.current_justified_checkpoint,
        finalized_checkpoint=pre.finalized_checkpoint,
        # [Modified in Gloas:EIP7688]
        inactivity_scores=InactivityScores(data=pre.inactivity_scores),
        current_sync_committee=pre.current_sync_committee,
        next_sync_committee=pre.next_sync_committee,
        # [Modified in Gloas:EIP7732]
        # Removed `latest_execution_payload_header`
        # [New in Gloas:EIP7732]
        latest_block_hash=pre.latest_execution_payload_header.block_hash,
        next_withdrawal_index=pre.next_withdrawal_index,
        next_withdrawal_validator_index=pre.next_withdrawal_validator_index,
        historical_summaries=pre.historical_summaries,
        deposit_requests_start_index=pre.deposit_requests_start_index,
        deposit_balance_to_consume=pre.deposit_balance_to_consume,
        exit_balance_to_consume=pre.exit_balance_to_consume,
        earliest_exit_epoch=pre.earliest_exit_epoch,
        consolidation_balance_to_consume=pre.consolidation_balance_to_consume,
        earliest_consolidation_epoch=pre.earliest_consolidation_epoch,
        # [Modified in Gloas:EIP7688]
        pending_deposits=PendingDeposits(data=pre.pending_deposits),
        # [Modified in Gloas:EIP7688]
        pending_partial_withdrawals=PendingPartialWithdrawals(data=pre.pending_partial_withdrawals),
        # [Modified in Gloas:EIP7688]
        pending_consolidations=PendingConsolidations(data=pre.pending_consolidations),
        proposer_lookahead=pre.proposer_lookahead,
        # [New in Gloas:EIP7732]
        builders=Builders(),
        # [New in Gloas:EIP7732]
        next_withdrawal_builder_index=BuilderIndex(0),
        # [New in Gloas:EIP7732]
        execution_payload_availability=ExecutionPayloadAvailability(
            data=[0b1 for _ in range(SLOTS_PER_HISTORICAL_ROOT)]
        ),
        # [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(
            parent_block_hash=pre.latest_execution_payload_header.parent_hash,
            parent_block_root=pre.latest_block_header.parent_root,
            block_hash=pre.latest_execution_payload_header.block_hash,
            prev_randao=pre.latest_execution_payload_header.prev_randao,
            fee_recipient=ExecutionAddress(),
            gas_limit=pre.latest_execution_payload_header.gas_limit,
            builder_index=BUILDER_INDEX_SELF_BUILD,
            slot=pre.latest_block_header.slot,
            value=Gwei(0),
            execution_payment=Gwei(0),
            blob_kzg_commitments=BlobKZGCommitments(),
            execution_requests_root=hash_tree_root(ExecutionRequests.empty()),
        ),
        # [New in Gloas:EIP7732]
        payload_expected_withdrawals=Withdrawals(),
        # [New in Gloas:EIP7732]
        ptc_window=PayloadTimelinessCommitteeWindow(),
    )

    # [New in Gloas:EIP7732]
    post.ptc_window = initialize_ptc_window(post)

    # [New in Gloas:EIP7732]
    onboard_builders_from_pending_deposits(post)

    return post

Upgrading attestations and attester slashings

A Gloas BeaconBlockBody can still contain earlier attestations and attester slashings. In order to pack them, the pre-Gloas data needs to be locally upgraded to Gloas before creating the block.

1
2
3
4
5
6
7
def upgrade_attestation_to_gloas(pre: fulu.Attestation) -> Attestation:
    return Attestation(
        aggregation_bits=AggregationBits(data=pre.aggregation_bits),
        data=pre.data,
        signature=pre.signature,
        committee_bits=pre.committee_bits,
    )
1
2
3
4
5
6
def upgrade_indexed_attestation_to_gloas(pre: fulu.IndexedAttestation) -> IndexedAttestation:
    return IndexedAttestation(
        attesting_indices=AttestingIndices(data=pre.attesting_indices),
        data=pre.data,
        signature=pre.signature,
    )
1
2
3
4
5
def upgrade_attester_slashing_to_gloas(pre: fulu.AttesterSlashing) -> AttesterSlashing:
    return AttesterSlashing(
        attestation_1=upgrade_indexed_attestation_to_gloas(pre.attestation_1),
        attestation_2=upgrade_indexed_attestation_to_gloas(pre.attestation_2),
    )