Gloas envelope consensus and more operations tests (#8781)

- Implement new `process_execution_payload` (as `process_execution_payload_envelope`).
- Implement new processing for deposit requests, including logic for adding new builders to the registry with index reuse.
- Enable a bunch more operations EF tests (most of them except bid processing/payload attestations/etc which we don't have code for yet).


Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Michael Sproul
2026-02-10 14:59:25 +11:00
committed by GitHub
parent 286b67f048
commit 7e275f8dc2
12 changed files with 643 additions and 34 deletions

View File

@@ -1,7 +1,8 @@
use crate::test_utils::TestRandom;
use crate::{
ChainSpec, Domain, Epoch, EthSpec, ExecutionBlockHash, ExecutionPayloadEnvelope, Fork,
ForkName, Hash256, SignedRoot, Slot,
BeaconState, BeaconStateError, ChainSpec, Domain, Epoch, EthSpec, ExecutionBlockHash,
ExecutionPayloadEnvelope, Fork, ForkName, Hash256, SignedRoot, Slot,
consts::gloas::BUILDER_INDEX_SELF_BUILD,
};
use bls::{PublicKey, Signature};
use context_deserialize::context_deserialize;
@@ -58,6 +59,42 @@ impl<E: EthSpec> SignedExecutionPayloadEnvelope<E> {
self.signature.verify(pubkey, message)
}
/// Verify `self.signature` using keys drawn from the beacon state.
pub fn verify_signature_with_state(
&self,
state: &BeaconState<E>,
spec: &ChainSpec,
) -> Result<bool, BeaconStateError> {
let builder_index = self.message.builder_index;
let pubkey_bytes = if builder_index == BUILDER_INDEX_SELF_BUILD {
let validator_index = state.latest_block_header().proposer_index;
state.get_validator(validator_index as usize)?.pubkey
} else {
state.get_builder(builder_index)?.pubkey
};
// TODO(gloas): Could use pubkey cache on state here, but it probably isn't worth
// it because this function is rarely used. Almost always the envelope should be signature
// verified prior to consensus code running.
let pubkey = pubkey_bytes.decompress()?;
// Ensure the state's epoch matches the message's epoch before determining the Fork.
if self.epoch() != state.current_epoch() {
return Err(BeaconStateError::SignedEnvelopeIncorrectEpoch {
state_epoch: state.current_epoch(),
envelope_epoch: self.epoch(),
});
}
Ok(self.verify_signature(
&pubkey,
&state.fork(),
state.genesis_validators_root(),
spec,
))
}
}
#[cfg(test)]

View File

@@ -23,7 +23,7 @@ use tree_hash_derive::TreeHash;
use typenum::Unsigned;
use crate::{
ExecutionBlockHash, ExecutionPayloadBid, Withdrawal,
Address, ExecutionBlockHash, ExecutionPayloadBid, Withdrawal,
attestation::{
AttestationData, AttestationDuty, BeaconCommittee, Checkpoint, CommitteeIndex, PTC,
ParticipationFlags, PendingAttestation,
@@ -174,6 +174,8 @@ pub enum BeaconStateError {
MerkleTreeError(merkle_proof::MerkleTreeError),
PartialWithdrawalCountInvalid(usize),
NonExecutionAddressWithdrawalCredential,
WithdrawalCredentialMissingVersion,
WithdrawalCredentialMissingAddress,
NoCommitteeFound(CommitteeIndex),
InvalidCommitteeIndex(CommitteeIndex),
/// `Attestation.data.index` field is invalid in overloaded data index scenario.
@@ -199,6 +201,10 @@ pub enum BeaconStateError {
ProposerLookaheadOutOfBounds {
i: usize,
},
SignedEnvelopeIncorrectEpoch {
state_epoch: Epoch,
envelope_epoch: Epoch,
},
InvalidIndicesCount,
InvalidExecutionPayloadAvailabilityIndex(usize),
}
@@ -1920,6 +1926,15 @@ impl<E: EthSpec> BeaconState<E> {
.ok_or(BeaconStateError::UnknownValidator(validator_index))
}
/// Safe indexer for the `builders` list.
///
/// Will return an error pre-Gloas, or for out-of-bounds indices.
pub fn get_builder(&self, builder_index: BuilderIndex) -> Result<&Builder, BeaconStateError> {
self.builders()?
.get(builder_index as usize)
.ok_or(BeaconStateError::UnknownBuilder(builder_index))
}
/// Add a validator to the registry and return the validator index that was allocated for it.
pub fn add_validator_to_registry(
&mut self,
@@ -1966,6 +1981,64 @@ impl<E: EthSpec> BeaconState<E> {
Ok(index)
}
/// Add a builder to the registry and return the builder index that was allocated for it.
pub fn add_builder_to_registry(
&mut self,
pubkey: PublicKeyBytes,
withdrawal_credentials: Hash256,
amount: u64,
slot: Slot,
spec: &ChainSpec,
) -> Result<BuilderIndex, BeaconStateError> {
// We are not yet using the spec's `set_or_append_list`, but could consider it if it crops
// up elsewhere. It has been retconned into the spec to support index reuse but so far
// index reuse is only relevant for builders.
let builder_index = self.get_index_for_new_builder()?;
let builders = self.builders_mut()?;
let version = *withdrawal_credentials
.as_slice()
.first()
.ok_or(BeaconStateError::WithdrawalCredentialMissingVersion)?;
let execution_address = withdrawal_credentials
.as_slice()
.get(12..)
.and_then(|bytes| Address::try_from(bytes).ok())
.ok_or(BeaconStateError::WithdrawalCredentialMissingAddress)?;
let builder = Builder {
pubkey,
version,
execution_address,
balance: amount,
deposit_epoch: slot.epoch(E::slots_per_epoch()),
withdrawable_epoch: spec.far_future_epoch,
};
if builder_index == builders.len() as u64 {
builders.push(builder)?;
} else {
*builders
.get_mut(builder_index as usize)
.ok_or(BeaconStateError::UnknownBuilder(builder_index))? = builder;
}
Ok(builder_index)
}
// TODO(gloas): Optimize this function if we see a lot of registered builders on-chain.
// A cache here could be quite fiddly because this calculation depends on withdrawable epoch
// and balance - a cache for this would need to be updated whenever either of those fields
// changes.
pub fn get_index_for_new_builder(&self) -> Result<BuilderIndex, BeaconStateError> {
let current_epoch = self.current_epoch();
for (index, builder) in self.builders()?.iter().enumerate() {
if builder.withdrawable_epoch <= current_epoch && builder.balance == 0 {
return Ok(index as u64);
}
}
Ok(self.builders()?.len() as u64)
}
/// Safe copy-on-write accessor for the `validators` list.
pub fn get_validator_cow(
&mut self,

View File

@@ -4,6 +4,8 @@ mod validator_registration_data;
mod validator_subscription;
pub use proposer_preparation_data::ProposerPreparationData;
pub use validator::{Validator, is_compounding_withdrawal_credential};
pub use validator::{
Validator, is_builder_withdrawal_credential, is_compounding_withdrawal_credential,
};
pub use validator_registration_data::{SignedValidatorRegistrationData, ValidatorRegistrationData};
pub use validator_subscription::ValidatorSubscription;

View File

@@ -319,6 +319,14 @@ pub fn is_compounding_withdrawal_credential(
.unwrap_or(false)
}
pub fn is_builder_withdrawal_credential(withdrawal_credentials: Hash256, spec: &ChainSpec) -> bool {
withdrawal_credentials
.as_slice()
.first()
.map(|prefix_byte| *prefix_byte == spec.builder_withdrawal_prefix_byte)
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;