Merge branch 'unstable' of https://github.com/sigp/lighthouse into gloas-block-and-bid-production

This commit is contained in:
Eitan Seri- Levi
2026-02-10 12:12:24 -08:00
68 changed files with 3276 additions and 1321 deletions

View File

@@ -1,8 +1,8 @@
use integer_sqrt::IntegerSquareRoot;
use safe_arith::SafeArith;
use smallvec::SmallVec;
use types::{AttestationData, BeaconState, ChainSpec, EthSpec};
use types::{
BeaconStateError as Error,
AttestationData, BeaconState, BeaconStateError as Error, ChainSpec, EthSpec,
consts::altair::{
NUM_FLAG_INDICES, TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX,
TIMELY_TARGET_FLAG_INDEX,
@@ -16,6 +16,8 @@ use types::{
///
/// This function will return an error if the source of the attestation doesn't match the
/// state's relevant justified checkpoint.
///
/// This function has been abstracted to work for all forks from Altair to Gloas.
pub fn get_attestation_participation_flag_indices<E: EthSpec>(
state: &BeaconState<E>,
data: &AttestationData,
@@ -27,13 +29,43 @@ pub fn get_attestation_participation_flag_indices<E: EthSpec>(
} else {
state.previous_justified_checkpoint()
};
// Matching roots.
let is_matching_source = data.source == justified_checkpoint;
// Matching target.
let is_matching_target = is_matching_source
&& data.target.root == *state.get_block_root_at_epoch(data.target.epoch)?;
let is_matching_head =
is_matching_target && data.beacon_block_root == *state.get_block_root(data.slot)?;
// [New in Gloas:EIP7732]
let payload_matches = if state.fork_name_unchecked().gloas_enabled() {
if state.is_attestation_same_slot(data)? {
// For same-slot attestations, data.index must be 0
if data.index != 0 {
return Err(Error::BadOverloadedDataIndex(data.index));
}
true
} else {
// For non same-slot attestations, check execution payload availability
let slot_index = data
.slot
.as_usize()
.safe_rem(E::slots_per_historical_root())?;
let payload_index = state
.execution_payload_availability()?
.get(slot_index)
.map(|avail| if avail { 1 } else { 0 })
.map_err(|_| Error::InvalidExecutionPayloadAvailabilityIndex(slot_index))?;
data.index == payload_index
}
} else {
// Essentially `payload_matches` is always true pre-Gloas (it is not considered for matching
// head).
true
};
// Matching head.
let is_matching_head = is_matching_target
&& data.beacon_block_root == *state.get_block_root(data.slot)?
&& payload_matches;
if !is_matching_source {
return Err(Error::IncorrectAttestationSource);

View File

@@ -0,0 +1,278 @@
use crate::BlockProcessingError;
use crate::VerifySignatures;
use crate::per_block_processing::compute_timestamp_at_slot;
use crate::per_block_processing::process_operations::{
process_consolidation_requests, process_deposit_requests_post_gloas,
process_withdrawal_requests,
};
use safe_arith::{ArithError, SafeArith};
use tree_hash::TreeHash;
use types::{
BeaconState, BeaconStateError, BuilderIndex, BuilderPendingPayment, ChainSpec, EthSpec,
ExecutionBlockHash, Hash256, SignedExecutionPayloadEnvelope, Slot,
};
macro_rules! envelope_verify {
($condition: expr, $result: expr) => {
if !$condition {
return Err($result);
}
};
}
#[derive(Debug, Clone)]
pub enum EnvelopeProcessingError {
/// Bad Signature
BadSignature,
BeaconStateError(BeaconStateError),
BlockProcessingError(BlockProcessingError),
ArithError(ArithError),
/// Envelope doesn't match latest beacon block header
LatestBlockHeaderMismatch {
envelope_root: Hash256,
block_header_root: Hash256,
},
/// Envelope doesn't match latest beacon block slot
SlotMismatch {
envelope_slot: Slot,
parent_state_slot: Slot,
},
/// The payload withdrawals don't match the state's payload withdrawals.
WithdrawalsRootMismatch {
state: Hash256,
payload: Hash256,
},
// The builder index doesn't match the committed bid.
BuilderIndexMismatch {
committed_bid: BuilderIndex,
envelope: BuilderIndex,
},
// The gas limit doesn't match the committed bid
GasLimitMismatch {
committed_bid: u64,
envelope: u64,
},
// The block hash doesn't match the committed bid
BlockHashMismatch {
committed_bid: ExecutionBlockHash,
envelope: ExecutionBlockHash,
},
// The parent hash doesn't match the previous execution payload
ParentHashMismatch {
state: ExecutionBlockHash,
envelope: ExecutionBlockHash,
},
// The previous randao didn't match the payload
PrevRandaoMismatch {
committed_bid: Hash256,
envelope: Hash256,
},
// The timestamp didn't match the payload
TimestampMismatch {
state: u64,
envelope: u64,
},
// Invalid state root
InvalidStateRoot {
state: Hash256,
envelope: Hash256,
},
// BitFieldError
BitFieldError(ssz::BitfieldError),
// Some kind of error calculating the builder payment index
BuilderPaymentIndexOutOfBounds(usize),
/// The envelope was deemed invalid by the execution engine.
ExecutionInvalid,
}
impl From<BeaconStateError> for EnvelopeProcessingError {
fn from(e: BeaconStateError) -> Self {
EnvelopeProcessingError::BeaconStateError(e)
}
}
impl From<BlockProcessingError> for EnvelopeProcessingError {
fn from(e: BlockProcessingError) -> Self {
EnvelopeProcessingError::BlockProcessingError(e)
}
}
impl From<ArithError> for EnvelopeProcessingError {
fn from(e: ArithError) -> Self {
EnvelopeProcessingError::ArithError(e)
}
}
/// Processes a `SignedExecutionPayloadEnvelope`
///
/// This function does all the state modifications inside `process_execution_payload()`
pub fn process_execution_payload_envelope<E: EthSpec>(
state: &mut BeaconState<E>,
parent_state_root: Option<Hash256>,
signed_envelope: &SignedExecutionPayloadEnvelope<E>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), EnvelopeProcessingError> {
if verify_signatures.is_true() {
// Verify Signed Envelope Signature
if !signed_envelope.verify_signature_with_state(state, spec)? {
return Err(EnvelopeProcessingError::BadSignature);
}
}
let envelope = &signed_envelope.message;
let payload = &envelope.payload;
let execution_requests = &envelope.execution_requests;
// Cache latest block header state root
if state.latest_block_header().state_root == Hash256::default() {
let previous_state_root = parent_state_root
.map(Ok)
.unwrap_or_else(|| state.canonical_root())?;
state.latest_block_header_mut().state_root = previous_state_root;
}
// Verify consistency with the beacon block
let latest_block_header_root = state.latest_block_header().tree_hash_root();
envelope_verify!(
envelope.beacon_block_root == latest_block_header_root,
EnvelopeProcessingError::LatestBlockHeaderMismatch {
envelope_root: envelope.beacon_block_root,
block_header_root: latest_block_header_root,
}
);
envelope_verify!(
envelope.slot == state.slot(),
EnvelopeProcessingError::SlotMismatch {
envelope_slot: envelope.slot,
parent_state_slot: state.slot(),
}
);
// Verify consistency with the committed bid
let committed_bid = state.latest_execution_payload_bid()?;
envelope_verify!(
envelope.builder_index == committed_bid.builder_index,
EnvelopeProcessingError::BuilderIndexMismatch {
committed_bid: committed_bid.builder_index,
envelope: envelope.builder_index,
}
);
envelope_verify!(
committed_bid.prev_randao == payload.prev_randao,
EnvelopeProcessingError::PrevRandaoMismatch {
committed_bid: committed_bid.prev_randao,
envelope: payload.prev_randao,
}
);
// Verify consistency with expected withdrawals
// NOTE: we don't bother hashing here except in case of error, because we can just compare for
// equality directly. This equality check could be more straight-forward if the types were
// changed to match (currently we are comparing VariableList to List). This could happen
// coincidentally when we adopt ProgressiveList.
envelope_verify!(
payload.withdrawals.len() == state.payload_expected_withdrawals()?.len()
&& payload
.withdrawals
.iter()
.eq(state.payload_expected_withdrawals()?.iter()),
EnvelopeProcessingError::WithdrawalsRootMismatch {
state: state.payload_expected_withdrawals()?.tree_hash_root(),
payload: payload.withdrawals.tree_hash_root(),
}
);
// Verify the gas limit
envelope_verify!(
committed_bid.gas_limit == payload.gas_limit,
EnvelopeProcessingError::GasLimitMismatch {
committed_bid: committed_bid.gas_limit,
envelope: payload.gas_limit,
}
);
// Verify the block hash
envelope_verify!(
committed_bid.block_hash == payload.block_hash,
EnvelopeProcessingError::BlockHashMismatch {
committed_bid: committed_bid.block_hash,
envelope: payload.block_hash,
}
);
// Verify consistency of the parent hash with respect to the previous execution payload
envelope_verify!(
payload.parent_hash == *state.latest_block_hash()?,
EnvelopeProcessingError::ParentHashMismatch {
state: *state.latest_block_hash()?,
envelope: payload.parent_hash,
}
);
// Verify timestamp
let state_timestamp = compute_timestamp_at_slot(state, state.slot(), spec)?;
envelope_verify!(
payload.timestamp == state_timestamp,
EnvelopeProcessingError::TimestampMismatch {
state: state_timestamp,
envelope: payload.timestamp,
}
);
// TODO(gloas): newPayload happens here in the spec, ensure we wire that up correctly
process_deposit_requests_post_gloas(state, &execution_requests.deposits, spec)?;
// TODO(gloas): gotta update these
process_withdrawal_requests(state, &execution_requests.withdrawals, spec)?;
process_consolidation_requests(state, &execution_requests.consolidations, spec)?;
// Queue the builder payment
let payment_index = E::slots_per_epoch()
.safe_add(state.slot().as_u64().safe_rem(E::slots_per_epoch())?)?
as usize;
let payment_mut = state
.builder_pending_payments_mut()?
.get_mut(payment_index)
.ok_or(EnvelopeProcessingError::BuilderPaymentIndexOutOfBounds(
payment_index,
))?;
// We have re-ordered the blanking out of the pending payment to avoid a double-lookup.
// This is semantically equivalent to the ordering used by the spec because we have taken a
// clone of the payment prior to doing the write.
let payment_withdrawal = payment_mut.withdrawal.clone();
*payment_mut = BuilderPendingPayment::default();
let amount = payment_withdrawal.amount;
if amount > 0 {
state
.builder_pending_withdrawals_mut()?
.push(payment_withdrawal)
.map_err(|e| EnvelopeProcessingError::BeaconStateError(e.into()))?;
}
// Cache the execution payload hash
let availability_index = state
.slot()
.as_usize()
.safe_rem(E::slots_per_historical_root())?;
state
.execution_payload_availability_mut()?
.set(availability_index, true)
.map_err(EnvelopeProcessingError::BitFieldError)?;
*state.latest_block_hash_mut()? = payload.block_hash;
// Verify the state root
let state_root = state.canonical_root()?;
envelope_verify!(
envelope.state_root == state_root,
EnvelopeProcessingError::InvalidStateRoot {
state: state_root,
envelope: envelope.state_root,
}
);
Ok(())
}

View File

@@ -20,6 +20,7 @@ pub mod all_caches;
pub mod block_replayer;
pub mod common;
pub mod consensus_context;
pub mod envelope_processing;
pub mod epoch_cache;
pub mod genesis;
pub mod per_block_processing;

View File

@@ -99,6 +99,8 @@ pub enum BlockProcessingError {
IncorrectExpectedWithdrawalsVariant,
MissingLastWithdrawal,
PendingAttestationInElectra,
/// Builder payment index out of bounds (Gloas)
BuilderPaymentIndexOutOfBounds(usize),
}
impl From<BeaconStateError> for BlockProcessingError {
@@ -372,6 +374,8 @@ pub enum AttestationInvalid {
BadSignature,
/// The indexed attestation created from this attestation was found to be invalid.
BadIndexedAttestation(IndexedAttestationInvalid),
/// The overloaded "data.index" field is invalid (post-Gloas).
BadOverloadedDataIndex,
}
impl From<BlockOperationError<IndexedAttestationInvalid>>

View File

@@ -5,6 +5,7 @@ use crate::common::{
slash_validator,
};
use crate::per_block_processing::errors::{BlockProcessingError, IntoWithIndex};
use bls::{PublicKeyBytes, SignatureBytes};
use ssz_types::FixedVector;
use typenum::U33;
use types::consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR};
@@ -38,9 +39,14 @@ pub fn process_operations<E: EthSpec, Payload: AbstractExecPayload<E>>(
process_bls_to_execution_changes(state, bls_to_execution_changes, verify_signatures, spec)?;
}
if state.fork_name_unchecked().electra_enabled() {
if state.fork_name_unchecked().electra_enabled() && !state.fork_name_unchecked().gloas_enabled()
{
state.update_pubkey_cache()?;
process_deposit_requests(state, &block_body.execution_requests()?.deposits, spec)?;
process_deposit_requests_pre_gloas(
state,
&block_body.execution_requests()?.deposits,
spec,
)?;
process_withdrawal_requests(state, &block_body.execution_requests()?.withdrawals, spec)?;
process_consolidation_requests(
state,
@@ -212,6 +218,148 @@ pub mod altair_deneb {
}
}
pub mod gloas {
use super::*;
use crate::common::update_progressive_balances_cache::update_progressive_balances_on_attestation;
pub fn process_attestations<'a, E: EthSpec, I>(
state: &mut BeaconState<E>,
attestations: I,
verify_signatures: VerifySignatures,
ctxt: &mut ConsensusContext<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError>
where
I: Iterator<Item = AttestationRef<'a, E>>,
{
attestations.enumerate().try_for_each(|(i, attestation)| {
process_attestation(state, attestation, i, ctxt, verify_signatures, spec)
})
}
pub fn process_attestation<E: EthSpec>(
state: &mut BeaconState<E>,
attestation: AttestationRef<E>,
att_index: usize,
ctxt: &mut ConsensusContext<E>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
let proposer_index = ctxt.get_proposer_index(state, spec)?;
let previous_epoch = ctxt.previous_epoch;
let current_epoch = ctxt.current_epoch;
let indexed_att = verify_attestation_for_block_inclusion(
state,
attestation,
ctxt,
verify_signatures,
spec,
)
.map_err(|e| e.into_with_index(att_index))?;
// Matching roots, participation flag indices
let data = attestation.data();
let inclusion_delay = state.slot().safe_sub(data.slot)?.as_u64();
let participation_flag_indices =
get_attestation_participation_flag_indices(state, data, inclusion_delay, spec)?;
// [New in EIP-7732]
let current_epoch_target = data.target.epoch == state.current_epoch();
let slot_mod = data
.slot
.as_usize()
.safe_rem(E::slots_per_epoch() as usize)?;
let payment_index = if current_epoch_target {
(E::slots_per_epoch() as usize).safe_add(slot_mod)?
} else {
slot_mod
};
// Cached here to avoid repeat lookups. The withdrawal amount is immutable throughout
// this whole function.
let payment_withdrawal_amount = state
.builder_pending_payments()?
.get(payment_index)
.ok_or(BlockProcessingError::BuilderPaymentIndexOutOfBounds(
payment_index,
))?
.withdrawal
.amount;
// Update epoch participation flags.
let mut proposer_reward_numerator = 0;
for index in indexed_att.attesting_indices_iter() {
let index = *index as usize;
let validator_effective_balance = state.epoch_cache().get_effective_balance(index)?;
let validator_slashed = state.slashings_cache().is_slashed(index);
// [New in Gloas:EIP7732]
// For same-slot attestations, check if we're setting any new flags
// If we are, this validator hasn't contributed to this slot's quorum yet
let mut will_set_new_flag = false;
for (flag_index, &weight) in PARTICIPATION_FLAG_WEIGHTS.iter().enumerate() {
let epoch_participation = state.get_epoch_participation_mut(
data.target.epoch,
previous_epoch,
current_epoch,
)?;
if participation_flag_indices.contains(&flag_index) {
let validator_participation = epoch_participation
.get_mut(index)
.ok_or(BeaconStateError::ParticipationOutOfBounds(index))?;
if !validator_participation.has_flag(flag_index)? {
validator_participation.add_flag(flag_index)?;
proposer_reward_numerator
.safe_add_assign(state.get_base_reward(index)?.safe_mul(weight)?)?;
will_set_new_flag = true;
update_progressive_balances_on_attestation(
state,
data.target.epoch,
flag_index,
validator_effective_balance,
validator_slashed,
)?;
}
}
}
// [New in Gloas:EIP7732]
// Add weight for same-slot attestations when any new flag is set.
// This ensures each validator contributes exactly once per slot.
if will_set_new_flag
&& state.is_attestation_same_slot(data)?
&& payment_withdrawal_amount > 0
{
let builder_payments = state.builder_pending_payments_mut()?;
let payment = builder_payments.get_mut(payment_index).ok_or(
BlockProcessingError::BuilderPaymentIndexOutOfBounds(payment_index),
)?;
payment
.weight
.safe_add_assign(validator_effective_balance)?;
}
}
let proposer_reward_denominator = WEIGHT_DENOMINATOR
.safe_sub(PROPOSER_WEIGHT)?
.safe_mul(WEIGHT_DENOMINATOR)?
.safe_div(PROPOSER_WEIGHT)?;
let proposer_reward = proposer_reward_numerator.safe_div(proposer_reward_denominator)?;
increase_balance(state, proposer_index as usize, proposer_reward)?;
// [New in Gloas:EIP7732]
// Update builder payment weight
// No-op, this is done inline above.
Ok(())
}
}
/// Validates each `ProposerSlashing` and updates the state, short-circuiting on an invalid object.
///
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
@@ -235,6 +383,31 @@ pub fn process_proposer_slashings<E: EthSpec>(
verify_proposer_slashing(proposer_slashing, state, verify_signatures, spec)
.map_err(|e| e.into_with_index(i))?;
// [New in Gloas:EIP7732]
// Remove the BuilderPendingPayment corresponding to this proposal
// if it is still in the 2-epoch window.
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());
let slot_in_epoch = slot.as_usize().safe_rem(E::SlotsPerEpoch::to_usize())?;
let payment_index = if proposal_epoch == state.current_epoch() {
Some(E::SlotsPerEpoch::to_usize().safe_add(slot_in_epoch)?)
} else if proposal_epoch == state.previous_epoch() {
Some(slot_in_epoch)
} else {
None
};
if let Some(index) = payment_index {
let payment = state
.builder_pending_payments_mut()?
.get_mut(index)
.ok_or(BlockProcessingError::BuilderPaymentIndexOutOfBounds(index))?;
*payment = BuilderPendingPayment::default();
}
}
slash_validator(
state,
proposer_slashing.signed_header_1.message.proposer_index as usize,
@@ -285,7 +458,15 @@ pub fn process_attestations<E: EthSpec, Payload: AbstractExecPayload<E>>(
ctxt: &mut ConsensusContext<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
if state.fork_name_unchecked().altair_enabled() {
if state.fork_name_unchecked().gloas_enabled() {
gloas::process_attestations(
state,
block_body.attestations(),
verify_signatures,
ctxt,
spec,
)?;
} else if state.fork_name_unchecked().altair_enabled() {
altair_deneb::process_attestations(
state,
block_body.attestations(),
@@ -586,7 +767,7 @@ pub fn process_withdrawal_requests<E: EthSpec>(
Ok(())
}
pub fn process_deposit_requests<E: EthSpec>(
pub fn process_deposit_requests_pre_gloas<E: EthSpec>(
state: &mut BeaconState<E>,
deposit_requests: &[DepositRequest],
spec: &ChainSpec,
@@ -613,6 +794,112 @@ pub fn process_deposit_requests<E: EthSpec>(
Ok(())
}
pub fn process_deposit_requests_post_gloas<E: EthSpec>(
state: &mut BeaconState<E>,
deposit_requests: &[DepositRequest],
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
for request in deposit_requests {
process_deposit_request_post_gloas(state, request, spec)?;
}
Ok(())
}
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 is_builder_prefix =
is_builder_withdrawal_credential(deposit_request.withdrawal_credentials, spec);
if is_builder || (is_builder_prefix && !is_validator) {
// 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<(), BlockProcessingError> {
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() {
state.add_builder_to_registry(
pubkey,
withdrawal_credentials,
amount,
slot,
spec,
)?;
}
}
Some(builder_index) => {
state
.builders_mut()?
.get_mut(builder_index as usize)
.ok_or(BeaconStateError::UnknownBuilder(builder_index))?
.balance
.safe_add_assign(amount)?;
}
}
Ok(())
}
// Make sure to build the pubkey cache before calling this function
pub fn process_consolidation_requests<E: EthSpec>(
state: &mut BeaconState<E>,

View File

@@ -52,8 +52,6 @@ pub fn verify_attestation_for_block_inclusion<'ctxt, E: EthSpec>(
///
/// Returns a descriptive `Err` if the attestation is malformed or does not accurately reflect the
/// prior blocks in `state`.
///
/// Spec v0.12.1
pub fn verify_attestation_for_state<'ctxt, E: EthSpec>(
state: &BeaconState<E>,
attestation: AttestationRef<'ctxt, E>,
@@ -74,7 +72,12 @@ pub fn verify_attestation_for_state<'ctxt, E: EthSpec>(
);
}
AttestationRef::Electra(_) => {
verify!(data.index == 0, Invalid::BadCommitteeIndex);
let fork_at_attestation_slot = spec.fork_name_at_slot::<E>(data.slot);
if fork_at_attestation_slot.gloas_enabled() {
verify!(data.index < 2, Invalid::BadOverloadedDataIndex);
} else {
verify!(data.index == 0, Invalid::BadCommitteeIndex);
}
}
}
@@ -89,8 +92,6 @@ pub fn verify_attestation_for_state<'ctxt, E: EthSpec>(
}
/// Check target epoch and source checkpoint.
///
/// Spec v0.12.1
fn verify_casper_ffg_vote<E: EthSpec>(
attestation: AttestationRef<E>,
state: &BeaconState<E>,

View File

@@ -1,4 +1,5 @@
use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use std::hint::black_box;
use swap_or_not_shuffle::{compute_shuffled_index, shuffle_list as fast_shuffle};
const SHUFFLE_ROUND_COUNT: u8 = 90;

View File

@@ -1,8 +1,9 @@
use criterion::{BatchSize, BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main};
use fixed_bytes::FixedBytesExtended;
use milhouse::List;
use rayon::prelude::*;
use ssz::Encode;
use std::hint::black_box;
use std::sync::Arc;
use types::{
BeaconState, Epoch, Eth1Data, EthSpec, Hash256, MainnetEthSpec, Validator,

View File

@@ -1,6 +1,7 @@
use crate::test_utils::TestRandom;
use crate::{Address, Epoch};
use crate::{Address, Epoch, ForkName};
use bls::PublicKeyBytes;
use context_deserialize::context_deserialize;
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
@@ -12,6 +13,7 @@ pub type BuilderIndex = u64;
#[derive(
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Encode, Decode, TestRandom, TreeHash,
)]
#[context_deserialize(ForkName)]
pub struct Builder {
pub pubkey: PublicKeyBytes,
#[serde(with = "serde_utils::quoted_u8")]

View File

@@ -1,9 +1,11 @@
use crate::test_utils::TestRandom;
use crate::{
ChainSpec, Domain, Epoch, EthSpec, ExecutionBlockHash, ExecutionPayloadEnvelope, Fork, 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;
use educe::Educe;
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
@@ -13,6 +15,7 @@ use tree_hash_derive::TreeHash;
#[derive(Debug, Clone, Serialize, Encode, Decode, Deserialize, TestRandom, TreeHash, Educe)]
#[educe(PartialEq, Hash(bound(E: EthSpec)))]
#[serde(bound = "E: EthSpec")]
#[context_deserialize(ForkName)]
pub struct SignedExecutionPayloadEnvelope<E: EthSpec> {
pub message: ExecutionPayloadEnvelope<E>,
pub signature: Signature,
@@ -56,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,8 +174,12 @@ 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.
BadOverloadedDataIndex(u64),
InvalidSelectionProof {
aggregator_index: u64,
},
@@ -197,7 +201,12 @@ pub enum BeaconStateError {
ProposerLookaheadOutOfBounds {
i: usize,
},
SignedEnvelopeIncorrectEpoch {
state_epoch: Epoch,
envelope_epoch: Epoch,
},
InvalidIndicesCount,
InvalidExecutionPayloadAvailabilityIndex(usize),
}
/// Control whether an epoch-indexed field can be indexed at the next epoch or not.
@@ -1917,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,
@@ -1963,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::*;