Compare commits

...

16 Commits

Author SHA1 Message Date
Jimmy Chen
ade55a26f2 Fix Gloas EF test failures and add test exclusions (#8687)
* Added gloas exclusion to `FinalityHandler`

* Fix Gloas EF test failures and add test exclusions

- Fix execution_payload_availability initialization in fork upgrade
to set all bits to true per spec (was all zeros)
- Fix minimal spec gloas_fork_version: [0x07,0x00,0x00,0x01] (was 0x00)
- Fix payload attestation signature domain to use get_domain instead
of compute_domain with genesis fork version
- Add proposer slashing handler to clear builder_pending_payment per
EIP-7732 spec
- Add Gloas test exclusions for unimplemented functionality:
- sanity_blocks, sanity_slots, random, transition, finality handlers
- deposit_request operation (requires builder deposit functionality)
- Python exclusions for check_all_files_accessed.py

* fmt
2026-01-21 11:01:56 +11:00
Jimmy Chen
c276fe6a1d format and lint fixes 2026-01-20 18:01:15 +11:00
Jimmy Chen
8b4c037fb4 Add get_builders_sweep_withdrawals and fix Gloas withdrawal tests. 2026-01-20 17:07:27 +11:00
Jimmy Chen
5c8acea305 Update get_expected_withdrawals to return processed_validators_sweep_count. 2026-01-20 16:25:24 +11:00
Michael Sproul
b403504aaa Extract get_pending_partial_withdrawals 2026-01-20 15:49:08 +11:00
Jimmy Chen
7d95ef3074 Add Gloas EF test infrastructure and fork choice exclusion.
- Add fork choice constants to consts.rs (PayloadStatus, timeliness indices)
- Add payload_timely_threshold() helper to EthSpec
- Exclude Gloas from fork_choice tests until implementation is complete
- Add PayloadAttestation operation test handler for EF tests
2026-01-20 15:47:49 +11:00
Jimmy Chen
ae38421589 Add TypeName implementations for Gloas types. 2026-01-20 15:00:00 +11:00
Jimmy Chen
0a152ed68d Fix gloas consensus-specs discrepancies and add EF tests
- Fix DOMAIN_BEACON_BUILDER value (0x1B -> 0x0B per spec)
- Add DOMAIN_PROPOSER_PREFERENCES (0x0D)
- Add min_builder_withdrawability_delay config (4096 epochs)
- Add MaxBuildersPerWithdrawalsSweep to EthSpec trait
- Add gloas_only/gloas_and_later handlers for EF tests
- Add SSZ static tests for all new Gloas types
2026-01-20 14:49:17 +11:00
Jimmy Chen
5897ea037b Update mainnet config to include gloas changes. 2026-01-20 14:35:12 +11:00
Michael Sproul
ee0a8a93af All ssz_static tests passing except DataColumnSidecar 2026-01-20 14:22:16 +11:00
Michael Sproul
7bf98d4ff6 BeaconState ssz_static tests passing 2026-01-20 09:51:29 +11:00
Michael Sproul
90122b7662 Refactor process_builder_withdrawals 2026-01-19 18:53:15 +11:00
Jimmy Chen
da98ffec8d Add new fields to BeaconState to match v1.7.0-alpha.1. 2026-01-19 16:45:39 +11:00
Jimmy Chen
4ffb11ed5c Fix attestation participation logic error. 2026-01-19 16:17:52 +11:00
shane-moore
91f483415a Squash of "Gloas modify process_attestations #8283" 2026-01-19 16:03:25 +11:00
Michael Sproul
2d96b3f193 Extract consensus changes from gloas-envelope-processing 2026-01-19 15:59:36 +11:00
48 changed files with 2515 additions and 293 deletions

View File

@@ -4794,7 +4794,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let proposal_epoch = proposal_slot.epoch(T::EthSpec::slots_per_epoch());
if head_state.current_epoch() == proposal_epoch {
return get_expected_withdrawals(&unadvanced_state, &self.spec)
.map(|(withdrawals, _)| withdrawals)
.map(|(withdrawals, _, _, _, _)| withdrawals)
.map_err(Error::PrepareProposerFailed);
}
@@ -4812,7 +4812,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
&self.spec,
)?;
get_expected_withdrawals(&advanced_state, &self.spec)
.map(|(withdrawals, _)| withdrawals)
.map(|(withdrawals, _, _, _, _)| withdrawals)
.map_err(Error::PrepareProposerFailed)
}

View File

@@ -1364,6 +1364,7 @@ async fn proposer_shuffling_root_consistency_at_fork_boundary() {
}
#[tokio::test]
#[allow(clippy::large_stack_frames)]
async fn proposer_shuffling_changing_with_lookahead() {
let initial_blocks = E::slots_per_epoch() * 4 - 1;
@@ -1412,7 +1413,7 @@ async fn proposer_shuffling_changing_with_lookahead() {
let consolidation_request: ConsolidationRequest = ConsolidationRequest {
source_address: validator_to_topup
.get_execution_withdrawal_address(spec)
.get_execution_withdrawal_address(spec, ForkName::Fulu)
.unwrap(),
source_pubkey: validator_to_topup.pubkey,
target_pubkey: validator_to_topup.pubkey,
@@ -1491,7 +1492,7 @@ async fn proposer_shuffling_changing_with_lookahead() {
let validator = current_epoch_state
.get_validator(validator_to_topup_index)
.unwrap();
assert!(validator.has_compounding_withdrawal_credential(spec));
assert!(validator.has_compounding_withdrawal_credential(spec, ForkName::Fulu));
assert_eq!(validator.effective_balance, 95_000_000_000);
// The shuffling for the current epoch from `prev_epoch_state` should match the shuffling

View File

@@ -32,7 +32,7 @@ pub fn get_next_withdrawals<T: BeaconChainTypes>(
}
match get_expected_withdrawals(&state, &chain.spec) {
Ok((withdrawals, _)) => Ok(withdrawals),
Ok((withdrawals, _, _, _, _)) => Ok(withdrawals),
Err(e) => Err(warp_utils::reject::custom_server_error(format!(
"failed to get expected withdrawal: {:?}",
e

View File

@@ -113,16 +113,18 @@ impl<E: EthSpec> BlsToExecutionChanges<E> {
.validators()
.get(validator_index as usize)
.is_none_or(|validator| {
let prune = validator.has_execution_withdrawal_credential(spec)
&& head_block
.message()
.body()
.bls_to_execution_changes()
.map_or(true, |recent_changes| {
!recent_changes
.iter()
.any(|c| c.message.validator_index == validator_index)
});
let prune = validator.has_execution_withdrawal_credential(
spec,
head_state.fork_name_unchecked(),
) && head_block
.message()
.body()
.bls_to_execution_changes()
.map_or(true, |recent_changes| {
!recent_changes
.iter()
.any(|c| c.message.validator_index == validator_index)
});
if prune {
validator_indices_pruned.push(validator_index);
}

View File

@@ -582,7 +582,12 @@ impl<E: EthSpec> OperationPool<E> {
address_change.signature_is_still_valid(&state.fork())
&& state
.get_validator(address_change.as_inner().message.validator_index as usize)
.is_ok_and(|validator| !validator.has_execution_withdrawal_credential(spec))
.is_ok_and(|validator| {
!validator.has_execution_withdrawal_credential(
spec,
state.fork_name_unchecked(),
)
})
},
|address_change| address_change.as_inner().clone(),
E::MaxBlsToExecutionChanges::to_usize(),

View File

@@ -35,6 +35,8 @@ impl<E: EthSpec> OnDiskConsensusContext<E> {
proposer_index,
current_block_root,
indexed_attestations,
indexed_payload_attestations: _,
// TODO(EIP-7732): add indexed_payload_attestations to the on-disk format.
} = ctxt;
OnDiskConsensusContext {
slot,

View File

@@ -22,13 +22,13 @@ TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615
# Genesis
# ---------------------------------------------------------------
# `2**14` (= 16,384)
# 2**14 (= 16,384) validators
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 16384
# Dec 1, 2020, 12pm UTC
MIN_GENESIS_TIME: 1606824000
# Mainnet initial fork version, recommend altering for testnets
# Initial fork version for mainnet
GENESIS_FORK_VERSION: 0x00000000
# 604800 seconds (7 days)
# 7 * 24 * 3,600 (= 604,800) seconds, 7 days
GENESIS_DELAY: 604800
# Forking
@@ -39,25 +39,34 @@ GENESIS_DELAY: 604800
# Altair
ALTAIR_FORK_VERSION: 0x01000000
ALTAIR_FORK_EPOCH: 74240 # Oct 27, 2021, 10:56:23am UTC
ALTAIR_FORK_EPOCH: 74240 # Oct 27, 2021, 10:56:23am UTC
# Bellatrix
BELLATRIX_FORK_VERSION: 0x02000000
BELLATRIX_FORK_EPOCH: 144896 # Sept 6, 2022, 11:34:47am UTC
BELLATRIX_FORK_EPOCH: 144896 # Sept 6, 2022, 11:34:47am UTC
# Capella
CAPELLA_FORK_VERSION: 0x03000000
CAPELLA_FORK_EPOCH: 194048 # April 12, 2023, 10:27:35pm UTC
CAPELLA_FORK_EPOCH: 194048 # April 12, 2023, 10:27:35pm UTC
# Deneb
DENEB_FORK_VERSION: 0x04000000
DENEB_FORK_EPOCH: 269568 # March 13, 2024, 01:55:35pm UTC
DENEB_FORK_EPOCH: 269568 # March 13, 2024, 01:55:35pm UTC
# Electra
ELECTRA_FORK_VERSION: 0x05000000
ELECTRA_FORK_EPOCH: 364032 # May 7, 2025, 10:05:11am UTC
ELECTRA_FORK_EPOCH: 364032 # May 7, 2025, 10:05:11am UTC
# Fulu
FULU_FORK_VERSION: 0x06000000
FULU_FORK_EPOCH: 411392 # December 3, 2025, 09:49:11pm UTC
FULU_FORK_EPOCH: 411392 # December 3, 2025, 09:49:11pm UTC
# Gloas
GLOAS_FORK_VERSION: 0x07000000
GLOAS_FORK_VERSION: 0x07000000 # temporary stub
GLOAS_FORK_EPOCH: 18446744073709551615
# EIP7441
EIP7441_FORK_VERSION: 0x08000000 # temporary stub
EIP7441_FORK_EPOCH: 18446744073709551615
# EIP7805
EIP7805_FORK_VERSION: 0x0a000000 # temporary stub
EIP7805_FORK_EPOCH: 18446744073709551615
# EIP7928
EIP7928_FORK_VERSION: 0x0b000000 # temporary stub
EIP7928_FORK_EPOCH: 18446744073709551615
# Time parameters
# ---------------------------------------------------------------
@@ -86,6 +95,28 @@ SYNC_MESSAGE_DUE_BPS: 3333
# 6667 basis points, ~67% of SLOT_DURATION_MS
CONTRIBUTION_DUE_BPS: 6667
# Gloas
# 2**12 (= 4,096) epochs
MIN_BUILDER_WITHDRAWABILITY_DELAY: 4096
# 2500 basis points, 25% of SLOT_DURATION_MS
ATTESTATION_DUE_BPS_GLOAS: 2500
# 5000 basis points, 50% of SLOT_DURATION_MS
AGGREGATE_DUE_BPS_GLOAS: 5000
# 2500 basis points, 25% of SLOT_DURATION_MS
SYNC_MESSAGE_DUE_BPS_GLOAS: 2500
# 5000 basis points, 50% of SLOT_DURATION_MS
CONTRIBUTION_DUE_BPS_GLOAS: 5000
# 7500 basis points, 75% of SLOT_DURATION_MS
PAYLOAD_ATTESTATION_DUE_BPS: 7500
# EIP7805
# 7500 basis points, 75% of SLOT_DURATION_MS
VIEW_FREEZE_CUTOFF_BPS: 7500
# 6667 basis points, ~67% of SLOT_DURATION_MS
INCLUSION_LIST_SUBMISSION_DUE_BPS: 6667
# 9167 basis points, ~92% of SLOT_DURATION_MS
PROPOSER_INCLUSION_LIST_CUTOFF_BPS: 9167
# Validator cycle
# ---------------------------------------------------------------
# 2**2 (= 4)
@@ -137,10 +168,6 @@ MAX_REQUEST_BLOCKS: 1024
EPOCHS_PER_SUBNET_SUBSCRIPTION: 256
# MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2 (= 33,024) epochs
MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024
# 5s
TTFB_TIMEOUT: 5
# 10s
RESP_TIMEOUT: 10
# 2**5 (= 32) slots
ATTESTATION_PROPAGATION_SLOT_RANGE: 32
# 500ms
@@ -154,9 +181,7 @@ ATTESTATION_SUBNET_COUNT: 64
# 0 bits
ATTESTATION_SUBNET_EXTRA_BITS: 0
# ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS (= 6 + 0) bits
# computed at runtime
ATTESTATION_SUBNET_PREFIX_BITS: 6
ATTESTATION_SUBNET_SHUFFLING_PREFIX_BITS: 3
# Deneb
# 2**7 (= 128) blocks
@@ -196,6 +221,22 @@ BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000
# 2**12 (= 4,096) epochs
MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096
# Gloas
# 2**7 (= 128) payloads
MAX_REQUEST_PAYLOADS: 128
# EIP7441
# 2**8 (= 256) epochs
EPOCHS_PER_SHUFFLING_PHASE: 256
# 2**1 (= 2) epochs
PROPOSER_SELECTION_GAP: 2
# EIP7805
# 2**4 (= 16) inclusion lists
MAX_REQUEST_INCLUSION_LIST: 16
# 2**13 (= 8,192) bytes
MAX_BYTES_PER_INCLUSION_LIST: 8192
# Blob Scheduling
# ---------------------------------------------------------------
@@ -204,5 +245,3 @@ BLOB_SCHEDULE:
MAX_BLOBS_PER_BLOCK: 15
- EPOCH: 419072 # January 7, 2026, 01:01:11am UTC
MAX_BLOBS_PER_BLOCK: 21
# Gloas

View File

@@ -1,4 +1,5 @@
use integer_sqrt::IntegerSquareRoot;
use safe_arith::SafeArith;
use smallvec::SmallVec;
use types::{AttestationData, BeaconState, ChainSpec, EthSpec};
use types::{
@@ -22,18 +23,47 @@ pub fn get_attestation_participation_flag_indices<E: EthSpec>(
inclusion_delay: u64,
spec: &ChainSpec,
) -> Result<SmallVec<[usize; NUM_FLAG_INDICES]>, Error> {
// Matching source
let justified_checkpoint = if data.target.epoch == state.current_epoch() {
state.current_justified_checkpoint()
} else {
state.previous_justified_checkpoint()
};
// Matching roots.
let is_matching_source = data.source == justified_checkpoint;
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)?;
// Matching target
let target_root = *state.get_block_root_at_epoch(data.target.epoch)?;
let target_root_matches = data.target.root == target_root;
let is_matching_target = is_matching_source && target_root_matches;
// Matching head
let head_root = *state.get_block_root(data.slot)?;
let head_root_matches = data.beacon_block_root == head_root;
let is_matching_head = if state.fork_name_unchecked().gloas_enabled() {
let payload_matches = 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 } as u64)
.map_err(|_| Error::InvalidExecutionPayloadAvailabilityIndex(slot_index))?;
data.index == payload_index
};
is_matching_target && head_root_matches && payload_matches
} else {
is_matching_target && head_root_matches
};
if !is_matching_source {
return Err(Error::IncorrectAttestationSource);

View File

@@ -0,0 +1,45 @@
use crate::per_block_processing::errors::{
BlockOperationError, PayloadAttestationInvalid as Invalid,
};
use ssz_types::VariableList;
use typenum::Unsigned;
use types::*;
pub fn get_indexed_payload_attestation<E: EthSpec>(
state: &BeaconState<E>,
slot: Slot,
payload_attestation: &PayloadAttestation<E>,
spec: &ChainSpec,
) -> Result<IndexedPayloadAttestation<E>, BlockOperationError<Invalid>> {
let attesting_indices = get_payload_attesting_indices(state, slot, payload_attestation, spec)?;
Ok(IndexedPayloadAttestation {
attesting_indices: VariableList::new(attesting_indices)?,
data: payload_attestation.data.clone(),
signature: payload_attestation.signature.clone(),
})
}
pub fn get_payload_attesting_indices<E: EthSpec>(
state: &BeaconState<E>,
slot: Slot,
payload_attestation: &PayloadAttestation<E>,
spec: &ChainSpec,
) -> Result<Vec<u64>, BeaconStateError> {
let ptc = state.get_ptc(slot, spec)?;
let bitlist = &payload_attestation.aggregation_bits;
if bitlist.len() != E::PTCSize::to_usize() {
return Err(BeaconStateError::InvalidBitfield);
}
let mut attesting_indices = Vec::<u64>::new();
for (i, index) in ptc.into_iter().enumerate() {
if let Ok(true) = bitlist.get(i) {
attesting_indices.push(index as u64);
}
}
attesting_indices.sort_unstable();
Ok(attesting_indices)
}

View File

@@ -1,6 +1,7 @@
mod deposit_data_tree;
mod get_attestation_participation;
mod get_attesting_indices;
mod get_payload_attesting_indices;
mod initiate_validator_exit;
mod slash_validator;
@@ -13,6 +14,9 @@ pub use get_attestation_participation::get_attestation_participation_flag_indice
pub use get_attesting_indices::{
attesting_indices_base, attesting_indices_electra, get_attesting_indices_from_state,
};
pub use get_payload_attesting_indices::{
get_indexed_payload_attestation, get_payload_attesting_indices,
};
pub use initiate_validator_exit::initiate_validator_exit;
pub use slash_validator::slash_validator;

View File

@@ -1,11 +1,16 @@
use crate::EpochCacheError;
use crate::common::{attesting_indices_base, attesting_indices_electra};
use crate::per_block_processing::errors::{AttestationInvalid, BlockOperationError};
use crate::common::{
attesting_indices_base, attesting_indices_electra, get_indexed_payload_attestation,
};
use crate::per_block_processing::errors::{
AttestationInvalid, BlockOperationError, PayloadAttestationInvalid,
};
use std::collections::{HashMap, hash_map::Entry};
use tree_hash::TreeHash;
use types::{
AbstractExecPayload, AttestationRef, BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec,
Hash256, IndexedAttestation, IndexedAttestationRef, SignedBeaconBlock, Slot,
Hash256, IndexedAttestation, IndexedAttestationRef, IndexedPayloadAttestation,
PayloadAttestation, SignedBeaconBlock, Slot,
};
#[derive(Debug, PartialEq, Clone)]
@@ -22,6 +27,8 @@ pub struct ConsensusContext<E: EthSpec> {
pub current_block_root: Option<Hash256>,
/// Cache of indexed attestations constructed during block processing.
pub indexed_attestations: HashMap<Hash256, IndexedAttestation<E>>,
/// Cache of indexed payload attestations constructed during block processing.
pub indexed_payload_attestations: HashMap<Hash256, IndexedPayloadAttestation<E>>,
}
#[derive(Debug, PartialEq, Clone)]
@@ -55,6 +62,7 @@ impl<E: EthSpec> ConsensusContext<E> {
proposer_index: None,
current_block_root: None,
indexed_attestations: HashMap::new(),
indexed_payload_attestations: HashMap::new(),
}
}
@@ -177,6 +185,25 @@ impl<E: EthSpec> ConsensusContext<E> {
.map(|indexed_attestation| (*indexed_attestation).to_ref())
}
pub fn get_indexed_payload_attestation<'a>(
&'a mut self,
state: &BeaconState<E>,
slot: Slot,
payload_attestation: &'a PayloadAttestation<E>,
spec: &ChainSpec,
) -> Result<&'a IndexedPayloadAttestation<E>, BlockOperationError<PayloadAttestationInvalid>>
{
let key = payload_attestation.tree_hash_root();
match self.indexed_payload_attestations.entry(key) {
Entry::Occupied(occupied) => Ok(occupied.into_mut()),
Entry::Vacant(vacant) => {
let indexed_payload_attestation =
get_indexed_payload_attestation(state, slot, payload_attestation, spec)?;
Ok(vacant.insert(indexed_payload_attestation))
}
}
}
pub fn num_cached_indexed_attestations(&self) -> usize {
self.indexed_attestations.len()
}

View File

@@ -0,0 +1,273 @@
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, process_withdrawal_requests,
};
use safe_arith::{ArithError, SafeArith};
use tree_hash::TreeHash;
use types::{
BeaconState, BeaconStateError, BuilderPendingPayment, ChainSpec, EthSpec, ExecutionBlockHash,
Hash256, SignedExecutionPayloadEnvelope, Slot,
};
// TODO(EIP-7732): don't use this redefinition..
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 withdrawals root doesn't match the state's latest withdrawals root
WithdrawalsRootMismatch {
state: Hash256,
envelope: Hash256,
},
// 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 blob KZG commitments root doesn't match the committed bid
BlobKzgCommitmentsRootMismatch {
committed_bid: Hash256,
envelope: Hash256,
},
// The previous randao didn't match the payload
PrevRandaoMismatch {
state: Hash256,
envelope: Hash256,
},
// The timestamp didn't match the payload
TimestampMismatch {
state: u64,
envelope: u64,
},
// Blob committments exceeded the maximum
BlobLimitExceeded {
max: usize,
envelope: usize,
},
// Invalid state root
InvalidStateRoot {
state: Hash256,
envelope: Hash256,
},
// BitFieldError
BitFieldError(ssz::BitfieldError),
// Some kind of error calculating the builder payment index
BuilderPaymentIndexOutOfBounds(usize),
}
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 envelope_processing<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
// TODO(EIP-7732): there is probably a more efficient way to do this..
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
envelope_verify!(
envelope.beacon_block_root == state.latest_block_header().tree_hash_root(),
EnvelopeProcessingError::LatestBlockHeaderMismatch {
envelope_root: envelope.beacon_block_root,
block_header_root: state.latest_block_header().tree_hash_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()?;
// builder index match already verified
if committed_bid.blob_kzg_commitments_root != envelope.blob_kzg_commitments.tree_hash_root() {
return Err(EnvelopeProcessingError::BlobKzgCommitmentsRootMismatch {
committed_bid: committed_bid.blob_kzg_commitments_root,
envelope: envelope.blob_kzg_commitments.tree_hash_root(),
});
};
// Verify the gas limit
envelope_verify!(
payload.gas_limit == committed_bid.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 prev_randao
envelope_verify!(
payload.prev_randao == *state.get_randao_mix(state.current_epoch())?,
EnvelopeProcessingError::PrevRandaoMismatch {
state: *state.get_randao_mix(state.current_epoch())?,
envelope: payload.prev_randao,
}
);
// Verify the 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,
}
);
// Verify the commitments are under limit
let max_blobs = spec.max_blobs_per_block(state.current_epoch()) as usize;
envelope_verify!(
envelope.blob_kzg_commitments.len() <= max_blobs,
EnvelopeProcessingError::BlobLimitExceeded {
max: max_blobs,
envelope: envelope.blob_kzg_commitments.len(),
}
);
// process electra operations
process_deposit_requests(state, &execution_requests.deposits, spec)?;
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 = state
.builder_pending_payments()?
.get(payment_index)
.ok_or(EnvelopeProcessingError::BuilderPaymentIndexOutOfBounds(
payment_index,
))?
.clone();
let amount = payment.withdrawal.amount;
if amount > 0 {
let _exit_queue_epoch = state.compute_exit_epoch_and_update_churn(amount, spec)?;
state
.builder_pending_withdrawals_mut()?
.push(payment.withdrawal)
.map_err(|e| EnvelopeProcessingError::BeaconStateError(e.into()))?;
}
*state
.builder_pending_payments_mut()?
.get_mut(payment_index)
.ok_or(EnvelopeProcessingError::BuilderPaymentIndexOutOfBounds(
payment_index,
))? = BuilderPendingPayment::default();
// cache the execution payload hash
let availability_index = state
.slot()
.safe_rem(E::slots_per_historical_root() as u64)?
.as_usize();
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
envelope_verify!(
envelope.state_root == state.canonical_root()?,
EnvelopeProcessingError::InvalidStateRoot {
state: state.canonical_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

@@ -1,11 +1,16 @@
use self::errors::ExecutionPayloadBidInvalid;
use crate::consensus_context::ConsensusContext;
use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid};
use rayon::prelude::*;
use safe_arith::{ArithError, SafeArith, SafeArithIter};
use signature_sets::{block_proposal_signature_set, get_pubkey_from_state, randao_signature_set};
use signature_sets::{
block_proposal_signature_set, execution_payload_bid_signature_set, get_pubkey_from_state,
randao_signature_set,
};
use std::borrow::Cow;
use tree_hash::TreeHash;
use typenum::Unsigned;
use types::consts::gloas::BUILDER_INDEX_FLAG;
use types::*;
pub use self::verify_attester_slashing::{
@@ -15,6 +20,7 @@ pub use self::verify_proposer_slashing::verify_proposer_slashing;
pub use altair::sync_committee::process_sync_aggregate;
pub use block_signature_verifier::{BlockSignatureVerifier, ParallelSignatureSets};
pub use is_valid_indexed_attestation::is_valid_indexed_attestation;
pub use is_valid_indexed_payload_attestation::is_valid_indexed_payload_attestation;
pub use process_operations::process_operations;
pub use verify_attestation::{
verify_attestation_for_block_inclusion, verify_attestation_for_state,
@@ -30,7 +36,9 @@ pub mod block_signature_verifier;
pub mod deneb;
pub mod errors;
mod is_valid_indexed_attestation;
mod is_valid_indexed_payload_attestation;
pub mod process_operations;
pub mod process_withdrawals;
pub mod signature_sets;
pub mod tests;
mod verify_attestation;
@@ -38,9 +46,9 @@ mod verify_attester_slashing;
mod verify_bls_to_execution_change;
mod verify_deposit;
mod verify_exit;
mod verify_payload_attestation;
mod verify_proposer_slashing;
use crate::common::decrease_balance;
use crate::common::update_progressive_balances_cache::{
initialize_progressive_balances_cache, update_progressive_balances_metrics,
};
@@ -172,14 +180,19 @@ pub fn per_block_processing<E: EthSpec, Payload: AbstractExecPayload<E>>(
// previous block.
if is_execution_enabled(state, block.body()) {
let body = block.body();
// TODO(EIP-7732): build out process_withdrawals variant for gloas
process_withdrawals::<E, Payload>(state, body.execution_payload()?, spec)?;
process_execution_payload::<E, Payload>(state, body, spec)?;
if state.fork_name_unchecked().gloas_enabled() {
process_withdrawals::gloas::process_withdrawals::<E>(state, spec)?;
process_execution_payload_bid(state, block, verify_signatures, spec)?;
} else {
process_withdrawals::capella::process_withdrawals::<E, Payload>(
state,
body.execution_payload()?,
spec,
)?;
process_execution_payload::<E, Payload>(state, body, spec)?;
}
}
// TODO(EIP-7732): build out process_execution_bid
// process_execution_bid(state, block, verify_signatures, spec)?;
process_randao(state, block, verify_randao, ctxt, spec)?;
process_eth1_data(state, block.body().eth1_data())?;
process_operations(state, block.body(), verify_signatures, ctxt, spec)?;
@@ -514,189 +527,472 @@ pub fn compute_timestamp_at_slot<E: EthSpec>(
.and_then(|since_genesis| state.genesis_time().safe_add(since_genesis))
}
/// Compute the next batch of withdrawals which should be included in a block.
///
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/electra/beacon-chain.md#new-get_expected_withdrawals
pub fn get_expected_withdrawals<E: EthSpec>(
pub fn convert_builder_index_to_validator_index(builder_index: BuilderIndex) -> u64 {
builder_index | BUILDER_INDEX_FLAG
}
pub fn convert_validator_index_to_builder_index(validator_index: u64) -> BuilderIndex {
validator_index & !BUILDER_INDEX_FLAG
}
pub fn get_balance_after_withdrawals<E: EthSpec>(
state: &BeaconState<E>,
validator_index: u64,
withdrawals: &[Withdrawal],
) -> Result<u64, BeaconStateError> {
let withdrawn = withdrawals
.iter()
.filter(|withdrawal| withdrawal.validator_index == validator_index)
.map(|withdrawal| withdrawal.amount)
.safe_sum()?;
state
.get_balance(validator_index as usize)?
.safe_sub(withdrawn)
.map_err(Into::into)
}
fn is_eligible_for_partial_withdrawals(
validator: &Validator,
balance: u64,
spec: &ChainSpec,
) -> Result<(Withdrawals<E>, Option<usize>), BlockProcessingError> {
) -> bool {
let has_sufficient_effective_balance =
validator.effective_balance >= spec.min_activation_balance;
let has_excess_balance = balance > spec.min_activation_balance;
validator.exit_epoch == spec.far_future_epoch
&& has_sufficient_effective_balance
&& has_excess_balance
}
pub fn get_builder_withdrawals<E: EthSpec>(
state: &BeaconState<E>,
withdrawal_index: &mut u64,
withdrawals: &mut Vec<Withdrawal>,
) -> Result<Option<usize>, BlockProcessingError> {
let Ok(builder_pending_withdrawals) = state.builder_pending_withdrawals() else {
// Pre-Gloas, nothing to do.
return Ok(None);
};
let withdrawals_limit = E::max_withdrawals_per_payload();
let mut processed_count = 0;
for withdrawal in builder_pending_withdrawals {
let has_reached_limit = withdrawals.len() == withdrawals_limit;
if has_reached_limit {
break;
}
let builder_index = withdrawal.builder_index;
withdrawals.push(Withdrawal {
index: *withdrawal_index,
validator_index: convert_builder_index_to_validator_index(builder_index),
address: withdrawal.fee_recipient,
amount: withdrawal.amount,
});
withdrawal_index.safe_add_assign(1)?;
processed_count.safe_add_assign(1)?;
}
Ok(Some(processed_count))
}
pub fn get_pending_partial_withdrawals<E: EthSpec>(
state: &BeaconState<E>,
withdrawal_index: &mut u64,
withdrawals: &mut Vec<Withdrawal>,
spec: &ChainSpec,
) -> Result<Option<usize>, BlockProcessingError> {
let Ok(pending_partial_withdrawals) = state.pending_partial_withdrawals() else {
// Pre-Electra nothing to do.
return Ok(None);
};
let epoch = state.current_epoch();
let withdrawals_limit = std::cmp::min(
withdrawals
.len()
.safe_add(spec.max_pending_partials_per_withdrawals_sweep as usize)?,
E::max_withdrawals_per_payload(),
);
block_verify!(
withdrawals.len() <= withdrawals_limit,
BlockProcessingError::WithdrawalsLimitExceeded {
limit: withdrawals_limit,
prior_withdrawals: withdrawals.len()
}
);
let mut processed_count = 0;
for withdrawal in pending_partial_withdrawals {
let is_withdrawable = withdrawal.withdrawable_epoch <= epoch;
let has_reached_limit = withdrawals.len() >= withdrawals_limit;
if !is_withdrawable || has_reached_limit {
break;
}
let validator_index = withdrawal.validator_index;
let validator = state.get_validator(validator_index as usize)?;
let balance = get_balance_after_withdrawals(state, validator_index, withdrawals)?;
if is_eligible_for_partial_withdrawals(validator, balance, spec) {
let withdrawal_amount = std::cmp::min(
balance.safe_sub(spec.min_activation_balance)?,
withdrawal.amount,
);
withdrawals.push(Withdrawal {
index: *withdrawal_index,
validator_index,
address: validator
.get_execution_withdrawal_address(spec, state.fork_name_unchecked())
.ok_or(BeaconStateError::NonExecutionAddressWithdrawalCredential)?,
amount: withdrawal_amount,
});
withdrawal_index.safe_add_assign(1)?;
}
processed_count.safe_add_assign(1)?;
}
Ok(Some(processed_count))
}
/// Get withdrawals from the builders sweep.
///
/// This function iterates through builders starting from `next_withdrawal_builder_index`
/// and adds withdrawals for builders whose withdrawable_epoch has been reached and have balance.
///
/// https://ethereum.github.io/consensus-specs/specs/gloas/beacon-chain/#new-get_builders_sweep_withdrawals
pub fn get_builders_sweep_withdrawals<E: EthSpec>(
state: &BeaconState<E>,
withdrawal_index: &mut u64,
withdrawals: &mut Vec<Withdrawal>,
) -> Result<Option<u64>, BlockProcessingError> {
let Ok(builders) = state.builders() else {
// Pre-Gloas, nothing to do.
return Ok(None);
};
if builders.is_empty() {
return Ok(Some(0));
}
let epoch = state.current_epoch();
let builders_limit = std::cmp::min(builders.len(), E::max_builders_per_withdrawals_sweep());
// Reserve one slot for validator sweep withdrawals
let withdrawals_limit = E::max_withdrawals_per_payload().saturating_sub(1);
block_verify!(
withdrawals.len() <= withdrawals_limit,
BlockProcessingError::WithdrawalsLimitExceeded {
limit: withdrawals_limit,
prior_withdrawals: withdrawals.len()
}
);
let mut processed_count: u64 = 0;
let mut builder_index = state.next_withdrawal_builder_index()?;
for _ in 0..builders_limit {
if withdrawals.len() >= withdrawals_limit {
break;
}
let builder = builders
.get(builder_index as usize)
.ok_or(BeaconStateError::UnknownBuilder(builder_index))?;
if builder.withdrawable_epoch <= epoch && builder.balance > 0 {
withdrawals.push(Withdrawal {
index: *withdrawal_index,
validator_index: convert_builder_index_to_validator_index(builder_index),
address: builder.execution_address,
amount: builder.balance,
});
withdrawal_index.safe_add_assign(1)?;
}
builder_index = builder_index.safe_add(1)?.safe_rem(builders.len() as u64)?;
processed_count.safe_add_assign(1)?;
}
Ok(Some(processed_count))
}
/// Get withdrawals from the validator sweep.
///
/// This function iterates through validators starting from `next_withdrawal_validator_index`
/// and adds full or partial withdrawals for eligible validators.
///
/// https://ethereum.github.io/consensus-specs/specs/capella/beacon-chain/#new-get_validators_sweep_withdrawals
pub fn get_validators_sweep_withdrawals<E: EthSpec>(
state: &BeaconState<E>,
withdrawal_index: &mut u64,
withdrawals: &mut Vec<Withdrawal>,
spec: &ChainSpec,
) -> Result<u64, BlockProcessingError> {
let epoch = state.current_epoch();
let mut withdrawal_index = state.next_withdrawal_index()?;
let mut validator_index = state.next_withdrawal_validator_index()?;
let mut withdrawals = Vec::<Withdrawal>::with_capacity(E::max_withdrawals_per_payload());
let fork_name = state.fork_name_unchecked();
// [New in Electra:EIP7251]
// Consume pending partial withdrawals
let processed_partial_withdrawals_count =
if let Ok(pending_partial_withdrawals) = state.pending_partial_withdrawals() {
let mut processed_partial_withdrawals_count = 0;
for withdrawal in pending_partial_withdrawals {
if withdrawal.withdrawable_epoch > epoch
|| withdrawals.len() == spec.max_pending_partials_per_withdrawals_sweep as usize
{
break;
}
let validator = state.get_validator(withdrawal.validator_index as usize)?;
let has_sufficient_effective_balance =
validator.effective_balance >= spec.min_activation_balance;
let total_withdrawn = withdrawals
.iter()
.filter_map(|w| {
(w.validator_index == withdrawal.validator_index).then_some(w.amount)
})
.safe_sum()?;
let balance = state
.get_balance(withdrawal.validator_index as usize)?
.safe_sub(total_withdrawn)?;
let has_excess_balance = balance > spec.min_activation_balance;
if validator.exit_epoch == spec.far_future_epoch
&& has_sufficient_effective_balance
&& has_excess_balance
{
let withdrawable_balance = std::cmp::min(
balance.safe_sub(spec.min_activation_balance)?,
withdrawal.amount,
);
withdrawals.push(Withdrawal {
index: withdrawal_index,
validator_index: withdrawal.validator_index,
address: validator
.get_execution_withdrawal_address(spec)
.ok_or(BeaconStateError::NonExecutionAddressWithdrawalCredential)?,
amount: withdrawable_balance,
});
withdrawal_index.safe_add_assign(1)?;
}
processed_partial_withdrawals_count.safe_add_assign(1)?;
}
Some(processed_partial_withdrawals_count)
} else {
None
};
let bound = std::cmp::min(
let mut validator_index = state.next_withdrawal_validator_index()?;
let validators_limit = std::cmp::min(
state.validators().len() as u64,
spec.max_validators_per_withdrawals_sweep,
);
for _ in 0..bound {
let withdrawals_limit = E::max_withdrawals_per_payload();
// There must be at least one space reserved for validator sweep withdrawals
block_verify!(
withdrawals.len() < withdrawals_limit,
BlockProcessingError::WithdrawalsLimitExceeded {
limit: withdrawals_limit,
prior_withdrawals: withdrawals.len()
}
);
let mut processed_count: u64 = 0;
for _ in 0..validators_limit {
if withdrawals.len() >= withdrawals_limit {
break;
}
let validator = state.get_validator(validator_index as usize)?;
let partially_withdrawn_balance = withdrawals
.iter()
.filter_map(|withdrawal| {
(withdrawal.validator_index == validator_index).then_some(withdrawal.amount)
})
.safe_sum()?;
let balance = state
.balances()
.get(validator_index as usize)
.ok_or(BeaconStateError::BalancesOutOfBounds(
validator_index as usize,
))?
.safe_sub(partially_withdrawn_balance)?;
let balance = get_balance_after_withdrawals(state, validator_index, withdrawals)?;
if validator.is_fully_withdrawable_validator(balance, epoch, spec, fork_name) {
withdrawals.push(Withdrawal {
index: withdrawal_index,
index: *withdrawal_index,
validator_index,
address: validator
.get_execution_withdrawal_address(spec)
.get_execution_withdrawal_address(spec, fork_name)
.ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?,
amount: balance,
});
withdrawal_index.safe_add_assign(1)?;
} else if validator.is_partially_withdrawable_validator(balance, spec, fork_name) {
withdrawals.push(Withdrawal {
index: withdrawal_index,
index: *withdrawal_index,
validator_index,
address: validator
.get_execution_withdrawal_address(spec)
.get_execution_withdrawal_address(spec, fork_name)
.ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?,
amount: balance.safe_sub(validator.get_max_effective_balance(spec, fork_name))?,
});
withdrawal_index.safe_add_assign(1)?;
}
if withdrawals.len() == E::max_withdrawals_per_payload() {
break;
}
validator_index = validator_index
.safe_add(1)?
.safe_rem(state.validators().len() as u64)?;
processed_count.safe_add_assign(1)?;
}
Ok(processed_count)
}
/// Compute the next batch of withdrawals which should be included in a block.
///
/// https://ethereum.github.io/consensus-specs/specs/gloas/beacon-chain/#modified-get_expected_withdrawals
#[allow(clippy::type_complexity)]
pub fn get_expected_withdrawals<E: EthSpec>(
state: &BeaconState<E>,
spec: &ChainSpec,
) -> Result<
(
Withdrawals<E>,
Option<usize>,
Option<usize>,
Option<u64>,
u64,
),
BlockProcessingError,
> {
let mut withdrawal_index = state.next_withdrawal_index()?;
let mut withdrawals = Vec::<Withdrawal>::with_capacity(E::max_withdrawals_per_payload());
// [New in Gloas:EIP7732]
// Get builder withdrawals
let processed_builder_withdrawals_count =
get_builder_withdrawals(state, &mut withdrawal_index, &mut withdrawals)?;
// [New in Electra:EIP7251]
// Get partial withdrawals.
let processed_partial_withdrawals_count =
get_pending_partial_withdrawals(state, &mut withdrawal_index, &mut withdrawals, spec)?;
// [New in Gloas:EIP7732]
// Get builders sweep withdrawals
let processed_builders_sweep_count =
get_builders_sweep_withdrawals(state, &mut withdrawal_index, &mut withdrawals)?;
// Get validators sweep withdrawals
let processed_validators_sweep_count =
get_validators_sweep_withdrawals(state, &mut withdrawal_index, &mut withdrawals, spec)?;
Ok((
withdrawals
.try_into()
.map_err(BlockProcessingError::SszTypesError)?,
processed_builder_withdrawals_count,
processed_partial_withdrawals_count,
processed_builders_sweep_count,
processed_validators_sweep_count,
))
}
/// Apply withdrawals to the state.
/// TODO(EIP-7732): abstract this out and create gloas variant
pub fn process_withdrawals<E: EthSpec, Payload: AbstractExecPayload<E>>(
pub fn process_execution_payload_bid<E: EthSpec, Payload: AbstractExecPayload<E>>(
state: &mut BeaconState<E>,
payload: Payload::Ref<'_>,
block: BeaconBlockRef<'_, E, Payload>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
if state.fork_name_unchecked().capella_enabled() {
let (expected_withdrawals, processed_partial_withdrawals_count) =
get_expected_withdrawals(state, spec)?;
let expected_root = expected_withdrawals.tree_hash_root();
let withdrawals_root = payload.withdrawals_root()?;
// Verify the bid signature
let signed_bid = block.body().signed_execution_payload_bid()?;
if expected_root != withdrawals_root {
return Err(BlockProcessingError::WithdrawalsRootMismatch {
expected: expected_root,
found: withdrawals_root,
});
}
let bid = &signed_bid.message;
let amount = bid.value;
let builder_index = bid.builder_index;
let builder = state.get_validator(builder_index as usize)?;
for withdrawal in expected_withdrawals.iter() {
decrease_balance(
state,
withdrawal.validator_index as usize,
withdrawal.amount,
)?;
}
// Update pending partial withdrawals [New in Electra:EIP7251]
if let Some(processed_partial_withdrawals_count) = processed_partial_withdrawals_count {
state
.pending_partial_withdrawals_mut()?
.pop_front(processed_partial_withdrawals_count)?;
}
// Update the next withdrawal index if this block contained withdrawals
if let Some(latest_withdrawal) = expected_withdrawals.last() {
*state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?;
// Update the next validator index to start the next withdrawal sweep
if expected_withdrawals.len() == E::max_withdrawals_per_payload() {
// Next sweep starts after the latest withdrawal's validator index
let next_validator_index = latest_withdrawal
.validator_index
.safe_add(1)?
.safe_rem(state.validators().len() as u64)?;
*state.next_withdrawal_validator_index_mut()? = next_validator_index;
}
}
// Advance sweep by the max length of the sweep if there was not a full set of withdrawals
if expected_withdrawals.len() != E::max_withdrawals_per_payload() {
let next_validator_index = state
.next_withdrawal_validator_index()?
.safe_add(spec.max_validators_per_withdrawals_sweep)?
.safe_rem(state.validators().len() as u64)?;
*state.next_withdrawal_validator_index_mut()? = next_validator_index;
}
Ok(())
// For self-builds, amount must be zero regardless of withdrawal credential prefix
if builder_index == block.proposer_index() {
block_verify!(amount == 0, ExecutionPayloadBidInvalid::BadAmount.into());
// TODO(EIP-7732): check with team if we should use ExecutionPayloadBidInvalid::BadSignature or a new error variant for this, like BadSelfBuildSignature
block_verify!(
signed_bid.signature.is_infinity(),
ExecutionPayloadBidInvalid::BadSignature.into()
);
} else {
// these shouldn't even be encountered but they're here for completeness
Ok(())
// Non-self builds require builder withdrawal credential
block_verify!(
builder.has_builder_withdrawal_credential(spec),
ExecutionPayloadBidInvalid::BadWithdrawalCredentials.into()
);
if verify_signatures.is_true() {
block_verify!(
execution_payload_bid_signature_set(
state,
|i| get_pubkey_from_state(state, i),
signed_bid,
spec
)?
.verify(),
ExecutionPayloadBidInvalid::BadSignature.into()
);
}
}
// Verify builder is active and not slashed
block_verify!(
builder.is_active_at(state.current_epoch()),
ExecutionPayloadBidInvalid::BuilderNotActive(builder_index).into()
);
block_verify!(
!builder.slashed,
ExecutionPayloadBidInvalid::BuilderSlashed(builder_index).into()
);
// Only perform payment related checks if amount > 0
if amount > 0 {
// Check that the builder has funds to cover the bid
let pending_payments = state
.builder_pending_payments()?
.iter()
.filter_map(|payment| {
if payment.withdrawal.builder_index == builder_index {
Some(payment.withdrawal.amount)
} else {
None
}
})
.safe_sum()?;
let pending_withdrawals = state
.builder_pending_withdrawals()?
.iter()
.filter_map(|withdrawal| {
if withdrawal.builder_index == builder_index {
Some(withdrawal.amount)
} else {
None
}
})
.safe_sum()?;
let builder_balance = state.get_balance(builder_index as usize)?;
block_verify!(
builder_balance
>= amount
.safe_add(pending_payments)?
.safe_add(pending_withdrawals)?
.safe_add(spec.min_activation_balance)?,
ExecutionPayloadBidInvalid::InsufficientBalance {
builder_index,
builder_balance,
bid_value: amount,
}
.into()
);
}
// Verify that the bid is for the current slot
block_verify!(
bid.slot == block.slot(),
ExecutionPayloadBidInvalid::SlotMismatch {
state_slot: block.slot(),
bid_slot: bid.slot,
}
.into()
);
// Verify that the bid is for the right parent block
let latest_block_hash = state.latest_block_hash()?;
block_verify!(
bid.parent_block_hash == *latest_block_hash,
ExecutionPayloadBidInvalid::ParentBlockHashMismatch {
state_block_hash: *latest_block_hash,
bid_parent_hash: bid.parent_block_hash,
}
.into()
);
block_verify!(
bid.parent_block_root == block.parent_root(),
ExecutionPayloadBidInvalid::ParentBlockRootMismatch {
block_parent_root: block.parent_root(),
bid_parent_root: bid.parent_block_root,
}
.into()
);
// Record the pending payment if there is some payment
if amount > 0 {
let pending_payment = BuilderPendingPayment {
weight: 0,
withdrawal: BuilderPendingWithdrawal {
fee_recipient: bid.fee_recipient,
amount,
builder_index,
},
};
let payment_index = (E::slots_per_epoch()
.safe_add(bid.slot.as_u64().safe_rem(E::slots_per_epoch())?)?)
as usize;
*state
.builder_pending_payments_mut()?
.get_mut(payment_index)
.ok_or(BlockProcessingError::BeaconStateError(
BeaconStateError::BuilderPendingPaymentsIndexNotSupported(payment_index),
))? = pending_payment;
}
// Cache the execution bid
*state.latest_execution_payload_bid_mut()? = bid.clone();
Ok(())
}

View File

@@ -41,6 +41,10 @@ pub enum BlockProcessingError {
index: usize,
reason: AttestationInvalid,
},
PayloadAttestationInvalid {
index: usize,
reason: PayloadAttestationInvalid,
},
DepositInvalid {
index: usize,
reason: DepositInvalid,
@@ -90,7 +94,17 @@ pub enum BlockProcessingError {
found: Hash256,
},
WithdrawalCredentialsInvalid,
/// This should be unreachable unless there's a logical flaw in the spec for withdrawals.
WithdrawalsLimitExceeded {
limit: usize,
prior_withdrawals: usize,
},
PendingAttestationInElectra,
ExecutionPayloadBidInvalid {
reason: ExecutionPayloadBidInvalid,
},
/// Builder payment index out of bounds (Gloas)
BuilderPaymentIndexOutOfBounds(usize),
}
impl From<BeaconStateError> for BlockProcessingError {
@@ -147,6 +161,12 @@ impl From<milhouse::Error> for BlockProcessingError {
}
}
impl From<ExecutionPayloadBidInvalid> for BlockProcessingError {
fn from(reason: ExecutionPayloadBidInvalid) -> Self {
Self::ExecutionPayloadBidInvalid { reason }
}
}
impl From<BlockOperationError<HeaderInvalid>> for BlockProcessingError {
fn from(e: BlockOperationError<HeaderInvalid>) -> BlockProcessingError {
match e {
@@ -200,7 +220,8 @@ impl_into_block_processing_error_with_index!(
AttestationInvalid,
DepositInvalid,
ExitInvalid,
BlsExecutionChangeInvalid
BlsExecutionChangeInvalid,
PayloadAttestationInvalid
);
pub type HeaderValidationError = BlockOperationError<HeaderInvalid>;
@@ -331,7 +352,10 @@ pub enum AttestationInvalid {
attestation: Slot,
},
/// Attestation slot is too far in the past to be included in a block.
IncludedTooLate { state: Slot, attestation: Slot },
IncludedTooLate {
state: Slot,
attestation: Slot,
},
/// Attestation target epoch does not match attestation slot.
TargetEpochSlotMismatch {
target_epoch: Epoch,
@@ -364,6 +388,7 @@ pub enum AttestationInvalid {
BadSignature,
/// The indexed attestation created from this attestation was found to be invalid.
BadIndexedAttestation(IndexedAttestationInvalid),
BadOverloadedDataIndex,
}
impl From<BlockOperationError<IndexedAttestationInvalid>>
@@ -401,6 +426,58 @@ pub enum IndexedAttestationInvalid {
SignatureSetError(SignatureSetError),
}
#[derive(Debug, PartialEq, Clone)]
pub enum PayloadAttestationInvalid {
/// Block root does not match the parent beacon block root.
BlockRootMismatch {
expected: Hash256,
found: Hash256,
},
/// The attestation slot is not the previous slot.
SlotMismatch {
expected: Slot,
found: Slot,
},
BadIndexedPayloadAttestation(IndexedPayloadAttestationInvalid),
}
impl From<BlockOperationError<IndexedPayloadAttestationInvalid>>
for BlockOperationError<PayloadAttestationInvalid>
{
fn from(e: BlockOperationError<IndexedPayloadAttestationInvalid>) -> Self {
match e {
BlockOperationError::Invalid(e) => BlockOperationError::invalid(
PayloadAttestationInvalid::BadIndexedPayloadAttestation(e),
),
BlockOperationError::BeaconStateError(e) => BlockOperationError::BeaconStateError(e),
BlockOperationError::SignatureSetError(e) => BlockOperationError::SignatureSetError(e),
BlockOperationError::SszTypesError(e) => BlockOperationError::SszTypesError(e),
BlockOperationError::BitfieldError(e) => BlockOperationError::BitfieldError(e),
BlockOperationError::ConsensusContext(e) => BlockOperationError::ConsensusContext(e),
BlockOperationError::ArithError(e) => BlockOperationError::ArithError(e),
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum IndexedPayloadAttestationInvalid {
/// The number of indices is 0.
IndicesEmpty,
/// The validator indices were not in increasing order.
///
/// The error occurred between the given `index` and `index + 1`
BadValidatorIndicesOrdering(usize),
/// The validator index is unknown. One cannot slash one who does not exist.
UnknownValidator(u64),
/// The indexed attestation aggregate signature was not valid.
BadSignature,
/// There was an error whilst attempting to get a set of signatures. The signatures may have
/// been invalid or an internal error occurred.
SignatureSetError(SignatureSetError),
/// Invalid Payload Status
PayloadStatusInvalid,
}
#[derive(Debug, PartialEq, Clone)]
pub enum DepositInvalid {
/// The signature (proof-of-possession) does not match the given pubkey.
@@ -440,6 +517,38 @@ pub enum ExitInvalid {
PendingWithdrawalInQueue(u64),
}
#[derive(Debug, PartialEq, Clone)]
pub enum ExecutionPayloadBidInvalid {
/// The builder sent a 0 amount
BadAmount,
/// The signature is invalid.
BadSignature,
/// The builder's withdrawal credential is invalid
BadWithdrawalCredentials,
/// The builder is not an active validator.
BuilderNotActive(u64),
/// The builder is slashed
BuilderSlashed(u64),
/// The builder has insufficient balance to cover the bid
InsufficientBalance {
builder_index: u64,
builder_balance: u64,
bid_value: u64,
},
/// Bid slot doesn't match state slot
SlotMismatch { state_slot: Slot, bid_slot: Slot },
/// The bid's parent block hash doesn't match the state's latest block hash
ParentBlockHashMismatch {
state_block_hash: ExecutionBlockHash,
bid_parent_hash: ExecutionBlockHash,
},
/// The bid's parent block root doesn't match the block's parent root
ParentBlockRootMismatch {
block_parent_root: Hash256,
bid_parent_root: Hash256,
},
}
#[derive(Debug, PartialEq, Clone)]
pub enum BlsExecutionChangeInvalid {
/// The specified validator is not in the state's validator registry.

View File

@@ -0,0 +1,50 @@
use super::errors::{BlockOperationError, IndexedPayloadAttestationInvalid as Invalid};
use super::signature_sets::{get_pubkey_from_state, indexed_payload_attestation_signature_set};
use crate::VerifySignatures;
use itertools::Itertools;
use types::*;
fn error(reason: Invalid) -> BlockOperationError<Invalid> {
BlockOperationError::invalid(reason)
}
pub fn is_valid_indexed_payload_attestation<E: EthSpec>(
state: &BeaconState<E>,
indexed_payload_attestation: &IndexedPayloadAttestation<E>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockOperationError<Invalid>> {
// Verify indices are non-empty and sorted (duplicates allowed)
let indices = &indexed_payload_attestation.attesting_indices;
verify!(!indices.is_empty(), Invalid::IndicesEmpty);
let check_sorted = |list: &[u64]| -> Result<(), BlockOperationError<Invalid>> {
list.iter()
.tuple_windows()
.enumerate()
.try_for_each(|(i, (x, y))| {
if x <= y {
Ok(())
} else {
Err(error(Invalid::BadValidatorIndicesOrdering(i)))
}
})?;
Ok(())
};
check_sorted(indices)?;
if verify_signatures.is_true() {
verify!(
indexed_payload_attestation_signature_set(
state,
|i| get_pubkey_from_state(state, i),
&indexed_payload_attestation.signature,
indexed_payload_attestation,
spec
)?
.verify(),
Invalid::BadSignature
);
}
Ok(())
}

View File

@@ -1,10 +1,11 @@
use super::*;
use crate::VerifySignatures;
use crate::common::{
get_attestation_participation_flag_indices, increase_balance, initiate_validator_exit,
slash_validator,
};
use crate::per_block_processing::errors::{BlockProcessingError, IntoWithIndex};
use crate::per_block_processing::verify_payload_attestation::verify_payload_attestation;
use safe_arith::SafeArith;
use ssz_types::FixedVector;
use typenum::U33;
use types::consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR};
@@ -38,7 +39,15 @@ 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().gloas_enabled() {
process_payload_attestations(
state,
block_body.payload_attestations()?.iter(),
verify_signatures,
ctxt,
spec,
)?;
} else if state.fork_name_unchecked().electra_enabled() {
state.update_pubkey_cache()?;
process_deposit_requests(state, &block_body.execution_requests()?.deposits, spec)?;
process_withdrawal_requests(state, &block_body.execution_requests()?.withdrawals, spec)?;
@@ -212,6 +221,149 @@ pub mod altair_deneb {
}
}
// TODO(EIP-7732): add test cases to `consensus/state_processing/src/per_block_processing/tests.rs` to handle gloas.
// The tests will require being able to build gloas blocks, which currently fails due to errors as mentioned here.
// https://github.com/sigp/lighthouse/pull/8273
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
};
// Accumulate weight for same-slot attestations
let mut accumulated_weight = 0;
// 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 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,
)?;
}
}
}
// Check that payment_index is valid and get payment amount
let builder_payments = state.builder_pending_payments_mut()?;
let payment_amount = builder_payments
.get(payment_index)
.ok_or(BlockProcessingError::BuilderPaymentIndexOutOfBounds(
payment_index,
))?
.withdrawal
.amount;
// Collect validators for Gloas builder payment processing
// We will only 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_amount > 0 {
accumulated_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)?;
// Update builder payment weight
if accumulated_weight > 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(accumulated_weight)?;
}
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 +387,30 @@ 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.
let slot = proposer_slashing.signed_header_1.message.slot;
let proposal_epoch = slot.epoch(E::slots_per_epoch());
let current_epoch = state.current_epoch();
let slot_in_epoch = slot.as_u64() % E::slots_per_epoch();
let payment_index = if proposal_epoch == current_epoch {
Some(E::slots_per_epoch() + slot_in_epoch)
} else if proposal_epoch == current_epoch.saturating_sub(1u64) {
Some(slot_in_epoch)
} else {
None
};
if let Some(index) = payment_index {
if let Ok(builder_pending_payments) = state.builder_pending_payments_mut() {
if let Some(payment) = builder_pending_payments.get_mut(index as usize) {
*payment = BuilderPendingPayment::default();
}
}
}
slash_validator(
state,
proposer_slashing.signed_header_1.message.proposer_index as usize,
@@ -285,7 +461,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(),
@@ -514,9 +698,10 @@ pub fn process_withdrawal_requests<E: EthSpec>(
let validator = state.get_validator(validator_index)?;
// Verify withdrawal credentials
let has_correct_credential = validator.has_execution_withdrawal_credential(spec);
let has_correct_credential =
validator.has_execution_withdrawal_credential(spec, state.fork_name_unchecked());
let is_correct_source_address = validator
.get_execution_withdrawal_address(spec)
.get_execution_withdrawal_address(spec, state.fork_name_unchecked())
.map(|addr| addr == request.source_address)
.unwrap_or(false);
@@ -561,7 +746,7 @@ pub fn process_withdrawal_requests<E: EthSpec>(
.safe_add(pending_balance_to_withdraw)?;
// Only allow partial withdrawals with compounding withdrawal credentials
if validator.has_compounding_withdrawal_credential(spec)
if validator.has_compounding_withdrawal_credential(spec, state.fork_name_unchecked())
&& has_sufficient_effective_balance
&& has_excess_balance
{
@@ -730,7 +915,9 @@ pub fn process_consolidation_request<E: EthSpec>(
let source_validator = state.get_validator(source_index)?;
// Verify the source withdrawal credentials
if let Some(withdrawal_address) = source_validator.get_execution_withdrawal_address(spec) {
if let Some(withdrawal_address) =
source_validator.get_execution_withdrawal_address(spec, state.fork_name_unchecked())
{
if withdrawal_address != consolidation_request.source_address {
return Ok(());
}
@@ -741,7 +928,7 @@ pub fn process_consolidation_request<E: EthSpec>(
let target_validator = state.get_validator(target_index)?;
// Verify the target has compounding withdrawal credentials
if !target_validator.has_compounding_withdrawal_credential(spec) {
if !target_validator.has_compounding_withdrawal_credential(spec, state.fork_name_unchecked()) {
return Ok(());
}
@@ -787,3 +974,52 @@ pub fn process_consolidation_request<E: EthSpec>(
Ok(())
}
// TODO(EIP-7732): Add test cases for `process_payload_attestations` to
// `consensus/state_processing/src/per_block_processing/tests.rs`.
// The tests will require being able to build Gloas blocks with PayloadAttestations,
// which currently fails due to incomplete Gloas block structure as mentioned here
// https://github.com/sigp/lighthouse/pull/8273
pub fn process_payload_attestation<E: EthSpec>(
state: &mut BeaconState<E>,
payload_attestation: &PayloadAttestation<E>,
att_index: usize,
verify_signatures: VerifySignatures,
ctxt: &mut ConsensusContext<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
verify_payload_attestation(state, payload_attestation, ctxt, verify_signatures, spec)
.map_err(|e| e.into_with_index(att_index))
}
pub fn process_payload_attestations<'a, E: EthSpec, I>(
state: &mut BeaconState<E>,
payload_attestations: I,
verify_signatures: VerifySignatures,
ctxt: &mut ConsensusContext<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError>
where
I: Iterator<Item = &'a PayloadAttestation<E>>,
{
// Ensure required caches are all built. These should be no-ops during regular operation.
// TODO(EIP-7732): verify necessary caches
state.build_committee_cache(RelativeEpoch::Current, spec)?;
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
initialize_epoch_cache(state, spec)?;
initialize_progressive_balances_cache(state, spec)?;
state.build_slashings_cache()?;
payload_attestations
.enumerate()
.try_for_each(|(i, payload_attestation)| {
process_payload_attestation(
state,
payload_attestation,
i,
verify_signatures,
ctxt,
spec,
)
})
}

View File

@@ -0,0 +1,174 @@
use super::errors::BlockProcessingError;
use super::get_expected_withdrawals;
use crate::common::decrease_balance;
use milhouse::List;
use safe_arith::SafeArith;
use tree_hash::TreeHash;
use types::{
AbstractExecPayload, BeaconState, BuilderPendingWithdrawal, ChainSpec, EthSpec, ExecPayload,
Withdrawals,
};
/// Check if a builder payment is withdrawable.
/// A builder payment is withdrawable if the builder is not slashed or
/// the builder's withdrawable epoch has been reached.
pub fn is_builder_payment_withdrawable<E: EthSpec>(
state: &BeaconState<E>,
withdrawal: &BuilderPendingWithdrawal,
) -> Result<bool, BlockProcessingError> {
let builder = state.get_validator(withdrawal.builder_index as usize)?;
let current_epoch = state.current_epoch();
Ok(builder.withdrawable_epoch >= current_epoch || !builder.slashed)
}
fn process_withdrawals_common<E: EthSpec>(
state: &mut BeaconState<E>,
expected_withdrawals: Withdrawals<E>,
partial_withdrawals_count: Option<usize>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
match state {
BeaconState::Capella(_)
| BeaconState::Deneb(_)
| BeaconState::Electra(_)
| BeaconState::Fulu(_)
| BeaconState::Gloas(_) => {
// Update pending partial withdrawals [New in Electra:EIP7251]
if let Some(partial_withdrawals_count) = partial_withdrawals_count {
state
.pending_partial_withdrawals_mut()?
.pop_front(partial_withdrawals_count)?;
}
// Update the next withdrawal index if this block contained withdrawals
if let Some(latest_withdrawal) = expected_withdrawals.last() {
*state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?;
// Update the next validator index to start the next withdrawal sweep
if expected_withdrawals.len() == E::max_withdrawals_per_payload() {
// Next sweep starts after the latest withdrawal's validator index
let next_validator_index = latest_withdrawal
.validator_index
.safe_add(1)?
.safe_rem(state.validators().len() as u64)?;
*state.next_withdrawal_validator_index_mut()? = next_validator_index;
}
}
// Advance sweep by the max length of the sweep if there was not a full set of withdrawals
if expected_withdrawals.len() != E::max_withdrawals_per_payload() {
let next_validator_index = state
.next_withdrawal_validator_index()?
.safe_add(spec.max_validators_per_withdrawals_sweep)?
.safe_rem(state.validators().len() as u64)?;
*state.next_withdrawal_validator_index_mut()? = next_validator_index;
}
Ok(())
}
// these shouldn't even be encountered but they're here for completeness
BeaconState::Base(_) | BeaconState::Altair(_) | BeaconState::Bellatrix(_) => Ok(()),
}
}
pub mod capella {
use super::*;
/// Apply withdrawals to the state.
pub fn process_withdrawals<E: EthSpec, Payload: AbstractExecPayload<E>>(
state: &mut BeaconState<E>,
payload: Payload::Ref<'_>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
// check if capella enabled because this function will run on the merge block where the fork is technically still Bellatrix
if state.fork_name_unchecked().capella_enabled() {
let (expected_withdrawals, _, partial_withdrawals_count, _, _) =
get_expected_withdrawals(state, spec)?;
let expected_root = expected_withdrawals.tree_hash_root();
let withdrawals_root = payload.withdrawals_root()?;
if expected_root != withdrawals_root {
return Err(BlockProcessingError::WithdrawalsRootMismatch {
expected: expected_root,
found: withdrawals_root,
});
}
for withdrawal in expected_withdrawals.iter() {
decrease_balance(
state,
withdrawal.validator_index as usize,
withdrawal.amount,
)?;
}
process_withdrawals_common(state, expected_withdrawals, partial_withdrawals_count, spec)
} else {
// these shouldn't even be encountered but they're here for completeness
Ok(())
}
}
}
pub mod gloas {
use super::*;
// TODO(EIP-7732): Add comprehensive tests for Gloas `process_withdrawals`:
// Similar to Capella version, these will be tested via:
// 1. EF consensus-spec tests in `testing/ef_tests/src/cases/operations.rs`
// 2. Integration tests via full block processing
// These tests would currently fail due to incomplete Gloas block structure as mentioned here, so we will implement them after block and payload processing is in a good state.
// https://github.com/sigp/lighthouse/pull/8273
/// Apply withdrawals to the state.
pub fn process_withdrawals<E: EthSpec>(
state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
if !state.is_parent_block_full() {
return Ok(());
}
// TODO(EIP-7732): Use processed_builders_sweep_count to call update_next_withdrawal_builder_index
let (
expected_withdrawals,
builder_withdrawals_count,
partial_withdrawals_count,
_processed_builders_sweep_count,
_,
) = get_expected_withdrawals(state, spec)?;
for withdrawal in expected_withdrawals.iter() {
decrease_balance(
state,
withdrawal.validator_index as usize,
withdrawal.amount,
)?;
}
if let (Ok(builder_pending_withdrawals), Some(builder_count)) = (
state.builder_pending_withdrawals(),
builder_withdrawals_count,
) {
let mut updated_builder_withdrawals =
Vec::with_capacity(E::builder_pending_withdrawals_limit());
for (i, withdrawal) in builder_pending_withdrawals.iter().enumerate() {
if i < builder_count {
if !is_builder_payment_withdrawable(state, withdrawal)? {
updated_builder_withdrawals.push(withdrawal.clone());
}
} else {
updated_builder_withdrawals.push(withdrawal.clone());
}
}
*state.builder_pending_withdrawals_mut()? = List::new(updated_builder_withdrawals)?;
}
// [New in Gloas:EIP7732] update_payload_expected_withdrawals
*state.payload_expected_withdrawals_mut()? = List::new(expected_withdrawals.to_vec())?;
process_withdrawals_common(state, expected_withdrawals, partial_withdrawals_count, spec)?;
Ok(())
}
}

View File

@@ -10,10 +10,11 @@ use typenum::Unsigned;
use types::{
AbstractExecPayload, AttesterSlashingRef, BeaconBlockRef, BeaconState, BeaconStateError,
ChainSpec, DepositData, Domain, Epoch, EthSpec, Fork, Hash256, InconsistentFork,
IndexedAttestation, IndexedAttestationRef, ProposerSlashing, SignedAggregateAndProof,
SignedBeaconBlock, SignedBeaconBlockHeader, SignedBlsToExecutionChange,
SignedContributionAndProof, SignedRoot, SignedVoluntaryExit, SigningData, Slot, SyncAggregate,
SyncAggregatorSelectionData,
IndexedAttestation, IndexedAttestationRef, IndexedPayloadAttestation, ProposerSlashing,
SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockHeader,
SignedBlsToExecutionChange, SignedContributionAndProof, SignedExecutionPayloadBid,
SignedExecutionPayloadEnvelope, SignedRoot, SignedVoluntaryExit, SigningData, Slot,
SyncAggregate, SyncAggregatorSelectionData,
};
pub type Result<T> = std::result::Result<T, Error>;
@@ -299,6 +300,40 @@ where
Ok(SignatureSet::multiple_pubkeys(signature, pubkeys, message))
}
pub fn indexed_payload_attestation_signature_set<'a, 'b, E, F>(
state: &'a BeaconState<E>,
get_pubkey: F,
signature: &'a AggregateSignature,
indexed_payload_attestation: &'b IndexedPayloadAttestation<E>,
spec: &'a ChainSpec,
) -> Result<SignatureSet<'a>>
where
E: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
{
let mut pubkeys = Vec::with_capacity(indexed_payload_attestation.attesting_indices.len());
for &validator_idx in indexed_payload_attestation.attesting_indices.iter() {
pubkeys.push(
get_pubkey(validator_idx as usize).ok_or(Error::ValidatorUnknown(validator_idx))?,
);
}
let epoch = indexed_payload_attestation
.data
.slot
.epoch(E::slots_per_epoch());
let domain = spec.get_domain(
epoch,
Domain::PTCAttester,
&state.fork(),
state.genesis_validators_root(),
);
let message = indexed_payload_attestation.data.signing_root(domain);
Ok(SignatureSet::multiple_pubkeys(signature, pubkeys, message))
}
/// Returns the signature set for the given `indexed_attestation` but pubkeys are supplied directly
/// instead of from the state.
pub fn indexed_attestation_signature_set_from_pubkeys<'a, 'b, E, F>(
@@ -332,6 +367,64 @@ where
Ok(SignatureSet::multiple_pubkeys(signature, pubkeys, message))
}
pub fn execution_envelope_signature_set<'a, E, F>(
state: &'a BeaconState<E>,
get_pubkey: F,
signed_envelope: &'a SignedExecutionPayloadEnvelope<E>,
spec: &'a ChainSpec,
) -> Result<SignatureSet<'a>>
where
E: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
{
let proposer_index = state.latest_block_header().proposer_index;
let builder_index = signed_envelope.message.builder_index(proposer_index);
let domain = spec.get_domain(
state.current_epoch(),
Domain::BeaconBuilder,
&state.fork(),
state.genesis_validators_root(),
);
let message = signed_envelope.message.signing_root(domain);
let pubkey =
get_pubkey(builder_index as usize).ok_or(Error::ValidatorUnknown(builder_index))?;
Ok(SignatureSet::single_pubkey(
&signed_envelope.signature,
pubkey,
message,
))
}
pub fn execution_payload_bid_signature_set<'a, E, F>(
state: &'a BeaconState<E>,
get_pubkey: F,
signed_execution_payload_bid: &'a SignedExecutionPayloadBid,
spec: &'a ChainSpec,
) -> Result<SignatureSet<'a>>
where
E: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
{
// TODO(EIP-7732): needs to handle self building!
let domain = spec.get_domain(
state.current_epoch(),
Domain::BeaconBuilder,
&state.fork(),
state.genesis_validators_root(),
);
let execution_payload_bid = &signed_execution_payload_bid.message;
let pubkey = get_pubkey(execution_payload_bid.builder_index as usize)
.ok_or(Error::ValidatorUnknown(execution_payload_bid.builder_index))?;
let message = execution_payload_bid.signing_root(domain);
Ok(SignatureSet::single_pubkey(
&signed_execution_payload_bid.signature,
pubkey,
message,
))
}
/// Returns the signature set for the given `attester_slashing` and corresponding `pubkeys`.
pub fn attester_slashing_signature_sets<'a, E, F>(
state: &'a BeaconState<E>,

View File

@@ -66,6 +66,10 @@ pub fn verify_attestation_for_state<'ctxt, E: EthSpec>(
// NOTE: choosing a validation based on the attestation's fork
// rather than the state's fork makes this simple, but technically the spec
// defines this verification based on the state's fork.
// Verify data.index based on attestation variant.
// The attestation variant is determined by the block body variant, which matches the fork.
// TODO(EIP-7732): discuss if it makes more sense to match on `ForkName` instead of attestation type. A reason against is an edge case like at the Gloas fork boundary, the first gloas block will contain attestations for a Fulu block, so I would think we would want this validation to still be with respect to fulu rules. But perhaps I'm wrong?
match attestation {
AttestationRef::Base(_) => {
verify!(
@@ -74,7 +78,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);
}
}
}

View File

@@ -0,0 +1,46 @@
use super::VerifySignatures;
use super::errors::{BlockOperationError, PayloadAttestationInvalid as Invalid};
use crate::ConsensusContext;
use crate::per_block_processing::is_valid_indexed_payload_attestation;
use safe_arith::SafeArith;
use types::*;
pub fn verify_payload_attestation<'ctxt, E: EthSpec>(
state: &mut BeaconState<E>,
payload_attestation: &'ctxt PayloadAttestation<E>,
ctxt: &'ctxt mut ConsensusContext<E>,
verify_signatures: VerifySignatures,
spec: &ChainSpec,
) -> Result<(), BlockOperationError<Invalid>> {
let data = &payload_attestation.data;
// Check that the attestation is for the parent beacon block
verify!(
data.beacon_block_root == state.latest_block_header().parent_root,
Invalid::BlockRootMismatch {
expected: state.latest_block_header().parent_root,
found: data.beacon_block_root,
}
);
// Check that the attestation is for the previous slot
verify!(
data.slot.safe_add(1)? == state.slot(),
Invalid::SlotMismatch {
expected: state.slot().saturating_sub(Slot::new(1)),
found: data.slot,
}
);
let indexed_payload_attestation =
ctxt.get_indexed_payload_attestation(state, data.slot, payload_attestation, spec)?;
is_valid_indexed_payload_attestation(
state,
indexed_payload_attestation,
verify_signatures,
spec,
)?;
Ok(())
}

View File

@@ -15,9 +15,9 @@ use std::collections::{BTreeSet, HashMap};
use tracing::instrument;
use typenum::Unsigned;
use types::{
ActivationQueue, BeaconState, BeaconStateError, ChainSpec, Checkpoint, DepositData, Epoch,
EthSpec, ExitCache, ForkName, ParticipationFlags, PendingDeposit, ProgressiveBalancesCache,
RelativeEpoch, Validator,
ActivationQueue, BeaconState, BeaconStateError, BuilderPendingPayment, ChainSpec, Checkpoint,
DepositData, Epoch, EthSpec, ExitCache, ForkName, ParticipationFlags, PendingDeposit,
ProgressiveBalancesCache, RelativeEpoch, Validator,
consts::altair::{
NUM_FLAG_INDICES, PARTICIPATION_FLAG_WEIGHTS, TIMELY_HEAD_FLAG_INDEX,
TIMELY_TARGET_FLAG_INDEX, WEIGHT_DENOMINATOR,
@@ -33,6 +33,7 @@ pub struct SinglePassConfig {
pub pending_consolidations: bool,
pub effective_balance_updates: bool,
pub proposer_lookahead: bool,
pub builder_pending_payments: bool,
}
impl Default for SinglePassConfig {
@@ -52,6 +53,7 @@ impl SinglePassConfig {
pending_consolidations: true,
effective_balance_updates: true,
proposer_lookahead: true,
builder_pending_payments: true,
}
}
@@ -65,6 +67,7 @@ impl SinglePassConfig {
pending_consolidations: false,
effective_balance_updates: false,
proposer_lookahead: false,
builder_pending_payments: false,
}
}
}
@@ -455,6 +458,12 @@ pub fn process_epoch_single_pass<E: EthSpec>(
)?;
}
// Process builder pending payments outside the single-pass loop, as they depend on balances for multiple
// validators and cannot be computed accurately inside the loop.
if fork_name.gloas_enabled() && conf.builder_pending_payments {
process_builder_pending_payments(state, state_ctxt, spec)?;
}
// Finally, finish updating effective balance caches. We need this to happen *after* processing
// of pending consolidations, which recomputes some effective balances.
if conf.effective_balance_updates {
@@ -473,7 +482,7 @@ pub fn process_epoch_single_pass<E: EthSpec>(
Ok(summary)
}
// TOOO(EIP-7917): use balances cache
// TODO(EIP-7917): use balances cache
pub fn process_proposer_lookahead<E: EthSpec>(
state: &mut BeaconState<E>,
spec: &ChainSpec,
@@ -503,6 +512,67 @@ pub fn process_proposer_lookahead<E: EthSpec>(
Ok(())
}
/// Calculate the quorum threshold for builder payments based on total active balance.
fn get_builder_payment_quorum_threshold<E: EthSpec>(
state_ctxt: &StateContext,
spec: &ChainSpec,
) -> Result<u64, Error> {
let per_slot_balance = state_ctxt
.total_active_balance
.safe_div(E::slots_per_epoch())?;
let quorum = per_slot_balance.safe_mul(spec.builder_payment_threshold_numerator)?;
quorum
.safe_div(spec.builder_payment_threshold_denominator)
.map_err(Error::from)
}
/// Process builder pending payments, moving qualifying payments to withdrawals.
/// TODO(EIP-7732): Add EF consensus-spec tests for `process_builder_pending_payments`
/// Currently blocked by EF consensus-spec-tests for Gloas not yet integrated.
fn process_builder_pending_payments<E: EthSpec>(
state: &mut BeaconState<E>,
state_ctxt: &StateContext,
spec: &ChainSpec,
) -> Result<(), Error> {
let quorum = get_builder_payment_quorum_threshold::<E>(state_ctxt, spec)?;
// Collect qualifying payments
let qualifying_payments = state
.builder_pending_payments()?
.iter()
.take(E::slots_per_epoch() as usize)
.filter(|payment| payment.weight > quorum)
.cloned()
.collect::<Vec<_>>();
// Update `builder_pending_withdrawals` with qualifying `builder_pending_payments`
qualifying_payments
.into_iter()
.try_for_each(|payment| -> Result<(), Error> {
let exit_queue_epoch =
state.compute_exit_epoch_and_update_churn(payment.withdrawal.amount, spec)?;
let _withdrawable_epoch =
exit_queue_epoch.safe_add(spec.min_validator_withdrawability_delay)?;
let withdrawal = payment.withdrawal.clone();
state.builder_pending_withdrawals_mut()?.push(withdrawal)?;
Ok(())
})?;
// Move remaining `builder_pending_payments` to start of list and set the rest to default
let new_payments = state
.builder_pending_payments()?
.iter()
.skip(E::slots_per_epoch() as usize)
.cloned()
.chain((0..E::slots_per_epoch() as usize).map(|_| BuilderPendingPayment::default()))
.collect::<Vec<_>>();
*state.builder_pending_payments_mut()? = Vector::new(new_payments)?;
Ok(())
}
fn process_single_inactivity_update(
inactivity_score: &mut Cow<u64>,
validator_info: &ValidatorInfo,

View File

@@ -14,6 +14,7 @@ pub enum Error {
EpochProcessingError(EpochProcessingError),
ArithError(ArithError),
InconsistentStateFork(InconsistentFork),
BitfieldError(ssz::BitfieldError),
}
impl From<ArithError> for Error {
@@ -22,6 +23,12 @@ impl From<ArithError> for Error {
}
}
impl From<ssz::BitfieldError> for Error {
fn from(e: ssz::BitfieldError) -> Self {
Self::BitfieldError(e)
}
}
/// Advances a state forward by one slot, performing per-epoch processing if required.
///
/// If the root of the supplied `state` is known, then it can be passed as `state_root`. If
@@ -50,6 +57,18 @@ pub fn per_slot_processing<E: EthSpec>(
state.slot_mut().safe_add_assign(1)?;
// Unset the next payload availability
if state.fork_name_unchecked().gloas_enabled() {
let next_slot_index = state
.slot()
.as_usize()
.safe_add(1)?
.safe_rem(E::slots_per_historical_root())?;
state
.execution_payload_availability_mut()?
.set(next_slot_index, false)?;
}
// Process fork upgrades here. Note that multiple upgrades can potentially run
// in sequence if they are scheduled in the same Epoch (common in testnets)
if state.slot().safe_rem(E::slots_per_epoch())? == 0 {

View File

@@ -82,7 +82,7 @@ pub fn upgrade_to_electra<E: EthSpec>(
// Ensure early adopters of compounding credentials go through the activation churn
let validators = post.validators().clone();
for (index, validator) in validators.iter().enumerate() {
if validator.has_compounding_withdrawal_credential(spec) {
if validator.has_compounding_withdrawal_credential(spec, post.fork_name_unchecked()) {
post.queue_excess_active_balance(index, spec)?;
}
}

View File

@@ -1,7 +1,7 @@
use bls::Hash256;
use milhouse::{List, Vector};
use ssz_types::BitVector;
use std::mem;
use typenum::Unsigned;
use types::{
BeaconState, BeaconStateError as Error, BeaconStateGloas, BuilderPendingPayment, ChainSpec,
EthSpec, ExecutionPayloadBid, Fork,
@@ -88,15 +88,23 @@ pub fn upgrade_state_to_gloas<E: EthSpec>(
pending_deposits: pre.pending_deposits.clone(),
pending_partial_withdrawals: pre.pending_partial_withdrawals.clone(),
pending_consolidations: pre.pending_consolidations.clone(),
proposer_lookahead: mem::take(&mut pre.proposer_lookahead),
// Gloas
execution_payload_availability: BitVector::default(), // All bits set to false initially
builders: List::default(),
next_withdrawal_builder_index: 0,
// All bits set to true per spec:
// execution_payload_availability = [0b1 for _ in range(SLOTS_PER_HISTORICAL_ROOT)]
execution_payload_availability: BitVector::from_bytes(
vec![0xFFu8; E::SlotsPerHistoricalRoot::to_usize() / 8].into(),
)
.expect("SlotsPerHistoricalRoot is always divisible by 8"),
builder_pending_payments: Vector::new(vec![
BuilderPendingPayment::default();
E::builder_pending_payments_limit()
])?,
builder_pending_withdrawals: List::default(), // Empty list initially,
latest_block_hash: pre.latest_execution_payload_header.block_hash,
latest_withdrawals_root: Hash256::default(),
payload_expected_withdrawals: List::default(),
// Caches
total_active_balance: pre.total_active_balance,
progressive_balances_cache: mem::take(&mut pre.progressive_balances_cache),
@@ -105,7 +113,6 @@ pub fn upgrade_state_to_gloas<E: EthSpec>(
exit_cache: mem::take(&mut pre.exit_cache),
slashings_cache: mem::take(&mut pre.slashings_cache),
epoch_cache: mem::take(&mut pre.epoch_cache),
proposer_lookahead: mem::take(&mut pre.proposer_lookahead),
});
Ok(post)
}

View File

@@ -1 +1,23 @@
# Mainnet preset - Gloas
# Misc
# ---------------------------------------------------------------
# 2**9 (= 512) validators
PTC_SIZE: 512
# Max operations per block
# ---------------------------------------------------------------
# 2**2 (= 4) attestations
MAX_PAYLOAD_ATTESTATIONS: 4
# State list lengths
# ---------------------------------------------------------------
# 2**40 (= 1,099,511,627,776) builder spots
BUILDER_REGISTRY_LIMIT: 1099511627776
# 2**20 (= 1,048,576) builder pending withdrawals
BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576
# Withdrawals processing
# ---------------------------------------------------------------
# 2**14 (= 16,384) builders
MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16384

View File

@@ -1 +1,23 @@
# Minimal preset - Gloas
# Misc
# ---------------------------------------------------------------
# [customized] 2**1 (= 2) validators
PTC_SIZE: 2
# Max operations per block
# ---------------------------------------------------------------
# 2**2 (= 4) attestations
MAX_PAYLOAD_ATTESTATIONS: 4
# State list lengths
# ---------------------------------------------------------------
# 2**40 (= 1,099,511,627,776) builder spots
BUILDER_REGISTRY_LIMIT: 1099511627776
# 2**20 (= 1,048,576) builder pending withdrawals
BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576
# Withdrawals processing
# ---------------------------------------------------------------
# [customized] 2**4 (= 16) builders
MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16

View File

@@ -11,6 +11,7 @@ mod payload_attestation;
mod payload_attestation_data;
mod payload_attestation_message;
mod pending_attestation;
mod ptc;
mod selection_proof;
mod shuffling_id;
mod signed_aggregate_and_proof;
@@ -36,6 +37,7 @@ pub use payload_attestation::PayloadAttestation;
pub use payload_attestation_data::PayloadAttestationData;
pub use payload_attestation_message::PayloadAttestationMessage;
pub use pending_attestation::PendingAttestation;
pub use ptc::PTC;
pub use selection_proof::SelectionProof;
pub use shuffling_id::AttestationShufflingId;
pub use signed_aggregate_and_proof::{

View File

@@ -5,7 +5,7 @@ use bls::AggregateSignature;
use context_deserialize::context_deserialize;
use educe::Educe;
use serde::{Deserialize, Serialize};
use ssz::BitList;
use ssz::BitVector;
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash;
@@ -17,7 +17,7 @@ use tree_hash_derive::TreeHash;
#[educe(PartialEq, Hash)]
#[context_deserialize(ForkName)]
pub struct PayloadAttestation<E: EthSpec> {
pub aggregation_bits: BitList<E::PTCSize>,
pub aggregation_bits: BitVector<E::PTCSize>,
pub data: PayloadAttestationData,
pub signature: AggregateSignature,
}

View File

@@ -0,0 +1,24 @@
use crate::EthSpec;
use ssz_types::FixedVector;
/// TODO(EIP-7732): is it easier to return u64 or usize?
#[derive(Clone, Debug, PartialEq)]
pub struct PTC<E: EthSpec>(pub FixedVector<usize, E::PTCSize>);
impl<'a, E: EthSpec> IntoIterator for &'a PTC<E> {
type Item = &'a usize;
type IntoIter = std::slice::Iter<'a, usize>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<E: EthSpec> IntoIterator for PTC<E> {
type Item = usize;
type IntoIter = std::vec::IntoIter<usize>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

View File

@@ -0,0 +1,24 @@
use crate::test_utils::TestRandom;
use crate::{Address, Epoch};
use bls::PublicKeyBytes;
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash;
pub type BuilderIndex = u64;
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Encode, Decode, TestRandom, TreeHash,
)]
pub struct Builder {
pub pubkey: PublicKeyBytes,
#[serde(with = "serde_utils::quoted_u8")]
pub version: u8,
pub execution_address: Address,
#[serde(with = "serde_utils::quoted_u64")]
pub balance: u64,
pub deposit_epoch: Epoch,
pub withdrawable_epoch: Epoch,
}

View File

@@ -1,5 +1,5 @@
use crate::test_utils::TestRandom;
use crate::{Address, Epoch, ForkName};
use crate::{Address, ForkName};
use context_deserialize::context_deserialize;
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
@@ -29,7 +29,6 @@ pub struct BuilderPendingWithdrawal {
pub amount: u64,
#[serde(with = "serde_utils::quoted_u64")]
pub builder_index: u64,
pub withdrawable_epoch: Epoch,
}
#[cfg(test)]

View File

@@ -1,7 +1,9 @@
mod builder;
mod builder_bid;
mod builder_pending_payment;
mod builder_pending_withdrawal;
pub use builder::{Builder, BuilderIndex};
pub use builder_bid::{
BuilderBid, BuilderBidBellatrix, BuilderBidCapella, BuilderBidDeneb, BuilderBidElectra,
BuilderBidFulu, SignedBuilderBid,

View File

@@ -36,6 +36,7 @@ pub enum Domain {
SyncCommitteeSelectionProof,
BeaconBuilder,
PTCAttester,
ProposerPreferences,
ApplicationMask(ApplicationDomain),
}
@@ -130,6 +131,7 @@ pub struct ChainSpec {
pub(crate) domain_aggregate_and_proof: u32,
pub(crate) domain_beacon_builder: u32,
pub(crate) domain_ptc_attester: u32,
pub(crate) domain_proposer_preferences: u32,
/*
* Fork choice
@@ -234,6 +236,7 @@ pub struct ChainSpec {
pub gloas_fork_epoch: Option<Epoch>,
pub builder_payment_threshold_numerator: u64,
pub builder_payment_threshold_denominator: u64,
pub min_builder_withdrawability_delay: Epoch,
/*
* Networking
@@ -500,6 +503,7 @@ impl ChainSpec {
Domain::AggregateAndProof => self.domain_aggregate_and_proof,
Domain::BeaconBuilder => self.domain_beacon_builder,
Domain::PTCAttester => self.domain_ptc_attester,
Domain::ProposerPreferences => self.domain_proposer_preferences,
Domain::SyncCommittee => self.domain_sync_committee,
Domain::ContributionAndProof => self.domain_contribution_and_proof,
Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof,
@@ -977,8 +981,9 @@ impl ChainSpec {
domain_voluntary_exit: 4,
domain_selection_proof: 5,
domain_aggregate_and_proof: 6,
domain_beacon_builder: 0x1B,
domain_beacon_builder: 0x0B,
domain_ptc_attester: 0x0C,
domain_proposer_preferences: 0x0D,
/*
* Fork choice
@@ -1102,6 +1107,7 @@ impl ChainSpec {
gloas_fork_epoch: None,
builder_payment_threshold_numerator: 6,
builder_payment_threshold_denominator: 10,
min_builder_withdrawability_delay: Epoch::new(4096),
/*
* Network specific
@@ -1242,7 +1248,7 @@ impl ChainSpec {
fulu_fork_version: [0x06, 0x00, 0x00, 0x01],
fulu_fork_epoch: None,
// Gloas
gloas_fork_version: [0x07, 0x00, 0x00, 0x00],
gloas_fork_version: [0x07, 0x00, 0x00, 0x01],
gloas_fork_epoch: None,
// Other
network_id: 2, // lighthouse testnet network id
@@ -1350,8 +1356,9 @@ impl ChainSpec {
domain_voluntary_exit: 4,
domain_selection_proof: 5,
domain_aggregate_and_proof: 6,
domain_beacon_builder: 0x1B,
domain_beacon_builder: 0x0B,
domain_ptc_attester: 0x0C,
domain_proposer_preferences: 0x0D,
/*
* Fork choice
@@ -1474,6 +1481,7 @@ impl ChainSpec {
gloas_fork_epoch: None,
builder_payment_threshold_numerator: 6,
builder_payment_threshold_denominator: 10,
min_builder_withdrawability_delay: Epoch::new(4096),
/*
* Network specific

View File

@@ -25,3 +25,17 @@ pub mod bellatrix {
pub mod deneb {
pub use kzg::VERSIONED_HASH_VERSION_KZG;
}
pub mod gloas {
pub const BUILDER_INDEX_SELF_BUILD: u64 = u64::MAX;
pub const BUILDER_INDEX_FLAG: u64 = 1 << 40;
// Fork choice constants
pub type PayloadStatus = u8;
pub const PAYLOAD_STATUS_PENDING: PayloadStatus = 0;
pub const PAYLOAD_STATUS_EMPTY: PayloadStatus = 1;
pub const PAYLOAD_STATUS_FULL: PayloadStatus = 2;
pub const ATTESTATION_TIMELINESS_INDEX: usize = 0;
pub const PTC_TIMELINESS_INDEX: usize = 1;
pub const NUM_BLOCK_TIMELINESS_DEADLINES: usize = 2;
}

View File

@@ -7,7 +7,7 @@ use safe_arith::{ArithError, SafeArith};
use serde::{Deserialize, Serialize};
use typenum::{
U0, U1, U2, U4, U8, U16, U17, U32, U64, U128, U256, U512, U625, U1024, U2048, U4096, U8192,
U65536, U131072, U262144, U1048576, U16777216, U33554432, U134217728, U1073741824,
U16384, U65536, U131072, U262144, U1048576, U16777216, U33554432, U134217728, U1073741824,
U1099511627776, UInt, Unsigned, bit::B0,
};
@@ -122,6 +122,10 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq +
type CellsPerExtBlob: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type NumberOfColumns: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type ProposerLookaheadSlots: Unsigned + Clone + Sync + Send + Debug + PartialEq;
/*
* New in Gloas
*/
type BuilderRegistryLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq;
/*
* Derived values (set these CAREFULLY)
*/
@@ -175,6 +179,7 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq +
type MaxPayloadAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type BuilderPendingPaymentsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type BuilderPendingWithdrawalsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type MaxBuildersPerWithdrawalsSweep: Unsigned + Clone + Sync + Send + Debug + PartialEq;
fn default_spec() -> ChainSpec;
@@ -427,6 +432,16 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq +
fn max_payload_attestations() -> usize {
Self::MaxPayloadAttestations::to_usize()
}
/// Returns the `MaxBuildersPerWithdrawalsSweep` constant for this specification.
fn max_builders_per_withdrawals_sweep() -> usize {
Self::MaxBuildersPerWithdrawalsSweep::to_usize()
}
/// Returns the `PAYLOAD_TIMELY_THRESHOLD` constant (PTC_SIZE / 2).
fn payload_timely_threshold() -> usize {
Self::PTCSize::to_usize() / 2
}
}
/// Macro to inherit some type values from another EthSpec.
@@ -484,6 +499,7 @@ impl EthSpec for MainnetEthSpec {
type CellsPerExtBlob = U128;
type NumberOfColumns = U128;
type ProposerLookaheadSlots = U64; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
type BuilderRegistryLimit = U1099511627776;
type SyncSubcommitteeSize = U128; // 512 committee size / 4 sync committee subnet count
type MaxPendingAttestations = U4096; // 128 max attestations * 32 slots per epoch
type SlotsPerEth1VotingPeriod = U2048; // 64 epochs * 32 slots per epoch
@@ -500,6 +516,7 @@ impl EthSpec for MainnetEthSpec {
type MaxPendingDepositsPerEpoch = U16;
type PTCSize = U512;
type MaxPayloadAttestations = U4;
type MaxBuildersPerWithdrawalsSweep = U16384;
fn default_spec() -> ChainSpec {
ChainSpec::mainnet()
@@ -543,6 +560,8 @@ impl EthSpec for MinimalEthSpec {
type NumberOfColumns = U128;
type ProposerLookaheadSlots = U16; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
type BuilderPendingPaymentsLimit = U16; // 2 * SLOTS_PER_EPOCH = 2 * 8 = 16
type PTCSize = U2;
type MaxBuildersPerWithdrawalsSweep = U16;
params_from_eth_spec!(MainnetEthSpec {
JustificationBitsLength,
@@ -573,8 +592,8 @@ impl EthSpec for MinimalEthSpec {
MaxAttestationsElectra,
MaxDepositRequestsPerPayload,
MaxWithdrawalRequestsPerPayload,
PTCSize,
MaxPayloadAttestations
MaxPayloadAttestations,
BuilderRegistryLimit
});
fn default_spec() -> ChainSpec {
@@ -647,8 +666,10 @@ impl EthSpec for GnosisEthSpec {
type CellsPerExtBlob = U128;
type NumberOfColumns = U128;
type ProposerLookaheadSlots = U32; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
type BuilderRegistryLimit = U1099511627776;
type PTCSize = U512;
type MaxPayloadAttestations = U2;
type MaxBuildersPerWithdrawalsSweep = U16384;
fn default_spec() -> ChainSpec {
ChainSpec::gnosis()

View File

@@ -1,3 +1,4 @@
use crate::consts::gloas::BUILDER_INDEX_SELF_BUILD;
use crate::test_utils::TestRandom;
use crate::{
EthSpec, ExecutionPayloadGloas, ExecutionRequests, ForkName, Hash256, KzgCommitments,
@@ -17,8 +18,10 @@ use tree_hash_derive::TreeHash;
pub struct ExecutionPayloadEnvelope<E: EthSpec> {
pub payload: ExecutionPayloadGloas<E>,
pub execution_requests: ExecutionRequests<E>,
// The builder index is private so that callers are forced to handle the case where it equals
// BUILDER_INDEX_SELF_BUILD.
#[serde(with = "serde_utils::quoted_u64")]
pub builder_index: u64,
builder_index: u64,
pub beacon_block_root: Hash256,
pub slot: Slot,
pub blob_kzg_commitments: KzgCommitments<E>,
@@ -27,6 +30,28 @@ pub struct ExecutionPayloadEnvelope<E: EthSpec> {
impl<E: EthSpec> SignedRoot for ExecutionPayloadEnvelope<E> {}
impl<E: EthSpec> ExecutionPayloadEnvelope<E> {
/// Fetch the validator index of the builder of this execution payload.
///
/// This falls back to the provided `proposer_index` if the builder index indicates
/// self-building.
pub fn builder_index(&self, proposer_index: u64) -> u64 {
if self.builder_index == BUILDER_INDEX_SELF_BUILD {
proposer_index
} else {
self.builder_index
}
}
/// Fetch the raw builder index, which may be `BUILDER_INDEX_SELF_BUILD` to indicate
/// self-building.
///
/// This method should be used sparingly.
pub fn raw_builder_index(&self) -> u64 {
self.builder_index
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -1,6 +1,9 @@
use crate::test_utils::TestRandom;
use crate::{EthSpec, ExecutionPayloadEnvelope};
use bls::Signature;
use crate::{
BeaconState, BeaconStateError, ChainSpec, Domain, Epoch, EthSpec, ExecutionBlockHash,
ExecutionPayloadEnvelope, Fork, Hash256, SignedRoot, Slot,
};
use bls::{PublicKey, Signature};
use educe::Educe;
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
@@ -15,6 +18,77 @@ pub struct SignedExecutionPayloadEnvelope<E: EthSpec> {
pub signature: Signature,
}
impl<E: EthSpec> SignedExecutionPayloadEnvelope<E> {
pub fn slot(&self) -> Slot {
self.message.slot
}
pub fn epoch(&self) -> Epoch {
self.slot().epoch(E::slots_per_epoch())
}
pub fn beacon_block_root(&self) -> Hash256 {
self.message.beacon_block_root
}
pub fn block_hash(&self) -> ExecutionBlockHash {
self.message.payload.block_hash
}
/// Verify `self.signature`.
///
/// The `parent_state` is the post-state of the beacon block with
/// block_root = self.message.beacon_block_root
/// TODO(EIP-7732): maybe delete this function later (it is inefficient)
pub fn verify_signature_with_state(
&self,
parent_state: &BeaconState<E>,
spec: &ChainSpec,
) -> Result<bool, BeaconStateError> {
let proposer_index = parent_state.latest_block_header().proposer_index;
let builder_index = self.message.builder_index(proposer_index) as usize;
let domain = spec.get_domain(
parent_state.current_epoch(),
Domain::BeaconBuilder,
&parent_state.fork(),
parent_state.genesis_validators_root(),
);
let pubkey = parent_state
.validators()
.get(builder_index)
.and_then(|v| {
let pk: Option<PublicKey> = v.pubkey.decompress().ok();
pk
})
.ok_or(BeaconStateError::UnknownValidator(builder_index))?;
let message = self.message.signing_root(domain);
Ok(self.signature.verify(&pubkey, message))
}
/// Verify `self.signature`.
pub fn verify_signature(
&self,
pubkey: &PublicKey,
fork: &Fork,
genesis_validators_root: Hash256,
spec: &ChainSpec,
) -> bool {
// Signed envelopes using the new BeaconBuilder domain per the spec:
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-verify_execution_payload_envelope_signature
let domain = spec.get_domain(
self.epoch(),
Domain::BeaconBuilder,
fork,
genesis_validators_root,
);
let message = self.message.signing_root(domain);
self.signature.verify(pubkey, message)
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -23,10 +23,11 @@ use tree_hash_derive::TreeHash;
use typenum::Unsigned;
use crate::{
BuilderPendingPayment, BuilderPendingWithdrawal, ExecutionBlockHash, ExecutionPayloadBid,
Builder, BuilderIndex, BuilderPendingPayment, BuilderPendingWithdrawal, ExecutionBlockHash,
ExecutionPayloadBid, Withdrawal,
attestation::{
AttestationDuty, BeaconCommittee, Checkpoint, CommitteeIndex, ParticipationFlags,
PendingAttestation,
AttestationData, AttestationDuty, BeaconCommittee, Checkpoint, CommitteeIndex, PTC,
ParticipationFlags, PendingAttestation,
},
block::{BeaconBlock, BeaconBlockHeader, SignedBeaconBlockHash},
consolidation::PendingConsolidation,
@@ -67,6 +68,7 @@ pub enum BeaconStateError {
EpochOutOfBounds,
SlotOutOfBounds,
UnknownValidator(usize),
UnknownBuilder(u64),
UnableToDetermineProducer,
InvalidBitfield,
EmptyCommittee,
@@ -168,12 +170,15 @@ pub enum BeaconStateError {
TotalActiveBalanceDiffUninitialized,
GeneralizedIndexNotSupported(usize),
IndexNotSupported(usize),
BuilderPendingPaymentsIndexNotSupported(usize),
InvalidFlagIndex(usize),
MerkleTreeError(merkle_proof::MerkleTreeError),
PartialWithdrawalCountInvalid(usize),
NonExecutionAddressWithdrawalCredential,
NoCommitteeFound(CommitteeIndex),
InvalidCommitteeIndex(CommitteeIndex),
/// `Attestation.data.index` field is invalid in overloaded data index scenario.
BadOverloadedDataIndex(u64),
InvalidSelectionProof {
aggregator_index: u64,
},
@@ -195,6 +200,9 @@ pub enum BeaconStateError {
ProposerLookaheadOutOfBounds {
i: usize,
},
InvalidIndicesCount,
PleaseNotifyTheDevs(String),
InvalidExecutionPayloadAvailabilityIndex(usize),
}
/// Control whether an epoch-indexed field can be indexed at the next epoch or not.
@@ -602,8 +610,17 @@ where
#[superstruct(only(Fulu, Gloas))]
#[serde(with = "ssz_types::serde_utils::quoted_u64_fixed_vec")]
pub proposer_lookahead: Vector<u64, E::ProposerLookaheadSlots>,
// Gloas
#[compare_fields(as_iter)]
#[test_random(default)]
#[superstruct(only(Gloas))]
pub builders: List<Builder, E::BuilderRegistryLimit>,
#[metastruct(exclude_from(tree_lists))]
#[serde(with = "serde_utils::quoted_u64")]
#[superstruct(only(Gloas), partial_getter(copy))]
pub next_withdrawal_builder_index: BuilderIndex,
#[test_random(default)]
#[superstruct(only(Gloas))]
#[metastruct(exclude_from(tree_lists))]
@@ -625,10 +642,10 @@ where
#[metastruct(exclude_from(tree_lists))]
pub latest_block_hash: ExecutionBlockHash,
#[compare_fields(as_iter)]
#[test_random(default)]
#[superstruct(only(Gloas))]
#[metastruct(exclude_from(tree_lists))]
pub latest_withdrawals_root: Hash256,
pub payload_expected_withdrawals: List<Withdrawal, E::MaxWithdrawalsPerPayload>,
// Caching (not in the spec)
#[serde(skip_serializing, skip_deserializing)]
@@ -1115,13 +1132,22 @@ impl<E: EthSpec> BeaconState<E> {
}
}
let gloas_enabled = self.fork_name_unchecked().gloas_enabled();
epoch
.slot_iter(E::slots_per_epoch())
.map(|slot| {
let mut preimage = seed.to_vec();
preimage.append(&mut int_to_bytes8(slot.as_u64()));
let seed = hash(&preimage);
self.compute_proposer_index(indices, &seed, spec)
if gloas_enabled {
self.compute_balance_weighted_selection(indices, &seed, 1, true, spec)?
.first()
.copied()
.ok_or(BeaconStateError::InsufficientValidators)
} else {
self.compute_proposer_index(indices, &seed, spec)
}
})
.collect()
}
@@ -1378,39 +1404,50 @@ impl<E: EthSpec> BeaconState<E> {
let epoch = self.current_epoch().safe_add(1)?;
let active_validator_indices = self.get_active_validator_indices(epoch, spec)?;
let active_validator_count = active_validator_indices.len();
let seed = self.get_seed(epoch, Domain::SyncCommittee, spec)?;
let max_effective_balance = spec.max_effective_balance_for_fork(self.fork_name_unchecked());
let max_random_value = if self.fork_name_unchecked().electra_enabled() {
MAX_RANDOM_VALUE
} else {
MAX_RANDOM_BYTE
};
let mut i = 0;
let mut sync_committee_indices = Vec::with_capacity(E::SyncCommitteeSize::to_usize());
while sync_committee_indices.len() < E::SyncCommitteeSize::to_usize() {
let shuffled_index = compute_shuffled_index(
i.safe_rem(active_validator_count)?,
active_validator_count,
if self.fork_name_unchecked().gloas_enabled() {
self.compute_balance_weighted_selection(
&active_validator_indices,
seed.as_slice(),
spec.shuffle_round_count,
E::SyncCommitteeSize::to_usize(),
true,
spec,
)
.ok_or(BeaconStateError::UnableToShuffle)?;
let candidate_index = *active_validator_indices
.get(shuffled_index)
.ok_or(BeaconStateError::ShuffleIndexOutOfBounds(shuffled_index))?;
let random_value = self.shuffling_random_value(i, seed.as_slice())?;
let effective_balance = self.get_validator(candidate_index)?.effective_balance;
if effective_balance.safe_mul(max_random_value)?
>= max_effective_balance.safe_mul(random_value)?
{
sync_committee_indices.push(candidate_index);
} else {
let active_validator_count = active_validator_indices.len();
let max_effective_balance =
spec.max_effective_balance_for_fork(self.fork_name_unchecked());
let max_random_value = if self.fork_name_unchecked().electra_enabled() {
MAX_RANDOM_VALUE
} else {
MAX_RANDOM_BYTE
};
let mut i = 0;
let mut sync_committee_indices = Vec::with_capacity(E::SyncCommitteeSize::to_usize());
while sync_committee_indices.len() < E::SyncCommitteeSize::to_usize() {
let shuffled_index = compute_shuffled_index(
i.safe_rem(active_validator_count)?,
active_validator_count,
seed.as_slice(),
spec.shuffle_round_count,
)
.ok_or(BeaconStateError::UnableToShuffle)?;
let candidate_index = *active_validator_indices
.get(shuffled_index)
.ok_or(BeaconStateError::ShuffleIndexOutOfBounds(shuffled_index))?;
let random_value = self.shuffling_random_value(i, seed.as_slice())?;
let effective_balance = self.get_validator(candidate_index)?.effective_balance;
if effective_balance.safe_mul(max_random_value)?
>= max_effective_balance.safe_mul(random_value)?
{
sync_committee_indices.push(candidate_index);
}
i.safe_add_assign(1)?;
}
i.safe_add_assign(1)?;
Ok(sync_committee_indices)
}
Ok(sync_committee_indices)
}
/// Compute the next sync committee.
@@ -2032,6 +2069,25 @@ impl<E: EthSpec> BeaconState<E> {
Ok(cache.get_attestation_duties(validator_index))
}
/// Check if the attestation is for the block proposed at the attestation slot.
///
/// Returns `true` if the attestation's block root matches the block root at the
/// attestation's slot, and the block root differs from the previous slot's root.
pub fn is_attestation_same_slot(
&self,
data: &AttestationData,
) -> Result<bool, BeaconStateError> {
if data.slot == 0 {
return Ok(true);
}
let blockroot = data.beacon_block_root;
let slot_blockroot = *self.get_block_root(data.slot)?;
let prev_blockroot = *self.get_block_root(data.slot.safe_sub(1)?)?;
Ok(blockroot == slot_blockroot && blockroot != prev_blockroot)
}
/// Compute the total active balance cache from scratch.
///
/// This method should rarely be invoked because single-pass epoch processing keeps the total
@@ -2292,6 +2348,7 @@ impl<E: EthSpec> BeaconState<E> {
}
}
/// Return true if the parent block was full (both beacon block and execution payload were present).
pub fn is_parent_block_full(&self) -> bool {
match self {
BeaconState::Base(_) | BeaconState::Altair(_) => false,
@@ -2594,11 +2651,16 @@ impl<E: EthSpec> BeaconState<E> {
.map_err(Into::into)
}
// TODO(EIP-7732): The consensus spec PR for this change mentions that some EF tests will be needed but haven't been created yet.
// We should integrate them once they are available.
// https://github.com/ethereum/consensus-specs/pull/4513
pub fn get_pending_balance_to_withdraw(
&self,
validator_index: usize,
) -> Result<u64, BeaconStateError> {
let mut pending_balance = 0;
// Sum pending partial withdrawals
for withdrawal in self
.pending_partial_withdrawals()?
.iter()
@@ -2606,6 +2668,27 @@ impl<E: EthSpec> BeaconState<E> {
{
pending_balance.safe_add_assign(withdrawal.amount)?;
}
// Sum builder pending withdrawals
if let Ok(builder_pending_withdrawals) = self.builder_pending_withdrawals() {
for withdrawal in builder_pending_withdrawals
.iter()
.filter(|withdrawal| withdrawal.builder_index as usize == validator_index)
{
pending_balance.safe_add_assign(withdrawal.amount)?;
}
}
// Sum builder pending payments
if let Ok(builder_pending_payments) = self.builder_pending_payments() {
for payment in builder_pending_payments
.iter()
.filter(|payment| payment.withdrawal.builder_index as usize == validator_index)
{
pending_balance.safe_add_assign(payment.withdrawal.amount)?;
}
}
Ok(pending_balance)
}
@@ -2878,6 +2961,120 @@ impl<E: EthSpec> BeaconState<E> {
Ok(())
}
/// Get the PTC
/// Requires the committee cache to be initialized.
/// TODO(EIP-7732): definitely gonna have to cache this..
pub fn get_ptc(&self, slot: Slot, spec: &ChainSpec) -> Result<PTC<E>, BeaconStateError> {
let committee_cache = self.committee_cache_at_slot(slot)?;
let committees = committee_cache.get_beacon_committees_at_slot(slot)?;
let seed = self.get_ptc_attester_seed(slot, spec)?;
let committee_indices: Vec<usize> = committees
.iter()
.flat_map(|committee| committee.committee.iter().copied())
.collect();
let selected_indices = self.compute_balance_weighted_selection(
&committee_indices,
&seed,
E::ptc_size(),
false,
spec,
)?;
Ok(PTC(FixedVector::new(selected_indices)?))
}
/// Compute the seed to use for the ptc attester selection at the given `slot`.
///
/// Spec v0.12.1
pub fn get_ptc_attester_seed(
&self,
slot: Slot,
spec: &ChainSpec,
) -> Result<Vec<u8>, BeaconStateError> {
let epoch = slot.epoch(E::slots_per_epoch());
let mut preimage = self
.get_seed(epoch, Domain::PTCAttester, spec)?
.as_slice()
.to_vec();
preimage.append(&mut int_to_bytes8(slot.as_u64()));
Ok(hash(&preimage))
}
/// 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.
fn compute_balance_weighted_selection(
&self,
indices: &[usize],
seed: &[u8],
size: usize,
shuffle_indices: bool,
spec: &ChainSpec,
) -> Result<Vec<usize>, BeaconStateError> {
let total = indices.len();
if total == 0 {
return Err(BeaconStateError::InvalidIndicesCount);
}
let mut selected = Vec::with_capacity(size);
let mut count = 0usize;
while selected.len() < size {
let mut next_index = count.safe_rem(total)?;
if shuffle_indices {
next_index =
compute_shuffled_index(next_index, total, seed, spec.shuffle_round_count)
.ok_or(BeaconStateError::UnableToShuffle)?;
}
let candidate_index = indices
.get(next_index)
.ok_or(BeaconStateError::InvalidIndicesCount)?;
if self.compute_balance_weighted_acceptance(*candidate_index, seed, count, spec)? {
selected.push(*candidate_index);
}
count.safe_add_assign(1)?;
}
Ok(selected)
}
/// Return whether to accept the selection of the validator `index`, with probability
/// proportional to its `effective_balance`, and randomness given by `seed` and `iteration`.
fn compute_balance_weighted_acceptance(
&self,
index: usize,
seed: &[u8],
iteration: usize,
spec: &ChainSpec,
) -> Result<bool, BeaconStateError> {
// TODO(EIP-7732): Consider grabbing effective balances from the epoch cache here.
// Note that this function will be used in a loop, so using cached values could be nice for performance.
// However, post-gloas, this function will be used in `compute_proposer_indices`, `get_next_sync_committee_indices`, and `get_ptc`, which has ~15 call sites in total
// so we will need to check each one to ensure epoch cache is initialized first, if we deem a good idea.
// Currently, we can't test if making the change would work since the test suite is not ready for gloas.
let effective_balance = self.get_effective_balance(index)?;
let max_effective_balance = spec.max_effective_balance_for_fork(self.fork_name_unchecked());
let random_value = self.shuffling_random_value(iteration, seed)?;
// this codepath should technically never be hit pre-gloas, but added this defensively
let max_random_value = if self.fork_name_unchecked().electra_enabled() {
MAX_RANDOM_VALUE
} else {
MAX_RANDOM_BYTE
};
Ok(effective_balance.safe_mul(max_random_value)?
>= max_effective_balance.safe_mul(random_value)?)
}
}
impl<E: EthSpec> ForkVersionDecode for BeaconState<E> {

View File

@@ -165,13 +165,41 @@ impl Validator {
}
/// Check if ``validator`` has an 0x02 prefixed "compounding" withdrawal credential.
pub fn has_compounding_withdrawal_credential(&self, spec: &ChainSpec) -> bool {
pub fn has_compounding_withdrawal_credential(
&self,
spec: &ChainSpec,
current_fork: ForkName,
) -> bool {
if current_fork.gloas_enabled() {
self.has_compounding_withdrawal_credential_gloas(spec)
} else {
self.has_compounding_withdrawal_credential_electra(spec)
}
}
/// Check if ``validator`` has an 0x02 prefixed "compounding" withdrawal credential
pub fn has_compounding_withdrawal_credential_electra(&self, spec: &ChainSpec) -> bool {
is_compounding_withdrawal_credential(self.withdrawal_credentials, spec)
}
/// Check if ``validator`` has an 0x02 prefixed "compounding" withdrawal credential or an 0x03 prefixed "builder" withdrawal credential
pub fn has_compounding_withdrawal_credential_gloas(&self, spec: &ChainSpec) -> bool {
is_compounding_withdrawal_credential(self.withdrawal_credentials, spec)
|| is_builder_withdrawal_credential(self.withdrawal_credentials, spec)
}
/// Check if ``validator`` has an 0x03 prefixed "builder" withdrawal credential.
pub fn has_builder_withdrawal_credential(&self, spec: &ChainSpec) -> bool {
is_builder_withdrawal_credential(self.withdrawal_credentials, spec)
}
/// Get the execution withdrawal address if this validator has one initialized.
pub fn get_execution_withdrawal_address(&self, spec: &ChainSpec) -> Option<Address> {
self.has_execution_withdrawal_credential(spec)
pub fn get_execution_withdrawal_address(
&self,
spec: &ChainSpec,
current_fork: ForkName,
) -> Option<Address> {
self.has_execution_withdrawal_credential(spec, current_fork)
.then(|| {
self.withdrawal_credentials
.as_slice()
@@ -202,7 +230,7 @@ impl Validator {
current_fork: ForkName,
) -> bool {
if current_fork.electra_enabled() {
self.is_fully_withdrawable_validator_electra(balance, epoch, spec)
self.is_fully_withdrawable_validator_electra(balance, epoch, spec, current_fork)
} else {
self.is_fully_withdrawable_validator_capella(balance, epoch, spec)
}
@@ -226,8 +254,9 @@ impl Validator {
balance: u64,
epoch: Epoch,
spec: &ChainSpec,
current_fork: ForkName,
) -> bool {
self.has_execution_withdrawal_credential(spec)
self.has_execution_withdrawal_credential(spec, current_fork)
&& self.withdrawable_epoch <= epoch
&& balance > 0
}
@@ -267,21 +296,25 @@ impl Validator {
let max_effective_balance = self.get_max_effective_balance(spec, current_fork);
let has_max_effective_balance = self.effective_balance == max_effective_balance;
let has_excess_balance = balance > max_effective_balance;
self.has_execution_withdrawal_credential(spec)
self.has_execution_withdrawal_credential(spec, current_fork)
&& has_max_effective_balance
&& has_excess_balance
}
/// Returns `true` if the validator has a 0x01 or 0x02 prefixed withdrawal credential.
pub fn has_execution_withdrawal_credential(&self, spec: &ChainSpec) -> bool {
self.has_compounding_withdrawal_credential(spec)
pub fn has_execution_withdrawal_credential(
&self,
spec: &ChainSpec,
current_fork: ForkName,
) -> bool {
self.has_compounding_withdrawal_credential(spec, current_fork)
|| self.has_eth1_withdrawal_credential(spec)
}
/// Returns the max effective balance for a validator in gwei.
pub fn get_max_effective_balance(&self, spec: &ChainSpec, current_fork: ForkName) -> u64 {
if current_fork >= ForkName::Electra {
if self.has_compounding_withdrawal_credential(spec) {
if self.has_compounding_withdrawal_credential(spec, current_fork) {
spec.max_effective_balance_electra
} else {
spec.min_activation_balance
@@ -319,6 +352,15 @@ pub fn is_compounding_withdrawal_credential(
.unwrap_or(false)
}
/// Check if the withdrawal credential has the builder withdrawal prefix (0x03).
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::*;

View File

@@ -55,6 +55,15 @@ async fn build_state<E: EthSpec>(validator_count: usize) -> BeaconState<E> {
.head_beacon_state_cloned()
}
// TODO(EIP-7732): Add tests for PTC (Payload Timeliness Committee) functions:
// - get_ptc: Test committee selection, size, balance-weighted selection
// - get_ptc_attester_seed: Test seed generation and determinism
// - compute_balance_weighted_selection: Test selection algorithm with various balances
// - compute_balance_weighted_acceptance: Test acceptance probability
// These tests require being able to build Gloas states with initialized committee caches,
// which currently fails due to incomplete Gloas block structure as mentioned here:
// https://github.com/sigp/lighthouse/pull/8273
// Similar to existing committee_consistency_test suite for get_beacon_committee.
async fn test_beacon_proposer_index<E: EthSpec>() {
let spec = E::default_spec();

View File

@@ -1,6 +1,6 @@
# To download/extract nightly tests, run:
# CONSENSUS_SPECS_TEST_VERSION=nightly make
CONSENSUS_SPECS_TEST_VERSION ?= v1.6.0-beta.1
CONSENSUS_SPECS_TEST_VERSION ?= v1.7.0-alpha.1
REPO_NAME := consensus-spec-tests
OUTPUT_DIR := ./$(REPO_NAME)

View File

@@ -47,6 +47,19 @@ excluded_paths = [
"bls12-381-tests/hash_to_G2",
"tests/.*/eip7732",
"tests/.*/eip7805",
# TODO(EIP-7732): Gloas execution_payload_bid tests require process_execution_payload_bid
# which is not yet implemented.
"tests/.*/gloas/operations/execution_payload_bid/.*",
# TODO(EIP-7732): Gloas deposit_request tests require builder deposit functionality
# (apply_deposit_for_builder, add_builder_to_registry) which is not yet implemented.
"tests/.*/gloas/operations/deposit_request/.*",
# TODO(EIP-7732): Gloas sanity, transition, random, finality, and fork_choice tests require
# full block processing which is not yet complete.
"tests/.*/gloas/sanity/.*",
"tests/.*/gloas/transition/.*",
"tests/.*/gloas/random/.*",
"tests/.*/gloas/finality/.*",
"tests/.*/gloas/fork_choice/.*",
# Ignore MatrixEntry SSZ tests for now.
"tests/.*/fulu/ssz_static/MatrixEntry/.*",
# EIP-7916 is still in draft and hasn't been implemented yet https://eips.ethereum.org/EIPS/eip-7916
@@ -59,8 +72,6 @@ excluded_paths = [
# Ignore full epoch tests for now (just test the sub-transitions).
"tests/.*/.*/epoch_processing/.*/pre_epoch.ssz_snappy",
"tests/.*/.*/epoch_processing/.*/post_epoch.ssz_snappy",
# Ignore gloas tests for now
"tests/.*/gloas/.*",
# Ignore KZG tests that target internal kzg library functions
"tests/.*/compute_verify_cell_kzg_proof_batch_challenge/.*",
"tests/.*/compute_challenge/.*",

View File

@@ -296,6 +296,11 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
self.description.clone()
}
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
// Gloas fork choice not yet implemented
fork_name != ForkName::Gloas
}
fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> {
let tester = Tester::new(self, testing_spec::<E>(fork_name))?;

View File

@@ -7,7 +7,8 @@ use ssz::Decode;
use state_processing::common::update_progressive_balances_cache::initialize_progressive_balances_cache;
use state_processing::epoch_cache::initialize_epoch_cache;
use state_processing::per_block_processing::process_operations::{
process_consolidation_requests, process_deposit_requests, process_withdrawal_requests,
altair_deneb, base, gloas, process_consolidation_requests, process_deposit_requests,
process_payload_attestation, process_withdrawal_requests,
};
use state_processing::{
ConsensusContext,
@@ -16,8 +17,8 @@ use state_processing::{
errors::BlockProcessingError,
process_block_header, process_execution_payload,
process_operations::{
altair_deneb, base, process_attester_slashings, process_bls_to_execution_changes,
process_deposits, process_exits, process_proposer_slashings,
process_attester_slashings, process_bls_to_execution_changes, process_deposits,
process_exits, process_proposer_slashings,
},
process_sync_aggregate, process_withdrawals,
},
@@ -27,8 +28,8 @@ use types::{
Attestation, AttesterSlashing, BeaconBlock, BeaconBlockBody, BeaconBlockBodyBellatrix,
BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconBlockBodyFulu,
BeaconState, BlindedPayload, ConsolidationRequest, Deposit, DepositRequest, ExecutionPayload,
ForkVersionDecode, FullPayload, ProposerSlashing, SignedBlsToExecutionChange,
SignedVoluntaryExit, SyncAggregate, WithdrawalRequest,
ForkVersionDecode, FullPayload, PayloadAttestation, ProposerSlashing,
SignedBlsToExecutionChange, SignedVoluntaryExit, SyncAggregate, WithdrawalRequest,
};
#[derive(Debug, Clone, Default, Deserialize)]
@@ -45,7 +46,7 @@ struct ExecutionMetadata {
/// Newtype for testing withdrawals.
#[derive(Debug, Clone, Deserialize)]
pub struct WithdrawalsPayload<E: EthSpec> {
payload: FullPayload<E>,
payload: ExecutionPayload<E>,
}
#[derive(Debug, Clone)]
@@ -99,7 +100,17 @@ impl<E: EthSpec> Operation<E> for Attestation<E> {
) -> Result<(), BlockProcessingError> {
initialize_epoch_cache(state, spec)?;
let mut ctxt = ConsensusContext::new(state.slot());
if state.fork_name_unchecked().altair_enabled() {
if state.fork_name_unchecked().gloas_enabled() {
initialize_progressive_balances_cache(state, spec)?;
gloas::process_attestation(
state,
self.to_ref(),
0,
&mut ctxt,
VerifySignatures::True,
spec,
)
} else if state.fork_name_unchecked().altair_enabled() {
initialize_progressive_balances_cache(state, spec)?;
altair_deneb::process_attestation(
state,
@@ -408,9 +419,7 @@ impl<E: EthSpec> Operation<E> for WithdrawalsPayload<E> {
ssz_decode_file_with(path, |bytes| {
ExecutionPayload::from_ssz_bytes_by_fork(bytes, fork_name)
})
.map(|payload| WithdrawalsPayload {
payload: payload.into(),
})
.map(|payload| WithdrawalsPayload { payload })
}
fn apply_to(
@@ -419,8 +428,16 @@ impl<E: EthSpec> Operation<E> for WithdrawalsPayload<E> {
spec: &ChainSpec,
_: &Operations<E, Self>,
) -> Result<(), BlockProcessingError> {
// TODO(EIP-7732): implement separate gloas and non-gloas variants of process_withdrawals
process_withdrawals::<_, FullPayload<_>>(state, self.payload.to_ref(), spec)
if state.fork_name_unchecked().gloas_enabled() {
process_withdrawals::gloas::process_withdrawals(state, spec)
} else {
let full_payload = FullPayload::from(self.payload.clone());
process_withdrawals::capella::process_withdrawals::<_, FullPayload<_>>(
state,
full_payload.to_ref(),
spec,
)
}
}
}
@@ -486,7 +503,10 @@ impl<E: EthSpec> Operation<E> for DepositRequest {
}
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
fork_name.electra_enabled()
// TODO(EIP-7732): Gloas deposit_request tests require builder deposit functionality
// (apply_deposit_for_builder, add_builder_to_registry) which is not yet implemented.
// https://github.com/sigp/lighthouse/issues/XXXX
fork_name.electra_enabled() && fork_name != ForkName::Gloas
}
fn decode(path: &Path, _fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
@@ -527,6 +547,32 @@ impl<E: EthSpec> Operation<E> for ConsolidationRequest {
}
}
impl<E: EthSpec> Operation<E> for PayloadAttestation<E> {
fn handler_name() -> String {
"payload_attestation".into()
}
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
fork_name.gloas_enabled()
}
fn decode(path: &Path, _fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path)
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
spec: &ChainSpec,
_extra: &Operations<E, Self>,
) -> Result<(), BlockProcessingError> {
initialize_epoch_cache(state, spec)?;
initialize_progressive_balances_cache(state, spec)?;
let mut ctxt = ConsensusContext::new(state.slot());
process_payload_attestation(state, self, 0, VerifySignatures::True, &mut ctxt, spec)
}
}
impl<E: EthSpec, O: Operation<E>> LoadCase for Operations<E, O> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
let spec = &testing_spec::<E>(fork_name);

View File

@@ -22,7 +22,7 @@ pub trait Handler {
// Add forks here to exclude them from EF spec testing. Helpful for adding future or
// unspecified forks.
fn disabled_forks(&self) -> Vec<ForkName> {
vec![ForkName::Gloas]
vec![]
}
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
@@ -305,6 +305,10 @@ impl<T, E> SszStaticHandler<T, E> {
Self::for_forks(vec![ForkName::Fulu])
}
pub fn gloas_only() -> Self {
Self::for_forks(vec![ForkName::Gloas])
}
pub fn altair_and_later() -> Self {
Self::for_forks(ForkName::list_all()[1..].to_vec())
}
@@ -329,6 +333,10 @@ impl<T, E> SszStaticHandler<T, E> {
Self::for_forks(ForkName::list_all()[6..].to_vec())
}
pub fn gloas_and_later() -> Self {
Self::for_forks(ForkName::list_all()[7..].to_vec())
}
pub fn pre_electra() -> Self {
Self::for_forks(ForkName::list_all()[0..5].to_vec())
}
@@ -362,6 +370,10 @@ impl<T, E> SszStaticWithSpecHandler<T, E> {
pub fn fulu_and_later() -> Self {
Self::for_forks(ForkName::list_all()[6..].to_vec())
}
pub fn gloas_and_later() -> Self {
Self::for_forks(ForkName::list_all()[7..].to_vec())
}
}
impl<T, E> Handler for SszStaticHandler<T, E>
@@ -479,10 +491,11 @@ impl<E: EthSpec + TypeName> Handler for SanityBlocksHandler<E> {
"blocks".into()
}
fn is_enabled_for_fork(&self, _fork_name: ForkName) -> bool {
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
// NOTE: v1.1.0-beta.4 doesn't mark the historical blocks test as requiring real crypto, so
// only run these tests with real crypto for now.
cfg!(not(feature = "fake_crypto"))
// TODO(EIP-7732): Gloas sanity tests require full block processing which is not yet complete
fork_name != ForkName::Gloas && cfg!(not(feature = "fake_crypto"))
}
}
@@ -507,7 +520,9 @@ impl<E: EthSpec + TypeName> Handler for SanitySlotsHandler<E> {
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
// Some sanity tests compute sync committees, which requires real crypto.
fork_name == ForkName::Base || cfg!(not(feature = "fake_crypto"))
// TODO(EIP-7732): Gloas sanity tests require full block processing which is not yet complete
fork_name != ForkName::Gloas
&& (fork_name == ForkName::Base || cfg!(not(feature = "fake_crypto")))
}
}
@@ -529,6 +544,11 @@ impl<E: EthSpec + TypeName> Handler for RandomHandler<E> {
fn handler_name(&self) -> String {
"random".into()
}
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
// TODO(EIP-7732): Gloas random tests require full block processing which is not yet complete
fork_name != ForkName::Gloas
}
}
#[derive(Educe)]
@@ -619,6 +639,11 @@ impl<E: EthSpec + TypeName> Handler for TransitionHandler<E> {
fn handler_name(&self) -> String {
"core".into()
}
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
// TODO(EIP-7732): Gloas transition tests require full block processing which is not yet complete
fork_name != ForkName::Gloas
}
}
#[derive(Educe)]
@@ -640,6 +665,11 @@ impl<E: EthSpec + TypeName> Handler for FinalityHandler<E> {
fn handler_name(&self) -> String {
"finality".into()
}
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
// TODO(EIP-7732): Gloas finality tests require full block processing which is not yet complete
fork_name != ForkName::Gloas
}
}
pub struct ForkChoiceHandler<E> {
@@ -687,6 +717,12 @@ impl<E: EthSpec + TypeName> Handler for ForkChoiceHandler<E> {
return false;
}
// TODO(EIP-7732): Gloas fork choice not yet implemented
// https://github.com/sigp/lighthouse/issues/XXXX
if fork_name == ForkName::Gloas {
return false;
}
// No FCU override tests prior to bellatrix.
if self.handler_name == "should_override_forkchoice_update"
&& !fork_name.bellatrix_enabled()

View File

@@ -55,6 +55,7 @@ type_name_generic!(BeaconBlockBodyCapella, "BeaconBlockBody");
type_name_generic!(BeaconBlockBodyDeneb, "BeaconBlockBody");
type_name_generic!(BeaconBlockBodyElectra, "BeaconBlockBody");
type_name_generic!(BeaconBlockBodyFulu, "BeaconBlockBody");
type_name_generic!(BeaconBlockBodyGloas, "BeaconBlockBody");
type_name!(BeaconBlockHeader);
type_name_generic!(BeaconState);
type_name!(BlobIdentifier);
@@ -69,6 +70,9 @@ type_name!(DepositData);
type_name!(DepositMessage);
type_name!(DepositRequest);
type_name!(Eth1Data);
type_name!(Builder);
type_name!(BuilderPendingPayment);
type_name!(BuilderPendingWithdrawal);
type_name!(WithdrawalRequest);
type_name_generic!(ExecutionPayload);
type_name_generic!(ExecutionPayloadBellatrix, "ExecutionPayload");
@@ -84,6 +88,10 @@ type_name_generic!(ExecutionPayloadHeaderDeneb, "ExecutionPayloadHeader");
type_name_generic!(ExecutionPayloadHeaderElectra, "ExecutionPayloadHeader");
type_name_generic!(ExecutionPayloadHeaderFulu, "ExecutionPayloadHeader");
type_name_generic!(ExecutionRequests);
type_name!(ExecutionPayloadBid);
type_name!(SignedExecutionPayloadBid);
type_name_generic!(ExecutionPayloadEnvelope);
type_name_generic!(SignedExecutionPayloadEnvelope);
type_name_generic!(BlindedPayload, "ExecutionPayloadHeader");
type_name!(Fork);
type_name!(ForkData);
@@ -91,6 +99,7 @@ type_name_generic!(HistoricalBatch);
type_name_generic!(IndexedAttestation);
type_name_generic!(IndexedAttestationBase, "IndexedAttestation");
type_name_generic!(IndexedAttestationElectra, "IndexedAttestation");
type_name_generic!(IndexedPayloadAttestation);
type_name_generic!(LightClientBootstrap);
type_name_generic!(LightClientBootstrapAltair, "LightClientBootstrap");
type_name_generic!(LightClientBootstrapCapella, "LightClientBootstrap");
@@ -143,6 +152,9 @@ type_name_generic!(LightClientUpdateDeneb, "LightClientUpdate");
type_name_generic!(LightClientUpdateElectra, "LightClientUpdate");
type_name_generic!(LightClientUpdateFulu, "LightClientUpdate");
type_name_generic!(PendingAttestation);
type_name_generic!(PayloadAttestation);
type_name!(PayloadAttestationData);
type_name!(PayloadAttestationMessage);
type_name!(PendingConsolidation);
type_name!(PendingPartialWithdrawal);
type_name!(PendingDeposit);

View File

@@ -118,6 +118,12 @@ fn operations_bls_to_execution_change() {
OperationsHandler::<MainnetEthSpec, SignedBlsToExecutionChange>::default().run();
}
#[test]
fn operations_payload_attestation() {
OperationsHandler::<MinimalEthSpec, PayloadAttestation<_>>::default().run();
OperationsHandler::<MainnetEthSpec, PayloadAttestation<_>>::default().run();
}
#[test]
fn sanity_blocks() {
SanityBlocksHandler::<MinimalEthSpec>::default().run();
@@ -241,8 +247,12 @@ mod ssz_static {
use ef_tests::{Handler, SszStaticHandler, SszStaticTHCHandler, SszStaticWithSpecHandler};
use types::state::HistoricalSummary;
use types::{
AttesterSlashingBase, AttesterSlashingElectra, ConsolidationRequest, DepositRequest,
LightClientBootstrapAltair, PendingDeposit, PendingPartialWithdrawal, WithdrawalRequest, *,
AttesterSlashingBase, AttesterSlashingElectra, Builder, BuilderPendingPayment,
BuilderPendingWithdrawal, ConsolidationRequest, DepositRequest, ExecutionPayloadBid,
ExecutionPayloadEnvelope, IndexedPayloadAttestation, LightClientBootstrapAltair,
PayloadAttestation, PayloadAttestationData, PayloadAttestationMessage, PendingDeposit,
PendingPartialWithdrawal, SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope,
WithdrawalRequest, *,
};
ssz_static_test!(attestation_data, AttestationData);
@@ -368,6 +378,10 @@ mod ssz_static {
.run();
SszStaticHandler::<BeaconBlockBodyFulu<MinimalEthSpec>, MinimalEthSpec>::fulu_only().run();
SszStaticHandler::<BeaconBlockBodyFulu<MainnetEthSpec>, MainnetEthSpec>::fulu_only().run();
SszStaticHandler::<BeaconBlockBodyGloas<MinimalEthSpec>, MinimalEthSpec>::gloas_only()
.run();
SszStaticHandler::<BeaconBlockBodyGloas<MainnetEthSpec>, MainnetEthSpec>::gloas_only()
.run();
}
// Altair and later
@@ -722,6 +736,81 @@ mod ssz_static {
SszStaticHandler::<ExecutionRequests<MinimalEthSpec>, MinimalEthSpec>::electra_and_later()
.run();
}
// Gloas and later
#[test]
fn builder() {
SszStaticHandler::<Builder, MinimalEthSpec>::gloas_and_later().run();
SszStaticHandler::<Builder, MainnetEthSpec>::gloas_and_later().run();
}
#[test]
fn builder_pending_payment() {
SszStaticHandler::<BuilderPendingPayment, MinimalEthSpec>::gloas_and_later().run();
SszStaticHandler::<BuilderPendingPayment, MainnetEthSpec>::gloas_and_later().run();
}
#[test]
fn builder_pending_withdrawal() {
SszStaticHandler::<BuilderPendingWithdrawal, MinimalEthSpec>::gloas_and_later().run();
SszStaticHandler::<BuilderPendingWithdrawal, MainnetEthSpec>::gloas_and_later().run();
}
#[test]
fn payload_attestation_data() {
SszStaticHandler::<PayloadAttestationData, MinimalEthSpec>::gloas_and_later().run();
SszStaticHandler::<PayloadAttestationData, MainnetEthSpec>::gloas_and_later().run();
}
#[test]
fn payload_attestation() {
SszStaticHandler::<PayloadAttestation<MinimalEthSpec>, MinimalEthSpec>::gloas_and_later()
.run();
SszStaticHandler::<PayloadAttestation<MainnetEthSpec>, MainnetEthSpec>::gloas_and_later()
.run();
}
#[test]
fn payload_attestation_message() {
SszStaticHandler::<PayloadAttestationMessage, MinimalEthSpec>::gloas_and_later().run();
SszStaticHandler::<PayloadAttestationMessage, MainnetEthSpec>::gloas_and_later().run();
}
#[test]
fn indexed_payload_attestation() {
SszStaticHandler::<IndexedPayloadAttestation<MinimalEthSpec>, MinimalEthSpec>::gloas_and_later()
.run();
SszStaticHandler::<IndexedPayloadAttestation<MainnetEthSpec>, MainnetEthSpec>::gloas_and_later()
.run();
}
#[test]
fn execution_payload_bid() {
SszStaticHandler::<ExecutionPayloadBid, MinimalEthSpec>::gloas_and_later().run();
SszStaticHandler::<ExecutionPayloadBid, MainnetEthSpec>::gloas_and_later().run();
}
#[test]
fn signed_execution_payload_bid() {
SszStaticHandler::<SignedExecutionPayloadBid, MinimalEthSpec>::gloas_and_later().run();
SszStaticHandler::<SignedExecutionPayloadBid, MainnetEthSpec>::gloas_and_later().run();
}
#[test]
fn execution_payload_envelope() {
SszStaticHandler::<ExecutionPayloadEnvelope<MinimalEthSpec>, MinimalEthSpec>::gloas_and_later()
.run();
SszStaticHandler::<ExecutionPayloadEnvelope<MainnetEthSpec>, MainnetEthSpec>::gloas_and_later()
.run();
}
#[test]
fn signed_execution_payload_envelope() {
SszStaticHandler::<SignedExecutionPayloadEnvelope<MinimalEthSpec>, MinimalEthSpec>::gloas_and_later()
.run();
SszStaticHandler::<SignedExecutionPayloadEnvelope<MainnetEthSpec>, MainnetEthSpec>::gloas_and_later()
.run();
}
}
#[test]