Gloas alpha spec 11 (#9511)

Alpha spec 11 changes


  


Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>

Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Eitan Seri-Levi
2026-06-30 11:12:52 -07:00
committed by GitHub
parent 572f7f565a
commit 369decc1df
53 changed files with 1198 additions and 500 deletions

View File

@@ -11,7 +11,10 @@ use signature_sets::{
use std::borrow::Cow;
use tree_hash::TreeHash;
use typenum::Unsigned;
use types::{consts::gloas::BUILDER_INDEX_SELF_BUILD, *};
use types::{
consts::gloas::{BUILDER_INDEX_SELF_BUILD, PAYLOAD_BUILDER_VERSION},
*,
};
pub use self::verify_attester_slashing::{
get_slashable_indices, get_slashable_indices_modular, verify_attester_slashing,
@@ -189,7 +192,8 @@ pub fn per_block_processing<E: EthSpec, Payload: AbstractExecPayload<E>>(
let body = block.body();
if state.fork_name_unchecked().gloas_enabled() {
withdrawals::gloas::process_withdrawals::<E>(state, spec)?;
process_execution_payload_bid(state, block, verify_signatures, spec)?;
let signed_bid = block.body().signed_execution_payload_bid()?;
process_execution_payload_bid(state, signed_bid, verify_signatures, spec)?;
} else {
if state.fork_name_unchecked().capella_enabled() {
withdrawals::capella_electra::process_withdrawals::<E, Payload>(
@@ -561,7 +565,7 @@ pub fn process_parent_execution_payload<E: EthSpec, Payload: AbstractExecPayload
if bid_parent_block_hash != parent_bid.block_hash {
// Parent was EMPTY -- no execution requests expected
block_verify!(
*requests == ExecutionRequests::default(),
*requests == ExecutionRequestsGloas::default(),
BlockProcessingError::NonEmptyParentExecutionRequests
);
return Ok(());
@@ -588,7 +592,7 @@ pub fn process_parent_execution_payload<E: EthSpec, Payload: AbstractExecPayload
/// 3. Updates `execution_payload_availability` and `latest_block_hash`
pub fn apply_parent_execution_payload<E: EthSpec>(
state: &mut BeaconState<E>,
requests: &ExecutionRequests<E>,
requests: &ExecutionRequestsGloas<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
let parent_bid = state.latest_execution_payload_bid()?.clone();
@@ -596,9 +600,11 @@ pub fn apply_parent_execution_payload<E: EthSpec>(
let parent_epoch = parent_slot.epoch(E::slots_per_epoch());
// Process execution requests from the parent's payload
process_operations::process_deposit_requests_post_gloas(state, &requests.deposits, spec)?;
process_operations::process_deposit_requests(state, &requests.deposits, spec)?;
process_operations::process_withdrawal_requests(state, &requests.withdrawals, spec)?;
process_operations::process_consolidation_requests(state, &requests.consolidations, spec)?;
process_operations::process_builder_deposit_requests(state, &requests.builder_deposits, spec)?;
process_operations::process_builder_exit_requests(state, &requests.builder_exits, spec)?;
// Queue the builder payment
if parent_epoch == state.current_epoch() {
@@ -666,15 +672,13 @@ pub fn settle_builder_payment<E: EthSpec>(
Ok(())
}
pub fn process_execution_payload_bid<E: EthSpec, Payload: AbstractExecPayload<E>>(
pub fn process_execution_payload_bid<E: EthSpec>(
state: &mut BeaconState<E>,
block: BeaconBlockRef<'_, E, Payload>,
signed_bid: &SignedExecutionPayloadBid<E>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
// Verify the bid signature
let signed_bid = block.body().signed_execution_payload_bid()?;
let bid = &signed_bid.message;
let amount = bid.value;
let builder_index = bid.builder_index;
@@ -698,6 +702,16 @@ pub fn process_execution_payload_bid<E: EthSpec, Payload: AbstractExecPayload<E>
ExecutionPayloadBidInvalid::BuilderNotActive(builder_index).into()
);
// Verify that the builder is a payload builder
block_verify!(
builder.version == PAYLOAD_BUILDER_VERSION,
ExecutionPayloadBidInvalid::InvalidBuilderVersion {
builder_index,
version: builder.version,
}
.into()
);
// Verify that the builder has funds to cover the bid
block_verify!(
state.can_builder_cover_bid(builder_index, amount, spec)?,
@@ -739,10 +753,10 @@ pub fn process_execution_payload_bid<E: EthSpec, Payload: AbstractExecPayload<E>
// Verify that the bid is for the current slot
block_verify!(
bid.slot == block.slot(),
bid.slot == state.slot(),
ExecutionPayloadBidInvalid::SlotMismatch {
bid_slot: bid.slot,
block_slot: block.slot(),
state_slot: state.slot(),
}
.into()
);
@@ -758,10 +772,11 @@ pub fn process_execution_payload_bid<E: EthSpec, Payload: AbstractExecPayload<E>
.into()
);
let expected_parent_root = *state.get_block_root(state.slot().safe_sub(1)?)?;
block_verify!(
bid.parent_block_root == block.parent_root(),
bid.parent_block_root == expected_parent_root,
ExecutionPayloadBidInvalid::ParentBlockRootMismatch {
block_parent_root: block.parent_root(),
block_parent_root: expected_parent_root,
bid_parent_root: bid.parent_block_root,
}
.into()
@@ -779,6 +794,7 @@ pub fn process_execution_payload_bid<E: EthSpec, Payload: AbstractExecPayload<E>
// Record the pending payment if there is some payment
if amount > 0 {
let proposer_index = state.get_beacon_proposer_index(state.slot(), spec)? as u64;
let pending_payment = BuilderPendingPayment {
weight: 0,
withdrawal: BuilderPendingWithdrawal {
@@ -786,6 +802,7 @@ pub fn process_execution_payload_bid<E: EthSpec, Payload: AbstractExecPayload<E>
amount,
builder_index,
},
proposer_index,
};
let payment_index = E::SlotsPerEpoch::to_usize()

View File

@@ -527,14 +527,16 @@ pub enum ExecutionPayloadBidInvalid {
BadSignature,
/// The builder is not active.
BuilderNotActive(u64),
/// The builder's version is not `PAYLOAD_BUILDER_VERSION`.
InvalidBuilderVersion { builder_index: u64, version: u8 },
/// The builder has insufficient balance to cover the bid
InsufficientBalance {
builder_index: u64,
builder_balance: u64,
bid_value: u64,
},
/// Bid slot doesn't match block slot
SlotMismatch { bid_slot: Slot, block_slot: Slot },
/// Bid slot doesn't match state slot
SlotMismatch { bid_slot: Slot, state_slot: Slot },
/// The bid's parent block hash doesn't match the state's latest block hash
ParentBlockHashMismatch {
state_block_hash: ExecutionBlockHash,

View File

@@ -4,13 +4,9 @@ use crate::common::{
get_attestation_participation_flag_indices, increase_balance, initiate_validator_exit,
slash_validator,
};
use crate::per_block_processing::builder::{
convert_validator_index_to_builder_index, is_builder_index,
};
use crate::per_block_processing::errors::{BlockProcessingError, ExitInvalid, IntoWithIndex};
use crate::per_block_processing::signature_sets::{exit_signature_set, get_pubkey_from_state};
use crate::per_block_processing::verify_payload_attestation::verify_payload_attestation;
use bls::{PublicKeyBytes, SignatureBytes};
use bls::PublicKeyBytes;
use ssz_types::FixedVector;
use typenum::U33;
use types::consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR};
@@ -54,11 +50,7 @@ pub fn process_operations<E: EthSpec, Payload: AbstractExecPayload<E>>(
)?;
} else if state.fork_name_unchecked().electra_enabled() {
state.update_pubkey_cache()?;
process_deposit_requests_pre_gloas(
state,
&block_body.execution_requests()?.deposits,
spec,
)?;
process_deposit_requests(state, &block_body.execution_requests()?.deposits, spec)?;
process_withdrawal_requests(state, &block_body.execution_requests()?.withdrawals, spec)?;
process_consolidation_requests(
state,
@@ -397,7 +389,9 @@ pub fn process_proposer_slashings<E: EthSpec>(
// [New in Gloas:EIP7732]
// Remove the BuilderPendingPayment corresponding to this proposal
// if it is still in the 2-epoch window.
// if it is still in the 2-epoch window. Only clear it when the slashed validator is
// the proposer associated with the payment; otherwise an unrelated same-slot
// equivocation could grief an honest proposer's payment.
if state.fork_name_unchecked().gloas_enabled() {
let slot = proposer_slashing.signed_header_1.message.slot;
let proposal_epoch = slot.epoch(E::slots_per_epoch());
@@ -416,7 +410,12 @@ pub fn process_proposer_slashings<E: EthSpec>(
.builder_pending_payments_mut()?
.get_mut(index)
.ok_or(BlockProcessingError::BuilderPaymentIndexOutOfBounds(index))?;
*payment = BuilderPendingPayment::default();
if payment.proposer_index
== proposer_slashing.signed_header_1.message.proposer_index
{
*payment = BuilderPendingPayment::default();
}
}
}
@@ -521,15 +520,6 @@ pub fn process_exits<E: EthSpec>(
.into_with_index(i));
}
// [New in Gloas:EIP7732]
if state.fork_name_unchecked().gloas_enabled()
&& is_builder_index(exit.message.validator_index)
{
process_builder_voluntary_exit(state, exit, verify_signatures, spec)
.map_err(|e| e.into_with_index(i))?;
continue;
}
verify_exit(state, Some(current_epoch), exit, verify_signatures, spec)
.map_err(|e| e.into_with_index(i))?;
@@ -538,59 +528,6 @@ pub fn process_exits<E: EthSpec>(
Ok(())
}
/// Process a builder voluntary exit. [New in Gloas:EIP7732]
fn process_builder_voluntary_exit<E: EthSpec>(
state: &mut BeaconState<E>,
signed_exit: &SignedVoluntaryExit,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockOperationError<ExitInvalid>> {
let builder_index =
convert_validator_index_to_builder_index(signed_exit.message.validator_index);
// Verify builder is known
state
.builders()?
.get(builder_index as usize)
.cloned()
.ok_or(BlockOperationError::invalid(ExitInvalid::ValidatorUnknown(
signed_exit.message.validator_index,
)))?;
// Verify the builder is active
if !state.is_active_builder(builder_index, spec)? {
return Err(BlockOperationError::invalid(ExitInvalid::NotActive(
signed_exit.message.validator_index,
)));
}
// Only exit builder if it has no pending withdrawals in the queue
let pending_balance = state.get_pending_balance_to_withdraw_for_builder(builder_index)?;
if pending_balance != 0 {
return Err(BlockOperationError::invalid(
ExitInvalid::PendingWithdrawalInQueue(signed_exit.message.validator_index),
));
}
if verify_signatures.is_true() {
verify!(
exit_signature_set(
state,
|i| get_pubkey_from_state(state, i),
signed_exit,
spec
)?
.verify(),
ExitInvalid::BadSignature
);
}
// Initiate builder exit
initiate_builder_exit(state, builder_index, spec)?;
Ok(())
}
/// Initiate the exit of a builder. [New in Gloas:EIP7732]
fn initiate_builder_exit<E: EthSpec>(
state: &mut BeaconState<E>,
@@ -874,7 +811,7 @@ pub fn process_withdrawal_requests<E: EthSpec>(
Ok(())
}
pub fn process_deposit_requests_pre_gloas<E: EthSpec>(
pub fn process_deposit_requests<E: EthSpec>(
state: &mut BeaconState<E>,
deposit_requests: &[DepositRequest],
spec: &ChainSpec,
@@ -904,18 +841,111 @@ pub fn process_deposit_requests_pre_gloas<E: EthSpec>(
Ok(())
}
pub fn process_deposit_requests_post_gloas<E: EthSpec>(
pub fn process_builder_deposit_requests<E: EthSpec>(
state: &mut BeaconState<E>,
deposit_requests: &[DepositRequest],
builder_deposit_requests: &[BuilderDepositRequest],
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
for request in deposit_requests {
process_deposit_request_post_gloas(state, request, spec)?;
for builder_deposit_request in builder_deposit_requests {
process_builder_deposit_request(state, builder_deposit_request, spec)?;
}
Ok(())
}
fn process_builder_deposit_request<E: EthSpec>(
state: &mut BeaconState<E>,
builder_deposit_request: &BuilderDepositRequest,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
let builder_index = state
.builders()?
.iter()
.position(|builder| builder.pubkey == builder_deposit_request.pubkey);
match builder_index {
None => {
if builder_deposit_request.is_valid_builder_deposit_signature(spec) {
let version = builder_deposit_request
.version()
.ok_or(BeaconStateError::WithdrawalCredentialMissingVersion)?;
let slot = state.slot();
state.add_builder_to_registry(
builder_deposit_request.pubkey,
version,
builder_deposit_request.withdrawal_credentials,
builder_deposit_request.amount,
slot,
spec,
)?;
}
}
Some(builder_index) => {
let current_epoch = state.current_epoch();
let builder = state
.builders_mut()?
.get_mut(builder_index)
.ok_or(BeaconStateError::UnknownBuilder(builder_index as u64))?;
// TODO(gloas): this is already different in `master`, needs an update when we go
// to spec 1.7.0-alpha.12+
builder
.balance
.safe_add_assign(builder_deposit_request.amount)?;
if builder.withdrawable_epoch != spec.far_future_epoch {
builder.withdrawable_epoch =
current_epoch.safe_add(spec.min_builder_withdrawability_delay)?;
}
}
}
Ok(())
}
pub fn process_builder_exit_requests<E: EthSpec>(
state: &mut BeaconState<E>,
builder_exit_requests: &[BuilderExitRequest],
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
for builder_exit_request in builder_exit_requests {
process_builder_exit_request(state, builder_exit_request, spec)?;
}
Ok(())
}
fn process_builder_exit_request<E: EthSpec>(
state: &mut BeaconState<E>,
builder_exit_request: &BuilderExitRequest,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
let Some(builder_index) = state
.builders()?
.iter()
.position(|builder| builder.pubkey == builder_exit_request.pubkey)
.map(|i| i as u64)
else {
return Ok(());
};
if !state.is_active_builder(builder_index, spec)? {
return Ok(());
}
if state.get_builder(builder_index)?.execution_address != builder_exit_request.source_address {
return Ok(());
}
if state.get_pending_balance_to_withdraw_for_builder(builder_index)? != 0 {
return Ok(());
}
initiate_builder_exit(state, builder_index, spec)?;
Ok(())
}
/// Check if there is a pending deposit for a new validator with the given pubkey.
// TODO(gloas): cache the deposit signature validation or remove this loop entirely if possible,
// it is `O(n * m)` where `n` is max 8192 and `m` is max 128M.
@@ -939,107 +969,6 @@ pub fn is_pending_validator<'a>(
})
}
pub fn process_deposit_request_post_gloas<E: EthSpec>(
state: &mut BeaconState<E>,
deposit_request: &DepositRequest,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
// [New in Gloas:EIP7732]
// Regardless of the withdrawal credentials prefix, if a builder/validator
// already exists with this pubkey, apply the deposit to their balance
// TODO(gloas): this could be more efficient in the builder case, see:
// https://github.com/sigp/lighthouse/issues/8783
let builder_index = state
.builders()?
.iter()
.enumerate()
.find(|(_, builder)| builder.pubkey == deposit_request.pubkey)
.map(|(i, _)| i as u64);
let is_builder = builder_index.is_some();
let validator_index = state.get_validator_index(&deposit_request.pubkey)?;
let is_validator = validator_index.is_some();
let has_builder_prefix =
is_builder_withdrawal_credential(deposit_request.withdrawal_credentials, spec);
if is_builder
|| (has_builder_prefix
&& !is_validator
&& !is_pending_validator(state.pending_deposits()?, &deposit_request.pubkey, spec))
{
// Apply builder deposits immediately
apply_deposit_for_builder(
state,
builder_index,
deposit_request.pubkey,
deposit_request.withdrawal_credentials,
deposit_request.amount,
deposit_request.signature.clone(),
state.slot(),
spec,
)?;
return Ok(());
}
// Add validator deposits to the queue
let slot = state.slot();
state.pending_deposits_mut()?.push(PendingDeposit {
pubkey: deposit_request.pubkey,
withdrawal_credentials: deposit_request.withdrawal_credentials,
amount: deposit_request.amount,
signature: deposit_request.signature.clone(),
slot,
})?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn apply_deposit_for_builder<E: EthSpec>(
state: &mut BeaconState<E>,
builder_index_opt: Option<BuilderIndex>,
pubkey: PublicKeyBytes,
withdrawal_credentials: Hash256,
amount: u64,
signature: SignatureBytes,
slot: Slot,
spec: &ChainSpec,
) -> Result<Option<BuilderIndex>, BeaconStateError> {
match builder_index_opt {
None => {
// Verify the deposit signature (proof of possession) which is not checked by the deposit contract
let deposit_data = DepositData {
pubkey,
withdrawal_credentials,
amount,
signature,
};
if is_valid_deposit_signature(&deposit_data, spec).is_ok() {
let builder_index = state.add_builder_to_registry(
pubkey,
withdrawal_credentials,
amount,
slot,
spec,
)?;
Ok(Some(builder_index))
} else {
Ok(None)
}
}
Some(builder_index) => {
state
.builders_mut()?
.get_mut(builder_index as usize)
.ok_or(BeaconStateError::UnknownBuilder(builder_index))?
.balance
.safe_add_assign(amount)?;
Ok(Some(builder_index))
}
}
}
// Make sure to build the pubkey cache before calling this function
pub fn process_consolidation_requests<E: EthSpec>(
state: &mut BeaconState<E>,

View File

@@ -2,7 +2,6 @@
//! validated individually, or alongside in others in a potentially cheaper bulk operation.
//!
//! This module exposes one function to extract each type of `SignatureSet` from a `BeaconBlock`.
use super::builder::{convert_validator_index_to_builder_index, is_builder_index};
use bls::{AggregateSignature, PublicKey, PublicKeyBytes, Signature, SignatureSet};
use ssz::DecodeError;
use std::borrow::Cow;
@@ -520,7 +519,10 @@ pub fn deposit_pubkey_signature_message(
}
/// Returns a signature set that is valid if the `SignedVoluntaryExit` was signed by the indicated
/// validator (or builder, in the case of a builder exit).
/// validator.
///
/// It is invalid for voluntary exits to be signed by builders. Builder exits are made via execution
/// requests per EIP-8282.
pub fn exit_signature_set<'a, E, F>(
state: &'a BeaconState<E>,
get_pubkey: F,
@@ -534,16 +536,8 @@ where
let exit = &signed_exit.message;
let validator_index = exit.validator_index;
let is_builder_exit =
state.fork_name_unchecked().gloas_enabled() && is_builder_index(validator_index);
let pubkey = if is_builder_exit {
let builder_index = convert_validator_index_to_builder_index(validator_index);
get_builder_pubkey_from_state(state, builder_index)
.ok_or(Error::ValidatorUnknown(validator_index))?
} else {
get_pubkey(validator_index as usize).ok_or(Error::ValidatorUnknown(validator_index))?
};
let pubkey =
get_pubkey(validator_index as usize).ok_or(Error::ValidatorUnknown(validator_index))?;
let domain = if state.fork_name_unchecked().deneb_enabled() {
// EIP-7044

View File

@@ -1,16 +1,15 @@
use crate::per_block_processing::process_operations::apply_deposit_for_builder;
use crate::per_block_processing::is_valid_deposit_signature;
use crate::per_block_processing::process_operations::is_pending_validator;
use milhouse::{List, Vector};
use safe_arith::SafeArith;
use ssz_types::BitVector;
use ssz_types::FixedVector;
use std::collections::HashMap;
use std::mem;
use ssz_types::{BitVector, FixedVector};
use std::{collections::HashMap, mem};
use tree_hash::TreeHash;
use typenum::Unsigned;
use types::{
BeaconState, BeaconStateError as Error, BeaconStateGloas, BuilderPendingPayment, ChainSpec,
EthSpec, ExecutionPayloadBid, ExecutionRequests, Fork, is_builder_withdrawal_credential,
DepositData, EthSpec, ExecutionPayloadBid, ExecutionRequestsGloas, Fork,
consts::gloas::PAYLOAD_BUILDER_VERSION, is_builder_withdrawal_credential,
};
/// Transform a `Fulu` state into a `Gloas` state.
@@ -79,7 +78,7 @@ pub fn upgrade_state_to_gloas<E: EthSpec>(
latest_execution_payload_bid: ExecutionPayloadBid {
block_hash: pre.latest_execution_payload_header.block_hash,
gas_limit: pre.latest_execution_payload_header.gas_limit,
execution_requests_root: ExecutionRequests::<E>::default().tree_hash_root(),
execution_requests_root: ExecutionRequestsGloas::<E>::default().tree_hash_root(),
..Default::default()
},
// Capella
@@ -187,37 +186,50 @@ fn onboard_builders_from_pending_deposits<E: EthSpec>(
continue;
}
if !builder_pubkey_to_index.contains_key(&deposit.pubkey) {
// Deposits without builder withdrawal credentials are for new validators.
if !is_builder_withdrawal_credential(deposit.withdrawal_credentials, spec) {
pending_deposits.push(deposit.clone())?;
continue;
match builder_pubkey_to_index.get(&deposit.pubkey).copied() {
None => {
// Deposits without builder withdrawal credentials are for new validators.
if !is_builder_withdrawal_credential(deposit.withdrawal_credentials, spec) {
pending_deposits.push(deposit.clone())?;
continue;
}
// If there is a valid pending deposit for a new validator with this pubkey,
// keep this deposit in the pending queue to be applied to that validator later.
if is_pending_validator(&pending_deposits, &deposit.pubkey, spec) {
pending_deposits.push(deposit.clone())?;
continue;
}
let deposit_data = DepositData {
pubkey: deposit.pubkey,
withdrawal_credentials: deposit.withdrawal_credentials,
amount: deposit.amount,
signature: deposit.signature.clone(),
};
if is_valid_deposit_signature(&deposit_data, spec).is_err() {
continue;
}
let builder_index = state.add_builder_to_registry(
deposit.pubkey,
PAYLOAD_BUILDER_VERSION,
deposit.withdrawal_credentials,
deposit.amount,
deposit.slot,
spec,
)?;
builder_pubkey_to_index.insert(deposit.pubkey, builder_index);
}
Some(builder_index) => {
let builder = state
.builders_mut()?
.get_mut(builder_index as usize)
.ok_or(Error::UnknownBuilder(builder_index))?;
// If there is a valid pending deposit for a new validator with this pubkey,
// keep this deposit in the pending queue to be applied to that validator later.
if is_pending_validator(&pending_deposits, &deposit.pubkey, spec) {
pending_deposits.push(deposit.clone())?;
continue;
builder.balance.safe_add_assign(deposit.amount)?;
}
}
let builder_index = builder_pubkey_to_index.get(&deposit.pubkey).copied();
if let Some(new_builder_index) = apply_deposit_for_builder(
state,
builder_index,
deposit.pubkey,
deposit.withdrawal_credentials,
deposit.amount,
deposit.signature.clone(),
deposit.slot,
spec,
)? {
builder_pubkey_to_index
.entry(deposit.pubkey)
.or_insert(new_builder_index);
}
}
*state.pending_deposits_mut()? = pending_deposits;