diff --git a/Cargo.lock b/Cargo.lock index c0f337a6a8e..27d50b9f839 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1981,7 +1981,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -3127,7 +3127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3295,8 +3295,7 @@ dependencies = [ [[package]] name = "ethereum_ssz" version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e462875ad8693755ea8913d6e905715c76ea4836e2254e18c9cf0f7a8f8c2a13" +source = "git+https://github.com/sigp/ethereum_ssz?branch=progressive#7dc07527875f4bbb6ecf5f2ec8c52733360fe220" dependencies = [ "alloy-primitives", "arbitrary", @@ -3311,9 +3310,8 @@ dependencies = [ [[package]] name = "ethereum_ssz_derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cd82c68120c89361e1a457245cf212f7d9f541bffaffed530c8f2d54a160b2" +version = "0.10.4" +source = "git+https://github.com/sigp/ethereum_ssz?branch=progressive#7dc07527875f4bbb6ecf5f2ec8c52733360fe220" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -5905,8 +5903,7 @@ dependencies = [ [[package]] name = "milhouse" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "259dd9da2ae5e0278b95da0b7ecef9c18c309d0a2d9e6db57ed33b9e8910c5e7" +source = "git+https://github.com/sigp/milhouse?branch=progressive-list#883e8183a5e6e1cb1024942d9b32b51d06c2d8dd" dependencies = [ "alloy-primitives", "arbitrary", @@ -6330,7 +6327,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -7181,7 +7178,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.117", @@ -7291,7 +7288,7 @@ dependencies = [ "once_cell", "socket2 0.5.10", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -7836,7 +7833,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -8593,8 +8590,7 @@ dependencies = [ [[package]] name = "ssz_types" version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d625e4de8e0057eefe7e0b1510ba1dd7adf10cd375fad6cc7fcceac7c39623c9" +source = "git+https://github.com/sigp/ssz_types?branch=progressive#d7e02dbb4e2829cc094a1365f4d8f118d7f470c0" dependencies = [ "arbitrary", "context_deserialize", @@ -8904,7 +8900,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -9465,8 +9461,7 @@ dependencies = [ [[package]] name = "tree_hash" version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fd51aa83d2eb83b04570808430808b5d24fdbf479a4d5ac5dee4a2e2dd2be4" +source = "git+https://github.com/sigp/tree_hash?branch=progressive#ca07459738c9b1584eec99e37f3a52774fa97dd0" dependencies = [ "alloy-primitives", "ethereum_hashing", @@ -9478,8 +9473,7 @@ dependencies = [ [[package]] name = "tree_hash_derive" version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8840ad4d852e325d3afa7fde8a50b2412f89dce47d7eb291c0cc7f87cd040f38" +source = "git+https://github.com/sigp/tree_hash?branch=progressive#ca07459738c9b1584eec99e37f3a52774fa97dd0" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -10268,7 +10262,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 50b17332321..a09a9e33961 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -273,3 +273,11 @@ incremental = false inherits = "release" debug = true +[patch.crates-io] +# FIXME(mac): EIP-7688 development patches +ssz_types = { git = "https://github.com/sigp/ssz_types", branch = "progressive" } +milhouse = { git = "https://github.com/sigp/milhouse", branch = "progressive-list" } +ethereum_ssz = { git = "https://github.com/sigp/ethereum_ssz", branch = "progressive" } +ethereum_ssz_derive = { git = "https://github.com/sigp/ethereum_ssz", branch = "progressive" } +tree_hash = { git = "https://github.com/sigp/tree_hash", branch = "progressive" } +tree_hash_derive = { git = "https://github.com/sigp/tree_hash", branch = "progressive" } diff --git a/beacon_node/beacon_chain/src/attestation_verification.rs b/beacon_node/beacon_chain/src/attestation_verification.rs index 635ca3a2ae2..b905c673e34 100644 --- a/beacon_node/beacon_chain/src/attestation_verification.rs +++ b/beacon_node/beacon_chain/src/attestation_verification.rs @@ -48,6 +48,7 @@ use state_processing::{ common::{ attesting_indices_base, attesting_indices_electra::{self, get_committee_indices}, + attesting_indices_gloas, }, per_block_processing::errors::{AttestationValidationError, BlockOperationError}, signature_sets::{ @@ -668,6 +669,16 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> { signed_aggregate.message.selection_proof.clone(), signed_aggregate.message.aggregate.data.clone(), ), + SignedAggregateAndProof::Gloas(signed_aggregate) => ( + signed_aggregate + .message + .aggregate + .committee_index() + .ok_or(Error::NotExactlyOneCommitteeBitSet(0))?, + signed_aggregate.message.aggregator_index, + signed_aggregate.message.selection_proof.clone(), + signed_aggregate.message.aggregate.data.clone(), + ), }; let slot = data.slot; @@ -704,6 +715,13 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> { ) .map_err(|e| BeaconChainError::from(e).into()) } + SignedAggregateAndProof::Gloas(signed_aggregate) => { + attesting_indices_gloas::get_indexed_attestation( + &committees, + &signed_aggregate.message.aggregate, + ) + .map_err(|e| BeaconChainError::from(e).into()) + } } }; @@ -1532,6 +1550,20 @@ pub fn obtain_indexed_attestation_and_committees_per_slot( } }) } + AttestationRef::Gloas(att) => { + attesting_indices_gloas::get_indexed_attestation(&committees, att) + .map(|attestation| (attestation, committees_per_slot)) + .map_err(|e| { + if let BlockOperationError::BeaconStateError(NoCommitteeFound(index)) = e { + Error::NoCommitteeForSlotAndIndex { + slot: att.data.slot, + index, + } + } else { + Error::Invalid(e) + } + }) + } } }) } diff --git a/beacon_node/beacon_chain/src/beacon_block_reward.rs b/beacon_node/beacon_chain/src/beacon_block_reward.rs index 88686696ed2..d345e6f5599 100644 --- a/beacon_node/beacon_chain/src/beacon_block_reward.rs +++ b/beacon_node/beacon_chain/src/beacon_block_reward.rs @@ -263,8 +263,9 @@ impl BeaconChain { .safe_mul(WEIGHT_DENOMINATOR)? .safe_div(PROPOSER_WEIGHT)?; - let mut current_epoch_participation = state.current_epoch_participation()?.clone(); - let mut previous_epoch_participation = state.previous_epoch_participation()?.clone(); + let mut current_epoch_participation = state.current_epoch_participation()?.to_owned_list(); + let mut previous_epoch_participation = + state.previous_epoch_participation()?.to_owned_list(); for attestation in block.body().attestations() { let data = attestation.data(); @@ -289,7 +290,8 @@ impl BeaconChain { }; let validator_participation = epoch_participation - .get_mut(index) + .as_mut() + .into_get_mut(index) .ok_or(BeaconStateError::ParticipationOutOfBounds(index))?; if participation_flag_indices.contains(&flag_index) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 569f0eea502..e047c8230a7 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1263,7 +1263,7 @@ impl BeaconChain { if header_from_payload != execution_payload_header { for txn in execution_payload.transactions() { debug!( - bytes = format!("0x{}", hex::encode(&**txn)), + bytes = format!("0x{}", hex::encode(txn)), "Reconstructed txn" ); } @@ -1760,6 +1760,12 @@ impl BeaconChain { att.committee_index() .ok_or(Error::AttestationCommitteeIndexNotSet)?, ), + AttestationRef::Gloas(att) => self.get_aggregated_attestation_electra( + att.data.slot, + &att.data.tree_hash_root(), + att.committee_index() + .ok_or(Error::AttestationCommitteeIndexNotSet)?, + ), } } @@ -5781,6 +5787,9 @@ impl BeaconChain { match slashing { AttesterSlashing::Base(slashing) => base.push(slashing), AttesterSlashing::Electra(slashing) => electra.push(slashing), + // Gloas-typed slashings cannot be included in pre-Gloas blocks, and + // Gloas blocks are produced via `complete_partial_beacon_block_gloas`. + AttesterSlashing::Gloas(_) => (), } (base, electra) }, @@ -5791,6 +5800,9 @@ impl BeaconChain { match attestation { Attestation::Base(attestation) => base.push(attestation), Attestation::Electra(attestation) => electra.push(attestation), + // Gloas-typed attestations cannot be included in pre-Gloas blocks, and + // Gloas blocks are produced via `complete_partial_beacon_block_gloas`. + Attestation::Gloas(_) => (), } (base, electra) }, diff --git a/beacon_node/beacon_chain/src/block_production/gloas.rs b/beacon_node/beacon_chain/src/block_production/gloas.rs index 82dad6f6ad1..99dbc4cc408 100644 --- a/beacon_node/beacon_chain/src/block_production/gloas.rs +++ b/beacon_node/beacon_chain/src/block_production/gloas.rs @@ -11,6 +11,7 @@ use execution_layer::{ }; use operation_pool::CompactAttestationRef; use ssz::Encode; +use ssz::ProgressiveBitList; use state_processing::common::{get_attesting_indices_from_state, get_indexed_payload_attestation}; use state_processing::envelope_processing::verify_execution_payload_envelope; use state_processing::epoch_cache::initialize_epoch_cache; @@ -28,15 +29,17 @@ use tracing::{Instrument, debug, debug_span, error, instrument, trace, warn}; use tree_hash::TreeHash; use types::consts::gloas::BUILDER_INDEX_SELF_BUILD; use types::{ - Address, Attestation, AttestationElectra, AttesterSlashing, AttesterSlashingElectra, - BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, BeaconState, BeaconStateError, - BuilderIndex, ChainSpec, Deposit, Eth1Data, EthSpec, ExecutionBlockHash, ExecutionPayloadBid, - ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequests, FullPayload, Graffiti, - Hash256, PayloadAttestation, ProposerSlashing, RelativeEpoch, SignedBeaconBlock, + Address, Attestation, AttestationGloas, AttesterSlashing, AttesterSlashingGloas, BeaconBlock, + BeaconBlockBodyGloas, BeaconBlockGloas, BeaconState, BeaconStateError, BuilderIndex, ChainSpec, + Deposit, Eth1Data, EthSpec, ExecutionBlockHash, ExecutionPayloadBid, ExecutionPayloadEnvelope, + ExecutionPayloadGloas, ExecutionRequestsGloas, FullPayload, Graffiti, Hash256, + IndexedAttestation, PayloadAttestation, ProposerSlashing, RelativeEpoch, SignedBeaconBlock, SignedBlsToExecutionChange, SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope, SignedVoluntaryExit, Slot, SyncAggregate, Withdrawal, Withdrawals, }; +use ssz_types::ProgressiveVariableList; + use crate::pending_payload_envelopes::PendingEnvelopeData; use crate::{ BeaconChain, BeaconChainError, BeaconChainTypes, BlockProductionError, @@ -61,8 +64,8 @@ pub struct PartialBeaconBlock { eth1_data: Eth1Data, graffiti: Graffiti, proposer_slashings: Vec, - attester_slashings: Vec>, - attestations: Vec>, + attester_slashings: Vec>, + attestations: Vec>, payload_attestations: Vec>, deposits: Vec, voluntary_exits: Vec, @@ -74,7 +77,7 @@ pub struct PartialBeaconBlock { /// The envelope requires the beacon_block_root which can only be computed after the block exists. pub struct ExecutionPayloadData { pub payload: ExecutionPayloadGloas, - pub execution_requests: ExecutionRequests, + pub execution_requests: ExecutionRequestsGloas, pub builder_index: BuilderIndex, pub slot: Slot, pub blobs_and_proofs: (types::BlobsList, types::KzgProofs), @@ -175,7 +178,7 @@ impl BeaconChain { .map(|env| env.message.execution_requests.clone()) .ok_or(BlockProductionError::MissingParentExecutionPayload)? } else { - ExecutionRequests::default() + ExecutionRequestsGloas::default() }; // Part 1/3 (blocking) @@ -259,7 +262,7 @@ impl BeaconChain { produce_at_slot: Slot, randao_reveal: Signature, graffiti: Graffiti, - parent_execution_requests: &ExecutionRequests, + parent_execution_requests: &ExecutionRequestsGloas, ) -> Result<(PartialBeaconBlock, BeaconState), BlockProductionError> { // It is invalid to try to produce a block using a state from a future slot. @@ -471,7 +474,13 @@ impl BeaconChain { .into_iter() .filter_map(|a| match a { AttesterSlashing::Base(_) => None, - AttesterSlashing::Electra(a) => Some(a), + // Re-type fork-boundary Electra slashings as Gloas: the serialized form is + // identical, only the merkleization differs [Modified in Gloas:EIP7688]. + AttesterSlashing::Electra(a) => Some(AttesterSlashingGloas { + attestation_1: IndexedAttestation::Electra(a.attestation_1).to_gloas(), + attestation_2: IndexedAttestation::Electra(a.attestation_2).to_gloas(), + }), + AttesterSlashing::Gloas(a) => Some(a), }) .collect::>(); @@ -479,7 +488,18 @@ impl BeaconChain { .into_iter() .filter_map(|a| match a { Attestation::Base(_) => None, - Attestation::Electra(a) => Some(a), + // Re-type fork-boundary Electra attestations as Gloas: the serialized form is + // identical, only the merkleization differs [Modified in Gloas:EIP7688]. + Attestation::Electra(a) => Some(AttestationGloas { + aggregation_bits: ProgressiveBitList::from_bytes( + a.aggregation_bits.into_bytes(), + ) + .ok()?, + data: a.data, + signature: a.signature, + committee_bits: a.committee_bits, + }), + Attestation::Gloas(a) => Some(a), }) .collect::>(); @@ -530,7 +550,7 @@ impl BeaconChain { &self, partial_beacon_block: PartialBeaconBlock, signed_execution_payload_bid: SignedExecutionPayloadBid, - parent_execution_requests: ExecutionRequests, + parent_execution_requests: ExecutionRequestsGloas, payload_data: Option>, mut state: BeaconState, verification: ProduceBlockVerification, @@ -573,30 +593,20 @@ impl BeaconChain { randao_reveal, eth1_data, graffiti, - proposer_slashings: proposer_slashings - .try_into() - .map_err(BlockProductionError::SszTypesError)?, - attester_slashings: attester_slashings - .try_into() - .map_err(BlockProductionError::SszTypesError)?, - attestations: attestations - .try_into() - .map_err(BlockProductionError::SszTypesError)?, - deposits: deposits - .try_into() - .map_err(BlockProductionError::SszTypesError)?, - voluntary_exits: voluntary_exits - .try_into() - .map_err(BlockProductionError::SszTypesError)?, + // [Modified in Gloas:EIP7688] the operation lists are progressive. Their + // lengths are bounded by the op pool packing limits above. + proposer_slashings: ProgressiveVariableList::from_iter(proposer_slashings), + attester_slashings: ProgressiveVariableList::from_iter(attester_slashings), + attestations: ProgressiveVariableList::from_iter(attestations), + deposits: ProgressiveVariableList::from_iter(deposits), + voluntary_exits: ProgressiveVariableList::from_iter(voluntary_exits), sync_aggregate, - bls_to_execution_changes: bls_to_execution_changes - .try_into() - .map_err(BlockProductionError::SszTypesError)?, + bls_to_execution_changes: ProgressiveVariableList::from_iter( + bls_to_execution_changes, + ), parent_execution_requests, signed_execution_payload_bid, - payload_attestations: payload_attestations - .try_into() - .map_err(BlockProductionError::SszTypesError)?, + payload_attestations: ProgressiveVariableList::from_iter(payload_attestations), _phantom: PhantomData::>, }, }), @@ -809,6 +819,13 @@ impl BeaconChain { should_override_builder, } = block_proposal_contents; + // [Modified in Gloas:EIP7688] convert the EL-derived execution requests and blob + // commitments to their progressive representations. The bid's requests root commits to + // the progressive merkleization. + let execution_requests = ExecutionRequestsGloas::from(&execution_requests); + let blob_kzg_commitments = + ProgressiveVariableList::from_iter(blob_kzg_commitments.iter().cloned()); + // TODO(gloas) since we are defaulting to local building, execution payment is 0 // execution payment should only be set to > 0 for trusted building. let bid = ExecutionPayloadBid:: { @@ -824,6 +841,7 @@ impl BeaconChain { execution_payment: EXECUTION_PAYMENT_TRUSTLESS_BUILD, blob_kzg_commitments, execution_requests_root: execution_requests.tree_hash_root(), + _phantom: PhantomData, }; // Store payload data for envelope construction after block is created @@ -1109,7 +1127,7 @@ where /// `exit_epoch`. A voluntary exit for the same validator would then fail with `AlreadyExited`. fn filter_voluntary_exits_for_parent_execution_requests( voluntary_exits: &mut Vec, - parent_execution_requests: &ExecutionRequests, + parent_execution_requests: &ExecutionRequestsGloas, pubkey_at_index: impl Fn(u64) -> Option, spec: &ChainSpec, ) { @@ -1139,7 +1157,7 @@ fn filter_voluntary_exits_for_parent_execution_requests( #[cfg(test)] mod tests { use super::*; - use ssz_types::VariableList; + use ssz_types::{ProgressiveVariableList, VariableList}; use types::{ConsolidationRequest, Epoch, MainnetEthSpec, VoluntaryExit, WithdrawalRequest}; type TestSpec = MainnetEthSpec; @@ -1161,17 +1179,18 @@ mod tests { fn requests( withdrawals: Vec, consolidations: Vec, - ) -> ExecutionRequests { - ExecutionRequests { - deposits: VariableList::empty(), - withdrawals: VariableList::new(withdrawals).unwrap(), - consolidations: VariableList::new(consolidations).unwrap(), + ) -> ExecutionRequestsGloas { + ExecutionRequestsGloas { + deposits: ProgressiveVariableList::empty(), + withdrawals: ProgressiveVariableList::new(withdrawals), + consolidations: ProgressiveVariableList::new(consolidations), + _phantom: PhantomData, } } fn run_filter( exits: &mut Vec, - requests: &ExecutionRequests, + requests: &ExecutionRequestsGloas, validator_pubkeys: &[PublicKeyBytes], spec: &ChainSpec, ) { @@ -1307,7 +1326,7 @@ mod tests { LocalBuildResult { payload_data: ExecutionPayloadData { payload: types::ExecutionPayloadGloas::default(), - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsGloas::default(), builder_index: BUILDER_INDEX_SELF_BUILD, slot: Slot::new(0), blobs_and_proofs: (VariableList::empty(), VariableList::empty()), diff --git a/beacon_node/beacon_chain/src/data_availability_checker.rs b/beacon_node/beacon_chain/src/data_availability_checker.rs index a0b117f0722..6c35eaa2f7f 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker.rs @@ -682,13 +682,13 @@ pub fn verify_columns_against_block( .message() .body() .signed_execution_payload_bid() - .map(|bid| bid.message.blob_kzg_commitments.clone()) + .map(|bid| bid.message.blob_kzg_commitments.to_vec()) .map_err(|_| { AvailabilityCheckError::Unexpected( "Gloas block missing signed_execution_payload_bid".to_string(), ) })?; - validate_data_columns_with_commitments(kzg, columns.iter(), commitments.as_ref()) + validate_data_columns_with_commitments(kzg, columns.iter(), &commitments) .map_err(AvailabilityCheckError::InvalidColumn) } else { verify_kzg_for_data_column_list(columns.iter(), kzg) @@ -1289,7 +1289,7 @@ mod test { column: DataColumn::::empty(), index: *d.index(), kzg_commitments: d.kzg_commitments().unwrap().clone(), - kzg_proofs: d.kzg_proofs().clone(), + kzg_proofs: d.as_fulu().expect("fulu sidecar").kzg_proofs.clone(), signed_block_header: d.signed_block_header().unwrap().clone(), kzg_commitments_inclusion_proof: d .kzg_commitments_inclusion_proof() diff --git a/beacon_node/beacon_chain/src/data_column_verification.rs b/beacon_node/beacon_chain/src/data_column_verification.rs index 45cd687b367..0a371d15d06 100644 --- a/beacon_node/beacon_chain/src/data_column_verification.rs +++ b/beacon_node/beacon_chain/src/data_column_verification.rs @@ -1115,7 +1115,7 @@ pub fn validate_data_column_sidecar_for_gossip_gloas< let kzg_verified = verify_kzg_for_data_column_with_commitments( data_column.clone(), cells_to_kzg_verify, - kzg_commitments.as_ref(), + kzg_commitments, kzg, seen_timestamp, ) diff --git a/beacon_node/beacon_chain/src/kzg_utils.rs b/beacon_node/beacon_chain/src/kzg_utils.rs index bc803efe932..fd8d0134b05 100644 --- a/beacon_node/beacon_chain/src/kzg_utils.rs +++ b/beacon_node/beacon_chain/src/kzg_utils.rs @@ -2,7 +2,7 @@ use kzg::{ Cell as KzgCell, CellRef as KzgCellRef, CellsAndKzgProofs, Error as KzgError, Kzg, KzgBlobRef, }; use rayon::prelude::*; -use ssz_types::{FixedVector, VariableList}; +use ssz_types::{FixedVector, ProgressiveVariableList, VariableList}; use std::sync::Arc; use tracing::instrument; use tree_hash::TreeHash; @@ -556,10 +556,9 @@ pub(crate) fn build_data_column_sidecars_gloas( |(index, (col, proofs))| -> Result>, String> { Ok(Arc::new(DataColumnSidecar::Gloas(DataColumnSidecarGloas { index: index as u64, - column: DataColumn::::try_from(col) - .map_err(|e| format!("MaxBlobCommitmentsPerBlock exceeded: {e:?}"))?, - kzg_proofs: VariableList::try_from(proofs) - .map_err(|e| format!("MaxBlobCommitmentsPerBlock exceeded: {e:?}"))?, + // [Modified in Gloas:EIP7688] the column and proofs lists are progressive. + column: ProgressiveVariableList::from_iter(col), + kzg_proofs: ProgressiveVariableList::from_iter(proofs), beacon_block_root, slot, }))) diff --git a/beacon_node/beacon_chain/src/naive_aggregation_pool.rs b/beacon_node/beacon_chain/src/naive_aggregation_pool.rs index 4d192cb5b95..10d9743f476 100644 --- a/beacon_node/beacon_chain/src/naive_aggregation_pool.rs +++ b/beacon_node/beacon_chain/src/naive_aggregation_pool.rs @@ -84,6 +84,22 @@ impl AttestationKey { slot, }) } + AttestationRef::Gloas(att) => { + let committee_index = att + .committee_bits + .iter() + .enumerate() + .filter_map(|(i, bit)| if bit { Some(i) } else { None }) + .at_most_one() + .map_err(|_| Error::MoreThanOneCommitteeBitSet)? + .ok_or(Error::NoCommitteeBitSet)?; + + Ok(Self { + data_root: att.data.tree_hash_root(), + committee_index: Some(committee_index as u64), + slot, + }) + } } } @@ -654,6 +670,10 @@ mod tests { .aggregation_bits .set(i, false) .expect("should unset aggregation bit"), + Attestation::Gloas(att) => att + .aggregation_bits + .set(i, false) + .expect("should unset aggregation bit"), } } diff --git a/beacon_node/beacon_chain/src/observed_aggregates.rs b/beacon_node/beacon_chain/src/observed_aggregates.rs index 8d4be693acd..236219e1c44 100644 --- a/beacon_node/beacon_chain/src/observed_aggregates.rs +++ b/beacon_node/beacon_chain/src/observed_aggregates.rs @@ -2,6 +2,7 @@ //! sync committee contributions if we've already seen them. use crate::sync_committee_verification::SyncCommitteeData; +use ssz::ProgressiveBitList; use ssz_types::{BitList, BitVector}; use std::collections::HashMap; use std::marker::PhantomData; @@ -113,6 +114,14 @@ pub trait SubsetItem { fn root(&self) -> Result; } +/// Convert a progressive aggregation bitfield (Gloas, EIP-7916) to a bounded `BitList` for subset +/// comparison. Valid Gloas attestations have at most `MaxValidatorsPerSlot` aggregation bits. +fn progressive_bits_to_bitlist( + bits: &ProgressiveBitList, +) -> Result, ssz::BitfieldError> { + BitList::from_bytes(bits.clone().into_bytes()) +} + impl SubsetItem for AttestationRef<'_, E> { type Item = BitList; fn is_subset(&self, other: &Self::Item) -> bool { @@ -124,6 +133,14 @@ impl SubsetItem for AttestationRef<'_, E> { false } Self::Electra(att) => att.aggregation_bits.is_subset(other), + Self::Gloas(att) => { + if let Ok(aggregation_bits) = + progressive_bits_to_bitlist::(&att.aggregation_bits) + { + return aggregation_bits.is_subset(other); + } + false + } } } @@ -136,6 +153,14 @@ impl SubsetItem for AttestationRef<'_, E> { false } Self::Electra(att) => other.is_subset(&att.aggregation_bits), + Self::Gloas(att) => { + if let Ok(aggregation_bits) = + progressive_bits_to_bitlist::(&att.aggregation_bits) + { + return other.is_subset(&aggregation_bits); + } + false + } } } @@ -146,6 +171,8 @@ impl SubsetItem for AttestationRef<'_, E> { .extend_aggregation_bits() .map_err(|_| Error::GetItemError), Self::Electra(att) => Ok(att.aggregation_bits.clone()), + Self::Gloas(att) => progressive_bits_to_bitlist::(&att.aggregation_bits) + .map_err(|_| Error::GetItemError), } } diff --git a/beacon_node/beacon_chain/src/payload_bid_verification/gossip_verified_bid.rs b/beacon_node/beacon_chain/src/payload_bid_verification/gossip_verified_bid.rs index 354705b92c5..001a486ab3b 100644 --- a/beacon_node/beacon_chain/src/payload_bid_verification/gossip_verified_bid.rs +++ b/beacon_node/beacon_chain/src/payload_bid_verification/gossip_verified_bid.rs @@ -308,7 +308,7 @@ mod tests { use super::is_gas_limit_target_compatible; use bls::Signature; use kzg::KzgCommitment; - use ssz_types::VariableList; + use ssz_types::ProgressiveVariableList; use types::{ Address, BeaconState, ChainSpec, EthSpec, ExecutionPayloadBid, MinimalEthSpec, ProposerPreferences, SignedProposerPreferences, Slot, @@ -416,7 +416,7 @@ mod tests { let commitments: Vec = (0..=max_blobs) .map(|_| KzgCommitment::empty_for_testing()) .collect(); - bid.blob_kzg_commitments = VariableList::new(commitments).unwrap(); + bid.blob_kzg_commitments = ProgressiveVariableList::new(commitments); let result = verify_bid_consistency::(&bid, current_slot, &prefs, &state, &spec); assert!(matches!( diff --git a/beacon_node/beacon_chain/src/payload_bid_verification/tests.rs b/beacon_node/beacon_chain/src/payload_bid_verification/tests.rs index ccdf64d41d2..73ffe2782da 100644 --- a/beacon_node/beacon_chain/src/payload_bid_verification/tests.rs +++ b/beacon_node/beacon_chain/src/payload_bid_verification/tests.rs @@ -9,7 +9,7 @@ use genesis::{generate_deterministic_keypairs, interop_genesis_state}; use kzg::KzgCommitment; use slot_clock::{SlotClock, TestingSlotClock}; use ssz::Encode; -use ssz_types::VariableList; +use ssz_types::ProgressiveVariableList; use state_processing::genesis::genesis_block; use store::{HotColdDB, StoreConfig}; use types::{ @@ -595,7 +595,7 @@ fn invalid_blob_kzg_commitments() { gas_limit: 30_000_000, value: 0, parent_block_root: ctx.genesis_block_root, - blob_kzg_commitments: VariableList::new(commitments).unwrap(), + blob_kzg_commitments: ProgressiveVariableList::new(commitments), ..ExecutionPayloadBid::default() }, signature: Signature::empty(), diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs index a20963302b0..46b30f5642d 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs @@ -312,12 +312,12 @@ mod tests { use std::marker::PhantomData; use bls::Signature; - use ssz_types::VariableList; + use ssz_types::ProgressiveVariableList; use types::{ BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, Eth1Data, ExecutionBlockHash, - ExecutionPayloadBid, ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequests, - Graffiti, Hash256, MinimalEthSpec, SignedBeaconBlock, SignedExecutionPayloadBid, Slot, - SyncAggregate, + ExecutionPayloadBid, ExecutionPayloadEnvelope, ExecutionPayloadGloas, + ExecutionRequestsGloas, Graffiti, Hash256, MinimalEthSpec, SignedBeaconBlock, + SignedExecutionPayloadBid, Slot, SyncAggregate, }; use super::verify_envelope_consistency; @@ -336,7 +336,7 @@ mod tests { slot_number: slot, ..ExecutionPayloadGloas::default() }, - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsGloas::default(), builder_index, beacon_block_root: Hash256::ZERO, parent_beacon_block_root: Hash256::ZERO, @@ -357,16 +357,16 @@ mod tests { deposit_count: 0, }, graffiti: Graffiti::default(), - proposer_slashings: VariableList::empty(), - attester_slashings: VariableList::empty(), - attestations: VariableList::empty(), - deposits: VariableList::empty(), - voluntary_exits: VariableList::empty(), + proposer_slashings: ProgressiveVariableList::empty(), + attester_slashings: ProgressiveVariableList::empty(), + attestations: ProgressiveVariableList::empty(), + deposits: ProgressiveVariableList::empty(), + voluntary_exits: ProgressiveVariableList::empty(), sync_aggregate: SyncAggregate::empty(), - bls_to_execution_changes: VariableList::empty(), - parent_execution_requests: ExecutionRequests::default(), + bls_to_execution_changes: ProgressiveVariableList::empty(), + parent_execution_requests: ExecutionRequestsGloas::default(), signed_execution_payload_bid: SignedExecutionPayloadBid::empty(), - payload_attestations: VariableList::empty(), + payload_attestations: ProgressiveVariableList::empty(), _phantom: PhantomData, }, }); diff --git a/beacon_node/beacon_chain/src/pending_payload_cache/mod.rs b/beacon_node/beacon_chain/src/pending_payload_cache/mod.rs index 2100a5fe9f7..44e49f9822d 100644 --- a/beacon_node/beacon_chain/src/pending_payload_cache/mod.rs +++ b/beacon_node/beacon_chain/src/pending_payload_cache/mod.rs @@ -222,7 +222,7 @@ impl PendingPayloadCache { .ok_or(AvailabilityCheckError::MissingBid(block_root))?; let kzg_verified_columns = KzgVerifiedDataColumn::from_batch_with_scoring_and_commitments( custody_columns, - bid.message.blob_kzg_commitments.as_ref(), + &bid.message.blob_kzg_commitments, &self.kzg, ) .map_err(AvailabilityCheckError::InvalidColumn)?; @@ -323,7 +323,7 @@ impl PendingPayloadCache { let all_data_columns = KzgVerifiedCustodyDataColumn::reconstruct_columns( &self.kzg, verified_data_columns, - bid.message.blob_kzg_commitments.as_ref(), + &bid.message.blob_kzg_commitments, &self.spec, ) .map_err(|e| { @@ -517,7 +517,7 @@ mod data_availability_checker_tests { use logging::create_test_tracing_subscriber; use types::test_utils::test_unstructured; use types::{ - ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequests, ForkName, + ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequestsGloas, ForkName, MinimalEthSpec, SignedExecutionPayloadEnvelope, }; @@ -618,7 +618,7 @@ mod data_availability_checker_tests { envelope: Arc::new(SignedExecutionPayloadEnvelope { message: ExecutionPayloadEnvelope { payload: ExecutionPayloadGloas::default(), - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsGloas::default(), builder_index: 0, beacon_block_root: block_root, parent_beacon_block_root: Hash256::random(), diff --git a/beacon_node/beacon_chain/src/pending_payload_cache/pending_column.rs b/beacon_node/beacon_chain/src/pending_payload_cache/pending_column.rs index 890c17ba67e..fce6a4bff4d 100644 --- a/beacon_node/beacon_chain/src/pending_payload_cache/pending_column.rs +++ b/beacon_node/beacon_chain/src/pending_payload_cache/pending_column.rs @@ -1,5 +1,5 @@ use kzg::KzgProof; -use ssz_types::VariableList; +use ssz_types::ProgressiveVariableList; use std::sync::Arc; use types::{Cell, ColumnIndex, DataColumnSidecar, DataColumnSidecarGloas, EthSpec, Hash256, Slot}; @@ -54,8 +54,9 @@ impl PendingColumn { // post-Gloas variants are introduced (or move construction to a fork-aware helper). Some(Arc::new(DataColumnSidecar::Gloas(DataColumnSidecarGloas { index, - column: VariableList::try_from(column).ok()?, - kzg_proofs: VariableList::try_from(kzg_proofs).ok()?, + // [Modified in Gloas:EIP7688] the column and proofs lists are progressive. + column: ProgressiveVariableList::from_iter(column), + kzg_proofs: ProgressiveVariableList::from_iter(kzg_proofs), slot, beacon_block_root, }))) diff --git a/beacon_node/beacon_chain/src/pending_payload_envelopes.rs b/beacon_node/beacon_chain/src/pending_payload_envelopes.rs index 8f7568d0175..5d4e461f265 100644 --- a/beacon_node/beacon_chain/src/pending_payload_envelopes.rs +++ b/beacon_node/beacon_chain/src/pending_payload_envelopes.rs @@ -91,7 +91,7 @@ impl PendingPayloadEnvelopes { #[cfg(test)] mod tests { use super::*; - use types::{ExecutionPayloadGloas, ExecutionRequests, Hash256, MainnetEthSpec}; + use types::{ExecutionPayloadGloas, ExecutionRequestsGloas, Hash256, MainnetEthSpec}; type E = MainnetEthSpec; @@ -102,7 +102,7 @@ mod tests { slot_number: slot, ..ExecutionPayloadGloas::default() }, - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsGloas::default(), builder_index: 0, beacon_block_root: Hash256::ZERO, parent_beacon_block_root: Hash256::ZERO, diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 62c7fb3a454..c955a80eefd 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -50,6 +50,8 @@ use rand::seq::SliceRandom; use rayon::prelude::*; use sensitive_url::SensitiveUrl; use slot_clock::{SlotClock, TestingSlotClock}; +#[cfg(feature = "arbitrary")] +use ssz_types::ProgressiveVariableList; use ssz_types::{RuntimeVariableList, VariableList}; use state_processing::ConsensusContext; use state_processing::per_block_processing::compute_timestamp_at_slot; @@ -1505,6 +1507,12 @@ where .unwrap(); Attestation::Electra(attn) } + Attestation::Gloas(mut attn) => { + attn.aggregation_bits + .set(aggregation_bit_index, true) + .unwrap(); + Attestation::Gloas(attn) + } Attestation::Base(mut attn) => { attn.aggregation_bits .set(aggregation_bit_index, true) @@ -1843,6 +1851,9 @@ where Attestation::Electra(ref mut att) => { att.aggregation_bits.set(i, true).unwrap() } + Attestation::Gloas(ref mut att) => { + att.aggregation_bits.set(i, true).unwrap() + } } *attestation.signature_mut() = { @@ -2323,6 +2334,23 @@ where attestation.signature.add_assign(&sk.sign(message)); } } + IndexedAttestation::Gloas(attestation) => { + for i in attestation.attesting_indices.iter() { + let sk = &self.validator_keypairs[*i as usize].sk; + + let genesis_validators_root = self.chain.genesis_validators_root; + + let domain = self.chain.spec.get_domain( + attestation.data.target.epoch, + Domain::BeaconAttester, + &fork, + genesis_validators_root, + ); + let message = attestation.data.signing_root(domain); + + attestation.signature.add_assign(&sk.sign(message)); + } + } } } @@ -2435,6 +2463,23 @@ where attestation.signature.add_assign(&sk.sign(message)); } } + IndexedAttestation::Gloas(attestation) => { + for i in attestation.attesting_indices.iter() { + let sk = &self.validator_keypairs[*i as usize].sk; + + let genesis_validators_root = self.chain.genesis_validators_root; + + let domain = self.chain.spec.get_domain( + attestation.data.target.epoch, + Domain::BeaconAttester, + &fork, + genesis_validators_root, + ); + let message = attestation.data.signing_root(domain); + + attestation.signature.add_assign(&sk.sign(message)); + } + } } } @@ -2592,7 +2637,7 @@ where epoch: Epoch, ) { let exit = self.make_voluntary_exit(validator_index, epoch); - block.body_mut().voluntary_exits_mut().push(exit).unwrap(); + block.body_mut().voluntary_exits_push(exit).unwrap(); } /// Create a new block, apply `block_modifier` to it, sign it and return it. @@ -3872,7 +3917,8 @@ pub fn generate_rand_block_and_blobs( .body .signed_execution_payload_bid .message - .blob_kzg_commitments = bundle.commitments.clone(); + .blob_kzg_commitments = + ProgressiveVariableList::from_iter(bundle.commitments.iter().cloned()); return Ok((block, blob_sidecars)); } _ => return Ok((block, blob_sidecars)), @@ -3953,9 +3999,9 @@ pub fn generate_data_column_sidecars_from_block( column, kzg_proofs, .. } = sidecar; // There's only one cell per column for a single blob - let cell_bytes: Vec = column.into_iter().next().unwrap().into(); + let cell_bytes: Vec = column.iter().next().unwrap().clone().into(); let kzg_cell = cell_bytes.try_into().unwrap(); - let kzg_proof = kzg_proofs.into_iter().next().unwrap(); + let kzg_proof = *kzg_proofs.iter().next().unwrap(); (kzg_cell, kzg_proof) }) .collect::<(Vec<_>, Vec<_>)>(); diff --git a/beacon_node/beacon_chain/tests/attestation_production.rs b/beacon_node/beacon_chain/tests/attestation_production.rs index 9d32b37134a..5a85708dc98 100644 --- a/beacon_node/beacon_chain/tests/attestation_production.rs +++ b/beacon_node/beacon_chain/tests/attestation_production.rs @@ -198,6 +198,9 @@ async fn produces_attestations() { Attestation::Electra(att) => { (att.aggregation_bits.len(), att.aggregation_bits.is_zero()) } + Attestation::Gloas(att) => { + (att.aggregation_bits.len(), att.aggregation_bits.is_zero()) + } }; assert_eq!(aggregation_bits_len, committee_len, "bad committee len"); assert!(aggregation_bits_zero, "some committee bits are set"); diff --git a/beacon_node/beacon_chain/tests/attestation_verification.rs b/beacon_node/beacon_chain/tests/attestation_verification.rs index 03b8ae58ac0..ed0645d23d6 100644 --- a/beacon_node/beacon_chain/tests/attestation_verification.rs +++ b/beacon_node/beacon_chain/tests/attestation_verification.rs @@ -346,6 +346,9 @@ impl GossipTester { SignedAggregateAndProofRefMut::Electra(att) => { att.message.aggregator_index = att.message.aggregator_index.checked_sub(1).unwrap(); } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.message.aggregator_index = att.message.aggregator_index.checked_sub(1).unwrap(); + } } Self { @@ -591,6 +594,9 @@ async fn aggregated_gossip_verification() { SignedAggregateAndProofRefMut::Electra(att) => { att.message.aggregate.data.slot = tester.slot() + 1 } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.message.aggregate.data.slot = tester.slot() + 1 + } }, |tester, err| { assert!(matches!( @@ -616,6 +622,11 @@ async fn aggregated_gossip_verification() { att.message.aggregate.data.target.epoch = too_early_slot.epoch(E::slots_per_epoch()); } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.message.aggregate.data.slot = too_early_slot; + att.message.aggregate.data.target.epoch = + too_early_slot.epoch(E::slots_per_epoch()); + } } }, |tester, err| { @@ -647,6 +658,9 @@ async fn aggregated_gossip_verification() { SignedAggregateAndProofRefMut::Electra(att) => { att.message.aggregate.data.target.epoch += 1 } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.message.aggregate.data.target.epoch += 1 + } }, |_, err| assert!(matches!(err, AttnError::InvalidTargetEpoch { .. })), ) @@ -663,6 +677,9 @@ async fn aggregated_gossip_verification() { SignedAggregateAndProofRefMut::Electra(att) => { att.message.aggregate.data.target.root = Hash256::repeat_byte(42) } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.message.aggregate.data.target.root = Hash256::repeat_byte(42) + } }, |_, err| assert!(matches!(err, AttnError::InvalidTargetRoot { .. })), ) @@ -680,6 +697,9 @@ async fn aggregated_gossip_verification() { SignedAggregateAndProofRefMut::Electra(att) => { att.message.aggregate.data.beacon_block_root = Hash256::repeat_byte(42) } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.message.aggregate.data.beacon_block_root = Hash256::repeat_byte(42) + } }, |_, err| { assert!(matches!( @@ -711,6 +731,12 @@ async fn aggregated_gossip_verification() { assert!(aggregation_bits.is_zero()); att.message.aggregate.signature = AggregateSignature::infinity() } + SignedAggregateAndProofRefMut::Gloas(att) => { + let aggregation_bits = &mut att.message.aggregate.aggregation_bits; + aggregation_bits.difference_inplace(&aggregation_bits.clone()); + assert!(aggregation_bits.is_zero()); + att.message.aggregate.signature = AggregateSignature::infinity() + } }, |_, err| assert!(matches!(err, AttnError::EmptyAggregationBitfield)), ) @@ -728,6 +754,9 @@ async fn aggregated_gossip_verification() { SignedAggregateAndProofRefMut::Electra(att) => { att.signature = tester.aggregator_sk.sign(Hash256::repeat_byte(42)) } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.signature = tester.aggregator_sk.sign(Hash256::repeat_byte(42)) + } }, |_, err| assert!(matches!(err, AttnError::InvalidSignature)), ) @@ -786,6 +815,21 @@ async fn aggregated_gossip_verification() { } }; } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.message.selection_proof = loop { + i += 1; + let proof: SelectionProof = tester + .aggregator_sk + .sign(Hash256::from_slice(&int_to_bytes32(i))) + .into(); + if proof + .is_aggregator(committee_len, &tester.harness.chain.spec) + .unwrap() + { + break proof.into(); + } + }; + } } }, |_, err| assert!(matches!(err, AttnError::InvalidSignature)), @@ -807,6 +851,9 @@ async fn aggregated_gossip_verification() { SignedAggregateAndProofRefMut::Electra(att) => { att.message.aggregate.signature = agg_sig; } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.message.aggregate.signature = agg_sig; + } } }, |_, err| assert!(matches!(err, AttnError::InvalidSignature)), @@ -825,6 +872,10 @@ async fn aggregated_gossip_verification() { att.message.aggregator_index = ::ValidatorRegistryLimit::to_u64() + 1 } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.message.aggregator_index = + ::ValidatorRegistryLimit::to_u64() + 1 + } }, |_, err| { assert!(matches!( @@ -850,6 +901,9 @@ async fn aggregated_gossip_verification() { SignedAggregateAndProofRefMut::Electra(att) => { att.message.aggregator_index = VALIDATOR_COUNT as u64 } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.message.aggregator_index = VALIDATOR_COUNT as u64 + } }, |_, err| { assert!(matches!( @@ -907,11 +961,14 @@ async fn aggregated_gossip_verification() { "gloas: aggregate with index >= 2", |_, a| match a.to_mut() { SignedAggregateAndProofRefMut::Base(_) => { - panic!("Expected Electra attestation variant"); + panic!("Expected Electra or Gloas attestation variant"); } SignedAggregateAndProofRefMut::Electra(att) => { att.message.aggregate.data.index = 2; } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.message.aggregate.data.index = 2; + } }, |_, err| { assert!( @@ -962,6 +1019,9 @@ async fn aggregated_gossip_verification() { SignedAggregateAndProofRefMut::Electra(att) => { att.message.aggregate.data.beacon_block_root = Hash256::repeat_byte(42) } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.message.aggregate.data.beacon_block_root = Hash256::repeat_byte(42) + } }, |tester, err| { assert!(matches!( @@ -1958,6 +2018,9 @@ async fn gloas_aggregated_attestation_same_slot_index_must_be_zero() { SignedAggregateAndProofRefMut::Electra(att) => { att.message.aggregate.data.index = 1; } + SignedAggregateAndProofRefMut::Gloas(att) => { + att.message.aggregate.data.index = 1; + } } let result = harness diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index 94d4b3b9dae..92d8e202081 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -24,7 +24,7 @@ use logging::create_test_tracing_subscriber; use slasher::{Config as SlasherConfig, Slasher}; use state_processing::{ BlockProcessingError, BlockSignatureStrategy, ConsensusContext, VerifyBlockRoot, - common::{attesting_indices_base, attesting_indices_electra}, + common::{attesting_indices_base, attesting_indices_electra, attesting_indices_gloas}, per_block_processing, per_slot_processing, }; use std::marker::PhantomData; @@ -364,12 +364,12 @@ fn update_data_column_signed_header( for old_custody_column_sidecar in data_columns.as_mut_slice() { let old_column_sidecar = old_custody_column_sidecar.as_data_column(); let new_column_sidecar = match old_column_sidecar.as_ref() { - DataColumnSidecar::Fulu(_) => { + DataColumnSidecar::Fulu(old_sidecar) => { Arc::new(DataColumnSidecar::Fulu(DataColumnSidecarFulu { - index: *old_column_sidecar.index(), - column: old_column_sidecar.column().clone(), - kzg_commitments: old_column_sidecar.kzg_commitments().unwrap().clone(), - kzg_proofs: old_column_sidecar.kzg_proofs().clone(), + index: old_sidecar.index, + column: old_sidecar.column.clone(), + kzg_commitments: old_sidecar.kzg_commitments.clone(), + kzg_proofs: old_sidecar.kzg_proofs.clone(), signed_block_header: signed_block.signed_block_header(), kzg_commitments_inclusion_proof: signed_block .message() @@ -900,8 +900,7 @@ async fn invalid_signature_proposer_slashing() { }; block .body_mut() - .proposer_slashings_mut() - .push(proposer_slashing) + .proposer_slashings_push(proposer_slashing) .expect("should update proposer slashing"); snapshots[block_index].beacon_block = Arc::new(SignedBeaconBlock::from_block(block, signature)); @@ -1021,9 +1020,13 @@ async fn invalid_signature_attester_slashing() { .expect("should update attester slashing"); } BeaconBlockBodyRefMut::Gloas(blk) => { - blk.attester_slashings - .push(attester_slashing.as_electra().unwrap().clone()) - .expect("should update attester slashing"); + // Re-type the Electra slashing as Gloas (EIP-7688): the serialized form is + // identical, only the merkleization differs. + let slashing = attester_slashing.as_electra().unwrap().clone(); + blk.attester_slashings.push(AttesterSlashingGloas { + attestation_1: IndexedAttestation::Electra(slashing.attestation_1).to_gloas(), + attestation_2: IndexedAttestation::Electra(slashing.attestation_2).to_gloas(), + }); } } snapshots[block_index].beacon_block = @@ -1143,8 +1146,7 @@ async fn invalid_signature_deposit() { .deconstruct(); block .body_mut() - .deposits_mut() - .push(deposit) + .deposits_push(deposit) .expect("should update deposit"); snapshots[block_index].beacon_block = Arc::new(SignedBeaconBlock::from_block(block, signature)); @@ -1192,8 +1194,7 @@ async fn invalid_signature_exit() { .deconstruct(); block .body_mut() - .voluntary_exits_mut() - .push(SignedVoluntaryExit { + .voluntary_exits_push(SignedVoluntaryExit { message: VoluntaryExit { epoch, validator_index: 0, @@ -1668,11 +1669,15 @@ async fn verify_block_for_gossip_doppelganger_detection() { Attestation::Electra(att) => { attesting_indices_electra::get_indexed_attestation_from_state(&state, att).unwrap() } + Attestation::Gloas(att) => { + attesting_indices_gloas::get_indexed_attestation_from_state(&state, att).unwrap() + } }; for index in match indexed_attestation { IndexedAttestation::Base(att) => att.attesting_indices.into_iter(), IndexedAttestation::Electra(att) => att.attesting_indices.into_iter(), + IndexedAttestation::Gloas(att) => att.attesting_indices.into_iter(), } { let index = index as usize; diff --git a/beacon_node/beacon_chain/tests/payload_invalidation.rs b/beacon_node/beacon_chain/tests/payload_invalidation.rs index 42a78d740f7..1fb67052558 100644 --- a/beacon_node/beacon_chain/tests/payload_invalidation.rs +++ b/beacon_node/beacon_chain/tests/payload_invalidation.rs @@ -1131,6 +1131,9 @@ async fn attesting_to_optimistic_head() { Attestation::Electra(att) => { att.aggregation_bits.set(0, true).unwrap(); } + Attestation::Gloas(att) => { + att.aggregation_bits.set(0, true).unwrap(); + } } attestation.data_mut().slot = slot; diff --git a/beacon_node/beacon_chain/tests/prepare_payload.rs b/beacon_node/beacon_chain/tests/prepare_payload.rs index de8bfb3865f..30ccd7bda21 100644 --- a/beacon_node/beacon_chain/tests/prepare_payload.rs +++ b/beacon_node/beacon_chain/tests/prepare_payload.rs @@ -184,7 +184,7 @@ async fn prepare_payload_generic( // created with eth1 withdrawal credentials in the interop genesis builder. let consolidation_request = harness.make_switch_to_compounding_request(1); - let execution_requests = ExecutionRequests:: { + let execution_requests = ExecutionRequestsElectra:: { deposits: VariableList::empty(), withdrawals: VariableList::empty(), consolidations: VariableList::new(vec![consolidation_request]).unwrap(), diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index 4d392ef5249..1d7a5995de8 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -1485,7 +1485,7 @@ async fn proposer_shuffling_changing_with_lookahead() { target_pubkey: validator_to_topup.pubkey, }; - let execution_requests = ExecutionRequests:: { + let execution_requests = ExecutionRequestsElectra:: { deposits: VariableList::new(vec![deposit_request]).unwrap(), withdrawals: vec![].try_into().unwrap(), consolidations: VariableList::new(vec![consolidation_request]).unwrap(), diff --git a/beacon_node/execution_layer/src/block_hash.rs b/beacon_node/execution_layer/src/block_hash.rs index e45bf477a2c..043482983c4 100644 --- a/beacon_node/execution_layer/src/block_hash.rs +++ b/beacon_node/execution_layer/src/block_hash.rs @@ -7,7 +7,7 @@ use keccak_hash::KECCAK_EMPTY_LIST_RLP; use triehash::ordered_trie_root; use types::{ EncodableExecutionBlockHeader, EthSpec, ExecutionBlockHash, ExecutionBlockHeader, - ExecutionPayloadRef, ExecutionRequests, Hash256, + ExecutionPayloadRef, Hash256, }; /// Calculate the block hash of an execution block. @@ -17,14 +17,12 @@ use types::{ pub fn calculate_execution_block_hash( payload: ExecutionPayloadRef, parent_beacon_block_root: Option, - execution_requests: Option<&ExecutionRequests>, + requests_hash: Option, ) -> (ExecutionBlockHash, Hash256) { // Calculate the transactions root. // We're currently using a deprecated Parity library for this. We should move to a // better alternative when one appears, possibly following Reth. - let rlp_transactions_root = ordered_trie_root::( - payload.transactions().iter().map(|txn_bytes| &**txn_bytes), - ); + let rlp_transactions_root = ordered_trie_root::(payload.transactions().iter()); // Calculate withdrawals root (post-Capella). let rlp_withdrawals_root = if let Ok(withdrawals) = payload.withdrawals() { @@ -39,7 +37,7 @@ pub fn calculate_execution_block_hash( let rlp_blob_gas_used = payload.blob_gas_used().ok(); let rlp_excess_blob_gas = payload.excess_blob_gas().ok(); - let requests_root = execution_requests.map(|requests| requests.requests_hash()); + let requests_root = requests_hash; // Construct the block header. let exec_block_header = ExecutionBlockHeader::from_payload( diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index d9dd9aaf4ce..bbe69de95e0 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -25,7 +25,7 @@ pub use types::{ }; use types::{ ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, - ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequests, + ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequestsElectra, KzgProofs, }; use types::{GRAFFITI_BYTES_LEN, Graffiti}; @@ -342,7 +342,7 @@ pub struct GetPayloadResponse { #[superstruct(only(Deneb, Electra, Fulu, Gloas), partial_getter(copy))] pub should_override_builder: bool, #[superstruct(only(Electra, Fulu, Gloas))] - pub requests: ExecutionRequests, + pub requests: ExecutionRequestsElectra, } impl GetPayloadResponse { @@ -380,7 +380,7 @@ impl From> ExecutionPayload, Uint256, Option>, - Option>, + Option>, ) { fn from(response: GetPayloadResponse) -> Self { diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index 8df7d2a54be..454238db487 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -1666,7 +1666,7 @@ mod test { .unwrap() .insert("transactions".into(), transactions); let ep: JsonExecutionPayload = serde_json::from_value(json)?; - Ok(ep.transactions().clone()) + Ok(ep.transactions_bounded().unwrap().clone()) } fn assert_transactions_serde( diff --git a/beacon_node/execution_layer/src/engine_api/json_structures.rs b/beacon_node/execution_layer/src/engine_api/json_structures.rs index fb516e3e161..3feb8271147 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -2,11 +2,14 @@ use super::*; use alloy_rlp::RlpEncodable; use serde::{Deserialize, Serialize}; use ssz::{Decode, Encode, TryFromIter}; -use ssz_types::{FixedVector, VariableList, typenum::Unsigned}; +use ssz_types::{FixedVector, ProgressiveVariableList, VariableList, typenum::Unsigned}; use strum::EnumString; use superstruct::superstruct; use types::data::BlobsList; -use types::execution::{ConsolidationRequests, DepositRequests, RequestType, WithdrawalRequests}; +use types::execution::{ + BlockAccessList, ConsolidationRequests, DepositRequests, ProgressiveTransactions, RequestType, + WithdrawalRequests, +}; use types::kzg_ext::KzgCommitments; use types::{Blob, KzgProof}; @@ -97,10 +100,22 @@ pub struct JsonExecutionPayload { pub base_fee_per_gas: Uint256, pub block_hash: ExecutionBlockHash, + #[superstruct( + only(Bellatrix, Capella, Deneb, Electra, Fulu), + partial_getter(rename = "transactions_bounded") + )] #[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")] pub transactions: Transactions, - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Gloas), partial_getter(rename = "transactions_progressive"))] + #[serde(with = "ssz_types::serde_utils::list_of_hex_prog_var_list")] + pub transactions: ProgressiveTransactions, + #[superstruct( + only(Capella, Deneb, Electra, Fulu), + partial_getter(rename = "withdrawals_bounded") + )] pub withdrawals: VariableList, + #[superstruct(only(Gloas), partial_getter(rename = "withdrawals_progressive"))] + pub withdrawals: ProgressiveVariableList, #[superstruct(only(Deneb, Electra, Fulu, Gloas))] #[serde(with = "serde_utils::u64_hex_be")] pub blob_gas_used: u64, @@ -108,8 +123,8 @@ pub struct JsonExecutionPayload { #[serde(with = "serde_utils::u64_hex_be")] pub excess_blob_gas: u64, #[superstruct(only(Gloas))] - #[serde(with = "ssz_types::serde_utils::hex_var_list")] - pub block_access_list: VariableList, + #[serde(with = "ssz_types::serde_utils::hex_prog_var_list")] + pub block_access_list: BlockAccessList, #[superstruct(only(Gloas))] #[serde(with = "serde_utils::u64_hex_be")] pub slot_number: u64, @@ -255,7 +270,7 @@ impl TryFrom> for JsonExecutionPayloadGloas base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, - withdrawals: withdrawals_to_json(payload.withdrawals)?, + withdrawals: payload.withdrawals.into_iter().map(Into::into).collect(), blob_gas_used: payload.blob_gas_used, excess_blob_gas: payload.excess_blob_gas, block_access_list: payload.block_access_list, @@ -430,7 +445,7 @@ impl TryFrom> for ExecutionPayloadGloas base_fee_per_gas: payload.base_fee_per_gas, block_hash: payload.block_hash, transactions: payload.transactions, - withdrawals: withdrawals_from_json(payload.withdrawals)?, + withdrawals: payload.withdrawals.into_iter().map(Into::into).collect(), blob_gas_used: payload.blob_gas_used, excess_blob_gas: payload.excess_blob_gas, block_access_list: payload.block_access_list, @@ -481,8 +496,8 @@ pub enum RequestsError { #[serde(transparent)] pub struct JsonExecutionRequests(pub Vec); -impl From> for JsonExecutionRequests { - fn from(requests: ExecutionRequests) -> Self { +impl From> for JsonExecutionRequests { + fn from(requests: ExecutionRequestsElectra) -> Self { let mut result = Vec::new(); if !requests.deposits.is_empty() { result.push(format!( @@ -509,11 +524,11 @@ impl From> for JsonExecutionRequests { } } -impl TryFrom for ExecutionRequests { +impl TryFrom for ExecutionRequestsElectra { type Error = RequestsError; fn try_from(value: JsonExecutionRequests) -> Result { - let mut requests = ExecutionRequests::default(); + let mut requests = ExecutionRequestsElectra::default(); let mut prev_prefix: Option = None; for (i, request) in value.0.into_iter().enumerate() { // hex string @@ -1212,7 +1227,7 @@ mod tests { // First check a valid request with all requests assert!( - ExecutionRequests::::try_from(JsonExecutionRequests(vec![ + ExecutionRequestsElectra::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Deposit.to_u8(), &deposit_request), create_request_string(RequestType::Withdrawal.to_u8(), &withdrawal_request), create_request_string(RequestType::Consolidation.to_u8(), &consolidation_request), @@ -1222,21 +1237,21 @@ mod tests { // Single requests assert!( - ExecutionRequests::::try_from(JsonExecutionRequests(vec![ + ExecutionRequestsElectra::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Deposit.to_u8(), &deposit_request), ])) .is_ok() ); assert!( - ExecutionRequests::::try_from(JsonExecutionRequests(vec![ + ExecutionRequestsElectra::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Withdrawal.to_u8(), &withdrawal_request), ])) .is_ok() ); assert!( - ExecutionRequests::::try_from(JsonExecutionRequests(vec![ + ExecutionRequestsElectra::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Consolidation.to_u8(), &consolidation_request), ])) .is_ok() @@ -1244,7 +1259,7 @@ mod tests { // Out of order assert!(matches!( - ExecutionRequests::::try_from(JsonExecutionRequests(vec![ + ExecutionRequestsElectra::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Withdrawal.to_u8(), &withdrawal_request), create_request_string(RequestType::Deposit.to_u8(), &deposit_request), ])) @@ -1253,7 +1268,7 @@ mod tests { )); assert!(matches!( - ExecutionRequests::::try_from(JsonExecutionRequests(vec![ + ExecutionRequestsElectra::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Consolidation.to_u8(), &consolidation_request), create_request_string(RequestType::Withdrawal.to_u8(), &withdrawal_request), ])) @@ -1262,7 +1277,7 @@ mod tests { )); assert!(matches!( - ExecutionRequests::::try_from(JsonExecutionRequests(vec![ + ExecutionRequestsElectra::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Consolidation.to_u8(), &consolidation_request), create_request_string(RequestType::Deposit.to_u8(), &deposit_request), ])) @@ -1272,7 +1287,7 @@ mod tests { // Multiple requests of same type assert!(matches!( - ExecutionRequests::::try_from(JsonExecutionRequests(vec![ + ExecutionRequestsElectra::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Deposit.to_u8(), &deposit_request), create_request_string(RequestType::Deposit.to_u8(), &deposit_request), ])) @@ -1282,7 +1297,7 @@ mod tests { // Invalid prefix assert!(matches!( - ExecutionRequests::::try_from(JsonExecutionRequests(vec![ + ExecutionRequestsElectra::::try_from(JsonExecutionRequests(vec![ create_request_string(42, &deposit_request), ])) .unwrap_err(), @@ -1291,7 +1306,7 @@ mod tests { // Prefix followed by no data assert!(matches!( - ExecutionRequests::::try_from(JsonExecutionRequests(vec![ + ExecutionRequestsElectra::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Deposit.to_u8(), &deposit_request), create_request_string( RequestType::Consolidation.to_u8(), @@ -1303,7 +1318,7 @@ mod tests { )); // Empty request assert!(matches!( - ExecutionRequests::::try_from(JsonExecutionRequests(vec![ + ExecutionRequestsElectra::::try_from(JsonExecutionRequests(vec![ create_request_string(RequestType::Deposit.to_u8(), &deposit_request), "0x".to_string() ])) diff --git a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs index ba94296b859..8c3a7ba45fa 100644 --- a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs +++ b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs @@ -9,7 +9,8 @@ use types::{ }; use types::{ ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, - ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequests, + ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequestsElectra, + ExecutionRequestsGloas, }; #[superstruct( @@ -47,8 +48,14 @@ pub struct NewPayloadRequest<'block, E: EthSpec> { pub versioned_hashes: Vec, #[superstruct(only(Deneb, Electra, Fulu, Gloas))] pub parent_beacon_block_root: Hash256, - #[superstruct(only(Electra, Fulu, Gloas))] - pub execution_requests: &'block ExecutionRequests, + #[superstruct( + only(Electra, Fulu), + partial_getter(rename = "execution_requests_basic") + )] + pub execution_requests: &'block ExecutionRequestsElectra, + // [Modified in Gloas:EIP7688] + #[superstruct(only(Gloas), partial_getter(rename = "execution_requests_gloas"))] + pub execution_requests: &'block ExecutionRequestsGloas, } impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { @@ -85,6 +92,16 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { } } + /// Return the EIP-7685 `requests_hash` for the execution requests, if any. + pub fn requests_hash(&self) -> Option { + match self { + Self::Bellatrix(_) | Self::Capella(_) | Self::Deneb(_) => None, + Self::Electra(request) => Some(request.execution_requests.requests_hash()), + Self::Fulu(request) => Some(request.execution_requests.requests_hash()), + Self::Gloas(request) => Some(request.execution_requests.requests_hash()), + } + } + pub fn execution_payload_ref(&self) -> ExecutionPayloadRef<'block, E> { match self { Self::Bellatrix(request) => ExecutionPayloadRef::Bellatrix(request.execution_payload), @@ -140,11 +157,8 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { return Err(Error::ZeroLengthTransaction); } - let (header_hash, rlp_transactions_root) = calculate_execution_block_hash( - payload, - parent_beacon_block_root, - self.execution_requests().ok().copied(), - ); + let (header_hash, rlp_transactions_root) = + calculate_execution_block_hash(payload, parent_beacon_block_root, self.requests_hash()); if header_hash != self.block_hash() { return Err(Error::BlockHashMismatch { diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 78076bee6c6..58b56539b16 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -48,7 +48,7 @@ use types::builder::BuilderBid; use types::execution::BlockProductionVersion; use types::kzg_ext::KzgCommitments; use types::{ - AbstractExecPayload, BlobsList, ExecutionPayloadDeneb, ExecutionRequests, KzgProofs, + AbstractExecPayload, BlobsList, ExecutionPayloadDeneb, ExecutionRequestsElectra, KzgProofs, SignedBlindedBeaconBlock, }; use types::{ @@ -206,7 +206,7 @@ pub struct BlockProposalContentsGloas { pub payload_value: Uint256, pub blob_kzg_commitments: KzgCommitments, pub blobs_and_proofs: (BlobsList, KzgProofs), - pub execution_requests: ExecutionRequests, + pub execution_requests: ExecutionRequestsElectra, pub should_override_builder: bool, } @@ -236,7 +236,7 @@ pub enum BlockProposalContents> { blobs_and_proofs: Option<(BlobsList, KzgProofs)>, // TODO(electra): this should probably be a separate variant/superstruct // See: https://github.com/sigp/lighthouse/issues/6981 - requests: Option>, + requests: Option>, }, } @@ -311,7 +311,7 @@ impl> BlockProposalContents>, Option<(BlobsList, KzgProofs)>, - Option>, + Option>, Uint256, ) { match self { diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index b05db6e8bdd..741386ba0ed 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -15,7 +15,7 @@ use parking_lot::Mutex; use rand::{Rng, SeedableRng, rngs::StdRng}; use serde::{Deserialize, Serialize}; use ssz::Decode; -use ssz_types::VariableList; +use ssz_types::{ProgressiveVariableList, VariableList}; use state_processing::per_block_processing::deneb::kzg_commitment_to_versioned_hash; use std::cmp::max; use std::collections::HashMap; @@ -26,8 +26,8 @@ use tree_hash_derive::TreeHash; use types::{ Blob, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, - ExecutionPayloadGloas, ExecutionPayloadHeader, ExecutionRequests, ForkName, Hash256, KzgProofs, - Transaction, Transactions, Uint256, + ExecutionPayloadGloas, ExecutionPayloadHeader, ExecutionRequestsElectra, ForkName, Hash256, + KzgProofs, Transaction, Transactions, Uint256, }; const TEST_BLOB_BUNDLE: &[u8] = include_bytes!("fixtures/mainnet/test_blobs_bundle.ssz"); @@ -172,10 +172,10 @@ pub struct ExecutionBlockGenerator { * Execution requests (electra+) */ /// Per-payload execution requests returned by `getPayload`. - execution_requests: HashMap>, + execution_requests: HashMap>, /// If set, the next call to `build_new_execution_payload` will associate these /// execution requests with the generated payload ID. - next_execution_requests: Option>, + next_execution_requests: Option>, } fn make_rng() -> Arc> { @@ -475,12 +475,12 @@ impl ExecutionBlockGenerator { self.blobs_bundles.get(id).cloned() } - pub fn get_execution_requests(&self, id: &PayloadId) -> Option> { + pub fn get_execution_requests(&self, id: &PayloadId) -> Option> { self.execution_requests.get(id).cloned() } /// Set execution requests to be returned alongside the next generated payload. - pub fn set_next_execution_requests(&mut self, requests: ExecutionRequests) { + pub fn set_next_execution_requests(&mut self, requests: ExecutionRequestsElectra) { self.next_execution_requests = Some(requests); } @@ -791,11 +791,11 @@ impl ExecutionBlockGenerator { extra_data: "block gen was here".as_bytes().to_vec().try_into().unwrap(), base_fee_per_gas: Uint256::from(1u64), block_hash: ExecutionBlockHash::zero(), - transactions: vec![].try_into().unwrap(), - withdrawals: pa.withdrawals.clone().try_into().unwrap(), + transactions: ProgressiveVariableList::empty(), + withdrawals: ProgressiveVariableList::new(pa.withdrawals.clone()), blob_gas_used: 0, excess_blob_gas: 0, - block_access_list: VariableList::empty(), + block_access_list: ProgressiveVariableList::empty(), slot_number: pa.slot_number.into(), }), _ => unreachable!(), @@ -815,11 +815,23 @@ impl ExecutionBlockGenerator { let max_blobs = max(1, self.min_blobs_count); let num_blobs = rng.random_range(self.min_blobs_count..=max_blobs); let (bundle, transactions) = generate_blobs(num_blobs, fork_name)?; - for tx in Vec::from(transactions) { - execution_payload - .transactions_mut() - .push(tx) - .map_err(|_| "transactions are full".to_string())?; + match &mut execution_payload { + ExecutionPayload::Gloas(payload) => { + for tx in Vec::from(transactions) { + payload + .transactions + .push(ProgressiveVariableList::::new(tx.into())); + } + } + _ => { + for tx in Vec::from(transactions) { + execution_payload + .transactions_bounded_mut() + .map_err(|e| format!("invalid payload variant: {e:?}"))? + .push(tx) + .map_err(|_| "transactions are full".to_string())?; + } + } } self.blobs_bundles.insert(id, bundle); } diff --git a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs index 9924fbe474c..2f44a770cf0 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -714,8 +714,25 @@ pub async fn handle_rpc( match maybe_payload { Some(payload) => { let payload_body: ExecutionPayloadBodyV1 = ExecutionPayloadBodyV1 { - transactions: payload.transactions().clone(), - withdrawals: payload.withdrawals().ok().cloned(), + transactions: payload + .transactions() + .iter() + .map(|tx| { + types::Transaction::::new( + tx.to_vec(), + ) + }) + .collect::, _>>() + .and_then(ssz_types::VariableList::new) + .unwrap(), + withdrawals: payload + .withdrawals() + .ok() + .map(|withdrawals| { + ssz_types::VariableList::new(withdrawals.to_vec()) + }) + .transpose() + .unwrap(), }; let json_payload_body: JsonExecutionPayloadBodyV1 = payload_body.try_into().unwrap(); diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index d456c9adc10..7dbaf8a34b0 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -35,7 +35,7 @@ use types::builder::{ }; use types::{ Address, BeaconState, ChainSpec, Epoch, EthSpec, ExecPayload, ExecutionPayload, - ExecutionPayloadHeaderRefMut, ExecutionRequests, ForkName, ForkVersionDecode, Hash256, + ExecutionPayloadHeaderRefMut, ExecutionRequestsElectra, ForkName, ForkVersionDecode, Hash256, SignedBlindedBeaconBlock, SignedRoot, SignedValidatorRegistrationData, Slot, Uint256, }; use warp::reply::{self, Reply}; @@ -585,7 +585,7 @@ impl MockBuilder { ExecutionPayload, Uint256, Option>, - Option>, + Option>, ) = payload_response.into(); match fork { diff --git a/beacon_node/execution_layer/src/versioned_hashes.rs b/beacon_node/execution_layer/src/versioned_hashes.rs index 21cfd5a3223..b191b89cb18 100644 --- a/beacon_node/execution_layer/src/versioned_hashes.rs +++ b/beacon_node/execution_layer/src/versioned_hashes.rs @@ -38,14 +38,14 @@ pub fn verify_versioned_hashes( } pub fn extract_versioned_hashes_from_transactions( - transactions: &types::Transactions, + transactions: types::TransactionsRef, ) -> Result, Error> { let mut versioned_hashes = Vec::new(); - for tx in transactions { + for tx in transactions.iter() { // TxEnvelope is non-exhaustive so unforunately we can (no longer) write an exhaustive // match here. - if let TxEnvelope::Eip4844(signed_tx_eip4844) = beacon_tx_to_tx_envelope(tx)? { + if let TxEnvelope::Eip4844(signed_tx_eip4844) = tx_bytes_to_tx_envelope(tx)? { versioned_hashes.extend( signed_tx_eip4844 .tx() @@ -63,9 +63,11 @@ pub fn extract_versioned_hashes_from_transactions( pub fn beacon_tx_to_tx_envelope( tx: &types::Transaction, ) -> Result { - let tx_bytes = Vec::from(tx.clone()); - TxEnvelope::decode(&mut tx_bytes.as_slice()) - .map_err(|e| Error::DecodingTransaction(e.to_string())) + tx_bytes_to_tx_envelope(tx) +} + +pub fn tx_bytes_to_tx_envelope(mut tx_bytes: &[u8]) -> Result { + TxEnvelope::decode(&mut tx_bytes).map_err(|e| Error::DecodingTransaction(e.to_string())) } #[cfg(test)] @@ -123,8 +125,9 @@ mod test { .map(|tx| Hash256::from_slice(&hex::decode(&tx[2..]).expect("should decode hex"))) .collect::>(); - let versioned_hashes = extract_versioned_hashes_from_transactions::(&raw_transactions) - .expect("should get versioned hashes"); + let versioned_hashes = + extract_versioned_hashes_from_transactions::((&raw_transactions).into()) + .expect("should get versioned hashes"); assert_eq!(versioned_hashes, expected_versioned_hashes); } } diff --git a/beacon_node/genesis/src/interop.rs b/beacon_node/genesis/src/interop.rs index 349b8f19c8b..bb58d6f8225 100644 --- a/beacon_node/genesis/src/interop.rs +++ b/beacon_node/genesis/src/interop.rs @@ -259,6 +259,101 @@ mod test { ); } + /// Verify the derived `progressive_container` root of a Gloas `BeaconState` against a manual + /// computation from its 46 field roots (EIP-7688). + /// + /// This guards against the `active_fields` attribute silently dropping trailing fields: if + /// the derive hashed fewer (or more) fields than listed here, the roots would differ. + #[test] + fn gloas_state_progressive_container_root() { + use tree_hash::TreeHash; + + let validator_count = 16; + let genesis_time = 42; + let spec = &types::ForkName::Gloas.make_genesis_spec(TestEthSpec::default_spec()); + + let keypairs = generate_deterministic_keypairs(validator_count); + + let mut state = interop_genesis_state::( + &keypairs, + genesis_time, + Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH), + None, + spec, + ) + .expect("should build state"); + + // Apply pending mutations and compute the root via the derived implementation. + let derived_root = state.canonical_root().expect("should compute root"); + + let gloas = state.as_gloas().expect("should be a Gloas state"); + + // All 46 spec fields, in container order. + let field_roots = [ + gloas.genesis_time.tree_hash_root(), + gloas.genesis_validators_root.tree_hash_root(), + gloas.slot.tree_hash_root(), + gloas.fork.tree_hash_root(), + gloas.latest_block_header.tree_hash_root(), + gloas.block_roots.tree_hash_root(), + gloas.state_roots.tree_hash_root(), + gloas.historical_roots.tree_hash_root(), + gloas.eth1_data.tree_hash_root(), + gloas.eth1_data_votes.tree_hash_root(), + gloas.eth1_deposit_index.tree_hash_root(), + gloas.validators.tree_hash_root(), + gloas.balances.tree_hash_root(), + gloas.randao_mixes.tree_hash_root(), + gloas.slashings.tree_hash_root(), + gloas.previous_epoch_participation.tree_hash_root(), + gloas.current_epoch_participation.tree_hash_root(), + gloas.justification_bits.tree_hash_root(), + gloas.previous_justified_checkpoint.tree_hash_root(), + gloas.current_justified_checkpoint.tree_hash_root(), + gloas.finalized_checkpoint.tree_hash_root(), + gloas.inactivity_scores.tree_hash_root(), + gloas.current_sync_committee.tree_hash_root(), + gloas.next_sync_committee.tree_hash_root(), + gloas.latest_block_hash.tree_hash_root(), + gloas.next_withdrawal_index.tree_hash_root(), + gloas.next_withdrawal_validator_index.tree_hash_root(), + gloas.historical_summaries.tree_hash_root(), + gloas.deposit_requests_start_index.tree_hash_root(), + gloas.deposit_balance_to_consume.tree_hash_root(), + gloas.exit_balance_to_consume.tree_hash_root(), + gloas.earliest_exit_epoch.tree_hash_root(), + gloas.consolidation_balance_to_consume.tree_hash_root(), + gloas.earliest_consolidation_epoch.tree_hash_root(), + gloas.pending_deposits.tree_hash_root(), + gloas.pending_partial_withdrawals.tree_hash_root(), + gloas.pending_consolidations.tree_hash_root(), + gloas.proposer_lookahead.tree_hash_root(), + gloas.builders.tree_hash_root(), + gloas.next_withdrawal_builder_index.tree_hash_root(), + gloas.execution_payload_availability.tree_hash_root(), + gloas.builder_pending_payments.tree_hash_root(), + gloas.builder_pending_withdrawals.tree_hash_root(), + gloas.latest_execution_payload_bid.tree_hash_root(), + gloas.payload_expected_withdrawals.tree_hash_root(), + gloas.ptc_window.tree_hash_root(), + ]; + assert_eq!(field_roots.len(), 46); + + let mut hasher = tree_hash::ProgressiveMerkleHasher::new(); + for root in &field_roots { + hasher.write(root.as_slice()).unwrap(); + } + let container_root = hasher.finish().unwrap(); + + // `active_fields = [1] * 46`. + let mut active_fields = [0u8; 32]; + active_fields[..5].fill(0xff); + active_fields[5] = 0x3f; + let expected = tree_hash::mix_in_active_fields(container_root, active_fields); + + assert_eq!(derived_root, expected); + } + #[test] fn interop_state_with_eth1() { let validator_count = 16; diff --git a/beacon_node/http_api/src/beacon/states.rs b/beacon_node/http_api/src/beacon/states.rs index 1b765aa2276..7c424bb71b9 100644 --- a/beacon_node/http_api/src/beacon/states.rs +++ b/beacon_node/http_api/src/beacon/states.rs @@ -55,7 +55,7 @@ pub fn get_beacon_state_pending_consolidations( }; Ok(( - consolidations.clone(), + consolidations.to_owned_list(), execution_optimistic, finalized, state.fork_name_unchecked(), @@ -101,7 +101,7 @@ pub fn get_beacon_state_pending_partial_withdrawals( }; Ok(( - withdrawals.clone(), + withdrawals.to_owned_list(), execution_optimistic, finalized, state.fork_name_unchecked(), @@ -147,7 +147,7 @@ pub fn get_beacon_state_pending_deposits( }; Ok(( - deposits.clone(), + deposits.to_owned_list(), execution_optimistic, finalized, state.fork_name_unchecked(), diff --git a/beacon_node/http_api/src/validator/mod.rs b/beacon_node/http_api/src/validator/mod.rs index 86399147746..d777d9c82f8 100644 --- a/beacon_node/http_api/src/validator/mod.rs +++ b/beacon_node/http_api/src/validator/mod.rs @@ -13,6 +13,7 @@ use beacon_chain::proposer_preferences_verification::ProposerPreferencesError; use beacon_chain::{AttestationError, BeaconChain, BeaconChainError, BeaconChainTypes}; use bls::PublicKeyBytes; use bytes::Bytes; +use context_deserialize::ContextDeserialize; use eth2::CONSENSUS_VERSION_HEADER; use eth2::types::{ Accept, BeaconCommitteeSubscription, EndpointVersion, Failure, GenericResponse, @@ -1001,19 +1002,39 @@ pub fn post_validator_aggregate_and_proofs( .and(task_spawner_filter.clone()) .and(chain_filter.clone()) .and(warp_utils::json::json()) + .and(warp::header::optional::(CONSENSUS_VERSION_HEADER)) .and(network_tx_filter.clone()) .then( // V1 and V2 are identical except V2 has a consensus version header in the request. - // We only require this header for SSZ deserialization, which isn't supported for - // this endpoint presently. + // The header (or, failing that, the fork at the current wall-clock slot) decides + // which attestation variant to deserialize: from Gloas onwards (EIP-7688) the + // variants are structurally identical in JSON but merkleize differently, so untagged + // deserialization cannot distinguish them. |_endpoint_version: EndpointVersion, not_synced_filter: Result<(), Rejection>, task_spawner: TaskSpawner, chain: Arc>, - aggregates: Vec>, + aggregates_json: serde_json::Value, + consensus_version: Option, network_tx: UnboundedSender>| { task_spawner.blocking_json_task(Priority::P0, move || { not_synced_filter?; + let fork_name = consensus_version.unwrap_or_else(|| { + chain + .slot_clock + .now() + .map(|slot| chain.spec.fork_name_at_slot::(slot)) + .unwrap_or(ForkName::Base) + }); + let aggregates = Vec::>::context_deserialize( + &aggregates_json, + fork_name, + ) + .map_err(|e| { + warp_utils::reject::custom_bad_request(format!( + "invalid aggregate and proofs: {e:?}" + )) + })?; let seen_timestamp = chain.slot_clock.now_duration().unwrap_or_default(); let mut verified_aggregates = Vec::with_capacity(aggregates.len()); let mut messages = Vec::with_capacity(aggregates.len()); diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 455a957337f..f64d7d432e9 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -885,9 +885,7 @@ impl ApiTester { for validator_indices in self.interesting_validator_indices() { let state_opt = state_id.state(&self.chain).ok(); let validators: Vec = match state_opt.as_ref() { - Some((state, _execution_optimistic, _finalized)) => { - state.validators().clone().to_vec() - } + Some((state, _execution_optimistic, _finalized)) => state.validators().to_vec(), None => vec![], }; let validator_index_ids = validator_indices @@ -982,9 +980,7 @@ impl ApiTester { for validator_indices in self.interesting_validator_indices() { let state_opt = state_id.state(&self.chain).ok(); let validators: Vec = match state_opt.as_ref() { - Some((state, _execution_optimistic, _finalized)) => { - state.validators().clone().to_vec() - } + Some((state, _execution_optimistic, _finalized)) => state.validators().to_vec(), None => vec![], }; @@ -2636,6 +2632,9 @@ impl ApiTester { AttesterSlashing::Electra(slashing) => { slashing.attestation_1.data.slot += 1; } + AttesterSlashing::Gloas(slashing) => { + slashing.attestation_1.data.slot += 1; + } } self.client @@ -2660,6 +2659,9 @@ impl ApiTester { AttesterSlashing::Electra(slashing) => { slashing.attestation_1.data.slot += 1; } + AttesterSlashing::Gloas(slashing) => { + slashing.attestation_1.data.slot += 1; + } } let fork_name = self @@ -3074,6 +3076,7 @@ impl ApiTester { execution_payment: 0, blob_kzg_commitments: Default::default(), execution_requests_root: Hash256::zero(), + _phantom: std::marker::PhantomData, }; let signed = SignedExecutionPayloadBid { @@ -5253,6 +5256,9 @@ impl ApiTester { SignedAggregateAndProof::Electra(aggregate) => { aggregate.message.aggregate.data.slot += 1; } + SignedAggregateAndProof::Gloas(aggregate) => { + aggregate.message.aggregate.data.slot += 1; + } } self.client @@ -5290,6 +5296,9 @@ impl ApiTester { SignedAggregateAndProof::Electra(aggregate) => { aggregate.message.aggregate.data.slot += 1; } + SignedAggregateAndProof::Gloas(aggregate) => { + aggregate.message.aggregate.data.slot += 1; + } } let fork_name = self diff --git a/beacon_node/lighthouse_network/src/types/pubsub.rs b/beacon_node/lighthouse_network/src/types/pubsub.rs index d486ca5129a..dd8efc905dd 100644 --- a/beacon_node/lighthouse_network/src/types/pubsub.rs +++ b/beacon_node/lighthouse_network/src/types/pubsub.rs @@ -7,12 +7,12 @@ use ssz::{Decode, Encode}; use std::io::{Error, ErrorKind}; use std::sync::Arc; use types::{ - AttesterSlashing, AttesterSlashingBase, AttesterSlashingElectra, DataColumnSidecar, - DataColumnSubnetId, EthSpec, ForkContext, ForkName, Hash256, LightClientFinalityUpdate, - LightClientOptimisticUpdate, PartialDataColumn, PartialDataColumnSidecar, - PayloadAttestationMessage, ProposerSlashing, SignedAggregateAndProof, - SignedAggregateAndProofBase, SignedAggregateAndProofElectra, SignedBeaconBlock, - SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockBellatrix, + AttesterSlashing, AttesterSlashingBase, AttesterSlashingElectra, AttesterSlashingGloas, + DataColumnSidecar, DataColumnSubnetId, EthSpec, ForkContext, ForkName, Hash256, + LightClientFinalityUpdate, LightClientOptimisticUpdate, PartialDataColumn, + PartialDataColumnSidecar, PayloadAttestationMessage, ProposerSlashing, SignedAggregateAndProof, + SignedAggregateAndProofBase, SignedAggregateAndProofElectra, SignedAggregateAndProofGloas, + SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockBellatrix, SignedBeaconBlockCapella, SignedBeaconBlockDeneb, SignedBeaconBlockElectra, SignedBeaconBlockFulu, SignedBeaconBlockGloas, SignedBlsToExecutionChange, SignedContributionAndProof, SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope, @@ -182,7 +182,15 @@ impl PubsubMessage { .get_fork_from_context_bytes(gossip_topic.fork_digest) { Some(&fork_name) => { - if fork_name.electra_enabled() { + // [Modified in Gloas:EIP7688] the Gloas and Electra containers + // serialize identically but merkleize differently, so decoding + // into the wrong variant produces invalid signing roots. + if fork_name.gloas_enabled() { + SignedAggregateAndProof::Gloas( + SignedAggregateAndProofGloas::from_ssz_bytes(data) + .map_err(|e| format!("{:?}", e))?, + ) + } else if fork_name.electra_enabled() { SignedAggregateAndProof::Electra( SignedAggregateAndProofElectra::from_ssz_bytes(data) .map_err(|e| format!("{:?}", e))?, @@ -291,7 +299,13 @@ impl PubsubMessage { .get_fork_from_context_bytes(gossip_topic.fork_digest) { Some(&fork_name) => { - if fork_name.electra_enabled() { + // [Modified in Gloas:EIP7688] see `BeaconAggregateAndProof` above. + if fork_name.gloas_enabled() { + AttesterSlashing::Gloas( + AttesterSlashingGloas::from_ssz_bytes(data) + .map_err(|e| format!("{:?}", e))?, + ) + } else if fork_name.electra_enabled() { AttesterSlashing::Electra( AttesterSlashingElectra::from_ssz_bytes(data) .map_err(|e| format!("{:?}", e))?, diff --git a/beacon_node/network/src/network_beacon_processor/tests.rs b/beacon_node/network/src/network_beacon_processor/tests.rs index 6b7c623230f..5ace5db8d57 100644 --- a/beacon_node/network/src/network_beacon_processor/tests.rs +++ b/beacon_node/network/src/network_beacon_processor/tests.rs @@ -44,9 +44,9 @@ use tokio::sync::mpsc; use types::data::BlobIdentifier; use types::{ AttesterSlashing, ChainSpec, DataColumnSidecarList, DataColumnSubnetId, Epoch, EthSpec, - ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequests, Hash256, MainnetEthSpec, - ProposerSlashing, SignedAggregateAndProof, SignedBeaconBlock, SignedExecutionPayloadEnvelope, - SignedVoluntaryExit, SingleAttestation, Slot, SubnetId, + ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequestsGloas, Hash256, + MainnetEthSpec, ProposerSlashing, SignedAggregateAndProof, SignedBeaconBlock, + SignedExecutionPayloadEnvelope, SignedVoluntaryExit, SingleAttestation, Slot, SubnetId, }; type E = MainnetEthSpec; @@ -1967,7 +1967,7 @@ fn make_test_payload_envelope( slot_number: slot, ..ExecutionPayloadGloas::default() }, - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsGloas::default(), builder_index: 0, beacon_block_root, parent_beacon_block_root: Hash256::ZERO, diff --git a/beacon_node/network/src/sync/tests/lookups.rs b/beacon_node/network/src/sync/tests/lookups.rs index 13eeaee9aac..b3b956bcb40 100644 --- a/beacon_node/network/src/sync/tests/lookups.rs +++ b/beacon_node/network/src/sync/tests/lookups.rs @@ -1264,8 +1264,15 @@ impl TestRig { let mut columns = range_sync_block.data_columns().expect("no columns"); let first = columns.first_mut().expect("empty columns"); let column = Arc::make_mut(first); - let proof = column.kzg_proofs_mut().first_mut().expect("no kzg proofs"); - *proof = kzg::KzgProof::empty(); + match column { + DataColumnSidecar::Fulu(sidecar) => { + let proof = sidecar.kzg_proofs.first_mut().expect("no kzg proofs"); + *proof = kzg::KzgProof::empty(); + } + DataColumnSidecar::Gloas(sidecar) => { + *sidecar.kzg_proofs.get_mut(0).expect("no kzg proofs") = kzg::KzgProof::empty(); + } + } self.upsert_block(block, blobs, Some(columns)); } diff --git a/beacon_node/operation_pool/src/attestation.rs b/beacon_node/operation_pool/src/attestation.rs index 045adfebe02..6679079c942 100644 --- a/beacon_node/operation_pool/src/attestation.rs +++ b/beacon_node/operation_pool/src/attestation.rs @@ -186,7 +186,9 @@ pub fn earliest_attestation_validators( let mut new_validators = match attestation.indexed { CompactIndexedAttestation::Base(indexed_att) => indexed_att.aggregation_bits.clone(), // This code path is obsolete post altair fork, so we just return an empty bitlist here. - CompactIndexedAttestation::Electra(_) => return BitList::with_capacity(0).unwrap(), + CompactIndexedAttestation::Electra(_) | CompactIndexedAttestation::Gloas(_) => { + return BitList::with_capacity(0).unwrap(); + } }; let state_attestations = if attestation.checkpoint.target_epoch == state.current_epoch() { diff --git a/beacon_node/operation_pool/src/attestation_storage.rs b/beacon_node/operation_pool/src/attestation_storage.rs index 9094c9cd4d4..51eebfc91c1 100644 --- a/beacon_node/operation_pool/src/attestation_storage.rs +++ b/beacon_node/operation_pool/src/attestation_storage.rs @@ -1,13 +1,13 @@ use crate::AttestationStats; use bls::AggregateSignature; use itertools::Itertools; -use ssz::{BitList, BitVector}; +use ssz::{BitList, BitVector, ProgressiveBitList}; use std::collections::{BTreeMap, HashMap, HashSet}; use superstruct::superstruct; use typenum::Unsigned; use types::{ Attestation, AttestationData, BeaconState, Checkpoint, Epoch, EthSpec, Hash256, Slot, - attestation::{AttestationBase, AttestationElectra}, + attestation::{AttestationBase, AttestationElectra, AttestationGloas}, }; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] @@ -24,7 +24,10 @@ pub struct CompactAttestationData { pub target_root: Hash256, } -#[superstruct(variants(Base, Electra), variant_attributes(derive(Debug, PartialEq,)))] +#[superstruct( + variants(Base, Electra, Gloas), + variant_attributes(derive(Debug, PartialEq,)) +)] #[derive(Debug, PartialEq)] pub struct CompactIndexedAttestation { pub attesting_indices: Vec, @@ -32,8 +35,10 @@ pub struct CompactIndexedAttestation { pub aggregation_bits: BitList, #[superstruct(only(Electra), partial_getter(rename = "aggregation_bits_electra"))] pub aggregation_bits: BitList, + #[superstruct(only(Gloas), partial_getter(rename = "aggregation_bits_gloas"))] + pub aggregation_bits: ProgressiveBitList, pub signature: AggregateSignature, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Gloas))] pub committee_bits: BitVector, } @@ -90,6 +95,14 @@ impl SplitAttestation { committee_bits: attn.committee_bits, }) } + Attestation::Gloas(attn) => { + CompactIndexedAttestation::Gloas(CompactIndexedAttestationGloas { + attesting_indices, + aggregation_bits: attn.aggregation_bits, + signature: attestation.signature().clone(), + committee_bits: attn.committee_bits, + }) + } }; Self { @@ -131,6 +144,12 @@ impl CompactAttestationRef<'_, E> { .enumerate() .filter_map(|(index, bit)| if bit { Some(index as u64) } else { None }) .collect(), + CompactIndexedAttestation::Gloas(indexed_att) => indexed_att + .committee_bits + .iter() + .enumerate() + .filter_map(|(index, bit)| if bit { Some(index as u64) } else { None }) + .collect(), } } @@ -149,6 +168,12 @@ impl CompactAttestationRef<'_, E> { committee_bits: indexed_att.committee_bits.clone(), }) } + CompactIndexedAttestation::Gloas(indexed_att) => Attestation::Gloas(AttestationGloas { + aggregation_bits: indexed_att.aggregation_bits.clone(), + data: self.attestation_data(), + signature: indexed_att.signature.clone(), + committee_bits: indexed_att.committee_bits.clone(), + }), } } } @@ -180,6 +205,9 @@ impl CompactIndexedAttestation { CompactIndexedAttestation::Electra(this), CompactIndexedAttestation::Electra(other), ) => this.should_aggregate(other), + (CompactIndexedAttestation::Gloas(this), CompactIndexedAttestation::Gloas(other)) => { + this.should_aggregate(other) + } _ => false, } } @@ -195,6 +223,9 @@ impl CompactIndexedAttestation { CompactIndexedAttestation::Electra(this), CompactIndexedAttestation::Electra(other), ) => this.aggregate_same_committee(other), + (CompactIndexedAttestation::Gloas(this), CompactIndexedAttestation::Gloas(other)) => { + this.aggregate_same_committee(other) + } _ => false, } } @@ -299,6 +330,88 @@ impl CompactIndexedAttestationElectra { } } +impl CompactIndexedAttestationGloas { + pub fn should_aggregate(&self, other: &Self) -> bool { + // Only aggregate attestations in the same committee. + self.committee_bits == other.committee_bits + && self + .aggregation_bits + .intersection(&other.aggregation_bits) + .is_zero() + } + + /// Returns `true` if aggregated, otherwise `false`. + pub fn aggregate_same_committee(&mut self, other: &Self) -> bool { + if self.committee_bits != other.committee_bits { + return false; + } + self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits); + self.attesting_indices = self + .attesting_indices + .drain(..) + .merge(other.attesting_indices.iter().copied()) + .dedup() + .collect(); + self.signature.add_assign_aggregate(&other.signature); + true + } + + pub fn aggregate_with_disjoint_committees(&mut self, other: &Self) -> Option<()> { + if !self + .committee_bits + .intersection(&other.committee_bits) + .is_zero() + { + return None; + } + // The attestation being aggregated in must only have 1 committee bit set. + if other.committee_bits.num_set_bits() != 1 { + return None; + } + + // Check we are aggregating in increasing committee index order (so we can append + // aggregation bits). + if self.committee_bits.highest_set_bit() >= other.committee_bits.highest_set_bit() { + return None; + } + + self.committee_bits = self.committee_bits.union(&other.committee_bits); + if let Some(agg_bits) = + progressive_bitlist_extend(&self.aggregation_bits, &other.aggregation_bits) + { + self.aggregation_bits = agg_bits; + + self.attesting_indices = self + .attesting_indices + .drain(..) + .merge(other.attesting_indices.iter().copied()) + .dedup() + .collect(); + self.signature.add_assign_aggregate(&other.signature); + + return Some(()); + } + + None + } + + pub fn committee_index(&self) -> Option { + self.committee_bits + .iter() + .enumerate() + .find(|&(_, bit)| bit) + .map(|(index, _)| index as u64) + } + + pub fn get_committee_indices(&self) -> Vec { + self.committee_bits + .iter() + .enumerate() + .filter_map(|(index, bit)| if bit { Some(index as u64) } else { None }) + .collect() + } +} + // TODO(electra): upstream this or a more efficient implementation fn bitlist_extend(list1: &BitList, list2: &BitList) -> Option> { let new_length = list1.len() + list2.len(); @@ -318,6 +431,28 @@ fn bitlist_extend(list1: &BitList, list2: &BitList) -> Option Some(list) } +/// Progressive (Gloas, EIP-7916) equivalent of [`bitlist_extend`]. +fn progressive_bitlist_extend( + list1: &ProgressiveBitList, + list2: &ProgressiveBitList, +) -> Option { + let new_length = list1.len() + list2.len(); + let mut list = ProgressiveBitList::with_capacity(new_length).ok()?; + + // Copy bits from list1. + for (i, bit) in list1.iter().enumerate() { + list.set(i, bit).ok()?; + } + + // Copy bits from list2, starting from the end of list1. + let offset = list1.len(); + for (i, bit) in list2.iter().enumerate() { + list.set(offset + i, bit).ok()?; + } + + Some(list) +} + impl AttestationMap { pub fn insert(&mut self, attestation: Attestation, attesting_indices: Vec) { let SplitAttestation { @@ -347,10 +482,10 @@ impl AttestationMap { } } - /// Aggregate Electra attestations for the same attestation data signed by different + /// Aggregate Electra/Gloas attestations for the same attestation data signed by different /// committees. /// - /// Non-Electra attestations are left as-is. + /// Base attestations are left as-is. pub fn aggregate_across_committees(&mut self, checkpoint_key: CheckpointKey) { let Some(attestation_map) = self.checkpoint_map.get_mut(&checkpoint_key) else { return; @@ -364,6 +499,10 @@ impl AttestationMap { u64, CompactIndexedAttestationElectra, > = BTreeMap::new(); + let mut best_gloas_attestations_by_committee: BTreeMap< + u64, + CompactIndexedAttestationGloas, + > = BTreeMap::new(); for committee_attestation in unaggregated_attestations { let mut electra_attestation = match committee_attestation { @@ -372,14 +511,40 @@ impl AttestationMap { { att } - CompactIndexedAttestation::Electra(att) => { - // Aggregate already covers multiple committees, leave it as-is. - aggregated_attestations.push(CompactIndexedAttestation::Electra(att)); + CompactIndexedAttestation::Gloas(att) + if att.committee_bits.num_set_bits() == 1 => + { + let mut gloas_attestation = att; + if let Some(committee_index) = gloas_attestation.committee_index() { + if let Some(existing_attestation) = + best_gloas_attestations_by_committee.get_mut(&committee_index) + { + // Search for the best (most aggregation bits) attestation for this + // committee index. + if gloas_attestation.aggregation_bits.num_set_bits() + > existing_attestation.aggregation_bits.num_set_bits() + { + // New attestation is better than the previously known one for + // this committee. Replace it. + std::mem::swap(existing_attestation, &mut gloas_attestation); + } + // Put the inferior attestation into the list of aggregated + // attestations without performing any cross-committee aggregation. + aggregated_attestations + .push(CompactIndexedAttestation::Gloas(gloas_attestation)); + } else { + // First attestation seen for this committee. Place it in the map + // provisionally. + best_gloas_attestations_by_committee + .insert(committee_index, gloas_attestation); + } + } continue; } - CompactIndexedAttestation::Base(att) => { - // Leave as-is. - aggregated_attestations.push(CompactIndexedAttestation::Base(att)); + other => { + // Aggregates that already cover multiple committees and Base attestations + // are left as-is. + aggregated_attestations.push(other); continue; } }; @@ -415,6 +580,12 @@ impl AttestationMap { .push(CompactIndexedAttestation::Electra(on_chain_aggregate)); } + if let Some(on_chain_aggregate) = + Self::compute_on_chain_aggregate_gloas(best_gloas_attestations_by_committee) + { + aggregated_attestations.push(CompactIndexedAttestation::Gloas(on_chain_aggregate)); + } + *compact_indexed_attestations = aggregated_attestations; } } @@ -429,6 +600,16 @@ impl AttestationMap { Some(on_chain_aggregate) } + pub fn compute_on_chain_aggregate_gloas( + mut attestations_by_committee: BTreeMap>, + ) -> Option> { + let (_, mut on_chain_aggregate) = attestations_by_committee.pop_first()?; + for (_, attestation) in attestations_by_committee { + on_chain_aggregate.aggregate_with_disjoint_committees(&attestation); + } + Some(on_chain_aggregate) + } + /// Iterate all attestations matching the given `checkpoint_key`. pub fn get_attestations<'a>( &'a self, diff --git a/beacon_node/store/benches/hdiff.rs b/beacon_node/store/benches/hdiff.rs index 1e295c18a18..3c66b67ef9c 100644 --- a/beacon_node/store/benches/hdiff.rs +++ b/beacon_node/store/benches/hdiff.rs @@ -26,14 +26,14 @@ pub fn all_benches(c: &mut Criterion) { let mut target_state = source_state.clone(); // Change all balances for i in 0..n { - let balance = target_state.balances_mut().get_mut(i).unwrap(); + let balance = target_state.balances_mut().into_get_mut(i).unwrap(); *balance += rng.random_range(1..=1_000_000); } // And some validator records for _ in 0..validator_mutations { let index = rng.random_range(1..n); // TODO: Only change a few things, and not the pubkey - *target_state.validators_mut().get_mut(index).unwrap() = rand_validator(&mut rng); + *target_state.validators_mut().into_get_mut(index).unwrap() = rand_validator(&mut rng); } for _ in 0..validator_additions { append_validator(&mut target_state, &mut rng); @@ -99,7 +99,7 @@ fn append_validator(state: &mut BeaconState, mut rng: impl Rng) { .balances_mut() .push(32_000_000_000 + rng.random_range(1..=1_000_000_000)) .unwrap(); - if let Ok(inactivity_scores) = state.inactivity_scores_mut() { + if let Ok(mut inactivity_scores) = state.inactivity_scores_mut() { inactivity_scores.push(0).unwrap(); } state diff --git a/beacon_node/store/src/hdiff.rs b/beacon_node/store/src/hdiff.rs index 85ac56454c4..183d7d61a9b 100644 --- a/beacon_node/store/src/hdiff.rs +++ b/beacon_node/store/src/hdiff.rs @@ -173,16 +173,16 @@ impl HDiffBuffer { pub fn from_state(mut beacon_state: BeaconState) -> Self { let _t = metrics::start_timer(&metrics::STORE_BEACON_HDIFF_BUFFER_FROM_STATE_TIME); // Set state.balances to empty list, and then serialize state as ssz - let balances_list = std::mem::take(beacon_state.balances_mut()); - let inactivity_scores = if let Ok(inactivity_scores) = beacon_state.inactivity_scores_mut() + let balances_list = beacon_state.take_balances(); + let inactivity_scores = if let Ok(inactivity_scores) = beacon_state.take_inactivity_scores() { - std::mem::take(inactivity_scores).to_vec() + inactivity_scores.to_vec() } else { // If this state is pre-altair consider the list empty. If the target state // is post altair, all its items will show up in the diff as is. vec![] }; - let validators = std::mem::take(beacon_state.validators_mut()).to_vec(); + let validators = beacon_state.take_validators().to_vec(); let historical_roots = std::mem::take(beacon_state.historical_roots_mut()).to_vec(); let historical_summaries = if let Ok(historical_summaries) = beacon_state.historical_summaries_mut() { @@ -212,15 +212,19 @@ impl HDiffBuffer { let mut state = BeaconState::from_ssz_bytes(&self.state, spec).map_err(Error::InvalidSszState)?; - *state.balances_mut() = List::try_from_iter(self.balances.iter().copied()) + state + .set_balances_from_iter(self.balances.iter().copied()) .map_err(|_| Error::InvalidBalancesLength)?; - if let Ok(inactivity_scores) = state.inactivity_scores_mut() { - *inactivity_scores = List::try_from_iter(self.inactivity_scores.iter().copied()) + // Pre-altair states have no inactivity scores. + if state.fork_name_unchecked().altair_enabled() { + state + .set_inactivity_scores_from_iter(self.inactivity_scores.iter().copied()) .map_err(|_| Error::InvalidBalancesLength)?; } - *state.validators_mut() = List::try_from_iter(self.validators.iter().cloned()) + state + .set_validators_from_iter(self.validators.iter().cloned()) .map_err(|_| Error::InvalidBalancesLength)?; *state.historical_roots_mut() = List::try_from_iter(self.historical_roots.iter().copied()) diff --git a/consensus/fork_choice/tests/tests.rs b/consensus/fork_choice/tests/tests.rs index 848834b4d86..5df4dabf6cf 100644 --- a/consensus/fork_choice/tests/tests.rs +++ b/consensus/fork_choice/tests/tests.rs @@ -766,6 +766,9 @@ async fn invalid_attestation_empty_bitfield() { IndexedAttestation::Electra(att) => { att.attesting_indices = vec![].try_into().unwrap(); } + IndexedAttestation::Gloas(att) => { + att.attesting_indices = Default::default(); + } }, |result| { assert_invalid_attestation!(result, InvalidAttestation::EmptyAggregationBitfield) diff --git a/consensus/state_processing/src/common/get_attesting_indices.rs b/consensus/state_processing/src/common/get_attesting_indices.rs index ff9f60c5ea3..726d60c9dde 100644 --- a/consensus/state_processing/src/common/get_attesting_indices.rs +++ b/consensus/state_processing/src/common/get_attesting_indices.rs @@ -47,7 +47,7 @@ pub mod attesting_indices_base { pub mod attesting_indices_electra { use crate::per_block_processing::errors::{AttestationInvalid as Invalid, BlockOperationError}; use safe_arith::SafeArith; - use ssz_types::{BitList, BitVector, VariableList}; + use ssz_types::{BitVector, VariableList}; use std::collections::HashSet; use types::*; @@ -58,7 +58,7 @@ pub mod attesting_indices_electra { committees: &[BeaconCommittee], attestation: &AttestationElectra, ) -> Result, BlockOperationError> { - let attesting_indices = get_attesting_indices::( + let attesting_indices = get_attesting_indices::( committees, &attestation.aggregation_bits, &attestation.committee_bits, @@ -85,15 +85,18 @@ pub mod attesting_indices_electra { att: &AttestationElectra, ) -> Result, BeaconStateError> { let committees = state.get_beacon_committees_at_slot(att.data.slot)?; - get_attesting_indices::(&committees, &att.aggregation_bits, &att.committee_bits) + get_attesting_indices::(&committees, &att.aggregation_bits, &att.committee_bits) } /// Returns validator indices which participated in the attestation, sorted by increasing index. /// /// Committees must be sorted by ascending order 0..committees_per_slot - pub fn get_attesting_indices( + /// + /// Generic over the aggregation bitfield type so it can serve both Electra (`BitList`) and + /// Gloas (`ProgressiveBitList`, EIP-7688) attestations. + pub fn get_attesting_indices( committees: &[BeaconCommittee], - aggregation_bits: &BitList, + aggregation_bits: &ssz::Bitfield, committee_bits: &BitVector, ) -> Result, BeaconStateError> { let mut attesting_indices = vec![]; @@ -159,6 +162,55 @@ pub mod attesting_indices_electra { } } +pub mod attesting_indices_gloas { + use crate::common::attesting_indices_electra; + use crate::per_block_processing::errors::{AttestationInvalid as Invalid, BlockOperationError}; + use ssz_types::ProgressiveVariableList; + use types::*; + + /// Compute a Gloas `IndexedAttestation` given a list of committees. + /// + /// Committees must be sorted by ascending order 0..committees_per_slot + pub fn get_indexed_attestation( + committees: &[BeaconCommittee], + attestation: &AttestationGloas, + ) -> Result, BlockOperationError> { + let attesting_indices = attesting_indices_electra::get_attesting_indices::( + committees, + &attestation.aggregation_bits, + &attestation.committee_bits, + )?; + + Ok(IndexedAttestation::Gloas(IndexedAttestationGloas { + attesting_indices: ProgressiveVariableList::new(attesting_indices), + data: attestation.data.clone(), + signature: attestation.signature.clone(), + _phantom: std::marker::PhantomData, + })) + } + + pub fn get_indexed_attestation_from_state( + beacon_state: &BeaconState, + attestation: &AttestationGloas, + ) -> Result, BlockOperationError> { + let committees = beacon_state.get_beacon_committees_at_slot(attestation.data.slot)?; + get_indexed_attestation(&committees, attestation) + } + + /// Shortcut for getting the attesting indices while fetching the committee from the state's cache. + pub fn get_attesting_indices_from_state( + state: &BeaconState, + att: &AttestationGloas, + ) -> Result, BeaconStateError> { + let committees = state.get_beacon_committees_at_slot(att.data.slot)?; + attesting_indices_electra::get_attesting_indices::( + &committees, + &att.aggregation_bits, + &att.committee_bits, + ) + } +} + /// Shortcut for getting the attesting indices while fetching the committee from the state's cache. pub fn get_attesting_indices_from_state( state: &BeaconState, @@ -175,5 +227,8 @@ pub fn get_attesting_indices_from_state( AttestationRef::Electra(att) => { attesting_indices_electra::get_attesting_indices_from_state::(state, att) } + AttestationRef::Gloas(att) => { + attesting_indices_gloas::get_attesting_indices_from_state::(state, att) + } } } diff --git a/consensus/state_processing/src/common/mod.rs b/consensus/state_processing/src/common/mod.rs index e550a6c48b1..e177ef7179e 100644 --- a/consensus/state_processing/src/common/mod.rs +++ b/consensus/state_processing/src/common/mod.rs @@ -12,7 +12,8 @@ pub mod update_progressive_balances_cache; pub use deposit_data_tree::DepositDataTree; pub use get_attestation_participation::get_attestation_participation_flag_indices; pub use get_attesting_indices::{ - attesting_indices_base, attesting_indices_electra, get_attesting_indices_from_state, + attesting_indices_base, attesting_indices_electra, attesting_indices_gloas, + get_attesting_indices_from_state, }; pub use get_payload_attesting_indices::{ get_indexed_payload_attestation, get_payload_attesting_indices, diff --git a/consensus/state_processing/src/consensus_context.rs b/consensus/state_processing/src/consensus_context.rs index bc7bd203847..6430c0eb7c1 100644 --- a/consensus/state_processing/src/consensus_context.rs +++ b/consensus/state_processing/src/consensus_context.rs @@ -1,6 +1,7 @@ use crate::EpochCacheError; use crate::common::{ - attesting_indices_base, attesting_indices_electra, get_indexed_payload_attestation, + attesting_indices_base, attesting_indices_electra, attesting_indices_gloas, + get_indexed_payload_attestation, }; use crate::per_block_processing::errors::{ AttestationInvalid, BlockOperationError, PayloadAttestationInvalid, @@ -181,6 +182,14 @@ impl ConsensusContext { Ok(vacant.insert(indexed_attestation)) } }, + AttestationRef::Gloas(attn) => match self.indexed_attestations.entry(key) { + Entry::Occupied(occupied) => Ok(occupied.into_mut()), + Entry::Vacant(vacant) => { + let indexed_attestation = + attesting_indices_gloas::get_indexed_attestation_from_state(state, attn)?; + Ok(vacant.insert(indexed_attestation)) + } + }, } .map(|indexed_attestation| (*indexed_attestation).to_ref()) } diff --git a/consensus/state_processing/src/genesis.rs b/consensus/state_processing/src/genesis.rs index c643ad56e34..37882cf8d31 100644 --- a/consensus/state_processing/src/genesis.rs +++ b/consensus/state_processing/src/genesis.rs @@ -227,7 +227,8 @@ pub fn process_activations( state: &mut BeaconState, spec: &ChainSpec, ) -> Result<(), BeaconStateError> { - let (validators, balances, _) = state.validators_and_balances_and_progressive_balances_mut(); + let (mut validators, balances, _) = + state.validators_and_balances_and_progressive_balances_mut(); let mut validators_iter = validators.iter_cow(); while let Some((index, validator)) = validators_iter.next_cow() { let validator = validator.into_mut()?; diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index f13f2a339b8..9cda1efa4f9 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -561,7 +561,7 @@ pub fn process_parent_execution_payload( state: &mut BeaconState, - requests: &ExecutionRequests, + requests: &ExecutionRequestsGloas, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { let parent_bid = state.latest_execution_payload_bid()?.clone(); let parent_slot = parent_bid.slot; let parent_epoch = parent_slot.epoch(E::slots_per_epoch()); + // [New in Gloas:EIP7688] the request lists are progressive and unbounded at the type level, + // so the spec's per-payload limits must be enforced at runtime. + let request_checks: [(&str, usize, usize); 3] = [ + ( + "deposit_requests", + requests.deposits.len(), + E::MaxDepositRequestsPerPayload::to_usize(), + ), + ( + "withdrawal_requests", + requests.withdrawals.len(), + E::MaxWithdrawalRequestsPerPayload::to_usize(), + ), + ( + "consolidation_requests", + requests.consolidations.len(), + E::MaxConsolidationRequestsPerPayload::to_usize(), + ), + ]; + for (kind, length, max) in request_checks { + block_verify!( + length <= max, + BlockProcessingError::OperationListTooLong { kind, length, max } + ); + } + // Process execution requests from the parent's payload - process_operations::process_deposit_requests_post_gloas(state, &requests.deposits, spec)?; - process_operations::process_withdrawal_requests(state, &requests.withdrawals, spec)?; - process_operations::process_consolidation_requests(state, &requests.consolidations, spec)?; + process_operations::process_deposit_requests_post_gloas(state, requests.deposits.iter(), spec)?; + process_operations::process_withdrawal_requests(state, requests.withdrawals.iter(), spec)?; + process_operations::process_consolidation_requests( + state, + requests.consolidations.iter(), + spec, + )?; // Queue the builder payment if parent_epoch == state.current_epoch() { diff --git a/consensus/state_processing/src/per_block_processing/errors.rs b/consensus/state_processing/src/per_block_processing/errors.rs index 93d668c8c9a..612c69a5672 100644 --- a/consensus/state_processing/src/per_block_processing/errors.rs +++ b/consensus/state_processing/src/per_block_processing/errors.rs @@ -22,6 +22,12 @@ pub enum BlockProcessingError { expected: usize, found: usize, }, + /// [New in Gloas:EIP7688] An operation list exceeds the spec's runtime limit. + OperationListTooLong { + kind: &'static str, + length: usize, + max: usize, + }, HeaderInvalid { reason: HeaderInvalid, }, @@ -421,6 +427,8 @@ impl From> pub enum IndexedAttestationInvalid { /// The number of indices is 0. IndicesEmpty, + /// The number of indices exceeds the runtime maximum \[New in Gloas:EIP7688\]. + IndicesExceedMaxLength { length: usize, max: usize }, /// The validator indices were not in increasing order. /// /// The error occurred between the given `index` and `index + 1` diff --git a/consensus/state_processing/src/per_block_processing/is_valid_indexed_attestation.rs b/consensus/state_processing/src/per_block_processing/is_valid_indexed_attestation.rs index 4bad3315cc4..f4eface0c6f 100644 --- a/consensus/state_processing/src/per_block_processing/is_valid_indexed_attestation.rs +++ b/consensus/state_processing/src/per_block_processing/is_valid_indexed_attestation.rs @@ -2,6 +2,7 @@ use super::errors::{BlockOperationError, IndexedAttestationInvalid as Invalid}; use super::signature_sets::{get_pubkey_from_state, indexed_attestation_signature_set}; use crate::VerifySignatures; use itertools::Itertools; +use typenum::Unsigned; use types::*; type Result = std::result::Result>; @@ -22,6 +23,18 @@ pub fn is_valid_indexed_attestation( // Verify that indices aren't empty verify!(!indices.is_empty(), Invalid::IndicesEmpty); + // [New in Gloas:EIP7688] the progressive `attesting_indices` list is unbounded at the type + // level, so the spec's `MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT` limit must be + // enforced at runtime. This is a no-op for pre-Gloas attestations whose SSZ types enforce an + // equal or tighter bound. + verify!( + indices.len() <= E::MaxValidatorsPerSlot::to_usize(), + Invalid::IndicesExceedMaxLength { + length: indices.len(), + max: E::MaxValidatorsPerSlot::to_usize(), + } + ); + // Check that indices are sorted and unique let check_sorted = |list: &[u64]| -> Result<()> { list.iter() diff --git a/consensus/state_processing/src/per_block_processing/process_operations.rs b/consensus/state_processing/src/per_block_processing/process_operations.rs index f88a325d4e9..05438072223 100644 --- a/consensus/state_processing/src/per_block_processing/process_operations.rs +++ b/consensus/state_processing/src/per_block_processing/process_operations.rs @@ -22,9 +22,15 @@ pub fn process_operations>( ctxt: &mut ConsensusContext, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { + // [New in Gloas:EIP7688] The operation lists are `ProgressiveList`s without type-level + // limits, so the spec's per-block limits are enforced at runtime instead. + if state.fork_name_unchecked().gloas_enabled() { + verify_operation_list_lengths(block_body)?; + } + process_proposer_slashings( state, - block_body.proposer_slashings(), + &block_body.proposer_slashings().to_cow_slice(), verify_signatures, ctxt, spec, @@ -37,11 +43,21 @@ pub fn process_operations>( spec, )?; process_attestations(state, block_body, verify_signatures, ctxt, spec)?; - process_deposits(state, block_body.deposits(), spec)?; - process_exits(state, block_body.voluntary_exits(), verify_signatures, spec)?; + process_deposits(state, &block_body.deposits().to_cow_slice(), spec)?; + process_exits( + state, + &block_body.voluntary_exits().to_cow_slice(), + verify_signatures, + spec, + )?; if let Ok(bls_to_execution_changes) = block_body.bls_to_execution_changes() { - process_bls_to_execution_changes(state, bls_to_execution_changes, verify_signatures, spec)?; + process_bls_to_execution_changes( + state, + &bls_to_execution_changes.to_cow_slice(), + verify_signatures, + spec, + )?; } if state.fork_name_unchecked().gloas_enabled() { @@ -59,10 +75,14 @@ pub fn process_operations>( &block_body.execution_requests()?.deposits, spec, )?; - process_withdrawal_requests(state, &block_body.execution_requests()?.withdrawals, spec)?; + process_withdrawal_requests( + state, + block_body.execution_requests()?.withdrawals.iter(), + spec, + )?; process_consolidation_requests( state, - &block_body.execution_requests()?.consolidations, + block_body.execution_requests()?.consolidations.iter(), spec, )?; } @@ -70,6 +90,62 @@ pub fn process_operations>( Ok(()) } +/// Verify the lengths of the (progressive) operation lists against the spec's runtime limits. +/// +/// [New in Gloas:EIP7688]: these limits used to be enforced by the SSZ types, but +/// `ProgressiveList` is unbounded so they must be checked explicitly. +pub fn verify_operation_list_lengths>( + block_body: BeaconBlockBodyRef, +) -> Result<(), BlockProcessingError> { + let checks: [(&str, usize, usize); 6] = [ + ( + "proposer_slashings", + block_body.proposer_slashings().len(), + E::MaxProposerSlashings::to_usize(), + ), + ( + "attester_slashings", + block_body.attester_slashings_len(), + E::MaxAttesterSlashingsElectra::to_usize(), + ), + ( + "attestations", + block_body.attestations_len(), + E::MaxAttestationsElectra::to_usize(), + ), + ( + "voluntary_exits", + block_body.voluntary_exits().len(), + E::MaxVoluntaryExits::to_usize(), + ), + ( + "bls_to_execution_changes", + block_body + .bls_to_execution_changes() + .map(|changes| changes.len()) + .unwrap_or(0), + E::MaxBlsToExecutionChanges::to_usize(), + ), + ( + "payload_attestations", + block_body + .payload_attestations() + .map(|atts| atts.len()) + .unwrap_or(0), + E::MaxPayloadAttestations::to_usize(), + ), + ]; + + for (kind, length, max) in checks { + block_verify!( + length <= max, + BlockProcessingError::OperationListTooLong { kind, length, max } + ); + } + + Ok(()) +} + pub mod base { use super::*; @@ -192,7 +268,7 @@ pub mod altair_deneb { let validator_slashed = state.slashings_cache().is_slashed(index); for (flag_index, &weight) in PARTICIPATION_FLAG_WEIGHTS.iter().enumerate() { - let epoch_participation = state.get_epoch_participation_mut( + let mut epoch_participation = state.get_epoch_participation_mut( data.target.epoch, previous_epoch, current_epoch, @@ -312,7 +388,7 @@ pub mod gloas { 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( + let mut epoch_participation = state.get_epoch_participation_mut( data.target.epoch, previous_epoch, current_epoch, @@ -731,7 +807,7 @@ pub fn apply_deposit( if let Some(index) = validator_index { // [Modified in Electra:EIP7251] - if let Ok(pending_deposits) = state.pending_deposits_mut() { + if let Ok(mut pending_deposits) = state.pending_deposits_mut() { pending_deposits.push(PendingDeposit { pubkey: deposit_data.pubkey, withdrawal_credentials: deposit_data.withdrawal_credentials, @@ -764,7 +840,7 @@ pub fn apply_deposit( )?; // [New in Electra:EIP7251] - if let Ok(pending_deposits) = state.pending_deposits_mut() { + if let Ok(mut pending_deposits) = state.pending_deposits_mut() { pending_deposits.push(PendingDeposit { pubkey: deposit_data.pubkey, withdrawal_credentials: deposit_data.withdrawal_credentials, @@ -779,9 +855,9 @@ pub fn apply_deposit( } // Make sure to build the pubkey cache before calling this function -pub fn process_withdrawal_requests( +pub fn process_withdrawal_requests<'a, E: EthSpec>( state: &mut BeaconState, - requests: &[WithdrawalRequest], + requests: impl IntoIterator, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { for request in requests { @@ -887,7 +963,7 @@ pub fn process_deposit_requests_pre_gloas( let slot = state.slot(); // [New in Electra:EIP7251] - if let Ok(pending_deposits) = state.pending_deposits_mut() { + if let Ok(mut pending_deposits) = state.pending_deposits_mut() { pending_deposits.push(PendingDeposit { pubkey: request.pubkey, withdrawal_credentials: request.withdrawal_credentials, @@ -901,9 +977,9 @@ pub fn process_deposit_requests_pre_gloas( Ok(()) } -pub fn process_deposit_requests_post_gloas( +pub fn process_deposit_requests_post_gloas<'a, E: EthSpec>( state: &mut BeaconState, - deposit_requests: &[DepositRequest], + deposit_requests: impl IntoIterator, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { for request in deposit_requests { @@ -1038,9 +1114,9 @@ pub fn apply_deposit_for_builder( } // Make sure to build the pubkey cache before calling this function -pub fn process_consolidation_requests( +pub fn process_consolidation_requests<'a, E: EthSpec>( state: &mut BeaconState, - consolidation_requests: &[ConsolidationRequest], + consolidation_requests: impl IntoIterator, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { for request in consolidation_requests { diff --git a/consensus/state_processing/src/per_block_processing/tests.rs b/consensus/state_processing/src/per_block_processing/tests.rs index 593a2557e86..69b71e93e3a 100644 --- a/consensus/state_processing/src/per_block_processing/tests.rs +++ b/consensus/state_processing/src/per_block_processing/tests.rs @@ -213,7 +213,6 @@ async fn valid_4_deposits() { let mut state = harness.get_current_state(); let (deposits, state) = harness.make_deposits(&mut state, 4, None, None); - let deposits = VariableList::try_from(deposits).unwrap(); let mut head_block = harness .chain @@ -222,9 +221,17 @@ async fn valid_4_deposits() { .clone() .deconstruct() .0; - *head_block.to_mut().body_mut().deposits_mut() = deposits; + head_block + .to_mut() + .body_mut() + .set_deposits_from_iter(deposits) + .unwrap(); - let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec); + let result = process_operations::process_deposits( + state, + &head_block.body().deposits().to_cow_slice(), + &spec, + ); // Expecting Ok because these are valid deposits. assert_eq!(result, Ok(())); @@ -237,7 +244,6 @@ async fn invalid_deposit_deposit_count_too_big() { let mut state = harness.get_current_state(); let (deposits, state) = harness.make_deposits(&mut state, 1, None, None); - let deposits = VariableList::try_from(deposits).unwrap(); let mut head_block = harness .chain @@ -246,11 +252,19 @@ async fn invalid_deposit_deposit_count_too_big() { .clone() .deconstruct() .0; - *head_block.to_mut().body_mut().deposits_mut() = deposits; + head_block + .to_mut() + .body_mut() + .set_deposits_from_iter(deposits) + .unwrap(); let big_deposit_count = NUM_DEPOSITS + 1; state.eth1_data_mut().deposit_count = big_deposit_count; - let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec); + let result = process_operations::process_deposits( + state, + &head_block.body().deposits().to_cow_slice(), + &spec, + ); // Expecting DepositCountInvalid because we incremented the deposit_count assert_eq!( @@ -269,7 +283,6 @@ async fn invalid_deposit_count_too_small() { let mut state = harness.get_current_state(); let (deposits, state) = harness.make_deposits(&mut state, 1, None, None); - let deposits = VariableList::try_from(deposits).unwrap(); let mut head_block = harness .chain @@ -278,11 +291,19 @@ async fn invalid_deposit_count_too_small() { .clone() .deconstruct() .0; - *head_block.to_mut().body_mut().deposits_mut() = deposits; + head_block + .to_mut() + .body_mut() + .set_deposits_from_iter(deposits) + .unwrap(); let small_deposit_count = NUM_DEPOSITS - 1; state.eth1_data_mut().deposit_count = small_deposit_count; - let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec); + let result = process_operations::process_deposits( + state, + &head_block.body().deposits().to_cow_slice(), + &spec, + ); // Expecting DepositCountInvalid because we decremented the deposit_count assert_eq!( @@ -301,7 +322,6 @@ async fn invalid_deposit_bad_merkle_proof() { let mut state = harness.get_current_state(); let (deposits, state) = harness.make_deposits(&mut state, 1, None, None); - let deposits = VariableList::try_from(deposits).unwrap(); let mut head_block = harness .chain @@ -310,13 +330,21 @@ async fn invalid_deposit_bad_merkle_proof() { .clone() .deconstruct() .0; - *head_block.to_mut().body_mut().deposits_mut() = deposits; + head_block + .to_mut() + .body_mut() + .set_deposits_from_iter(deposits) + .unwrap(); let bad_index = state.eth1_deposit_index() as usize; // Manually offsetting deposit count and index to trigger bad merkle proof state.eth1_data_mut().deposit_count += 1; *state.eth1_deposit_index_mut() += 1; - let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec); + let result = process_operations::process_deposits( + state, + &head_block.body().deposits().to_cow_slice(), + &spec, + ); // Expecting BadMerkleProof because the proofs were created with different indices assert_eq!( @@ -336,7 +364,6 @@ async fn invalid_deposit_wrong_sig() { let (deposits, state) = harness.make_deposits(&mut state, 1, None, Some(SignatureBytes::empty())); - let deposits = VariableList::try_from(deposits).unwrap(); let mut head_block = harness .chain @@ -345,9 +372,17 @@ async fn invalid_deposit_wrong_sig() { .clone() .deconstruct() .0; - *head_block.to_mut().body_mut().deposits_mut() = deposits; + head_block + .to_mut() + .body_mut() + .set_deposits_from_iter(deposits) + .unwrap(); - let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec); + let result = process_operations::process_deposits( + state, + &head_block.body().deposits().to_cow_slice(), + &spec, + ); // Expecting Ok(()) even though the block signature does not correspond to the correct public key assert_eq!(result, Ok(())); } @@ -360,7 +395,6 @@ async fn invalid_deposit_invalid_pub_key() { let (deposits, state) = harness.make_deposits(&mut state, 1, Some(PublicKeyBytes::empty()), None); - let deposits = VariableList::try_from(deposits).unwrap(); let mut head_block = harness .chain @@ -369,9 +403,17 @@ async fn invalid_deposit_invalid_pub_key() { .clone() .deconstruct() .0; - *head_block.to_mut().body_mut().deposits_mut() = deposits; + head_block + .to_mut() + .body_mut() + .set_deposits_from_iter(deposits) + .unwrap(); - let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec); + let result = process_operations::process_deposits( + state, + &head_block.body().deposits().to_cow_slice(), + &spec, + ); // Expecting Ok(()) even though we passed in invalid publickeybytes in the public key field of the deposit data. assert_eq!(result, Ok(())); @@ -390,14 +432,12 @@ async fn invalid_attestation_no_committee_for_index() { .clone() .deconstruct() .0; - head_block - .to_mut() - .body_mut() - .attestations_mut() - .next() - .unwrap() - .data_mut() - .index += 1; + let mut first = true; + head_block.to_mut().body_mut().attestations_apply(|att| { + if std::mem::take(&mut first) { + att.into_data_mut().index += 1; + } + }); let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attestations( &mut state, @@ -439,14 +479,12 @@ async fn invalid_attestation_wrong_justified_checkpoint() { .source; let mut new_justified_checkpoint = old_justified_checkpoint; new_justified_checkpoint.epoch += Epoch::new(1); - head_block - .to_mut() - .body_mut() - .attestations_mut() - .next() - .unwrap() - .data_mut() - .source = new_justified_checkpoint; + let mut first = true; + head_block.to_mut().body_mut().attestations_apply(|att| { + if std::mem::take(&mut first) { + att.into_data_mut().source = new_justified_checkpoint; + } + }); let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attestations( @@ -486,14 +524,18 @@ async fn invalid_attestation_bad_aggregation_bitfield_len() { .deconstruct() .0; // Use Electra method since harness runs at Electra fork - *head_block - .to_mut() - .body_mut() - .attestations_mut() - .next() - .unwrap() - .aggregation_bits_electra_mut() - .unwrap() = Bitfield::with_capacity(spec.target_committee_size).unwrap(); + let mut first = true; + head_block.to_mut().body_mut().attestations_apply(|att| { + if std::mem::take(&mut first) { + let AttestationRefMut::Electra(att) = att else { + panic!("harness should produce Electra attestations"); + }; + att.aggregation_bits = Bitfield::< + ssz_types::length::Variable<::MaxValidatorsPerSlot>, + >::with_capacity(spec.target_committee_size) + .unwrap(); + } + }); let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attestations( @@ -527,13 +569,12 @@ async fn invalid_attestation_bad_signature() { .clone() .deconstruct() .0; - *head_block - .to_mut() - .body_mut() - .attestations_mut() - .next() - .unwrap() - .signature_mut() = AggregateSignature::empty(); + let mut first = true; + head_block.to_mut().body_mut().attestations_apply(|att| { + if std::mem::take(&mut first) { + *att.into_signature_mut() = AggregateSignature::empty(); + } + }); let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attestations( @@ -570,14 +611,12 @@ async fn invalid_attestation_included_too_early() { .0; let new_attesation_slot = head_block.body().attestations().next().unwrap().data().slot + Slot::new(MainnetEthSpec::slots_per_epoch()); - head_block - .to_mut() - .body_mut() - .attestations_mut() - .next() - .unwrap() - .data_mut() - .slot = new_attesation_slot; + let mut first = true; + head_block.to_mut().body_mut().attestations_apply(|att| { + if std::mem::take(&mut first) { + att.into_data_mut().slot = new_attesation_slot; + } + }); let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attestations( @@ -620,15 +659,12 @@ async fn invalid_attestation_target_epoch_slot_mismatch() { .clone() .deconstruct() .0; - head_block - .to_mut() - .body_mut() - .attestations_mut() - .next() - .unwrap() - .data_mut() - .target - .epoch += Epoch::new(1); + let mut first = true; + head_block.to_mut().body_mut().attestations_apply(|att| { + if std::mem::take(&mut first) { + att.into_data_mut().target.epoch += Epoch::new(1); + } + }); let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attestations( @@ -684,6 +720,9 @@ async fn invalid_attester_slashing_not_slashable() { AttesterSlashing::Electra(attester_slashing) => { attester_slashing.attestation_1 = attester_slashing.attestation_2.clone(); } + AttesterSlashing::Gloas(attester_slashing) => { + attester_slashing.attestation_1 = attester_slashing.attestation_2.clone(); + } } let mut state = harness.get_current_state(); @@ -721,6 +760,10 @@ async fn invalid_attester_slashing_1_invalid() { attester_slashing.attestation_1.attesting_indices = VariableList::try_from(vec![2, 1]).unwrap(); } + AttesterSlashing::Gloas(attester_slashing) => { + attester_slashing.attestation_1.attesting_indices = + ssz_types::ProgressiveVariableList::new(vec![2, 1]); + } } let mut state = harness.get_current_state(); @@ -761,6 +804,10 @@ async fn invalid_attester_slashing_2_invalid() { attester_slashing.attestation_2.attesting_indices = VariableList::try_from(vec![2, 1]).unwrap(); } + AttesterSlashing::Gloas(attester_slashing) => { + attester_slashing.attestation_2.attesting_indices = + ssz_types::ProgressiveVariableList::new(vec![2, 1]); + } } let mut state = harness.get_current_state(); diff --git a/consensus/state_processing/src/per_block_processing/verify_attestation.rs b/consensus/state_processing/src/per_block_processing/verify_attestation.rs index 64b7a31afb8..cf1954cb0d6 100644 --- a/consensus/state_processing/src/per_block_processing/verify_attestation.rs +++ b/consensus/state_processing/src/per_block_processing/verify_attestation.rs @@ -79,6 +79,10 @@ pub fn verify_attestation_for_state<'ctxt, E: EthSpec>( verify!(data.index == 0, Invalid::BadCommitteeIndex); } } + AttestationRef::Gloas(_) => { + // [Modified in Gloas:EIP7732] `index < 2` indicates the payload status being attested. + verify!(data.index < 2, Invalid::BadOverloadedDataIndex); + } } // Verify the Casper FFG vote. diff --git a/consensus/state_processing/src/per_block_processing/withdrawals.rs b/consensus/state_processing/src/per_block_processing/withdrawals.rs index 8a09e35cdfc..1324f7fda6e 100644 --- a/consensus/state_processing/src/per_block_processing/withdrawals.rs +++ b/consensus/state_processing/src/per_block_processing/withdrawals.rs @@ -4,7 +4,7 @@ use crate::per_block_processing::builder::{ is_builder_index, }; use crate::per_block_processing::errors::BlockProcessingError; -use milhouse::List; +use milhouse::ProgressiveList; use safe_arith::{SafeArith, SafeArithIter}; use tree_hash::TreeHash; use types::{ @@ -354,7 +354,7 @@ fn update_payload_expected_withdrawals( state: &mut BeaconState, withdrawals: &Withdrawals, ) -> Result<(), BlockProcessingError> { - *state.payload_expected_withdrawals_mut()? = List::new(withdrawals.to_vec())?; + *state.payload_expected_withdrawals_mut()? = ProgressiveList::new(withdrawals.to_vec())?; Ok(()) } diff --git a/consensus/state_processing/src/per_epoch_processing/altair/participation_flag_updates.rs b/consensus/state_processing/src/per_epoch_processing/altair/participation_flag_updates.rs index f9b60437548..3d11b7de3a3 100644 --- a/consensus/state_processing/src/per_epoch_processing/altair/participation_flag_updates.rs +++ b/consensus/state_processing/src/per_epoch_processing/altair/participation_flag_updates.rs @@ -1,15 +1,10 @@ use crate::EpochProcessingError; -use milhouse::List; -use types::attestation::ParticipationFlags; use types::core::EthSpec; use types::state::BeaconState; pub fn process_participation_flag_updates( state: &mut BeaconState, ) -> Result<(), EpochProcessingError> { - *state.previous_epoch_participation_mut()? = - std::mem::take(state.current_epoch_participation_mut()?); - *state.current_epoch_participation_mut()? = - List::repeat(ParticipationFlags::default(), state.validators().len())?; + state.rotate_participation_flags()?; Ok(()) } diff --git a/consensus/state_processing/src/per_epoch_processing/effective_balance_updates.rs b/consensus/state_processing/src/per_epoch_processing/effective_balance_updates.rs index 6d00cb77ff1..569353958ac 100644 --- a/consensus/state_processing/src/per_epoch_processing/effective_balance_updates.rs +++ b/consensus/state_processing/src/per_epoch_processing/effective_balance_updates.rs @@ -20,7 +20,8 @@ pub fn process_effective_balance_updates( .safe_div(spec.hysteresis_quotient)?; let downward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_downward_multiplier)?; let upward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_upward_multiplier)?; - let (validators, balances, _) = state.validators_and_balances_and_progressive_balances_mut(); + let (mut validators, balances, _) = + state.validators_and_balances_and_progressive_balances_mut(); let mut validators_iter = validators.iter_cow(); while let Some((index, validator)) = validators_iter.next_cow() { diff --git a/consensus/state_processing/src/per_epoch_processing/epoch_processing_summary.rs b/consensus/state_processing/src/per_epoch_processing/epoch_processing_summary.rs index 3c043a65f2d..377de24ee91 100644 --- a/consensus/state_processing/src/per_epoch_processing/epoch_processing_summary.rs +++ b/consensus/state_processing/src/per_epoch_processing/epoch_processing_summary.rs @@ -1,11 +1,11 @@ use super::base::{TotalBalances, ValidatorStatus, validator_statuses::InclusionInfo}; use crate::metrics; -use milhouse::List; +use milhouse::AnyList; use std::sync::Arc; use types::{ BeaconStateError, Epoch, EthSpec, ParticipationFlags, ProgressiveBalancesCache, SyncCommittee, consts::altair::{TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX}, - state::Validators, + state::ValidatorsOwned, }; /// Provides a summary of validator participation during the epoch. @@ -26,20 +26,20 @@ pub enum EpochProcessingSummary { #[derive(PartialEq, Debug)] pub struct ParticipationEpochSummary { /// Copy of the validator registry prior to mutation. - validators: Validators, + validators: ValidatorsOwned, /// Copy of the participation flags for the previous epoch. - previous_epoch_participation: List, + previous_epoch_participation: AnyList, /// Copy of the participation flags for the current epoch. - current_epoch_participation: List, + current_epoch_participation: AnyList, previous_epoch: Epoch, current_epoch: Epoch, } impl ParticipationEpochSummary { pub fn new( - validators: Validators, - previous_epoch_participation: List, - current_epoch_participation: List, + validators: ValidatorsOwned, + previous_epoch_participation: AnyList, + current_epoch_participation: AnyList, previous_epoch: Epoch, current_epoch: Epoch, ) -> Self { diff --git a/consensus/state_processing/src/per_epoch_processing/single_pass.rs b/consensus/state_processing/src/per_epoch_processing/single_pass.rs index 881e6bb16c1..78c7fa89008 100644 --- a/consensus/state_processing/src/per_epoch_processing/single_pass.rs +++ b/consensus/state_processing/src/per_epoch_processing/single_pass.rs @@ -200,11 +200,11 @@ pub fn process_epoch_single_pass( // Split the state into several disjoint mutable borrows. let ( - validators, - balances, + mut validators, + mut balances, previous_epoch_participation, current_epoch_participation, - inactivity_scores, + mut inactivity_scores, progressive_balances, exit_cache, epoch_cache, @@ -215,9 +215,9 @@ pub fn process_epoch_single_pass( // Take a snapshot of the validators and participation before mutating. This is used for // informational purposes (e.g. by the validator monitor). let summary = ParticipationEpochSummary::new( - validators.clone(), - previous_epoch_participation.clone(), - current_epoch_participation.clone(), + validators.as_ref().to_owned_list(), + previous_epoch_participation.to_owned_list(), + current_epoch_participation.to_owned_list(), previous_epoch, current_epoch, ); @@ -379,16 +379,15 @@ pub fn process_epoch_single_pass( // of the `pending_deposits` list. But we may as well preserve the write ordering used // by the spec and do this first. if let Some(ctxt) = pending_deposits_ctxt { - let mut new_balance_deposits = List::try_from_iter( - state - .pending_deposits()? - .iter_from(ctxt.next_deposit_index)? - .cloned(), - )?; + let mut new_balance_deposits: Vec = state + .pending_deposits()? + .iter_from(ctxt.next_deposit_index)? + .cloned() + .collect(); for deposit in ctxt.deposits_to_postpone { - new_balance_deposits.push(deposit)?; + new_balance_deposits.push(deposit); } - *state.pending_deposits_mut()? = new_balance_deposits; + state.set_pending_deposits_from_iter(new_balance_deposits)?; *state.deposit_balance_to_consume_mut()? = ctxt.deposit_balance_to_consume; // `new_validator_deposits` may contain multiple deposits with the same pubkey where @@ -422,7 +421,7 @@ pub fn process_epoch_single_pass( if conf.effective_balance_updates { // Re-process effective balance updates for validators affected by top-up of new validators. let ( - validators, + mut validators, balances, _, current_epoch_participation, @@ -1260,9 +1259,9 @@ fn process_pending_consolidations( ) -> Result<(), Error> { let mut next_pending_consolidation: usize = 0; let next_epoch = state.next_epoch()?; - let pending_consolidations = state.pending_consolidations()?.clone(); + let pending_consolidations = state.pending_consolidations()?.to_owned_list(); - for pending_consolidation in &pending_consolidations { + for pending_consolidation in pending_consolidations.iter() { let source_index = pending_consolidation.source_index as usize; let target_index = pending_consolidation.target_index as usize; let source_validator = state.get_validator(source_index)?; @@ -1300,7 +1299,7 @@ fn process_pending_consolidations( } // Re-process effective balance updates for validators affected by consolidations. - let (validators, balances, _, current_epoch_participation, _, progressive_balances, _, _) = + let (mut validators, balances, _, current_epoch_participation, _, progressive_balances, _, _) = state.mutable_validator_fields()?; for &validator_index in validators_in_consolidations { let balance = *balances diff --git a/consensus/state_processing/src/upgrade/altair.rs b/consensus/state_processing/src/upgrade/altair.rs index 022175ff999..66bdb1af250 100644 --- a/consensus/state_processing/src/upgrade/altair.rs +++ b/consensus/state_processing/src/upgrade/altair.rs @@ -31,7 +31,7 @@ pub fn translate_participation( let committee = state.get_beacon_committee(data.slot, data.index)?; let attesting_indices = get_attesting_indices::(committee.committee, &attestation.aggregation_bits)?; - let epoch_participation = state.previous_epoch_participation_mut()?; + let mut epoch_participation = state.previous_epoch_participation_mut()?; for index in attesting_indices { for flag_index in &participation_flag_indices { diff --git a/consensus/state_processing/src/upgrade/electra.rs b/consensus/state_processing/src/upgrade/electra.rs index 258b28a45bd..75d843ed9d1 100644 --- a/consensus/state_processing/src/upgrade/electra.rs +++ b/consensus/state_processing/src/upgrade/electra.rs @@ -41,7 +41,7 @@ pub fn upgrade_to_electra( *post.consolidation_balance_to_consume_mut()? = post.get_consolidation_churn_limit(spec)?; // Add validators that are not yet active to pending balance deposits - let validators = post.validators().clone(); + let validators = post.validators(); let pre_activation = validators .iter() .enumerate() @@ -54,14 +54,14 @@ pub fn upgrade_to_electra( for index in pre_activation { let balance = post .balances_mut() - .get_mut(index) + .into_get_mut(index) .ok_or(Error::UnknownValidator(index))?; let balance_copy = *balance; *balance = 0_u64; let validator = post .validators_mut() - .get_mut(index) + .into_get_mut(index) .ok_or(Error::UnknownValidator(index))?; validator.effective_balance = 0; validator.activation_eligibility_epoch = spec.far_future_epoch; @@ -80,7 +80,7 @@ pub fn upgrade_to_electra( } // Ensure early adopters of compounding credentials go through the activation churn - let validators = post.validators().clone(); + let validators = post.validators().to_owned_list(); for (index, validator) in validators.iter().enumerate() { if validator.has_compounding_withdrawal_credential(spec) { post.queue_excess_active_balance(index, spec)?; diff --git a/consensus/state_processing/src/upgrade/gloas.rs b/consensus/state_processing/src/upgrade/gloas.rs index c26547e3041..63f0ee2cc1b 100644 --- a/consensus/state_processing/src/upgrade/gloas.rs +++ b/consensus/state_processing/src/upgrade/gloas.rs @@ -1,6 +1,6 @@ use crate::per_block_processing::process_operations::apply_deposit_for_builder; use crate::per_block_processing::process_operations::is_pending_validator; -use milhouse::{List, Vector}; +use milhouse::{ProgressiveList, Vector}; use safe_arith::SafeArith; use ssz_types::BitVector; use ssz_types::FixedVector; @@ -10,7 +10,8 @@ use tree_hash::TreeHash; use typenum::Unsigned; use types::{ BeaconState, BeaconStateError as Error, BeaconStateGloas, BuilderPendingPayment, ChainSpec, - EthSpec, ExecutionPayloadBid, ExecutionRequests, Fork, is_builder_withdrawal_credential, + EthSpec, ExecutionPayloadBid, ExecutionRequestsGloas, Fork, PendingDeposit, + is_builder_withdrawal_credential, }; /// Transform a `Fulu` state into a `Gloas` state. @@ -56,22 +57,29 @@ pub fn upgrade_state_to_gloas( eth1_data_votes: mem::take(&mut pre.eth1_data_votes), eth1_deposit_index: pre.eth1_deposit_index, // Registry - validators: mem::take(&mut pre.validators), - balances: mem::take(&mut pre.balances), + // [Modified in Gloas:EIP7688] rebuild as progressive lists. + validators: ProgressiveList::try_from_iter(pre.validators.iter().cloned())?, + balances: ProgressiveList::try_from_iter(pre.balances.iter().copied())?, // Randomness randao_mixes: pre.randao_mixes.clone(), // Slashings slashings: pre.slashings.clone(), - // `Participation - previous_epoch_participation: mem::take(&mut pre.previous_epoch_participation), - current_epoch_participation: mem::take(&mut pre.current_epoch_participation), + // Participation + // [Modified in Gloas:EIP7688] rebuild as progressive lists. + previous_epoch_participation: ProgressiveList::try_from_iter( + pre.previous_epoch_participation.iter().cloned(), + )?, + current_epoch_participation: ProgressiveList::try_from_iter( + pre.current_epoch_participation.iter().cloned(), + )?, // Finality justification_bits: pre.justification_bits.clone(), previous_justified_checkpoint: pre.previous_justified_checkpoint, current_justified_checkpoint: pre.current_justified_checkpoint, finalized_checkpoint: pre.finalized_checkpoint, // Inactivity - inactivity_scores: mem::take(&mut pre.inactivity_scores), + // [Modified in Gloas:EIP7688] rebuild as a progressive list. + inactivity_scores: ProgressiveList::try_from_iter(pre.inactivity_scores.iter().copied())?, // Sync committees current_sync_committee: pre.current_sync_committee.clone(), next_sync_committee: pre.next_sync_committee.clone(), @@ -79,7 +87,7 @@ pub fn upgrade_state_to_gloas( latest_execution_payload_bid: ExecutionPayloadBid { block_hash: pre.latest_execution_payload_header.block_hash, gas_limit: pre.latest_execution_payload_header.gas_limit, - execution_requests_root: ExecutionRequests::::default().tree_hash_root(), + execution_requests_root: ExecutionRequestsGloas::::default().tree_hash_root(), ..Default::default() }, // Capella @@ -93,12 +101,17 @@ pub fn upgrade_state_to_gloas( earliest_exit_epoch: pre.earliest_exit_epoch, consolidation_balance_to_consume: pre.consolidation_balance_to_consume, earliest_consolidation_epoch: pre.earliest_consolidation_epoch, - pending_deposits: pre.pending_deposits.clone(), - pending_partial_withdrawals: pre.pending_partial_withdrawals.clone(), - pending_consolidations: pre.pending_consolidations.clone(), + // [Modified in Gloas:EIP7688] rebuild as progressive lists. + pending_deposits: ProgressiveList::try_from_iter(pre.pending_deposits.iter().cloned())?, + pending_partial_withdrawals: ProgressiveList::try_from_iter( + pre.pending_partial_withdrawals.iter().cloned(), + )?, + pending_consolidations: ProgressiveList::try_from_iter( + pre.pending_consolidations.iter().cloned(), + )?, proposer_lookahead: mem::take(&mut pre.proposer_lookahead), // Gloas - builders: List::default(), + builders: ProgressiveList::default(), next_withdrawal_builder_index: 0, // All bits set to true per spec: // execution_payload_availability = [0b1 for _ in range(SLOTS_PER_HISTORICAL_ROOT)] @@ -107,9 +120,9 @@ pub fn upgrade_state_to_gloas( ) .map_err(|_| Error::InvalidBitfield)?, builder_pending_payments: Vector::from_elem(BuilderPendingPayment::default())?, - builder_pending_withdrawals: List::default(), // Empty list initially, + builder_pending_withdrawals: ProgressiveList::default(), // Empty list initially, latest_block_hash: pre.latest_execution_payload_header.block_hash, - payload_expected_withdrawals: List::default(), + payload_expected_withdrawals: ProgressiveList::default(), ptc_window: Vector::from_elem(FixedVector::from_elem(0))?, // placeholder, will be initialized below // Caches total_active_balance: pre.total_active_balance, @@ -167,9 +180,9 @@ fn onboard_builders_from_pending_deposits( spec: &ChainSpec, ) -> Result<(), Error> { // Clone pending deposits to avoid borrow conflicts when mutating state. - let current_pending_deposits = state.pending_deposits()?.clone(); + let current_pending_deposits = state.pending_deposits()?.to_vec(); - let mut pending_deposits = List::empty(); + let mut pending_deposits: Vec = Vec::new(); // TODO(gloas): introduce a global builder pubkey cache, see: // https://github.com/sigp/lighthouse/issues/8783 @@ -183,21 +196,21 @@ fn onboard_builders_from_pending_deposits( for deposit in ¤t_pending_deposits { // Deposits for existing validators stay in the pending queue. if state.get_validator_index(&deposit.pubkey)?.is_some() { - pending_deposits.push(deposit.clone())?; + pending_deposits.push(deposit.clone()); continue; } if !builder_pubkey_to_index.contains_key(&deposit.pubkey) { // Deposits without builder withdrawal credentials are for new validators. if !is_builder_withdrawal_credential(deposit.withdrawal_credentials, spec) { - pending_deposits.push(deposit.clone())?; + pending_deposits.push(deposit.clone()); continue; } // If there is a valid pending deposit for a new validator with this pubkey, // keep this deposit in the pending queue to be applied to that validator later. if is_pending_validator(&pending_deposits, &deposit.pubkey, spec) { - pending_deposits.push(deposit.clone())?; + pending_deposits.push(deposit.clone()); continue; } } @@ -220,7 +233,7 @@ fn onboard_builders_from_pending_deposits( } } - *state.pending_deposits_mut()? = pending_deposits; + state.set_pending_deposits_from_iter(pending_deposits)?; Ok(()) } diff --git a/consensus/state_processing/src/verify_operation.rs b/consensus/state_processing/src/verify_operation.rs index 8e67c3da43e..ea01639ad7c 100644 --- a/consensus/state_processing/src/verify_operation.rs +++ b/consensus/state_processing/src/verify_operation.rs @@ -388,7 +388,9 @@ impl TryFrom, E>> verified_against: slashing.verified_against, _phantom: PhantomData, }), - AttesterSlashing::Electra(_) => Err("non-base attester slashing".to_string()), + AttesterSlashing::Electra(_) | AttesterSlashing::Gloas(_) => { + Err("non-base attester slashing".to_string()) + } } } } diff --git a/consensus/types/benches/benches.rs b/consensus/types/benches/benches.rs index 85d7de980b2..615bfbcb01e 100644 --- a/consensus/types/benches/benches.rs +++ b/consensus/types/benches/benches.rs @@ -1,6 +1,5 @@ use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main}; use fixed_bytes::FixedBytesExtended; -use milhouse::List; use rayon::prelude::*; use ssz::Encode; use std::hint::black_box; @@ -27,23 +26,21 @@ fn get_state(validator_count: usize) -> BeaconState { .expect("should add balance"); } - *state.validators_mut() = List::new( - (0..validator_count) - .collect::>() - .par_iter() - .map(|&i| Validator { - pubkey: generate_deterministic_keypair(i).pk.compress(), - withdrawal_credentials: Hash256::from_low_u64_le(i as u64), - effective_balance: spec.max_effective_balance, - slashed: false, - activation_eligibility_epoch: Epoch::new(0), - activation_epoch: Epoch::new(0), - exit_epoch: Epoch::from(u64::MAX), - withdrawable_epoch: Epoch::from(u64::MAX), - }) - .collect(), - ) - .unwrap(); + let validators: Vec = (0..validator_count) + .collect::>() + .par_iter() + .map(|&i| Validator { + pubkey: generate_deterministic_keypair(i).pk.compress(), + withdrawal_credentials: Hash256::from_low_u64_le(i as u64), + effective_balance: spec.max_effective_balance, + slashed: false, + activation_eligibility_epoch: Epoch::new(0), + activation_epoch: Epoch::new(0), + exit_epoch: Epoch::from(u64::MAX), + withdrawable_epoch: Epoch::from(u64::MAX), + }) + .collect(); + state.set_validators_from_iter(validators).unwrap(); state } diff --git a/consensus/types/src/attestation/aggregate_and_proof.rs b/consensus/types/src/attestation/aggregate_and_proof.rs index 76e33faf88b..8b1dfebf9e6 100644 --- a/consensus/types/src/attestation/aggregate_and_proof.rs +++ b/consensus/types/src/attestation/aggregate_and_proof.rs @@ -7,14 +7,15 @@ use tree_hash_derive::TreeHash; use crate::{ attestation::{ - Attestation, AttestationBase, AttestationElectra, AttestationRef, SelectionProof, + Attestation, AttestationBase, AttestationElectra, AttestationGloas, AttestationRef, + SelectionProof, }, core::{ChainSpec, Domain, EthSpec, Hash256, SignedRoot}, fork::{Fork, ForkName}, }; #[superstruct( - variants(Base, Electra), + variants(Base, Electra, Gloas), variant_attributes( derive( Debug, @@ -129,6 +130,11 @@ impl AggregateAndProof { aggregate, selection_proof: selection_proof.into(), }), + Attestation::Gloas(aggregate) => Self::Gloas(AggregateAndProofGloas { + aggregator_index, + aggregate, + selection_proof: selection_proof.into(), + }), } } diff --git a/consensus/types/src/attestation/attestation.rs b/consensus/types/src/attestation/attestation.rs index 4cfb7a4d243..8703a2f4b65 100644 --- a/consensus/types/src/attestation/attestation.rs +++ b/consensus/types/src/attestation/attestation.rs @@ -7,15 +7,16 @@ use bls::{AggregateSignature, SecretKey, Signature}; use context_deserialize::{ContextDeserialize, context_deserialize}; use educe::Educe; use serde::{Deserialize, Deserializer, Serialize}; +use ssz::ProgressiveBitList; use ssz_derive::{Decode, Encode}; -use ssz_types::{BitList, BitVector}; +use ssz_types::{BitList, BitVector, ProgressiveVariableList}; use superstruct::superstruct; use tree_hash_derive::TreeHash; use crate::{ attestation::{ AttestationData, Checkpoint, IndexedAttestation, IndexedAttestationBase, - IndexedAttestationElectra, + IndexedAttestationElectra, IndexedAttestationGloas, }, core::{ChainSpec, Domain, EthSpec, Hash256, SignedRoot, Slot, SlotData}, fork::{Fork, ForkName}, @@ -38,7 +39,7 @@ impl From for Error { } #[superstruct( - variants(Base, Electra), + variants(Base, Electra, Gloas), variant_attributes( derive( Debug, @@ -59,6 +60,11 @@ impl From for Error { arbitrary(bound = "E: EthSpec") ) ), + specific_variant_attributes(Gloas( + // EIP-7688: the Gloas `Attestation` is a `ProgressiveContainer` with all 4 fields + // active. + tree_hash(struct_behaviour = "progressive_container", active_fields(1, 1, 1, 1)) + )), ref_attributes(derive(TreeHash), tree_hash(enum_behaviour = "transparent")), cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") @@ -79,9 +85,12 @@ pub struct Attestation { pub aggregation_bits: BitList, #[superstruct(only(Electra), partial_getter(rename = "aggregation_bits_electra"))] pub aggregation_bits: BitList, + // [Modified in Gloas:EIP7688] + #[superstruct(only(Gloas), partial_getter(rename = "aggregation_bits_gloas"))] + pub aggregation_bits: ProgressiveBitList, pub data: AttestationData, pub signature: AggregateSignature, - #[superstruct(only(Electra))] + #[superstruct(only(Electra, Gloas))] pub committee_bits: BitVector, } @@ -93,6 +102,7 @@ impl Hash for Attestation { match self { Attestation::Base(att) => att.hash(state), Attestation::Electra(att) => att.hash(state), + Attestation::Gloas(att) => att.hash(state), } } } @@ -110,24 +120,38 @@ impl Attestation { payload_present: bool, spec: &ChainSpec, ) -> Result { - if spec.fork_name_at_slot::(slot).electra_enabled() { + if spec.fork_name_at_slot::(slot).gloas_enabled() { let mut committee_bits: BitVector = BitVector::default(); committee_bits .set(committee_index as usize, true) .map_err(|_| Error::InvalidCommitteeIndex)?; // Gloas attestation data index now indicates payload presence. - // Pre-gloas index is always 0. - let index = if spec.fork_name_at_slot::(slot).gloas_enabled() && payload_present { - 1u64 - } else { - 0u64 - }; + let index = if payload_present { 1u64 } else { 0u64 }; + Ok(Attestation::Gloas(AttestationGloas { + // [Modified in Gloas:EIP7688] + aggregation_bits: ProgressiveBitList::with_capacity(committee_length) + .map_err(|_| Error::InvalidCommitteeLength)?, + data: AttestationData { + slot, + index, + beacon_block_root, + source, + target, + }, + committee_bits, + signature: AggregateSignature::infinity(), + })) + } else if spec.fork_name_at_slot::(slot).electra_enabled() { + let mut committee_bits: BitVector = BitVector::default(); + committee_bits + .set(committee_index as usize, true) + .map_err(|_| Error::InvalidCommitteeIndex)?; Ok(Attestation::Electra(AttestationElectra { aggregation_bits: BitList::with_capacity(committee_length) .map_err(|_| Error::InvalidCommitteeLength)?, data: AttestationData { slot, - index, + index: 0, beacon_block_root, source, target, @@ -160,17 +184,25 @@ impl Attestation { AttestationRef::Base(oth) => { att.aggregate(oth); } - AttestationRef::Electra(_) => { - debug_assert!(false, "Cannot aggregate base and electra attestations"); + AttestationRef::Electra(_) | AttestationRef::Gloas(_) => { + debug_assert!(false, "Cannot aggregate attestations from different forks"); } }, Attestation::Electra(att) => match other { - AttestationRef::Base(_) => { - debug_assert!(false, "Cannot aggregate base and electra attestations"); - } AttestationRef::Electra(oth) => { att.aggregate(oth); } + AttestationRef::Base(_) | AttestationRef::Gloas(_) => { + debug_assert!(false, "Cannot aggregate attestations from different forks"); + } + }, + Attestation::Gloas(att) => match other { + AttestationRef::Gloas(oth) => { + att.aggregate(oth); + } + AttestationRef::Base(_) | AttestationRef::Electra(_) => { + debug_assert!(false, "Cannot aggregate attestations from different forks"); + } }, } } @@ -201,6 +233,13 @@ impl Attestation { genesis_validators_root, spec, ), + Attestation::Gloas(att) => att.sign( + secret_key, + committee_position, + fork, + genesis_validators_root, + spec, + ), } } @@ -213,6 +252,7 @@ impl Attestation { match self { Attestation::Base(att) => att.add_signature(signature, committee_position), Attestation::Electra(att) => att.add_signature(signature, committee_position), + Attestation::Gloas(att) => att.add_signature(signature, committee_position), } } @@ -220,6 +260,7 @@ impl Attestation { match self { Attestation::Base(att) => Some(att.data.index), Attestation::Electra(att) => att.committee_index(), + Attestation::Gloas(att) => att.committee_index(), } } @@ -227,6 +268,7 @@ impl Attestation { match self { Attestation::Base(att) => HashSet::from([att.data.index]), Attestation::Electra(att) => att.get_committee_indices().into_iter().collect(), + Attestation::Gloas(att) => att.get_committee_indices().into_iter().collect(), } } @@ -234,6 +276,7 @@ impl Attestation { match self { Attestation::Base(att) => att.aggregation_bits.is_zero(), Attestation::Electra(att) => att.aggregation_bits.is_zero(), + Attestation::Gloas(att) => att.aggregation_bits.is_zero(), } } @@ -241,6 +284,7 @@ impl Attestation { match self { Attestation::Base(att) => att.aggregation_bits.num_set_bits(), Attestation::Electra(att) => att.aggregation_bits.num_set_bits(), + Attestation::Gloas(att) => att.aggregation_bits.num_set_bits(), } } @@ -248,6 +292,7 @@ impl Attestation { match self { Attestation::Base(att) => att.aggregation_bits.get(index), Attestation::Electra(att) => att.aggregation_bits.get(index), + Attestation::Gloas(att) => att.aggregation_bits.get(index), } } @@ -258,6 +303,7 @@ impl Attestation { match self { Self::Base(attn) => attn.to_single_attestation_with_attester_index(attester_index), Self::Electra(attn) => attn.to_single_attestation_with_attester_index(attester_index), + Self::Gloas(attn) => attn.to_single_attestation_with_attester_index(attester_index), } } @@ -265,6 +311,28 @@ impl Attestation { match self { Self::Base(attn) => attn.get_aggregation_bits(), Self::Electra(attn) => attn.get_aggregation_bits(), + Self::Gloas(attn) => attn.get_aggregation_bits(), + } + } +} + +impl<'a, E: EthSpec> AttestationRefMut<'a, E> { + /// Consuming variant of `data_mut` that ties the returned reference to the underlying + /// attestation rather than to `self`, making it usable from closures. + pub fn into_data_mut(self) -> &'a mut AttestationData { + match self { + Self::Base(att) => &mut att.data, + Self::Electra(att) => &mut att.data, + Self::Gloas(att) => &mut att.data, + } + } + + /// Consuming variant of `signature_mut`, see [`Self::into_data_mut`]. + pub fn into_signature_mut(self) -> &'a mut AggregateSignature { + match self { + Self::Base(att) => &mut att.signature, + Self::Electra(att) => &mut att.signature, + Self::Gloas(att) => &mut att.signature, } } } @@ -274,6 +342,7 @@ impl AttestationRef<'_, E> { match self { Self::Base(att) => Attestation::Base(att.clone()), Self::Electra(att) => Attestation::Electra(att.clone()), + Self::Gloas(att) => Attestation::Gloas(att.clone()), } } @@ -281,6 +350,7 @@ impl AttestationRef<'_, E> { match self { Self::Base(att) => att.aggregation_bits.is_zero(), Self::Electra(att) => att.aggregation_bits.is_zero(), + Self::Gloas(att) => att.aggregation_bits.is_zero(), } } @@ -288,6 +358,7 @@ impl AttestationRef<'_, E> { match self { Self::Base(att) => att.aggregation_bits.num_set_bits(), Self::Electra(att) => att.aggregation_bits.num_set_bits(), + Self::Gloas(att) => att.aggregation_bits.num_set_bits(), } } @@ -295,6 +366,7 @@ impl AttestationRef<'_, E> { match self { AttestationRef::Base(att) => Some(att.data.index), AttestationRef::Electra(att) => att.committee_index(), + AttestationRef::Gloas(att) => att.committee_index(), } } @@ -314,6 +386,13 @@ impl AttestationRef<'_, E> { .filter(|(_i, bit)| *bit) .map(|(i, _bit)| i) .collect::>(), + Self::Gloas(att) => att + .aggregation_bits + .iter() + .enumerate() + .filter(|(_i, bit)| *bit) + .map(|(i, _bit)| i) + .collect::>(), } } } @@ -416,6 +495,105 @@ impl AttestationElectra { } } +impl AttestationGloas { + pub fn committee_index(&self) -> Option { + self.committee_bits + .iter() + .enumerate() + .find(|&(_, bit)| bit) + .map(|(index, _)| index as u64) + } + + pub fn get_aggregation_bits(&self) -> Vec { + self.aggregation_bits + .iter() + .enumerate() + .filter_map(|(index, bit)| if bit { Some(index as u64) } else { None }) + .collect() + } + + pub fn get_committee_indices(&self) -> Vec { + self.committee_bits + .iter() + .enumerate() + .filter_map(|(index, bit)| if bit { Some(index as u64) } else { None }) + .collect() + } + + /// Aggregate another Attestation into this one. + /// + /// The aggregation bitfields must be disjoint, and the data must be the same. + pub fn aggregate(&mut self, other: &Self) { + debug_assert_eq!(self.data, other.data); + self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits); + self.signature.add_assign_aggregate(&other.signature); + } + + /// Signs `self`, setting the `committee_position`'th bit of `aggregation_bits` to `true`. + /// + /// Returns an `AlreadySigned` error if the `committee_position`'th bit is already `true`. + pub fn sign( + &mut self, + secret_key: &SecretKey, + committee_position: usize, + fork: &Fork, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), Error> { + let domain = spec.get_domain( + self.data.target.epoch, + Domain::BeaconAttester, + fork, + genesis_validators_root, + ); + let message = self.data.signing_root(domain); + + self.add_signature(&secret_key.sign(message), committee_position) + } + + /// Adds `signature` to `self` and sets the `committee_position`'th bit of `aggregation_bits` + /// to `true`. + /// + /// Returns an `AlreadySigned` error if the `committee_position`'th bit is already `true`. + pub fn add_signature( + &mut self, + signature: &Signature, + committee_position: usize, + ) -> Result<(), Error> { + if self + .aggregation_bits + .get(committee_position) + .map_err(Error::BitfieldError)? + { + Err(Error::AlreadySigned(committee_position)) + } else { + self.aggregation_bits + .set(committee_position, true) + .map_err(Error::BitfieldError)?; + + self.signature.add_assign(signature); + + Ok(()) + } + } + + pub fn to_single_attestation_with_attester_index( + &self, + attester_index: u64, + ) -> Result { + let Some(committee_index) = self.committee_index() else { + return Err(Error::InvalidCommitteeIndex); + }; + + Ok(SingleAttestation { + committee_index, + attester_index, + data: self.data.clone(), + signature: self.signature.clone(), + }) + } +} + impl AttestationBase { /// Aggregate another Attestation into this one. /// @@ -517,6 +695,7 @@ impl SlotData for AttestationRef<'_, E> { pub enum AttestationOnDisk { Base(AttestationBase), Electra(AttestationElectra), + Gloas(AttestationGloas), } impl AttestationOnDisk { @@ -524,6 +703,7 @@ impl AttestationOnDisk { match self { AttestationOnDisk::Base(att) => AttestationRefOnDisk::Base(att), AttestationOnDisk::Electra(att) => AttestationRefOnDisk::Electra(att), + AttestationOnDisk::Gloas(att) => AttestationRefOnDisk::Gloas(att), } } } @@ -533,6 +713,7 @@ impl AttestationOnDisk { pub enum AttestationRefOnDisk<'a, E: EthSpec> { Base(&'a AttestationBase), Electra(&'a AttestationElectra), + Gloas(&'a AttestationGloas), } impl From> for AttestationOnDisk { @@ -540,6 +721,7 @@ impl From> for AttestationOnDisk { match attestation { Attestation::Base(attestation) => Self::Base(attestation), Attestation::Electra(attestation) => Self::Electra(attestation), + Attestation::Gloas(attestation) => Self::Gloas(attestation), } } } @@ -549,6 +731,7 @@ impl From> for Attestation { match attestation { AttestationOnDisk::Base(attestation) => Self::Base(attestation), AttestationOnDisk::Electra(attestation) => Self::Electra(attestation), + AttestationOnDisk::Gloas(attestation) => Self::Gloas(attestation), } } } @@ -558,6 +741,7 @@ impl<'a, E: EthSpec> From> for AttestationRefOnDisk<'a, E> match attestation { AttestationRef::Base(attestation) => Self::Base(attestation), AttestationRef::Electra(attestation) => Self::Electra(attestation), + AttestationRef::Gloas(attestation) => Self::Gloas(attestation), } } } @@ -567,6 +751,7 @@ impl<'a, E: EthSpec> From> for AttestationRef<'a, E> match attestation { AttestationRefOnDisk::Base(attestation) => Self::Base(attestation), AttestationRefOnDisk::Electra(attestation) => Self::Electra(attestation), + AttestationRefOnDisk::Gloas(attestation) => Self::Gloas(attestation), } } } @@ -576,7 +761,11 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for Attestation { where D: Deserializer<'de>, { - if context.electra_enabled() { + if context.gloas_enabled() { + AttestationGloas::::deserialize(deserializer) + .map_err(serde::de::Error::custom) + .map(Attestation::Gloas) + } else if context.electra_enabled() { AttestationElectra::::deserialize(deserializer) .map_err(serde::de::Error::custom) .map(Attestation::Electra) @@ -627,7 +816,14 @@ impl SingleAttestation { &self, fork_name: ForkName, ) -> Result, ssz_types::Error> { - if fork_name.electra_enabled() { + if fork_name.gloas_enabled() { + Ok(IndexedAttestation::Gloas(IndexedAttestationGloas { + attesting_indices: ProgressiveVariableList::new(vec![self.attester_index]), + data: self.data.clone(), + signature: self.signature.clone(), + _phantom: std::marker::PhantomData, + })) + } else if fork_name.electra_enabled() { Ok(IndexedAttestation::Electra(IndexedAttestationElectra { attesting_indices: vec![self.attester_index].try_into()?, data: self.data.clone(), diff --git a/consensus/types/src/attestation/indexed_attestation.rs b/consensus/types/src/attestation/indexed_attestation.rs index ae15f474f39..37c37fc94c5 100644 --- a/consensus/types/src/attestation/indexed_attestation.rs +++ b/consensus/types/src/attestation/indexed_attestation.rs @@ -9,7 +9,7 @@ use educe::Educe; use serde::{Deserialize, Serialize}; use ssz::Encode; use ssz_derive::{Decode, Encode}; -use ssz_types::VariableList; +use ssz_types::{ProgressiveVariableList, VariableList}; use superstruct::superstruct; use tree_hash_derive::TreeHash; @@ -21,7 +21,7 @@ use crate::{attestation::AttestationData, core::EthSpec, fork::ForkName}; /// /// Spec v0.12.1 #[superstruct( - variants(Base, Electra), + variants(Base, Electra, Gloas), variant_attributes( derive( Debug, @@ -41,7 +41,12 @@ use crate::{attestation::AttestationData, core::EthSpec, fork::ForkName}; derive(arbitrary::Arbitrary), arbitrary(bound = "E: EthSpec"), ), - ) + ), + specific_variant_attributes(Gloas( + // EIP-7688: the Gloas `IndexedAttestation` is a `ProgressiveContainer` with all 3 + // fields active. + tree_hash(struct_behaviour = "progressive_container", active_fields(1, 1, 1)) + )) )] #[cfg_attr( feature = "arbitrary", @@ -62,8 +67,20 @@ pub struct IndexedAttestation { #[superstruct(only(Electra), partial_getter(rename = "attesting_indices_electra"))] #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] pub attesting_indices: VariableList, + // [Modified in Gloas:EIP7688] + #[superstruct(only(Gloas), partial_getter(rename = "attesting_indices_gloas"))] + #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] + pub attesting_indices: ProgressiveVariableList, pub data: AttestationData, pub signature: AggregateSignature, + // The Gloas variant has no fields referencing `E`, so it requires a phantom field. This is + // skipped for all (de)serialization and hashing purposes. + #[superstruct(only(Gloas))] + #[ssz(skip_serializing, skip_deserializing)] + #[tree_hash(skip_hashing)] + #[serde(skip)] + #[cfg_attr(feature = "arbitrary", arbitrary(default))] + pub _phantom: std::marker::PhantomData, } impl IndexedAttestation { @@ -87,6 +104,7 @@ impl IndexedAttestation { match self { IndexedAttestation::Base(att) => att.attesting_indices.len(), IndexedAttestation::Electra(att) => att.attesting_indices.len(), + IndexedAttestation::Gloas(att) => att.attesting_indices.len(), } } @@ -94,6 +112,7 @@ impl IndexedAttestation { match self { IndexedAttestation::Base(att) => att.attesting_indices.to_vec(), IndexedAttestation::Electra(att) => att.attesting_indices.to_vec(), + IndexedAttestation::Gloas(att) => att.attesting_indices.to_vec(), } } @@ -101,6 +120,7 @@ impl IndexedAttestation { match self { IndexedAttestation::Base(att) => att.attesting_indices.is_empty(), IndexedAttestation::Electra(att) => att.attesting_indices.is_empty(), + IndexedAttestation::Gloas(att) => att.attesting_indices.is_empty(), } } @@ -108,6 +128,7 @@ impl IndexedAttestation { match self { IndexedAttestation::Base(att) => att.attesting_indices.iter(), IndexedAttestation::Electra(att) => att.attesting_indices.iter(), + IndexedAttestation::Gloas(att) => att.attesting_indices.iter(), } } @@ -115,10 +136,11 @@ impl IndexedAttestation { match self { IndexedAttestation::Base(att) => att.attesting_indices.first(), IndexedAttestation::Electra(att) => att.attesting_indices.first(), + IndexedAttestation::Gloas(att) => att.attesting_indices.first(), } } - pub fn to_electra(self) -> IndexedAttestationElectra { + pub fn to_electra(self) -> Result, ssz_types::Error> { match self { Self::Base(att) => { let extended_attesting_indices: VariableList = @@ -127,13 +149,38 @@ impl IndexedAttestation { // Note a unit test in consensus/types/src/eth_spec.rs asserts this invariant for // all known specs - IndexedAttestationElectra { + Ok(IndexedAttestationElectra { attesting_indices: extended_attesting_indices, data: att.data, signature: att.signature, - } + }) + } + Self::Electra(att) => Ok(att), + Self::Gloas(att) => { + let attesting_indices: VariableList = + VariableList::new(att.attesting_indices.to_vec())?; + + Ok(IndexedAttestationElectra { + attesting_indices, + data: att.data, + signature: att.signature, + }) } - Self::Electra(att) => att, + } + } + + pub fn to_gloas(self) -> IndexedAttestationGloas { + let attesting_indices = ProgressiveVariableList::new(self.attesting_indices_to_vec()); + let (data, signature) = match self { + Self::Base(att) => (att.data, att.signature), + Self::Electra(att) => (att.data, att.signature), + Self::Gloas(att) => return att, + }; + IndexedAttestationGloas { + attesting_indices, + data, + signature, + _phantom: std::marker::PhantomData, } } } @@ -152,6 +199,7 @@ impl IndexedAttestationRef<'_, E> { match self { IndexedAttestationRef::Base(att) => att.attesting_indices.len(), IndexedAttestationRef::Electra(att) => att.attesting_indices.len(), + IndexedAttestationRef::Gloas(att) => att.attesting_indices.len(), } } @@ -159,6 +207,7 @@ impl IndexedAttestationRef<'_, E> { match self { IndexedAttestationRef::Base(att) => att.attesting_indices.to_vec(), IndexedAttestationRef::Electra(att) => att.attesting_indices.to_vec(), + IndexedAttestationRef::Gloas(att) => att.attesting_indices.to_vec(), } } @@ -166,6 +215,7 @@ impl IndexedAttestationRef<'_, E> { match self { IndexedAttestationRef::Base(att) => att.attesting_indices.is_empty(), IndexedAttestationRef::Electra(att) => att.attesting_indices.is_empty(), + IndexedAttestationRef::Gloas(att) => att.attesting_indices.is_empty(), } } @@ -173,6 +223,7 @@ impl IndexedAttestationRef<'_, E> { match self { IndexedAttestationRef::Base(att) => att.attesting_indices.iter(), IndexedAttestationRef::Electra(att) => att.attesting_indices.iter(), + IndexedAttestationRef::Gloas(att) => att.attesting_indices.iter(), } } @@ -180,6 +231,7 @@ impl IndexedAttestationRef<'_, E> { match self { IndexedAttestationRef::Base(att) => att.attesting_indices.first(), IndexedAttestationRef::Electra(att) => att.attesting_indices.first(), + IndexedAttestationRef::Gloas(att) => att.attesting_indices.first(), } } @@ -187,6 +239,7 @@ impl IndexedAttestationRef<'_, E> { match self { IndexedAttestationRef::Base(att) => IndexedAttestation::Base(att.clone()), IndexedAttestationRef::Electra(att) => IndexedAttestation::Electra(att.clone()), + IndexedAttestationRef::Gloas(att) => IndexedAttestation::Gloas(att.clone()), } } } @@ -201,6 +254,7 @@ impl Hash for IndexedAttestation { match self { IndexedAttestation::Base(att) => att.attesting_indices.hash(state), IndexedAttestation::Electra(att) => att.attesting_indices.hash(state), + IndexedAttestation::Gloas(att) => att.attesting_indices.hash(state), }; self.data().hash(state); self.signature().as_ssz_bytes().hash(state); diff --git a/consensus/types/src/attestation/mod.rs b/consensus/types/src/attestation/mod.rs index 5b59b83e726..d55a109151f 100644 --- a/consensus/types/src/attestation/mod.rs +++ b/consensus/types/src/attestation/mod.rs @@ -18,18 +18,21 @@ mod signed_aggregate_and_proof; mod subnet_id; pub use aggregate_and_proof::{ - AggregateAndProof, AggregateAndProofBase, AggregateAndProofElectra, AggregateAndProofRef, + AggregateAndProof, AggregateAndProofBase, AggregateAndProofElectra, AggregateAndProofGloas, + AggregateAndProofRef, }; pub use attestation::{ - Attestation, AttestationBase, AttestationElectra, AttestationOnDisk, AttestationRef, - AttestationRefMut, AttestationRefOnDisk, Error as AttestationError, SingleAttestation, + Attestation, AttestationBase, AttestationElectra, AttestationGloas, AttestationOnDisk, + AttestationRef, AttestationRefMut, AttestationRefOnDisk, Error as AttestationError, + SingleAttestation, }; pub use attestation_data::AttestationData; pub use attestation_duty::AttestationDuty; pub use beacon_committee::{BeaconCommittee, OwnedBeaconCommittee}; pub use checkpoint::Checkpoint; pub use indexed_attestation::{ - IndexedAttestation, IndexedAttestationBase, IndexedAttestationElectra, IndexedAttestationRef, + IndexedAttestation, IndexedAttestationBase, IndexedAttestationElectra, IndexedAttestationGloas, + IndexedAttestationRef, }; pub use indexed_payload_attestation::IndexedPayloadAttestation; pub use participation_flags::ParticipationFlags; @@ -42,7 +45,7 @@ pub use selection_proof::SelectionProof; pub use shuffling_id::AttestationShufflingId; pub use signed_aggregate_and_proof::{ SignedAggregateAndProof, SignedAggregateAndProofBase, SignedAggregateAndProofElectra, - SignedAggregateAndProofRefMut, + SignedAggregateAndProofGloas, SignedAggregateAndProofRefMut, }; pub use subnet_id::SubnetId; diff --git a/consensus/types/src/attestation/signed_aggregate_and_proof.rs b/consensus/types/src/attestation/signed_aggregate_and_proof.rs index f9db76e9d2e..9b1cf2db3f5 100644 --- a/consensus/types/src/attestation/signed_aggregate_and_proof.rs +++ b/consensus/types/src/attestation/signed_aggregate_and_proof.rs @@ -1,14 +1,14 @@ use bls::{SecretKey, Signature}; -use context_deserialize::context_deserialize; -use serde::{Deserialize, Serialize}; +use context_deserialize::{ContextDeserialize, context_deserialize}; +use serde::{Deserialize, Deserializer, Serialize}; use ssz_derive::{Decode, Encode}; use superstruct::superstruct; use tree_hash_derive::TreeHash; use crate::{ attestation::{ - AggregateAndProof, AggregateAndProofBase, AggregateAndProofElectra, AggregateAndProofRef, - Attestation, AttestationRef, SelectionProof, + AggregateAndProof, AggregateAndProofBase, AggregateAndProofElectra, AggregateAndProofGloas, + AggregateAndProofRef, Attestation, AttestationRef, SelectionProof, }, core::{ChainSpec, Domain, EthSpec, Hash256, SignedRoot}, fork::{Fork, ForkName}, @@ -19,7 +19,7 @@ use crate::{ /// /// Spec v0.12.1 #[superstruct( - variants(Base, Electra), + variants(Base, Electra, Gloas), variant_attributes( derive( Debug, @@ -107,6 +107,9 @@ impl SignedAggregateAndProof { signature, }) } + AggregateAndProof::Gloas(message) => { + SignedAggregateAndProof::Gloas(SignedAggregateAndProofGloas { message, signature }) + } } } @@ -124,3 +127,27 @@ impl SignedAggregateAndProof { }) } } + +impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for SignedAggregateAndProof { + fn context_deserialize(deserializer: D, context: ForkName) -> Result + where + D: Deserializer<'de>, + { + // [Modified in Gloas:EIP7688] the Gloas and Electra variants are structurally identical + // in JSON but merkleize differently, so untagged deserialization cannot distinguish them + // and the fork context must decide. + if context.gloas_enabled() { + SignedAggregateAndProofGloas::::deserialize(deserializer) + .map_err(serde::de::Error::custom) + .map(SignedAggregateAndProof::Gloas) + } else if context.electra_enabled() { + SignedAggregateAndProofElectra::::deserialize(deserializer) + .map_err(serde::de::Error::custom) + .map(SignedAggregateAndProof::Electra) + } else { + SignedAggregateAndProofBase::::deserialize(deserializer) + .map_err(serde::de::Error::custom) + .map(SignedAggregateAndProof::Base) + } + } +} diff --git a/consensus/types/src/block/beacon_block.rs b/consensus/types/src/block/beacon_block.rs index 639a89d7e65..6148e08e2d7 100644 --- a/consensus/types/src/block/beacon_block.rs +++ b/consensus/types/src/block/beacon_block.rs @@ -7,7 +7,7 @@ use fixed_bytes::FixedBytesExtended; use serde::{Deserialize, Deserializer, Serialize}; use ssz::{Decode, DecodeError}; use ssz_derive::{Decode, Encode}; -use ssz_types::{BitList, BitVector, FixedVector, VariableList}; +use ssz_types::{BitList, BitVector, FixedVector, ProgressiveVariableList, VariableList}; use superstruct::superstruct; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; @@ -25,8 +25,8 @@ use crate::{ core::{ChainSpec, Domain, Epoch, EthSpec, Graffiti, Hash256, SignedRoot, Slot}, deposit::{Deposit, DepositData}, execution::{ - AbstractExecPayload, BlindedPayload, Eth1Data, ExecutionPayload, ExecutionRequests, - FullPayload, + AbstractExecPayload, BlindedPayload, Eth1Data, ExecutionPayload, ExecutionRequestsElectra, + ExecutionRequestsGloas, FullPayload, }, exit::{SignedVoluntaryExit, VoluntaryExit}, fork::{Fork, ForkName, InconsistentFork, map_fork_name}, @@ -650,7 +650,7 @@ impl> EmptyBlock for BeaconBlockElec execution_payload: Payload::Electra::default(), bls_to_execution_changes: VariableList::empty(), blob_kzg_commitments: VariableList::empty(), - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsElectra::default(), }, } } @@ -684,7 +684,7 @@ impl> EmptyBlock for BeaconBlockFulu execution_payload: Payload::Fulu::default(), bls_to_execution_changes: VariableList::empty(), blob_kzg_commitments: VariableList::empty(), - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsElectra::default(), }, } } @@ -706,16 +706,16 @@ impl> EmptyBlock for BeaconBlockGloa deposit_count: 0, }, graffiti: Graffiti::default(), - proposer_slashings: VariableList::empty(), - attester_slashings: VariableList::empty(), - attestations: VariableList::empty(), - deposits: VariableList::empty(), - voluntary_exits: VariableList::empty(), + proposer_slashings: ProgressiveVariableList::empty(), + attester_slashings: ProgressiveVariableList::empty(), + attestations: ProgressiveVariableList::empty(), + deposits: ProgressiveVariableList::empty(), + voluntary_exits: ProgressiveVariableList::empty(), sync_aggregate: SyncAggregate::empty(), - bls_to_execution_changes: VariableList::empty(), - parent_execution_requests: ExecutionRequests::default(), + bls_to_execution_changes: ProgressiveVariableList::empty(), + parent_execution_requests: ExecutionRequestsGloas::default(), signed_execution_payload_bid: SignedExecutionPayloadBid::empty(), - payload_attestations: VariableList::empty(), + payload_attestations: ProgressiveVariableList::empty(), _phantom: PhantomData, }, } diff --git a/consensus/types/src/block/beacon_block_body.rs b/consensus/types/src/block/beacon_block_body.rs index 071c9e76d48..7128c097596 100644 --- a/consensus/types/src/block/beacon_block_body.rs +++ b/consensus/types/src/block/beacon_block_body.rs @@ -7,15 +7,16 @@ use merkle_proof::MerkleTree; use metastruct::metastruct; use serde::{Deserialize, Deserializer, Serialize}; use ssz_derive::{Decode, Encode}; -use ssz_types::{FixedVector, VariableList}; +use ssz_types::{FixedVector, ProgressiveVariableList, VariableList}; use superstruct::superstruct; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; use crate::{ - SignedExecutionPayloadBid, + ListRef, SignedExecutionPayloadBid, attestation::{ - AttestationBase, AttestationElectra, AttestationRef, AttestationRefMut, PayloadAttestation, + AttestationBase, AttestationElectra, AttestationGloas, AttestationRef, AttestationRefMut, + PayloadAttestation, }, complete_kzg_commitment_merkle_proof, core::{EthSpec, Graffiti, Hash256}, @@ -24,16 +25,18 @@ use crate::{ AbstractExecPayload, BlindedPayload, BlindedPayloadBellatrix, BlindedPayloadCapella, BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadFulu, Eth1Data, ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, - ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequests, - FullPayload, FullPayloadBellatrix, FullPayloadCapella, FullPayloadDeneb, - FullPayloadElectra, FullPayloadFulu, SignedBlsToExecutionChange, + ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, + ExecutionRequestsElectra, ExecutionRequestsGloas, FullPayload, FullPayloadBellatrix, + FullPayloadCapella, FullPayloadDeneb, FullPayloadElectra, FullPayloadFulu, + SignedBlsToExecutionChange, }, exit::SignedVoluntaryExit, fork::{ForkName, map_fork_name}, kzg_ext::KzgCommitments, light_client::consts::{EXECUTION_PAYLOAD_INDEX, EXECUTION_PAYLOAD_PROOF_LEN}, slashing::{ - AttesterSlashingBase, AttesterSlashingElectra, AttesterSlashingRef, ProposerSlashing, + AttesterSlashingBase, AttesterSlashingElectra, AttesterSlashingGloas, AttesterSlashingRef, + ProposerSlashing, }, state::BeaconStateError, sync_committee::SyncAggregate, @@ -85,7 +88,15 @@ pub const BLOB_KZG_COMMITMENTS_INDEX: usize = 11; Deneb(metastruct(mappings(beacon_block_body_deneb_fields(groups(fields))))), Electra(metastruct(mappings(beacon_block_body_electra_fields(groups(fields))))), Fulu(metastruct(mappings(beacon_block_body_fulu_fields(groups(fields))))), - Gloas(metastruct(mappings(beacon_block_body_gloas_fields(groups(fields))))), + Gloas( + metastruct(mappings(beacon_block_body_gloas_fields(groups(fields)))), + // EIP-7688: the Gloas `BeaconBlockBody` is a `ProgressiveContainer` with all 13 + // spec fields active. + tree_hash( + struct_behaviour = "progressive_container", + active_fields(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + ) + ), ), cast_error( ty = "BeaconStateError", @@ -110,30 +121,49 @@ pub struct BeaconBlockBody = FullPay pub randao_reveal: Signature, pub eth1_data: Eth1Data, pub graffiti: Graffiti, + #[superstruct( + only(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu), + partial_getter(rename = "proposer_slashings_basic") + )] pub proposer_slashings: VariableList, + #[superstruct(only(Gloas), partial_getter(rename = "proposer_slashings_progressive"))] + pub proposer_slashings: ProgressiveVariableList, #[superstruct( only(Base, Altair, Bellatrix, Capella, Deneb), partial_getter(rename = "attester_slashings_base") )] pub attester_slashings: VariableList, E::MaxAttesterSlashings>, #[superstruct( - only(Electra, Fulu, Gloas), + only(Electra, Fulu), partial_getter(rename = "attester_slashings_electra") )] pub attester_slashings: VariableList, E::MaxAttesterSlashingsElectra>, + #[superstruct(only(Gloas), partial_getter(rename = "attester_slashings_gloas"))] + pub attester_slashings: ProgressiveVariableList>, #[superstruct( only(Base, Altair, Bellatrix, Capella, Deneb), partial_getter(rename = "attestations_base") )] pub attestations: VariableList, E::MaxAttestations>, + #[superstruct(only(Electra, Fulu), partial_getter(rename = "attestations_electra"))] + pub attestations: VariableList, E::MaxAttestationsElectra>, + #[superstruct(only(Gloas), partial_getter(rename = "attestations_gloas"))] + pub attestations: ProgressiveVariableList>, #[superstruct( - only(Electra, Fulu, Gloas), - partial_getter(rename = "attestations_electra") + only(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu), + partial_getter(rename = "deposits_basic") )] - pub attestations: VariableList, E::MaxAttestationsElectra>, pub deposits: VariableList, + #[superstruct(only(Gloas), partial_getter(rename = "deposits_progressive"))] + pub deposits: ProgressiveVariableList, + #[superstruct( + only(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu), + partial_getter(rename = "voluntary_exits_basic") + )] pub voluntary_exits: VariableList, + #[superstruct(only(Gloas), partial_getter(rename = "voluntary_exits_progressive"))] + pub voluntary_exits: ProgressiveVariableList, #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] pub sync_aggregate: SyncAggregate, // We flatten the execution payload so that serde can use the name of the inner type, @@ -157,19 +187,27 @@ pub struct BeaconBlockBody = FullPay #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] #[serde(flatten)] pub execution_payload: Payload::Fulu, - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct( + only(Capella, Deneb, Electra, Fulu), + partial_getter(rename = "bls_to_execution_changes_basic") + )] pub bls_to_execution_changes: VariableList, + #[superstruct( + only(Gloas), + partial_getter(rename = "bls_to_execution_changes_progressive") + )] + pub bls_to_execution_changes: ProgressiveVariableList, #[superstruct(only(Deneb, Electra, Fulu))] pub blob_kzg_commitments: KzgCommitments, #[superstruct(only(Electra, Fulu))] - pub execution_requests: ExecutionRequests, + pub execution_requests: ExecutionRequestsElectra, #[superstruct(only(Gloas))] pub signed_execution_payload_bid: SignedExecutionPayloadBid, #[superstruct(only(Gloas))] - pub payload_attestations: VariableList, E::MaxPayloadAttestations>, + pub payload_attestations: ProgressiveVariableList>, #[superstruct(only(Gloas))] - pub parent_execution_requests: ExecutionRequests, + pub parent_execution_requests: ExecutionRequestsGloas, #[superstruct(only(Base, Altair, Gloas))] #[metastruct(exclude_from(fields))] #[ssz(skip_serializing, skip_deserializing)] @@ -184,6 +222,27 @@ impl> BeaconBlockBody { self.to_ref().execution_payload() } + pub fn proposer_slashings(&self) -> ListRef<'_, ProposerSlashing, E::MaxProposerSlashings> { + self.to_ref().proposer_slashings() + } + + pub fn deposits(&self) -> ListRef<'_, Deposit, E::MaxDeposits> { + self.to_ref().deposits() + } + + pub fn voluntary_exits(&self) -> ListRef<'_, SignedVoluntaryExit, E::MaxVoluntaryExits> { + self.to_ref().voluntary_exits() + } + + pub fn bls_to_execution_changes( + &self, + ) -> Result< + ListRef<'_, SignedBlsToExecutionChange, E::MaxBlsToExecutionChanges>, + BeaconStateError, + > { + self.to_ref().bls_to_execution_changes() + } + /// Returns the name of the fork pertaining to `self`. pub fn fork_name(&self) -> ForkName { self.to_ref().fork_name() @@ -343,6 +402,63 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, }) } + pub fn proposer_slashings(&self) -> ListRef<'a, ProposerSlashing, E::MaxProposerSlashings> { + match self { + Self::Base(body) => ListRef::Basic(&body.proposer_slashings), + Self::Altair(body) => ListRef::Basic(&body.proposer_slashings), + Self::Bellatrix(body) => ListRef::Basic(&body.proposer_slashings), + Self::Capella(body) => ListRef::Basic(&body.proposer_slashings), + Self::Deneb(body) => ListRef::Basic(&body.proposer_slashings), + Self::Electra(body) => ListRef::Basic(&body.proposer_slashings), + Self::Fulu(body) => ListRef::Basic(&body.proposer_slashings), + Self::Gloas(body) => ListRef::Progressive(&body.proposer_slashings), + } + } + + pub fn deposits(&self) -> ListRef<'a, Deposit, E::MaxDeposits> { + match self { + Self::Base(body) => ListRef::Basic(&body.deposits), + Self::Altair(body) => ListRef::Basic(&body.deposits), + Self::Bellatrix(body) => ListRef::Basic(&body.deposits), + Self::Capella(body) => ListRef::Basic(&body.deposits), + Self::Deneb(body) => ListRef::Basic(&body.deposits), + Self::Electra(body) => ListRef::Basic(&body.deposits), + Self::Fulu(body) => ListRef::Basic(&body.deposits), + Self::Gloas(body) => ListRef::Progressive(&body.deposits), + } + } + + pub fn voluntary_exits(&self) -> ListRef<'a, SignedVoluntaryExit, E::MaxVoluntaryExits> { + match self { + Self::Base(body) => ListRef::Basic(&body.voluntary_exits), + Self::Altair(body) => ListRef::Basic(&body.voluntary_exits), + Self::Bellatrix(body) => ListRef::Basic(&body.voluntary_exits), + Self::Capella(body) => ListRef::Basic(&body.voluntary_exits), + Self::Deneb(body) => ListRef::Basic(&body.voluntary_exits), + Self::Electra(body) => ListRef::Basic(&body.voluntary_exits), + Self::Fulu(body) => ListRef::Basic(&body.voluntary_exits), + Self::Gloas(body) => ListRef::Progressive(&body.voluntary_exits), + } + } + + pub fn bls_to_execution_changes( + &self, + ) -> Result< + ListRef<'a, SignedBlsToExecutionChange, E::MaxBlsToExecutionChanges>, + BeaconStateError, + > { + match self { + Self::Base(_) | Self::Altair(_) | Self::Bellatrix(_) => { + Err(BeaconStateError::IncorrectStateVariant) + } + Self::Capella(body) => Ok(ListRef::Basic(&body.bls_to_execution_changes)), + Self::Deneb(body) => Ok(ListRef::Basic(&body.bls_to_execution_changes)), + Self::Electra(body) => Ok(ListRef::Basic(&body.bls_to_execution_changes)), + Self::Fulu(body) => Ok(ListRef::Basic(&body.bls_to_execution_changes)), + Self::Gloas(body) => Ok(ListRef::Progressive(&body.bls_to_execution_changes)), + } + } + pub fn attestations(&self) -> Box> + 'a> { match self { Self::Base(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)), @@ -352,7 +468,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Self::Deneb(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)), Self::Electra(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)), Self::Fulu(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)), - Self::Gloas(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)), + Self::Gloas(body) => Box::new(body.attestations.iter().map(AttestationRef::Gloas)), } } @@ -396,40 +512,224 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Self::Gloas(body) => Box::new( body.attester_slashings .iter() - .map(AttesterSlashingRef::Electra), + .map(AttesterSlashingRef::Gloas), ), } } } impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRefMut<'a, E, Payload> { - pub fn attestations_mut( - &'a mut self, - ) -> Box> + 'a> { + /// Apply a mutation to every attestation in the block body. + pub fn attestations_apply(&mut self, mut f: impl FnMut(AttestationRefMut<'_, E>)) { + match self { + Self::Base(body) => body + .attestations + .iter_mut() + .for_each(|att| f(AttestationRefMut::Base(att))), + Self::Altair(body) => body + .attestations + .iter_mut() + .for_each(|att| f(AttestationRefMut::Base(att))), + Self::Bellatrix(body) => body + .attestations + .iter_mut() + .for_each(|att| f(AttestationRefMut::Base(att))), + Self::Capella(body) => body + .attestations + .iter_mut() + .for_each(|att| f(AttestationRefMut::Base(att))), + Self::Deneb(body) => body + .attestations + .iter_mut() + .for_each(|att| f(AttestationRefMut::Base(att))), + Self::Electra(body) => body + .attestations + .iter_mut() + .for_each(|att| f(AttestationRefMut::Electra(att))), + Self::Fulu(body) => body + .attestations + .iter_mut() + .for_each(|att| f(AttestationRefMut::Electra(att))), + Self::Gloas(body) => body + .attestations + .iter_mut() + .for_each(|att| f(AttestationRefMut::Gloas(att))), + } + } + + /// Append a voluntary exit to the block body. + pub fn voluntary_exits_push( + &mut self, + exit: SignedVoluntaryExit, + ) -> Result<(), BeaconStateError> { + match self { + Self::Base(body) => body + .voluntary_exits + .push(exit) + .map_err(BeaconStateError::SszTypesError), + Self::Altair(body) => body + .voluntary_exits + .push(exit) + .map_err(BeaconStateError::SszTypesError), + Self::Bellatrix(body) => body + .voluntary_exits + .push(exit) + .map_err(BeaconStateError::SszTypesError), + Self::Capella(body) => body + .voluntary_exits + .push(exit) + .map_err(BeaconStateError::SszTypesError), + Self::Deneb(body) => body + .voluntary_exits + .push(exit) + .map_err(BeaconStateError::SszTypesError), + Self::Electra(body) => body + .voluntary_exits + .push(exit) + .map_err(BeaconStateError::SszTypesError), + Self::Fulu(body) => body + .voluntary_exits + .push(exit) + .map_err(BeaconStateError::SszTypesError), + Self::Gloas(body) => { + body.voluntary_exits.push(exit); + Ok(()) + } + } + } + + /// Append a proposer slashing to the block body. + pub fn proposer_slashings_push( + &mut self, + slashing: ProposerSlashing, + ) -> Result<(), BeaconStateError> { + match self { + Self::Base(body) => body + .proposer_slashings + .push(slashing) + .map_err(BeaconStateError::SszTypesError), + Self::Altair(body) => body + .proposer_slashings + .push(slashing) + .map_err(BeaconStateError::SszTypesError), + Self::Bellatrix(body) => body + .proposer_slashings + .push(slashing) + .map_err(BeaconStateError::SszTypesError), + Self::Capella(body) => body + .proposer_slashings + .push(slashing) + .map_err(BeaconStateError::SszTypesError), + Self::Deneb(body) => body + .proposer_slashings + .push(slashing) + .map_err(BeaconStateError::SszTypesError), + Self::Electra(body) => body + .proposer_slashings + .push(slashing) + .map_err(BeaconStateError::SszTypesError), + Self::Fulu(body) => body + .proposer_slashings + .push(slashing) + .map_err(BeaconStateError::SszTypesError), + Self::Gloas(body) => { + body.proposer_slashings.push(slashing); + Ok(()) + } + } + } + + /// Append a deposit to the block body. + pub fn deposits_push(&mut self, deposit: Deposit) -> Result<(), BeaconStateError> { + match self { + Self::Base(body) => body + .deposits + .push(deposit) + .map_err(BeaconStateError::SszTypesError), + Self::Altair(body) => body + .deposits + .push(deposit) + .map_err(BeaconStateError::SszTypesError), + Self::Bellatrix(body) => body + .deposits + .push(deposit) + .map_err(BeaconStateError::SszTypesError), + Self::Capella(body) => body + .deposits + .push(deposit) + .map_err(BeaconStateError::SszTypesError), + Self::Deneb(body) => body + .deposits + .push(deposit) + .map_err(BeaconStateError::SszTypesError), + Self::Electra(body) => body + .deposits + .push(deposit) + .map_err(BeaconStateError::SszTypesError), + Self::Fulu(body) => body + .deposits + .push(deposit) + .map_err(BeaconStateError::SszTypesError), + Self::Gloas(body) => { + body.deposits.push(deposit); + Ok(()) + } + } + } + + /// Replace the deposits list with the given deposits. + pub fn set_deposits_from_iter( + &mut self, + deposits: impl IntoIterator, + ) -> Result<(), BeaconStateError> { match self { - Self::Base(body) => Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base)), + Self::Base(body) => { + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; + } Self::Altair(body) => { - Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base)) + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; } Self::Bellatrix(body) => { - Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base)) + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; } Self::Capella(body) => { - Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base)) + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; } Self::Deneb(body) => { - Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base)) + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; } Self::Electra(body) => { - Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra)) + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; } Self::Fulu(body) => { - Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra)) + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; } Self::Gloas(body) => { - Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra)) + body.deposits = deposits.into_iter().collect(); } } + Ok(()) + } + + /// Apply a mutation to every voluntary exit in the block body. + pub fn voluntary_exits_apply(&mut self, mut f: impl FnMut(&mut SignedVoluntaryExit)) { + match self { + Self::Base(body) => body.voluntary_exits.iter_mut().for_each(&mut f), + Self::Altair(body) => body.voluntary_exits.iter_mut().for_each(&mut f), + Self::Bellatrix(body) => body.voluntary_exits.iter_mut().for_each(&mut f), + Self::Capella(body) => body.voluntary_exits.iter_mut().for_each(&mut f), + Self::Deneb(body) => body.voluntary_exits.iter_mut().for_each(&mut f), + Self::Electra(body) => body.voluntary_exits.iter_mut().for_each(&mut f), + Self::Fulu(body) => body.voluntary_exits.iter_mut().for_each(&mut f), + Self::Gloas(body) => body.voluntary_exits.iter_mut().for_each(&mut f), + } } } @@ -1135,4 +1435,56 @@ mod tests { use crate::core::MainnetEthSpec; ssz_and_tree_hash_tests!(BeaconBlockBodyAltair); } + mod gloas { + use super::super::*; + use crate::block::BeaconBlock; + use crate::core::{ChainSpec, MainnetEthSpec}; + + /// Verify the derived `progressive_container` root of the Gloas body against a manual + /// computation from its 13 field roots (EIP-7688). + /// + /// This guards against the `active_fields` attribute silently dropping trailing fields: + /// if the derive hashed fewer (or more) fields than listed here, the roots would differ. + #[test] + fn gloas_body_progressive_container_root() { + type E = MainnetEthSpec; + let spec: ChainSpec = ForkName::Gloas.make_genesis_spec(E::default_spec()); + let block: BeaconBlock = BeaconBlock::empty(&spec); + let BeaconBlock::Gloas(block) = block else { + panic!("expected a Gloas block"); + }; + let body = &block.body; + + // All 13 spec fields, in container order. + let field_roots = [ + body.randao_reveal.tree_hash_root(), + body.eth1_data.tree_hash_root(), + body.graffiti.tree_hash_root(), + body.proposer_slashings.tree_hash_root(), + body.attester_slashings.tree_hash_root(), + body.attestations.tree_hash_root(), + body.deposits.tree_hash_root(), + body.voluntary_exits.tree_hash_root(), + body.sync_aggregate.tree_hash_root(), + body.bls_to_execution_changes.tree_hash_root(), + body.signed_execution_payload_bid.tree_hash_root(), + body.payload_attestations.tree_hash_root(), + body.parent_execution_requests.tree_hash_root(), + ]; + + let mut hasher = tree_hash::ProgressiveMerkleHasher::new(); + for root in &field_roots { + hasher.write(root.as_slice()).unwrap(); + } + let container_root = hasher.finish().unwrap(); + + // `active_fields = [1] * 13`. + let mut active_fields = [0u8; 32]; + active_fields[0] = 0xff; + active_fields[1] = 0x1f; + let expected = tree_hash::mix_in_active_fields(container_root, active_fields); + + assert_eq!(body.tree_hash_root(), expected); + } + } } diff --git a/consensus/types/src/block/signed_beacon_block.rs b/consensus/types/src/block/signed_beacon_block.rs index 1a87a519d0f..a7e56d56c11 100644 --- a/consensus/types/src/block/signed_beacon_block.rs +++ b/consensus/types/src/block/signed_beacon_block.rs @@ -351,13 +351,13 @@ impl> SignedBeaconBlock self.message() .body() .blob_kzg_commitments() + .map(|c| c.len()) .or_else(|_| { self.message() .body() .signed_execution_payload_bid() - .map(|bid| &bid.message.blob_kzg_commitments) + .map(|bid| bid.message.blob_kzg_commitments.len()) }) - .map(|c| c.len()) .unwrap_or(0) } diff --git a/consensus/types/src/builder/builder_bid.rs b/consensus/types/src/builder/builder_bid.rs index df7893b909a..ddd877c46f9 100644 --- a/consensus/types/src/builder/builder_bid.rs +++ b/consensus/types/src/builder/builder_bid.rs @@ -12,7 +12,7 @@ use crate::{ execution::{ ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, - ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, ExecutionRequests, + ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, ExecutionRequestsElectra, }, fork::{ForkName, ForkVersionDecode}, kzg_ext::KzgCommitments, @@ -59,7 +59,7 @@ pub struct BuilderBid { #[superstruct(only(Deneb, Electra, Fulu))] pub blob_kzg_commitments: KzgCommitments, #[superstruct(only(Electra, Fulu))] - pub execution_requests: ExecutionRequests, + pub execution_requests: ExecutionRequestsElectra, #[serde(with = "serde_utils::quoted_u256")] pub value: Uint256, pub pubkey: PublicKeyBytes, diff --git a/consensus/types/src/core/list_ref.rs b/consensus/types/src/core/list_ref.rs new file mode 100644 index 00000000000..649477ad3df --- /dev/null +++ b/consensus/types/src/core/list_ref.rs @@ -0,0 +1,89 @@ +//! A read-only view over a list field that is a fixed-capacity [`VariableList`] in some fork +//! variants and an (unbounded) [`ProgressiveVariableList`] in others (EIP-7688). +//! +//! Both back onto a flat slice, so the view simply exposes the slice API the two share. Used by +//! [`BeaconBlockBody`](crate::BeaconBlockBody), [`ExecutionPayload`](crate::ExecutionPayload) and +//! [`DataColumnSidecar`](crate::DataColumnSidecar) accessors that span the Gloas fork boundary. + +use ssz_types::typenum::Unsigned; +use ssz_types::{ProgressiveVariableList, VariableList}; + +/// View over a list that is a `VariableList` pre-Gloas and a `ProgressiveVariableList` from Gloas +/// onwards (EIP-7688). Both variants back onto a flat slice. +#[derive(Debug)] +pub enum ListRef<'a, T, N: Unsigned> { + Basic(&'a VariableList), + Progressive(&'a ProgressiveVariableList), +} + +// Manual `Clone`/`Copy` impls to avoid spurious `T: Clone`/`T: Copy` bounds from the derive. +impl Clone for ListRef<'_, T, N> { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for ListRef<'_, T, N> {} + +impl<'a, T, N: Unsigned> ListRef<'a, T, N> { + /// The underlying contents as a slice. + pub fn as_slice(self) -> &'a [T] { + match self { + Self::Basic(list) => list, + Self::Progressive(list) => list, + } + } + + pub fn len(self) -> usize { + self.as_slice().len() + } + + pub fn is_empty(self) -> bool { + self.as_slice().is_empty() + } + + pub fn get(self, index: usize) -> Option<&'a T> { + self.as_slice().get(index) + } + + pub fn first(self) -> Option<&'a T> { + self.as_slice().first() + } + + pub fn iter(self) -> std::slice::Iter<'a, T> { + self.as_slice().iter() + } + + pub fn to_vec(self) -> Vec + where + T: Clone, + { + self.as_slice().to_vec() + } + + /// Borrow the contents as a slice, for passing lists to functions taking `Cow<[T]>`. + pub fn to_cow_slice(self) -> std::borrow::Cow<'a, [T]> + where + T: Clone, + { + std::borrow::Cow::Borrowed(self.as_slice()) + } +} + +impl<'a, T, N: Unsigned> IntoIterator for ListRef<'a, T, N> { + type Item = &'a T; + type IntoIter = std::slice::Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T, N: Unsigned> IntoIterator for &ListRef<'a, T, N> { + type Item = &'a T; + type IntoIter = std::slice::Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} diff --git a/consensus/types/src/core/mod.rs b/consensus/types/src/core/mod.rs index f722ac51917..8a72ee8e36e 100644 --- a/consensus/types/src/core/mod.rs +++ b/consensus/types/src/core/mod.rs @@ -7,6 +7,7 @@ mod enr_fork_id; mod eth_spec; mod execution_block_hash; mod graffiti; +mod list_ref; mod non_zero_usize; mod preset; mod relative_epoch; @@ -28,6 +29,7 @@ pub use enr_fork_id::EnrForkId; pub use eth_spec::{EthSpec, EthSpecId, GNOSIS, GnosisEthSpec, MainnetEthSpec, MinimalEthSpec}; pub use execution_block_hash::ExecutionBlockHash; pub use graffiti::{GRAFFITI_BYTES_LEN, Graffiti, GraffitiString}; +pub use list_ref::ListRef; pub use non_zero_usize::new_non_zero_usize; pub use preset::{ AltairPreset, BasePreset, BellatrixPreset, CapellaPreset, DenebPreset, ElectraPreset, diff --git a/consensus/types/src/data/data_column_sidecar.rs b/consensus/types/src/data/data_column_sidecar.rs index d15651730fe..3778cb2d686 100644 --- a/consensus/types/src/data/data_column_sidecar.rs +++ b/consensus/types/src/data/data_column_sidecar.rs @@ -10,12 +10,13 @@ use serde::{Deserialize, Serialize}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use ssz_types::Error as SszError; -use ssz_types::{FixedVector, VariableList}; +use ssz_types::{FixedVector, ProgressiveVariableList, VariableList}; use superstruct::superstruct; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; use crate::{ + ListRef, block::{BLOB_KZG_COMMITMENTS_INDEX, BeaconBlockHeader, SignedBeaconBlockHeader}, core::{Epoch, EthSpec, Hash256, Slot}, data::{ @@ -81,12 +82,21 @@ pub struct DataColumnSidecar { #[serde(with = "serde_utils::quoted_u64")] pub index: ColumnIndex, #[serde(with = "ssz_types::serde_utils::list_of_hex_fixed_vec")] + #[superstruct(only(Fulu), partial_getter(rename = "column_fulu"))] pub column: DataColumn, + // [Modified in Gloas:EIP7688] + #[serde(with = "ssz_types::serde_utils::list_of_hex_prog_fixed_vec")] + #[superstruct(only(Gloas), partial_getter(rename = "column_gloas"))] + pub column: ProgressiveVariableList>, /// All the KZG commitments associated with the block, used for verifying sample cells. /// In Gloas, commitments come from `block.body.signed_execution_payload_bid.message.blob_kzg_commitments`. #[superstruct(only(Fulu))] pub kzg_commitments: KzgCommitments, + #[superstruct(only(Fulu), partial_getter(rename = "kzg_proofs_fulu"))] pub kzg_proofs: VariableList, + // [Modified in Gloas:EIP7688] + #[superstruct(only(Gloas), partial_getter(rename = "kzg_proofs_gloas"))] + pub kzg_proofs: ProgressiveVariableList, #[superstruct(only(Fulu))] pub signed_block_header: SignedBeaconBlockHeader, /// An inclusion proof, proving the inclusion of `blob_kzg_commitments` in `BeaconBlockBody`. @@ -99,6 +109,22 @@ pub struct DataColumnSidecar { } impl DataColumnSidecar { + /// Unified view over the `column` field across forks (EIP-7688). + pub fn column(&self) -> ListRef<'_, Cell, E::MaxBlobCommitmentsPerBlock> { + match self { + DataColumnSidecar::Fulu(sidecar) => ListRef::Basic(&sidecar.column), + DataColumnSidecar::Gloas(sidecar) => ListRef::Progressive(&sidecar.column), + } + } + + /// Unified view over the `kzg_proofs` field across forks (EIP-7688). + pub fn kzg_proofs(&self) -> ListRef<'_, KzgProof, E::MaxBlobCommitmentsPerBlock> { + match self { + DataColumnSidecar::Fulu(sidecar) => ListRef::Basic(&sidecar.kzg_proofs), + DataColumnSidecar::Gloas(sidecar) => ListRef::Progressive(&sidecar.kzg_proofs), + } + } + pub fn slot(&self) -> Slot { match self { DataColumnSidecar::Fulu(column) => column.slot(), @@ -282,8 +308,8 @@ impl DataColumnSidecarGloas { // min size is one cell Self { index: 0, - column: VariableList::new(vec![Cell::::default()]).unwrap(), - kzg_proofs: VariableList::new(vec![KzgProof::empty()]).unwrap(), + column: ProgressiveVariableList::new(vec![Cell::::default()]), + kzg_proofs: ProgressiveVariableList::new(vec![KzgProof::empty()]), slot: Slot::new(0), beacon_block_root: Hash256::ZERO, } @@ -294,8 +320,8 @@ impl DataColumnSidecarGloas { pub fn max_size(max_blobs_per_block: usize) -> usize { Self { index: 0, - column: VariableList::new(vec![Cell::::default(); max_blobs_per_block]).unwrap(), - kzg_proofs: VariableList::new(vec![KzgProof::empty(); max_blobs_per_block]).unwrap(), + column: ProgressiveVariableList::new(vec![Cell::::default(); max_blobs_per_block]), + kzg_proofs: ProgressiveVariableList::new(vec![KzgProof::empty(); max_blobs_per_block]), slot: Slot::new(0), beacon_block_root: Hash256::ZERO, } diff --git a/consensus/types/src/execution/execution_payload.rs b/consensus/types/src/execution/execution_payload.rs index c444c03157b..1ce897ea775 100644 --- a/consensus/types/src/execution/execution_payload.rs +++ b/consensus/types/src/execution/execution_payload.rs @@ -4,15 +4,16 @@ use fixed_bytes::Uint256; use serde::{Deserialize, Deserializer, Serialize}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; -use ssz_types::{FixedVector, VariableList}; +use ssz_types::{FixedVector, ProgressiveVariableList, VariableList}; use superstruct::superstruct; use tree_hash_derive::TreeHash; use crate::{ + ListRef, core::{Address, EthSpec, ExecutionBlockHash, Hash256, Slot}, fork::{ForkName, ForkVersionDecode}, state::BeaconStateError, - withdrawal::Withdrawals, + withdrawal::{Withdrawal, Withdrawals}, }; pub type Transaction = VariableList; @@ -21,6 +22,91 @@ pub type Transactions = VariableList< ::MaxTransactionsPerPayload, >; +/// Progressive transactions list \[Modified in Gloas:EIP7688\]. +pub type ProgressiveTransactions = ProgressiveVariableList>; + +/// Progressive withdrawals list \[Modified in Gloas:EIP7688\]. +pub type ProgressiveWithdrawals = ProgressiveVariableList; + +/// RLP encoded block access list \[New in Gloas:EIP7928\]. +pub type BlockAccessList = ProgressiveVariableList; + +/// Unified read access to the `transactions` of any `ExecutionPayload` variant. +#[derive(Debug)] +pub enum TransactionsRef<'a, E: EthSpec> { + Bounded(&'a Transactions), + Progressive(&'a ProgressiveTransactions), +} + +impl Clone for TransactionsRef<'_, E> { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for TransactionsRef<'_, E> {} + +impl<'a, E: EthSpec> TransactionsRef<'a, E> { + pub fn len(self) -> usize { + match self { + Self::Bounded(transactions) => transactions.len(), + Self::Progressive(transactions) => transactions.len(), + } + } + + pub fn is_empty(self) -> bool { + self.len() == 0 + } + + /// Iterate the transactions as byte slices. + pub fn iter(self) -> TransactionsIter<'a, E> { + match self { + Self::Bounded(transactions) => TransactionsIter::Bounded(transactions.iter()), + Self::Progressive(transactions) => TransactionsIter::Progressive(transactions.iter()), + } + } +} + +impl<'a, E: EthSpec> From<&'a Transactions> for TransactionsRef<'a, E> { + fn from(transactions: &'a Transactions) -> Self { + Self::Bounded(transactions) + } +} + +impl<'a, E: EthSpec> From<&'a ProgressiveTransactions> for TransactionsRef<'a, E> { + fn from(transactions: &'a ProgressiveTransactions) -> Self { + Self::Progressive(transactions) + } +} + +impl<'a, E: EthSpec> IntoIterator for TransactionsRef<'a, E> { + type Item = &'a [u8]; + type IntoIter = TransactionsIter<'a, E>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +pub enum TransactionsIter<'a, E: EthSpec> { + Bounded(std::slice::Iter<'a, Transaction<::MaxBytesPerTransaction>>), + Progressive(std::slice::Iter<'a, ProgressiveVariableList>), +} + +impl<'a, E: EthSpec> Iterator for TransactionsIter<'a, E> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option<&'a [u8]> { + match self { + Self::Bounded(iter) => iter.next().map(|transaction| transaction.as_ref()), + Self::Progressive(iter) => iter.next().map(|transaction| transaction.as_slice()), + } + } +} + +/// A reference to the withdrawals of any post-Capella `ExecutionPayload` variant. +pub type WithdrawalsRef<'a, E> = ListRef<'a, Withdrawal, ::MaxWithdrawalsPerPayload>; + #[superstruct( variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), variant_attributes( @@ -44,6 +130,14 @@ pub type Transactions = VariableList< arbitrary(bound = "E: EthSpec"), ), ), + specific_variant_attributes(Gloas( + // EIP-7688: the Gloas `ExecutionPayload` is a `ProgressiveContainer` with all 19 + // spec fields active. + tree_hash( + struct_behaviour = "progressive_container", + active_fields(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + ) + )), cast_error( ty = "BeaconStateError", expr = "BeaconStateError::IncorrectStateVariant" @@ -96,10 +190,23 @@ pub struct ExecutionPayload { pub base_fee_per_gas: Uint256, #[superstruct(getter(copy))] pub block_hash: ExecutionBlockHash, + #[superstruct( + only(Bellatrix, Capella, Deneb, Electra, Fulu), + partial_getter(rename = "transactions_bounded") + )] #[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")] pub transactions: Transactions, - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] + // TODO(spec fix): enable once spec is updated + // #[serde(with = "ssz_types::serde_utils::list_of_hex_prog_var_list")] + #[superstruct(only(Gloas), partial_getter(rename = "transactions_progressive"))] + pub transactions: ProgressiveTransactions, + #[superstruct( + only(Capella, Deneb, Electra, Fulu), + partial_getter(rename = "withdrawals_bounded") + )] pub withdrawals: Withdrawals, + #[superstruct(only(Gloas), partial_getter(rename = "withdrawals_progressive"))] + pub withdrawals: ProgressiveWithdrawals, #[superstruct(only(Deneb, Electra, Fulu, Gloas), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] pub blob_gas_used: u64, @@ -107,9 +214,10 @@ pub struct ExecutionPayload { #[serde(with = "serde_utils::quoted_u64")] pub excess_blob_gas: u64, /// EIP-7928: Block access list + // TODO(spec fix): enable once spec is updated + // #[serde(with = "ssz_types::serde_utils::hex_prog_var_list")] #[superstruct(only(Gloas))] - #[serde(with = "ssz_types::serde_utils::hex_var_list")] - pub block_access_list: VariableList, + pub block_access_list: BlockAccessList, #[superstruct(only(Gloas), partial_getter(copy))] pub slot_number: Slot, } @@ -122,6 +230,38 @@ impl<'a, E: EthSpec> ExecutionPayloadRef<'a, E> { payload.clone().into() }) } + + /// Unified access to the `transactions` field across all variants. + pub fn transactions(self) -> TransactionsRef<'a, E> { + map_execution_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + TransactionsRef::from(&payload.transactions) + }) + } + + /// Unified access to the `withdrawals` field (Capella and later). + pub fn withdrawals(self) -> Result, BeaconStateError> { + match self { + Self::Bellatrix(_) => Err(BeaconStateError::IncorrectStateVariant), + Self::Capella(payload) => Ok(ListRef::Basic(&payload.withdrawals)), + Self::Deneb(payload) => Ok(ListRef::Basic(&payload.withdrawals)), + Self::Electra(payload) => Ok(ListRef::Basic(&payload.withdrawals)), + Self::Fulu(payload) => Ok(ListRef::Basic(&payload.withdrawals)), + Self::Gloas(payload) => Ok(ListRef::Progressive(&payload.withdrawals)), + } + } +} + +impl ExecutionPayload { + /// Unified access to the `transactions` field across all variants. + pub fn transactions(&self) -> TransactionsRef<'_, E> { + self.to_ref().transactions() + } + + /// Unified access to the `withdrawals` field (Capella and later). + pub fn withdrawals(&self) -> Result, BeaconStateError> { + self.to_ref().withdrawals() + } } impl ForkVersionDecode for ExecutionPayload { diff --git a/consensus/types/src/execution/execution_payload_bid.rs b/consensus/types/src/execution/execution_payload_bid.rs index 87097bbd3bd..2943373668d 100644 --- a/consensus/types/src/execution/execution_payload_bid.rs +++ b/consensus/types/src/execution/execution_payload_bid.rs @@ -1,9 +1,10 @@ -use crate::kzg_ext::KzgCommitments; +use crate::kzg_ext::ProgressiveKzgCommitments; use crate::{Address, EthSpec, ExecutionBlockHash, ForkName, Hash256, SignedRoot, Slot}; use context_deserialize::context_deserialize; use educe::Educe; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; +use std::marker::PhantomData; use tree_hash_derive::TreeHash; #[derive(Default, Debug, Clone, Serialize, Encode, Decode, Deserialize, TreeHash, Educe)] @@ -32,8 +33,14 @@ pub struct ExecutionPayloadBid { pub value: u64, #[serde(with = "serde_utils::quoted_u64")] pub execution_payment: u64, - pub blob_kzg_commitments: KzgCommitments, + // [Modified in Gloas:EIP7688] + pub blob_kzg_commitments: ProgressiveKzgCommitments, pub execution_requests_root: Hash256, + #[ssz(skip_serializing, skip_deserializing)] + #[tree_hash(skip_hashing)] + #[serde(skip)] + #[cfg_attr(feature = "arbitrary", arbitrary(default))] + pub _phantom: PhantomData, } impl SignedRoot for ExecutionPayloadBid {} diff --git a/consensus/types/src/execution/execution_payload_envelope.rs b/consensus/types/src/execution/execution_payload_envelope.rs index 87a0ea7a63e..87756e0472d 100644 --- a/consensus/types/src/execution/execution_payload_envelope.rs +++ b/consensus/types/src/execution/execution_payload_envelope.rs @@ -1,4 +1,4 @@ -use crate::execution::{ExecutionPayloadGloas, ExecutionRequests}; +use crate::execution::{ExecutionPayloadGloas, ExecutionRequestsGloas}; use crate::{EthSpec, ForkName, Hash256, SignedRoot, Slot}; use context_deserialize::context_deserialize; use educe::Educe; @@ -19,7 +19,8 @@ use tree_hash_derive::TreeHash; #[serde(bound = "E: EthSpec")] pub struct ExecutionPayloadEnvelope { pub payload: ExecutionPayloadGloas, - pub execution_requests: ExecutionRequests, + // [Modified in Gloas:EIP7688] + pub execution_requests: ExecutionRequestsGloas, #[serde(with = "serde_utils::quoted_u64")] pub builder_index: u64, pub beacon_block_root: Hash256, @@ -31,7 +32,7 @@ impl ExecutionPayloadEnvelope { pub fn empty() -> Self { Self { payload: ExecutionPayloadGloas::default(), - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsGloas::default(), builder_index: 0, beacon_block_root: Hash256::zero(), parent_beacon_block_root: Hash256::zero(), diff --git a/consensus/types/src/execution/execution_requests.rs b/consensus/types/src/execution/execution_requests.rs index 218b7edc170..9224e6fca8d 100644 --- a/consensus/types/src/execution/execution_requests.rs +++ b/consensus/types/src/execution/execution_requests.rs @@ -1,11 +1,13 @@ use alloy_primitives::Bytes; -use context_deserialize::context_deserialize; +use context_deserialize::{ContextDeserialize, context_deserialize}; use educe::Educe; use ethereum_hashing::{DynamicContext, Sha256Context}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use ssz::Encode; use ssz_derive::{Decode, Encode}; -use ssz_types::VariableList; +use ssz_types::{ProgressiveVariableList, VariableList}; +use std::marker::PhantomData; +use superstruct::superstruct; use tree_hash_derive::TreeHash; use crate::{ @@ -23,65 +25,185 @@ pub type WithdrawalRequests = pub type ConsolidationRequests = VariableList::MaxConsolidationRequestsPerPayload>; +/// EIP-7685 execution requests. +/// +/// The `Electra` variant (bounded `VariableList`s) is used from Electra through Fulu. The `Gloas` +/// variant \[Modified in Gloas:EIP7688\] uses unbounded `ProgressiveVariableList`s; its per-list +/// `Max*RequestsPerPayload` limits are no longer enforced by the type and MUST be checked at +/// runtime where the spec requires it. +#[superstruct( + variants(Electra, Gloas), + variant_attributes( + derive( + Debug, + Default, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Educe, + ), + educe(PartialEq, Eq, Hash(bound(E: EthSpec))), + context_deserialize(ForkName), + serde(bound = "E: EthSpec"), + cfg_attr( + feature = "arbitrary", + derive(arbitrary::Arbitrary), + arbitrary(bound = "E: EthSpec"), + ), + ), + specific_variant_attributes(Gloas(tree_hash( + struct_behaviour = "progressive_container", + active_fields(1, 1, 1) + ))), + cast_error(ty = "String", expr = "String::from(\"unexpected execution requests variant\")"), + partial_getter_error( + ty = "String", + expr = "String::from(\"unexpected execution requests variant\")" + ) +)] #[cfg_attr( feature = "arbitrary", derive(arbitrary::Arbitrary), arbitrary(bound = "E: EthSpec") )] -#[derive(Debug, Educe, Default, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)] -#[serde(bound = "E: EthSpec")] -#[educe(PartialEq, Eq, Hash(bound(E: EthSpec)))] -#[context_deserialize(ForkName)] +#[derive(Debug, Clone, Serialize, Encode, TreeHash, Educe)] +#[educe(PartialEq)] +#[serde(bound = "E: EthSpec", untagged)] +#[ssz(enum_behaviour = "transparent")] +#[tree_hash(enum_behaviour = "transparent")] pub struct ExecutionRequests { + #[superstruct(only(Electra), partial_getter(rename = "deposits_electra"))] pub deposits: DepositRequests, + #[superstruct(only(Gloas), partial_getter(rename = "deposits_gloas"))] + pub deposits: ProgressiveVariableList, + #[superstruct(only(Electra), partial_getter(rename = "withdrawals_electra"))] pub withdrawals: WithdrawalRequests, + #[superstruct(only(Gloas), partial_getter(rename = "withdrawals_gloas"))] + pub withdrawals: ProgressiveVariableList, + #[superstruct(only(Electra), partial_getter(rename = "consolidations_electra"))] pub consolidations: ConsolidationRequests, + #[superstruct(only(Gloas), partial_getter(rename = "consolidations_gloas"))] + pub consolidations: ProgressiveVariableList, + // Phantom for the unused `E` in the Gloas variant; skipped everywhere. + #[superstruct(only(Gloas))] + #[ssz(skip_serializing, skip_deserializing)] + #[tree_hash(skip_hashing)] + #[serde(skip)] + #[cfg_attr(feature = "arbitrary", arbitrary(default))] + pub _phantom: PhantomData, +} + +impl Default for ExecutionRequests { + fn default() -> Self { + Self::Electra(ExecutionRequestsElectra::default()) + } +} + +impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionRequests { + fn context_deserialize(deserializer: D, context: ForkName) -> Result + where + D: Deserializer<'de>, + { + if context.gloas_enabled() { + ExecutionRequestsGloas::::deserialize(deserializer) + .map_err(serde::de::Error::custom) + .map(ExecutionRequests::Gloas) + } else { + ExecutionRequestsElectra::::deserialize(deserializer) + .map_err(serde::de::Error::custom) + .map(ExecutionRequests::Electra) + } + } +} + +/// Build the EIP-7685 requests list from each request kind's `(is_empty, ssz_bytes)`. +fn build_execution_requests_list(encoded: [(RequestType, bool, Vec); 3]) -> Vec { + encoded + .into_iter() + .filter(|(_, is_empty, _)| !is_empty) + .map(|(request_type, _, ssz_bytes)| { + Bytes::from_iter([request_type.to_u8()].into_iter().chain(ssz_bytes)) + }) + .collect() +} + +/// Generate the execution layer `requests_hash` based on EIP-7685: +/// `sha256(sha256(requests_0) ++ sha256(requests_1) ++ ...)`. +fn execution_requests_hash(requests_list: &[Bytes]) -> Hash256 { + let mut hasher = DynamicContext::new(); + for request in requests_list { + let mut request_hasher = DynamicContext::new(); + request_hasher.update(request); + hasher.update(&request_hasher.finalize()); + } + hasher.finalize().into() } impl ExecutionRequests { - /// Returns the encoding according to EIP-7685 to send - /// to the execution layer over the engine api. + /// Returns the encoding according to EIP-7685 to send to the execution layer over the engine + /// api. pub fn get_execution_requests_list(&self) -> Vec { - let mut requests_list = Vec::new(); - if !self.deposits.is_empty() { - requests_list.push(Bytes::from_iter( - [RequestType::Deposit.to_u8()] - .into_iter() - .chain(self.deposits.as_ssz_bytes()), - )); - } - if !self.withdrawals.is_empty() { - requests_list.push(Bytes::from_iter( - [RequestType::Withdrawal.to_u8()] - .into_iter() - .chain(self.withdrawals.as_ssz_bytes()), - )); - } - if !self.consolidations.is_empty() { - requests_list.push(Bytes::from_iter( - [RequestType::Consolidation.to_u8()] - .into_iter() - .chain(self.consolidations.as_ssz_bytes()), - )); + match self { + Self::Electra(r) => r.get_execution_requests_list(), + Self::Gloas(r) => r.get_execution_requests_list(), } - requests_list } /// Generate the execution layer `requests_hash` based on EIP-7685. - /// - /// `sha256(sha256(requests_0) ++ sha256(requests_1) ++ ...)` pub fn requests_hash(&self) -> Hash256 { - let mut hasher = DynamicContext::new(); + execution_requests_hash(&self.get_execution_requests_list()) + } +} - for request in self.get_execution_requests_list().iter() { - let mut request_hasher = DynamicContext::new(); - request_hasher.update(request); - let request_hash = request_hasher.finalize(); +/// Implements the EIP-7685 requests list/hash accessors for an `ExecutionRequests` variant. +macro_rules! impl_execution_requests_accessors { + ($variant:ident) => { + impl $variant { + /// EIP-7685 requests list to send to the EL over the engine API. + pub fn get_execution_requests_list(&self) -> Vec { + build_execution_requests_list([ + ( + RequestType::Deposit, + self.deposits.is_empty(), + self.deposits.as_ssz_bytes(), + ), + ( + RequestType::Withdrawal, + self.withdrawals.is_empty(), + self.withdrawals.as_ssz_bytes(), + ), + ( + RequestType::Consolidation, + self.consolidations.is_empty(), + self.consolidations.as_ssz_bytes(), + ), + ]) + } - hasher.update(&request_hash); + /// EIP-7685 `requests_hash`. + pub fn requests_hash(&self) -> Hash256 { + execution_requests_hash(&self.get_execution_requests_list()) + } } + }; +} + +impl_execution_requests_accessors!(ExecutionRequestsElectra); +impl_execution_requests_accessors!(ExecutionRequestsGloas); - hasher.finalize().into() +impl From<&ExecutionRequestsElectra> for ExecutionRequestsGloas { + /// Re-type the bounded (Electra) requests as the progressive Gloas variant. Infallible: the + /// progressive lists have no capacity limit. + fn from(requests: &ExecutionRequestsElectra) -> Self { + Self { + deposits: requests.deposits.iter().cloned().collect(), + withdrawals: requests.withdrawals.iter().cloned().collect(), + consolidations: requests.consolidations.iter().cloned().collect(), + _phantom: std::marker::PhantomData, + } } } @@ -117,5 +239,10 @@ mod tests { use super::*; - ssz_and_tree_hash_tests!(ExecutionRequests); + ssz_and_tree_hash_tests!(ExecutionRequestsElectra); + + mod gloas { + use super::*; + ssz_and_tree_hash_tests!(ExecutionRequestsGloas); + } } diff --git a/consensus/types/src/execution/mod.rs b/consensus/types/src/execution/mod.rs index a3d4ed87301..ec456fa5cee 100644 --- a/consensus/types/src/execution/mod.rs +++ b/consensus/types/src/execution/mod.rs @@ -17,9 +17,10 @@ pub use bls_to_execution_change::BlsToExecutionChange; pub use eth1_data::Eth1Data; pub use execution_block_header::{EncodableExecutionBlockHeader, ExecutionBlockHeader}; pub use execution_payload::{ - ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, - ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionPayloadRef, - Transaction, Transactions, + BlockAccessList, ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, + ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, + ExecutionPayloadRef, ProgressiveTransactions, ProgressiveWithdrawals, Transaction, + Transactions, TransactionsIter, TransactionsRef, WithdrawalsRef, }; pub use execution_payload_bid::ExecutionPayloadBid; pub use execution_payload_envelope::ExecutionPayloadEnvelope; @@ -29,7 +30,8 @@ pub use execution_payload_header::{ ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, }; pub use execution_requests::{ - ConsolidationRequests, DepositRequests, ExecutionRequests, RequestType, WithdrawalRequests, + ConsolidationRequests, DepositRequests, ExecutionRequests, ExecutionRequestsElectra, + ExecutionRequestsGloas, RequestType, WithdrawalRequests, }; pub use payload::{ AbstractExecPayload, BlindedPayload, BlindedPayloadBellatrix, BlindedPayloadCapella, diff --git a/consensus/types/src/kzg_ext/mod.rs b/consensus/types/src/kzg_ext/mod.rs index 09305716ab7..cda88b547ff 100644 --- a/consensus/types/src/kzg_ext/mod.rs +++ b/consensus/types/src/kzg_ext/mod.rs @@ -5,7 +5,7 @@ pub use kzg::{Error as KzgError, Kzg, KzgCommitment, KzgProof}; use crate::core::EthSpec; use crate::{BeaconStateError, Hash256}; use merkle_proof::{MerkleTree, MerkleTreeError}; -use ssz_types::{FixedVector, VariableList}; +use ssz_types::{FixedVector, ProgressiveVariableList, VariableList}; use tree_hash::{BYTES_PER_CHUNK, TreeHash}; // Note on List limit: @@ -20,6 +20,12 @@ pub type KzgProofs = VariableList::MaxCellsPerBlock> pub type KzgCommitments = VariableList::MaxBlobCommitmentsPerBlock>; +/// Progressive (EIP-7688) variant of `KzgCommitments`, used from Gloas onwards. +/// +/// The `MaxBlobCommitmentsPerBlock` limit is no longer enforced by the type and MUST be checked +/// at runtime where the spec requires it (e.g. on gossip verification of execution payload bids). +pub type ProgressiveKzgCommitments = ProgressiveVariableList; + /// Util method helpful for logging. pub fn format_kzg_commitments(commitments: &[KzgCommitment]) -> String { let commitment_strings: Vec = commitments.iter().map(|x| x.to_string()).collect(); diff --git a/consensus/types/src/slashing/attester_slashing.rs b/consensus/types/src/slashing/attester_slashing.rs index c01da71e1d1..7a6b681a94b 100644 --- a/consensus/types/src/slashing/attester_slashing.rs +++ b/consensus/types/src/slashing/attester_slashing.rs @@ -6,13 +6,16 @@ use superstruct::superstruct; use tree_hash_derive::TreeHash; use crate::{ - attestation::{IndexedAttestationBase, IndexedAttestationElectra, IndexedAttestationRef}, + attestation::{ + IndexedAttestationBase, IndexedAttestationElectra, IndexedAttestationGloas, + IndexedAttestationRef, + }, core::EthSpec, fork::ForkName, }; #[superstruct( - variants(Base, Electra), + variants(Base, Electra, Gloas), variant_attributes( derive( Educe, @@ -61,6 +64,7 @@ pub struct AttesterSlashing { pub enum AttesterSlashingOnDisk { Base(AttesterSlashingBase), Electra(AttesterSlashingElectra), + Gloas(AttesterSlashingGloas), } #[derive(Debug, Clone, Encode)] @@ -68,6 +72,7 @@ pub enum AttesterSlashingOnDisk { pub enum AttesterSlashingRefOnDisk<'a, E: EthSpec> { Base(&'a AttesterSlashingBase), Electra(&'a AttesterSlashingElectra), + Gloas(&'a AttesterSlashingGloas), } impl From> for AttesterSlashingOnDisk { @@ -75,6 +80,7 @@ impl From> for AttesterSlashingOnDisk { match attester_slashing { AttesterSlashing::Base(attester_slashing) => Self::Base(attester_slashing), AttesterSlashing::Electra(attester_slashing) => Self::Electra(attester_slashing), + AttesterSlashing::Gloas(attester_slashing) => Self::Gloas(attester_slashing), } } } @@ -84,6 +90,7 @@ impl From> for AttesterSlashing { match attester_slashing { AttesterSlashingOnDisk::Base(attester_slashing) => Self::Base(attester_slashing), AttesterSlashingOnDisk::Electra(attester_slashing) => Self::Electra(attester_slashing), + AttesterSlashingOnDisk::Gloas(attester_slashing) => Self::Gloas(attester_slashing), } } } @@ -95,6 +102,7 @@ impl<'a, E: EthSpec> From> for AttesterSlashing AttesterSlashingRefOnDisk::Electra(attester_slashing) => { Self::Electra(attester_slashing) } + AttesterSlashingRefOnDisk::Gloas(attester_slashing) => Self::Gloas(attester_slashing), } } } @@ -104,6 +112,7 @@ impl<'a, E: EthSpec> From> for AttesterSlashingRefOnD match attester_slashing { AttesterSlashingRef::Base(attester_slashing) => Self::Base(attester_slashing), AttesterSlashingRef::Electra(attester_slashing) => Self::Electra(attester_slashing), + AttesterSlashingRef::Gloas(attester_slashing) => Self::Gloas(attester_slashing), } } } @@ -117,6 +126,9 @@ impl<'a, E: EthSpec> AttesterSlashingRef<'a, E> { AttesterSlashingRef::Electra(attester_slashing) => { AttesterSlashing::Electra(attester_slashing.clone()) } + AttesterSlashingRef::Gloas(attester_slashing) => { + AttesterSlashing::Gloas(attester_slashing.clone()) + } } } @@ -128,6 +140,9 @@ impl<'a, E: EthSpec> AttesterSlashingRef<'a, E> { AttesterSlashingRef::Electra(attester_slashing) => { IndexedAttestationRef::Electra(&attester_slashing.attestation_1) } + AttesterSlashingRef::Gloas(attester_slashing) => { + IndexedAttestationRef::Gloas(&attester_slashing.attestation_1) + } } } @@ -139,6 +154,9 @@ impl<'a, E: EthSpec> AttesterSlashingRef<'a, E> { AttesterSlashingRef::Electra(attester_slashing) => { IndexedAttestationRef::Electra(&attester_slashing.attestation_2) } + AttesterSlashingRef::Gloas(attester_slashing) => { + IndexedAttestationRef::Gloas(&attester_slashing.attestation_2) + } } } } @@ -152,6 +170,9 @@ impl AttesterSlashing { AttesterSlashing::Electra(attester_slashing) => { IndexedAttestationRef::Electra(&attester_slashing.attestation_1) } + AttesterSlashing::Gloas(attester_slashing) => { + IndexedAttestationRef::Gloas(&attester_slashing.attestation_1) + } } } @@ -163,6 +184,9 @@ impl AttesterSlashing { AttesterSlashing::Electra(attester_slashing) => { IndexedAttestationRef::Electra(&attester_slashing.attestation_2) } + AttesterSlashing::Gloas(attester_slashing) => { + IndexedAttestationRef::Gloas(&attester_slashing.attestation_2) + } } } } @@ -172,7 +196,15 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for Vec, { - if context.electra_enabled() { + if context.gloas_enabled() { + >>::deserialize(deserializer) + .map_err(serde::de::Error::custom) + .map(|vec| { + vec.into_iter() + .map(AttesterSlashing::Gloas) + .collect::>() + }) + } else if context.electra_enabled() { >>::deserialize(deserializer) .map_err(serde::de::Error::custom) .map(|vec| { diff --git a/consensus/types/src/slashing/mod.rs b/consensus/types/src/slashing/mod.rs index 551b8e31377..12f042ceb52 100644 --- a/consensus/types/src/slashing/mod.rs +++ b/consensus/types/src/slashing/mod.rs @@ -2,7 +2,7 @@ mod attester_slashing; mod proposer_slashing; pub use attester_slashing::{ - AttesterSlashing, AttesterSlashingBase, AttesterSlashingElectra, AttesterSlashingOnDisk, - AttesterSlashingRef, AttesterSlashingRefOnDisk, + AttesterSlashing, AttesterSlashingBase, AttesterSlashingElectra, AttesterSlashingGloas, + AttesterSlashingOnDisk, AttesterSlashingRef, AttesterSlashingRefOnDisk, }; pub use proposer_slashing::ProposerSlashing; diff --git a/consensus/types/src/state/beacon_state.rs b/consensus/types/src/state/beacon_state.rs index 027acfab7f9..a994dea2872 100644 --- a/consensus/types/src/state/beacon_state.rs +++ b/consensus/types/src/state/beacon_state.rs @@ -8,7 +8,7 @@ use ethereum_hashing::hash; use fixed_bytes::FixedBytesExtended; use int_to_bytes::{int_to_bytes4, int_to_bytes8}; use metastruct::{NumFields, metastruct}; -use milhouse::{List, Vector}; +use milhouse::{AnyList, AnyListMut, AnyListRef, List, ProgressiveList, Vector}; use safe_arith::{ArithError, SafeArith, SafeArithIter}; use serde::{Deserialize, Deserializer, Serialize}; use ssz::{Decode, DecodeError, Encode, ssz_encode}; @@ -74,6 +74,22 @@ pub type Validators = List::ValidatorRegistryLimit, BTreeMap>; pub type Balances = List::ValidatorRegistryLimit>; +// Progressive (EIP-7688) variants of the above, used from Gloas onwards. +pub type ProgressiveValidators = ProgressiveList>; +pub type ProgressiveBalances = ProgressiveList; + +// Views over list fields that are (fixed-capacity) `List`s pre-Gloas and `ProgressiveList`s +// from Gloas onwards (EIP-7688). +pub type ValidatorsRef<'a, E> = + AnyListRef<'a, Validator, ::ValidatorRegistryLimit, BTreeMap>; +pub type ValidatorsMut<'a, E> = + AnyListMut<'a, Validator, ::ValidatorRegistryLimit, BTreeMap>; +pub type BalancesRef<'a, E> = AnyListRef<'a, u64, ::ValidatorRegistryLimit>; +pub type BalancesMut<'a, E> = AnyListMut<'a, u64, ::ValidatorRegistryLimit>; +pub type ValidatorsOwned = + AnyList::ValidatorRegistryLimit, BTreeMap>; +pub type BalancesOwned = AnyList::ValidatorRegistryLimit>; + #[derive(Debug, PartialEq, Clone)] pub enum BeaconStateError { /// A state for a different hard-fork was required -- a severe logic error. @@ -221,6 +237,9 @@ pub enum BeaconStateError { InvalidIndicesCount, InvalidBuilderPendingPaymentsIndex(usize), InvalidExecutionPayloadAvailabilityIndex(usize), + /// Merkle proofs against the `BeaconState` use progressive-container generalized indices + /// from Gloas (EIP-7688) onwards, which are not implemented yet. + ProgressiveMerkleProofNotSupported, } /// Control whether an epoch-indexed field can be indexed at the next epoch or not. @@ -396,20 +415,31 @@ impl From for Hash256 { )), num_fields(all()), )), - Gloas(metastruct( - mappings( - map_beacon_state_gloas_fields(), - map_beacon_state_gloas_tree_list_fields(mutable, fallible, groups(tree_lists)), - map_beacon_state_gloas_tree_list_fields_immutable(groups(tree_lists)), + Gloas( + metastruct( + mappings( + map_beacon_state_gloas_fields(), + map_beacon_state_gloas_tree_list_fields(mutable, fallible, groups(tree_lists)), + map_beacon_state_gloas_tree_list_fields_immutable(groups(tree_lists)), + ), + bimappings(bimap_beacon_state_gloas_tree_list_fields( + other_type = "BeaconStateGloas", + self_mutable, + fallible, + groups(tree_lists) + )), + num_fields(all()), ), - bimappings(bimap_beacon_state_gloas_tree_list_fields( - other_type = "BeaconStateGloas", - self_mutable, - fallible, - groups(tree_lists) - )), - num_fields(all()), - )) + // EIP-7688: the Gloas `BeaconState` is a `ProgressiveContainer` with all 46 spec + // fields active. The cache fields are excluded via `tree_hash(skip_hashing)`. + tree_hash( + struct_behaviour = "progressive_container", + active_fields( + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ) + ) + ) ), cast_error( ty = "BeaconStateError", @@ -476,11 +506,28 @@ where // Registry #[compare_fields(as_iter)] #[cfg_attr(feature = "arbitrary", arbitrary(default))] + #[superstruct( + only(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu), + partial_getter(rename = "validators_basic") + )] pub validators: Validators, + #[compare_fields(as_iter)] + #[cfg_attr(feature = "arbitrary", arbitrary(default))] + #[superstruct(only(Gloas), partial_getter(rename = "validators_progressive"))] + pub validators: ProgressiveValidators, #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] #[compare_fields(as_iter)] #[cfg_attr(feature = "arbitrary", arbitrary(default))] + #[superstruct( + only(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu), + partial_getter(rename = "balances_basic") + )] pub balances: List, + #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] + #[compare_fields(as_iter)] + #[cfg_attr(feature = "arbitrary", arbitrary(default))] + #[superstruct(only(Gloas), partial_getter(rename = "balances_progressive"))] + pub balances: ProgressiveBalances, // Randomness #[cfg_attr(feature = "arbitrary", arbitrary(default))] @@ -501,13 +548,31 @@ where // Participation (Altair and later) #[compare_fields(as_iter)] - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct( + only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu), + partial_getter(rename = "previous_epoch_participation_basic") + )] #[cfg_attr(feature = "arbitrary", arbitrary(default))] - #[compare_fields(as_iter)] pub previous_epoch_participation: List, - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] + #[compare_fields(as_iter)] + #[superstruct( + only(Gloas), + partial_getter(rename = "previous_epoch_participation_progressive") + )] + #[cfg_attr(feature = "arbitrary", arbitrary(default))] + pub previous_epoch_participation: ProgressiveList, + #[superstruct( + only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu), + partial_getter(rename = "current_epoch_participation_basic") + )] #[cfg_attr(feature = "arbitrary", arbitrary(default))] pub current_epoch_participation: List, + #[superstruct( + only(Gloas), + partial_getter(rename = "current_epoch_participation_progressive") + )] + #[cfg_attr(feature = "arbitrary", arbitrary(default))] + pub current_epoch_participation: ProgressiveList, // Finality #[cfg_attr(feature = "arbitrary", arbitrary(default))] @@ -525,9 +590,16 @@ where // Inactivity #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] - #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct( + only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu), + partial_getter(rename = "inactivity_scores_basic") + )] #[cfg_attr(feature = "arbitrary", arbitrary(default))] pub inactivity_scores: List, + #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] + #[superstruct(only(Gloas), partial_getter(rename = "inactivity_scores_progressive"))] + #[cfg_attr(feature = "arbitrary", arbitrary(default))] + pub inactivity_scores: ProgressiveList, // Light-client sync committees #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))] @@ -610,17 +682,41 @@ where pub earliest_consolidation_epoch: Epoch, #[compare_fields(as_iter)] #[cfg_attr(feature = "arbitrary", arbitrary(default))] - #[superstruct(only(Electra, Fulu, Gloas))] + #[superstruct(only(Electra, Fulu), partial_getter(rename = "pending_deposits_basic"))] pub pending_deposits: List, #[compare_fields(as_iter)] #[cfg_attr(feature = "arbitrary", arbitrary(default))] - #[superstruct(only(Electra, Fulu, Gloas))] + #[superstruct(only(Gloas), partial_getter(rename = "pending_deposits_progressive"))] + pub pending_deposits: ProgressiveList, + #[compare_fields(as_iter)] + #[cfg_attr(feature = "arbitrary", arbitrary(default))] + #[superstruct( + only(Electra, Fulu), + partial_getter(rename = "pending_partial_withdrawals_basic") + )] pub pending_partial_withdrawals: List, #[compare_fields(as_iter)] #[cfg_attr(feature = "arbitrary", arbitrary(default))] - #[superstruct(only(Electra, Fulu, Gloas))] + #[superstruct( + only(Gloas), + partial_getter(rename = "pending_partial_withdrawals_progressive") + )] + pub pending_partial_withdrawals: ProgressiveList, + #[compare_fields(as_iter)] + #[cfg_attr(feature = "arbitrary", arbitrary(default))] + #[superstruct( + only(Electra, Fulu), + partial_getter(rename = "pending_consolidations_basic") + )] pub pending_consolidations: List, + #[compare_fields(as_iter)] + #[cfg_attr(feature = "arbitrary", arbitrary(default))] + #[superstruct( + only(Gloas), + partial_getter(rename = "pending_consolidations_progressive") + )] + pub pending_consolidations: ProgressiveList, // Fulu #[compare_fields(as_iter)] @@ -632,7 +728,7 @@ where #[compare_fields(as_iter)] #[cfg_attr(feature = "arbitrary", arbitrary(default))] #[superstruct(only(Gloas))] - pub builders: List, + pub builders: ProgressiveList, #[metastruct(exclude_from(tree_lists))] #[serde(with = "serde_utils::quoted_u64")] @@ -652,8 +748,7 @@ where #[compare_fields(as_iter)] #[cfg_attr(feature = "arbitrary", arbitrary(default))] #[superstruct(only(Gloas))] - pub builder_pending_withdrawals: - List, + pub builder_pending_withdrawals: ProgressiveList, #[cfg_attr(feature = "arbitrary", arbitrary(default))] #[superstruct(only(Gloas))] @@ -663,7 +758,7 @@ where #[compare_fields(as_iter)] #[cfg_attr(feature = "arbitrary", arbitrary(default))] #[superstruct(only(Gloas))] - pub payload_expected_withdrawals: List, + pub payload_expected_withdrawals: ProgressiveList, #[compare_fields(as_iter)] #[cfg_attr(feature = "arbitrary", arbitrary(default))] @@ -716,7 +811,136 @@ where pub epoch_cache: EpochCache, } +/// Generate accessors for a `BeaconState` list field that is stored as a (fixed-capacity) `List` +/// in the `basic(..)` variants and as a `ProgressiveList` in the `progressive(..)` variants +/// (EIP-7688). +/// +/// The first form (no `absent(..)` variants) generates infallible accessors, the second form +/// generates accessors returning `Result` with `IncorrectStateVariant` for the `absent(..)` +/// variants. These replace the getters that superstruct can no longer generate due to the +/// differing field types. +macro_rules! impl_any_list_accessors { + ($field:ident, $field_mut:ident, $ref_ty:ty, $mut_ty:ty, + basic($($basic:ident),* $(,)?), + progressive($($prog:ident),* $(,)?)) => { + pub fn $field(&self) -> $ref_ty { + match self { + $( Self::$basic(state) => AnyListRef::Basic(&state.$field), )* + $( Self::$prog(state) => AnyListRef::Progressive(&state.$field), )* + } + } + + pub fn $field_mut(&mut self) -> $mut_ty { + match self { + $( Self::$basic(state) => AnyListMut::Basic(&mut state.$field), )* + $( Self::$prog(state) => AnyListMut::Progressive(&mut state.$field), )* + } + } + }; + ($field:ident, $field_mut:ident, $ref_ty:ty, $mut_ty:ty, + basic($($basic:ident),* $(,)?), + progressive($($prog:ident),* $(,)?), + absent($($absent:ident),* $(,)?)) => { + pub fn $field(&self) -> Result<$ref_ty, BeaconStateError> { + match self { + $( Self::$absent(_) => Err(BeaconStateError::IncorrectStateVariant), )* + $( Self::$basic(state) => Ok(AnyListRef::Basic(&state.$field)), )* + $( Self::$prog(state) => Ok(AnyListRef::Progressive(&state.$field)), )* + } + } + + pub fn $field_mut(&mut self) -> Result<$mut_ty, BeaconStateError> { + match self { + $( Self::$absent(_) => Err(BeaconStateError::IncorrectStateVariant), )* + $( Self::$basic(state) => Ok(AnyListMut::Basic(&mut state.$field)), )* + $( Self::$prog(state) => Ok(AnyListMut::Progressive(&mut state.$field)), )* + } + } + }; +} + impl BeaconState { + // Accessors for the dual-representation (EIP-7688) list fields. These intentionally use the + // same names as the getters superstruct used to generate, so that call sites which only use + // the API common to `List` and `ProgressiveList` keep working unchanged. + impl_any_list_accessors!( + validators, + validators_mut, + ValidatorsRef<'_, E>, + ValidatorsMut<'_, E>, + basic(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu), + progressive(Gloas) + ); + + impl_any_list_accessors!( + balances, + balances_mut, + BalancesRef<'_, E>, + BalancesMut<'_, E>, + basic(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu), + progressive(Gloas) + ); + + impl_any_list_accessors!( + previous_epoch_participation, + previous_epoch_participation_mut, + AnyListRef<'_, ParticipationFlags, E::ValidatorRegistryLimit>, + AnyListMut<'_, ParticipationFlags, E::ValidatorRegistryLimit>, + basic(Altair, Bellatrix, Capella, Deneb, Electra, Fulu), + progressive(Gloas), + absent(Base) + ); + + impl_any_list_accessors!( + current_epoch_participation, + current_epoch_participation_mut, + AnyListRef<'_, ParticipationFlags, E::ValidatorRegistryLimit>, + AnyListMut<'_, ParticipationFlags, E::ValidatorRegistryLimit>, + basic(Altair, Bellatrix, Capella, Deneb, Electra, Fulu), + progressive(Gloas), + absent(Base) + ); + + impl_any_list_accessors!( + inactivity_scores, + inactivity_scores_mut, + AnyListRef<'_, u64, E::ValidatorRegistryLimit>, + AnyListMut<'_, u64, E::ValidatorRegistryLimit>, + basic(Altair, Bellatrix, Capella, Deneb, Electra, Fulu), + progressive(Gloas), + absent(Base) + ); + + impl_any_list_accessors!( + pending_deposits, + pending_deposits_mut, + AnyListRef<'_, PendingDeposit, E::PendingDepositsLimit>, + AnyListMut<'_, PendingDeposit, E::PendingDepositsLimit>, + basic(Electra, Fulu), + progressive(Gloas), + absent(Base, Altair, Bellatrix, Capella, Deneb) + ); + + impl_any_list_accessors!( + pending_partial_withdrawals, + pending_partial_withdrawals_mut, + AnyListRef<'_, PendingPartialWithdrawal, E::PendingPartialWithdrawalsLimit>, + AnyListMut<'_, PendingPartialWithdrawal, E::PendingPartialWithdrawalsLimit>, + basic(Electra, Fulu), + progressive(Gloas), + absent(Base, Altair, Bellatrix, Capella, Deneb) + ); + + impl_any_list_accessors!( + pending_consolidations, + pending_consolidations_mut, + AnyListRef<'_, PendingConsolidation, E::PendingConsolidationsLimit>, + AnyListMut<'_, PendingConsolidation, E::PendingConsolidationsLimit>, + basic(Electra, Fulu), + progressive(Gloas), + absent(Base, Altair, Bellatrix, Capella, Deneb) + ); + /// Create a new BeaconState suitable for genesis. /// /// Not a complete genesis state, see `initialize_beacon_state_from_eth1`. @@ -1803,22 +2027,30 @@ impl BeaconState { pub fn validators_and_balances_and_progressive_balances_mut<'a>( &'a mut self, ) -> ( - &'a mut Validators, - &'a mut Balances, + ValidatorsMut<'a, E>, + BalancesMut<'a, E>, &'a mut ProgressiveBalancesCache, ) { - map_beacon_state_ref_mut_into_beacon_state_ref!(&'a _, self.to_mut(), |inner, cons| { - if false { - cons(&*inner); - unreachable!() - } else { + macro_rules! validators_and_balances { + ($state:expr, $list_variant:ident) => { ( - &mut inner.validators, - &mut inner.balances, - &mut inner.progressive_balances_cache, + AnyListMut::$list_variant(&mut $state.validators), + AnyListMut::$list_variant(&mut $state.balances), + &mut $state.progressive_balances_cache, ) - } - }) + }; + } + + match self { + Self::Base(state) => validators_and_balances!(state, Basic), + Self::Altair(state) => validators_and_balances!(state, Basic), + Self::Bellatrix(state) => validators_and_balances!(state, Basic), + Self::Capella(state) => validators_and_balances!(state, Basic), + Self::Deneb(state) => validators_and_balances!(state, Basic), + Self::Electra(state) => validators_and_balances!(state, Basic), + Self::Fulu(state) => validators_and_balances!(state, Basic), + Self::Gloas(state) => validators_and_balances!(state, Progressive), + } } #[allow(clippy::type_complexity)] @@ -1826,90 +2058,207 @@ impl BeaconState { &mut self, ) -> Result< ( - &mut Validators, - &mut Balances, - &List, - &List, - &mut List, + ValidatorsMut<'_, E>, + BalancesMut<'_, E>, + AnyListRef<'_, ParticipationFlags, E::ValidatorRegistryLimit>, + AnyListRef<'_, ParticipationFlags, E::ValidatorRegistryLimit>, + AnyListMut<'_, u64, E::ValidatorRegistryLimit>, &mut ProgressiveBalancesCache, &mut ExitCache, &mut EpochCache, ), BeaconStateError, > { + macro_rules! mutable_validator_fields { + ($state:expr, $list_variant:ident) => { + Ok(( + AnyListMut::$list_variant(&mut $state.validators), + AnyListMut::$list_variant(&mut $state.balances), + AnyListRef::$list_variant(&$state.previous_epoch_participation), + AnyListRef::$list_variant(&$state.current_epoch_participation), + AnyListMut::$list_variant(&mut $state.inactivity_scores), + &mut $state.progressive_balances_cache, + &mut $state.exit_cache, + &mut $state.epoch_cache, + )) + }; + } + match self { BeaconState::Base(_) => Err(BeaconStateError::IncorrectStateVariant), - BeaconState::Altair(state) => Ok(( - &mut state.validators, - &mut state.balances, - &state.previous_epoch_participation, - &state.current_epoch_participation, - &mut state.inactivity_scores, - &mut state.progressive_balances_cache, - &mut state.exit_cache, - &mut state.epoch_cache, - )), - BeaconState::Bellatrix(state) => Ok(( - &mut state.validators, - &mut state.balances, - &state.previous_epoch_participation, - &state.current_epoch_participation, - &mut state.inactivity_scores, - &mut state.progressive_balances_cache, - &mut state.exit_cache, - &mut state.epoch_cache, - )), - BeaconState::Capella(state) => Ok(( - &mut state.validators, - &mut state.balances, - &state.previous_epoch_participation, - &state.current_epoch_participation, - &mut state.inactivity_scores, - &mut state.progressive_balances_cache, - &mut state.exit_cache, - &mut state.epoch_cache, - )), - BeaconState::Deneb(state) => Ok(( - &mut state.validators, - &mut state.balances, - &state.previous_epoch_participation, - &state.current_epoch_participation, - &mut state.inactivity_scores, - &mut state.progressive_balances_cache, - &mut state.exit_cache, - &mut state.epoch_cache, - )), - BeaconState::Electra(state) => Ok(( - &mut state.validators, - &mut state.balances, - &state.previous_epoch_participation, - &state.current_epoch_participation, - &mut state.inactivity_scores, - &mut state.progressive_balances_cache, - &mut state.exit_cache, - &mut state.epoch_cache, - )), - BeaconState::Fulu(state) => Ok(( - &mut state.validators, - &mut state.balances, - &state.previous_epoch_participation, - &state.current_epoch_participation, - &mut state.inactivity_scores, - &mut state.progressive_balances_cache, - &mut state.exit_cache, - &mut state.epoch_cache, - )), - BeaconState::Gloas(state) => Ok(( - &mut state.validators, - &mut state.balances, - &state.previous_epoch_participation, - &state.current_epoch_participation, + BeaconState::Altair(state) => mutable_validator_fields!(state, Basic), + BeaconState::Bellatrix(state) => mutable_validator_fields!(state, Basic), + BeaconState::Capella(state) => mutable_validator_fields!(state, Basic), + BeaconState::Deneb(state) => mutable_validator_fields!(state, Basic), + BeaconState::Electra(state) => mutable_validator_fields!(state, Basic), + BeaconState::Fulu(state) => mutable_validator_fields!(state, Basic), + BeaconState::Gloas(state) => mutable_validator_fields!(state, Progressive), + } + } + + /// Take ownership of the validators list, leaving an empty list in its place. + /// + /// Used by the database layer for efficient diffing. + pub fn take_validators(&mut self) -> ValidatorsOwned { + match self { + Self::Base(state) => AnyList::Basic(std::mem::take(&mut state.validators)), + Self::Altair(state) => AnyList::Basic(std::mem::take(&mut state.validators)), + Self::Bellatrix(state) => AnyList::Basic(std::mem::take(&mut state.validators)), + Self::Capella(state) => AnyList::Basic(std::mem::take(&mut state.validators)), + Self::Deneb(state) => AnyList::Basic(std::mem::take(&mut state.validators)), + Self::Electra(state) => AnyList::Basic(std::mem::take(&mut state.validators)), + Self::Fulu(state) => AnyList::Basic(std::mem::take(&mut state.validators)), + Self::Gloas(state) => AnyList::Progressive(std::mem::take(&mut state.validators)), + } + } + + /// Replace the validators list, preserving the fork-appropriate representation. + pub fn set_validators_from_iter( + &mut self, + iter: impl IntoIterator, + ) -> Result<(), BeaconStateError> { + match self { + Self::Base(state) => state.validators = List::try_from_iter(iter)?, + Self::Altair(state) => state.validators = List::try_from_iter(iter)?, + Self::Bellatrix(state) => state.validators = List::try_from_iter(iter)?, + Self::Capella(state) => state.validators = List::try_from_iter(iter)?, + Self::Deneb(state) => state.validators = List::try_from_iter(iter)?, + Self::Electra(state) => state.validators = List::try_from_iter(iter)?, + Self::Fulu(state) => state.validators = List::try_from_iter(iter)?, + Self::Gloas(state) => state.validators = ProgressiveList::try_from_iter(iter)?, + } + Ok(()) + } + + /// Take ownership of the balances list, leaving an empty list in its place. + /// + /// Used by the database layer for efficient diffing. + pub fn take_balances(&mut self) -> BalancesOwned { + match self { + Self::Base(state) => AnyList::Basic(std::mem::take(&mut state.balances)), + Self::Altair(state) => AnyList::Basic(std::mem::take(&mut state.balances)), + Self::Bellatrix(state) => AnyList::Basic(std::mem::take(&mut state.balances)), + Self::Capella(state) => AnyList::Basic(std::mem::take(&mut state.balances)), + Self::Deneb(state) => AnyList::Basic(std::mem::take(&mut state.balances)), + Self::Electra(state) => AnyList::Basic(std::mem::take(&mut state.balances)), + Self::Fulu(state) => AnyList::Basic(std::mem::take(&mut state.balances)), + Self::Gloas(state) => AnyList::Progressive(std::mem::take(&mut state.balances)), + } + } + + /// Replace the balances list, preserving the fork-appropriate representation. + pub fn set_balances_from_iter( + &mut self, + iter: impl IntoIterator, + ) -> Result<(), BeaconStateError> { + match self { + Self::Base(state) => state.balances = List::try_from_iter(iter)?, + Self::Altair(state) => state.balances = List::try_from_iter(iter)?, + Self::Bellatrix(state) => state.balances = List::try_from_iter(iter)?, + Self::Capella(state) => state.balances = List::try_from_iter(iter)?, + Self::Deneb(state) => state.balances = List::try_from_iter(iter)?, + Self::Electra(state) => state.balances = List::try_from_iter(iter)?, + Self::Fulu(state) => state.balances = List::try_from_iter(iter)?, + Self::Gloas(state) => state.balances = ProgressiveList::try_from_iter(iter)?, + } + Ok(()) + } + + /// Take ownership of the inactivity scores list, leaving an empty list in its place. + /// + /// Used by the database layer for efficient diffing. Errors on Base states (no such field). + pub fn take_inactivity_scores( + &mut self, + ) -> Result, BeaconStateError> { + match self { + Self::Base(_) => Err(BeaconStateError::IncorrectStateVariant), + Self::Altair(state) => Ok(AnyList::Basic(std::mem::take(&mut state.inactivity_scores))), + Self::Bellatrix(state) => { + Ok(AnyList::Basic(std::mem::take(&mut state.inactivity_scores))) + } + Self::Capella(state) => { + Ok(AnyList::Basic(std::mem::take(&mut state.inactivity_scores))) + } + Self::Deneb(state) => Ok(AnyList::Basic(std::mem::take(&mut state.inactivity_scores))), + Self::Electra(state) => { + Ok(AnyList::Basic(std::mem::take(&mut state.inactivity_scores))) + } + Self::Fulu(state) => Ok(AnyList::Basic(std::mem::take(&mut state.inactivity_scores))), + Self::Gloas(state) => Ok(AnyList::Progressive(std::mem::take( &mut state.inactivity_scores, - &mut state.progressive_balances_cache, - &mut state.exit_cache, - &mut state.epoch_cache, - )), + ))), + } + } + + /// Rotate the participation flags at the epoch boundary. + /// + /// Sets `previous_epoch_participation = current_epoch_participation` and resets the current + /// participation to default flags for every validator. + pub fn rotate_participation_flags(&mut self) -> Result<(), BeaconStateError> { + let num_validators = self.validators().len(); + + macro_rules! rotate_basic { + ($state:expr) => {{ + $state.previous_epoch_participation = + std::mem::take(&mut $state.current_epoch_participation); + $state.current_epoch_participation = + List::repeat(ParticipationFlags::default(), num_validators)?; + }}; + } + + match self { + Self::Base(_) => return Err(BeaconStateError::IncorrectStateVariant), + Self::Altair(state) => rotate_basic!(state), + Self::Bellatrix(state) => rotate_basic!(state), + Self::Capella(state) => rotate_basic!(state), + Self::Deneb(state) => rotate_basic!(state), + Self::Electra(state) => rotate_basic!(state), + Self::Fulu(state) => rotate_basic!(state), + Self::Gloas(state) => { + state.previous_epoch_participation = + std::mem::take(&mut state.current_epoch_participation); + state.current_epoch_participation = ProgressiveList::try_from_iter( + std::iter::repeat_n(ParticipationFlags::default(), num_validators), + )?; + } + } + Ok(()) + } + + /// Replace the pending deposits list, preserving the fork-appropriate representation. + pub fn set_pending_deposits_from_iter( + &mut self, + iter: impl IntoIterator, + ) -> Result<(), BeaconStateError> { + match self { + Self::Base(_) + | Self::Altair(_) + | Self::Bellatrix(_) + | Self::Capella(_) + | Self::Deneb(_) => return Err(BeaconStateError::IncorrectStateVariant), + Self::Electra(state) => state.pending_deposits = List::try_from_iter(iter)?, + Self::Fulu(state) => state.pending_deposits = List::try_from_iter(iter)?, + Self::Gloas(state) => state.pending_deposits = ProgressiveList::try_from_iter(iter)?, + } + Ok(()) + } + + /// Replace the inactivity scores list, preserving the fork-appropriate representation. + pub fn set_inactivity_scores_from_iter( + &mut self, + iter: impl IntoIterator, + ) -> Result<(), BeaconStateError> { + match self { + Self::Base(_) => return Err(BeaconStateError::IncorrectStateVariant), + Self::Altair(state) => state.inactivity_scores = List::try_from_iter(iter)?, + Self::Bellatrix(state) => state.inactivity_scores = List::try_from_iter(iter)?, + Self::Capella(state) => state.inactivity_scores = List::try_from_iter(iter)?, + Self::Deneb(state) => state.inactivity_scores = List::try_from_iter(iter)?, + Self::Electra(state) => state.inactivity_scores = List::try_from_iter(iter)?, + Self::Fulu(state) => state.inactivity_scores = List::try_from_iter(iter)?, + Self::Gloas(state) => state.inactivity_scores = ProgressiveList::try_from_iter(iter)?, } + Ok(()) } /// Get the balance of a single validator. @@ -1926,7 +2275,7 @@ impl BeaconState { validator_index: usize, ) -> Result<&mut u64, BeaconStateError> { self.balances_mut() - .get_mut(validator_index) + .into_get_mut(validator_index) .ok_or(BeaconStateError::BalancesOutOfBounds(validator_index)) } @@ -1978,7 +2327,7 @@ impl BeaconState { validator_index: usize, ) -> Result<&mut Validator, BeaconStateError> { self.validators_mut() - .get_mut(validator_index) + .into_get_mut(validator_index) .ok_or(BeaconStateError::UnknownValidator(validator_index)) } @@ -2011,13 +2360,13 @@ impl BeaconState { self.balances_mut().push(amount)?; // Altair or later initializations. - if let Ok(previous_epoch_participation) = self.previous_epoch_participation_mut() { + if let Ok(mut previous_epoch_participation) = self.previous_epoch_participation_mut() { previous_epoch_participation.push(ParticipationFlags::default())?; } - if let Ok(current_epoch_participation) = self.current_epoch_participation_mut() { + if let Ok(mut current_epoch_participation) = self.current_epoch_participation_mut() { current_epoch_participation.push(ParticipationFlags::default())?; } - if let Ok(inactivity_scores) = self.inactivity_scores_mut() { + if let Ok(mut inactivity_scores) = self.inactivity_scores_mut() { inactivity_scores.push(0)?; } @@ -2101,7 +2450,7 @@ impl BeaconState { validator_index: usize, ) -> Result, BeaconStateError> { self.validators_mut() - .get_cow(validator_index) + .into_get_cow(validator_index) .ok_or(BeaconStateError::UnknownValidator(validator_index)) } @@ -2131,7 +2480,7 @@ impl BeaconState { validator_index: usize, ) -> Result<&mut u64, BeaconStateError> { self.inactivity_scores_mut()? - .get_mut(validator_index) + .into_get_mut(validator_index) .ok_or(BeaconStateError::InactivityScoresOutOfBounds( validator_index, )) @@ -2305,35 +2654,18 @@ impl BeaconState { *self.total_active_balance_mut() = None; } - /// Get a mutable reference to the epoch participation flags for `epoch`. + /// Get a mutable view of the epoch participation flags for `epoch`. pub fn get_epoch_participation_mut( &mut self, epoch: Epoch, previous_epoch: Epoch, current_epoch: Epoch, - ) -> Result<&mut List, BeaconStateError> { + ) -> Result, BeaconStateError> + { if epoch == current_epoch { - match self { - BeaconState::Base(_) => Err(BeaconStateError::IncorrectStateVariant), - BeaconState::Altair(state) => Ok(&mut state.current_epoch_participation), - BeaconState::Bellatrix(state) => Ok(&mut state.current_epoch_participation), - BeaconState::Capella(state) => Ok(&mut state.current_epoch_participation), - BeaconState::Deneb(state) => Ok(&mut state.current_epoch_participation), - BeaconState::Electra(state) => Ok(&mut state.current_epoch_participation), - BeaconState::Fulu(state) => Ok(&mut state.current_epoch_participation), - BeaconState::Gloas(state) => Ok(&mut state.current_epoch_participation), - } + self.current_epoch_participation_mut() } else if epoch == previous_epoch { - match self { - BeaconState::Base(_) => Err(BeaconStateError::IncorrectStateVariant), - BeaconState::Altair(state) => Ok(&mut state.previous_epoch_participation), - BeaconState::Bellatrix(state) => Ok(&mut state.previous_epoch_participation), - BeaconState::Capella(state) => Ok(&mut state.previous_epoch_participation), - BeaconState::Deneb(state) => Ok(&mut state.previous_epoch_participation), - BeaconState::Electra(state) => Ok(&mut state.previous_epoch_participation), - BeaconState::Fulu(state) => Ok(&mut state.previous_epoch_participation), - BeaconState::Gloas(state) => Ok(&mut state.previous_epoch_participation), - } + self.previous_epoch_participation_mut() } else { Err(BeaconStateError::EpochOutOfBounds) } @@ -2859,7 +3191,7 @@ impl BeaconState { ) -> Result<(), BeaconStateError> { let balance = self .balances_mut() - .get_mut(validator_index) + .into_get_mut(validator_index) .ok_or(BeaconStateError::UnknownValidator(validator_index))?; if *balance > spec.min_activation_balance { let excess_balance = balance.safe_sub(spec.min_activation_balance)?; @@ -2884,7 +3216,7 @@ impl BeaconState { ) -> Result<(), BeaconStateError> { let validator = self .validators_mut() - .get_mut(validator_index) + .into_get_mut(validator_index) .ok_or(BeaconStateError::UnknownValidator(validator_index))?; AsMut::<[u8; 32]>::as_mut(&mut validator.withdrawal_credentials)[0] = spec.compounding_withdrawal_prefix_byte; @@ -3436,6 +3768,12 @@ impl BeaconState { } pub fn compute_current_sync_committee_proof(&self) -> Result, BeaconStateError> { + // [Modified in Gloas:EIP7688] the state is a progressive container with different + // generalized indices, which are not implemented yet. + if self.fork_name_unchecked().gloas_enabled() { + return Err(BeaconStateError::ProgressiveMerkleProofNotSupported); + } + // Sync committees are top-level fields, subtract off the generalized indices // for the internal nodes. Result should be 22 or 23, the field offset of the committee // in the `BeaconState`: @@ -3451,6 +3789,12 @@ impl BeaconState { } pub fn compute_next_sync_committee_proof(&self) -> Result, BeaconStateError> { + // [Modified in Gloas:EIP7688] the state is a progressive container with different + // generalized indices, which are not implemented yet. + if self.fork_name_unchecked().gloas_enabled() { + return Err(BeaconStateError::ProgressiveMerkleProofNotSupported); + } + // Sync committees are top-level fields, subtract off the generalized indices // for the internal nodes. Result should be 22 or 23, the field offset of the committee // in the `BeaconState`: @@ -3466,6 +3810,12 @@ impl BeaconState { } pub fn compute_finalized_root_proof(&self) -> Result, BeaconStateError> { + // [Modified in Gloas:EIP7688] the state is a progressive container with different + // generalized indices, which are not implemented yet. + if self.fork_name_unchecked().gloas_enabled() { + return Err(BeaconStateError::ProgressiveMerkleProofNotSupported); + } + // Finalized root is the right child of `finalized_checkpoint`, divide by two to get // the generalized index of `state.finalized_checkpoint`. let checkpoint_root_gindex = if self.fork_name_unchecked().electra_enabled() { diff --git a/consensus/types/src/state/mod.rs b/consensus/types/src/state/mod.rs index a3bb1b8c9f0..d8779cb5f7f 100644 --- a/consensus/types/src/state/mod.rs +++ b/consensus/types/src/state/mod.rs @@ -15,9 +15,11 @@ mod slashings_cache; pub use activation_queue::ActivationQueue; pub use balance::Balance; pub use beacon_state::{ - BeaconState, BeaconStateAltair, BeaconStateBase, BeaconStateBellatrix, BeaconStateCapella, - BeaconStateDeneb, BeaconStateElectra, BeaconStateError, BeaconStateFulu, BeaconStateGloas, - BeaconStateHash, BeaconStateRef, CACHED_EPOCHS, DEFAULT_PRE_ELECTRA_WS_PERIOD, Validators, + Balances, BalancesMut, BalancesOwned, BalancesRef, BeaconState, BeaconStateAltair, + BeaconStateBase, BeaconStateBellatrix, BeaconStateCapella, BeaconStateDeneb, + BeaconStateElectra, BeaconStateError, BeaconStateFulu, BeaconStateGloas, BeaconStateHash, + BeaconStateRef, CACHED_EPOCHS, DEFAULT_PRE_ELECTRA_WS_PERIOD, ProgressiveBalances, + ProgressiveValidators, Validators, ValidatorsMut, ValidatorsOwned, ValidatorsRef, }; pub use committee_cache::{ CommitteeCache, compute_committee_index_in_epoch, compute_committee_range_in_epoch, diff --git a/lcli/src/indexed_attestations.rs b/lcli/src/indexed_attestations.rs index ccc14171128..e8a685bc7fa 100644 --- a/lcli/src/indexed_attestations.rs +++ b/lcli/src/indexed_attestations.rs @@ -1,6 +1,8 @@ use clap::ArgMatches; use clap_utils::parse_required; -use state_processing::common::{attesting_indices_base, attesting_indices_electra}; +use state_processing::common::{ + attesting_indices_base, attesting_indices_electra, attesting_indices_gloas, +}; use std::fs::File; use std::io::Read; use std::path::{Path, PathBuf}; @@ -41,6 +43,9 @@ pub fn run(matches: &ArgMatches) -> Result<(), String> { Attestation::Electra(att) => { attesting_indices_electra::get_indexed_attestation_from_state(&state, &att) } + Attestation::Gloas(att) => { + attesting_indices_gloas::get_indexed_attestation_from_state(&state, &att) + } }) .collect::, _>>() .map_err(|e| format!("Error constructing indexed attestation: {:?}", e))?; diff --git a/slasher/src/lib.rs b/slasher/src/lib.rs index b41aa23f7fe..cc8ae7ae46d 100644 --- a/slasher/src/lib.rs +++ b/slasher/src/lib.rs @@ -28,7 +28,9 @@ pub use database::{ }; pub use error::Error; -use types::{AttesterSlashing, AttesterSlashingBase, AttesterSlashingElectra}; +use types::{ + AttesterSlashing, AttesterSlashingBase, AttesterSlashingElectra, AttesterSlashingGloas, +}; use types::{EthSpec, IndexedAttestation, ProposerSlashing}; #[derive(Debug, PartialEq)] @@ -68,10 +70,18 @@ impl AttesterSlashingStatus { attestation_2: new.clone(), })) } + // A slashing involving a gloas attestation type must return an + // `AttesterSlashingGloas` type. + (IndexedAttestation::Gloas(_), _) | (_, IndexedAttestation::Gloas(_)) => { + Some(AttesterSlashing::Gloas(AttesterSlashingGloas { + attestation_1: existing.clone().to_gloas(), + attestation_2: new_attestation.clone().to_gloas(), + })) + } // A slashing involving an electra attestation type must return an `AttesterSlashingElectra` type (_, _) => Some(AttesterSlashing::Electra(AttesterSlashingElectra { - attestation_1: existing.clone().to_electra(), - attestation_2: new_attestation.clone().to_electra(), + attestation_1: existing.clone().to_electra().ok()?, + attestation_2: new_attestation.clone().to_electra().ok()?, })), } } @@ -82,10 +92,18 @@ impl AttesterSlashingStatus { attestation_2: existing_att.clone(), })) } + // A slashing involving a gloas attestation type must return an + // `AttesterSlashingGloas` type. + (IndexedAttestation::Gloas(_), _) | (_, IndexedAttestation::Gloas(_)) => { + Some(AttesterSlashing::Gloas(AttesterSlashingGloas { + attestation_1: new_attestation.clone().to_gloas(), + attestation_2: existing.clone().to_gloas(), + })) + } // A slashing involving an electra attestation type must return an `AttesterSlashingElectra` type (_, _) => Some(AttesterSlashing::Electra(AttesterSlashingElectra { - attestation_1: new_attestation.clone().to_electra(), - attestation_2: existing.clone().to_electra(), + attestation_1: new_attestation.clone().to_electra().ok()?, + attestation_2: existing.clone().to_electra().ok()?, })), }, } diff --git a/slasher/src/test_utils.rs b/slasher/src/test_utils.rs index 82b7f885ec6..332dd8c5703 100644 --- a/slasher/src/test_utils.rs +++ b/slasher/src/test_utils.rs @@ -4,8 +4,8 @@ use std::collections::HashSet; use std::sync::Arc; use types::{ AttestationData, AttesterSlashing, AttesterSlashingBase, AttesterSlashingElectra, - BeaconBlockHeader, ChainSpec, Checkpoint, Epoch, EthSpec, Hash256, IndexedAttestation, - MainnetEthSpec, SignedBeaconBlockHeader, Slot, + AttesterSlashingGloas, BeaconBlockHeader, ChainSpec, Checkpoint, Epoch, EthSpec, Hash256, + IndexedAttestation, MainnetEthSpec, SignedBeaconBlockHeader, Slot, attestation::{IndexedAttestationBase, IndexedAttestationElectra}, }; @@ -72,10 +72,17 @@ pub fn att_slashing( attestation_2: att2.clone(), }) } + // A slashing involving a gloas attestation type must return a gloas AttesterSlashing type + (IndexedAttestation::Gloas(_), _) | (_, IndexedAttestation::Gloas(_)) => { + AttesterSlashing::Gloas(AttesterSlashingGloas { + attestation_1: attestation_1.clone().to_gloas(), + attestation_2: attestation_2.clone().to_gloas(), + }) + } // A slashing involving an electra attestation type must return an electra AttesterSlashing type (_, _) => AttesterSlashing::Electra(AttesterSlashingElectra { - attestation_1: attestation_1.clone().to_electra(), - attestation_2: attestation_2.clone().to_electra(), + attestation_1: attestation_1.clone().to_electra().unwrap(), + attestation_2: attestation_2.clone().to_electra().unwrap(), }), } } diff --git a/testing/ef_tests/check_all_files_accessed.py b/testing/ef_tests/check_all_files_accessed.py index 723c5e7e9e8..1d16b85e8f8 100755 --- a/testing/ef_tests/check_all_files_accessed.py +++ b/testing/ef_tests/check_all_files_accessed.py @@ -60,13 +60,6 @@ "tests/.*/gloas/ssz_static/ExecutionPayloadHeader/.*", # ForkChoiceNode is internal to fork choice and probably doesn't need SSZ tests. "tests/.*/gloas/ssz_static/ForkChoiceNode/.*", - # EIP-7916 is still in draft and hasn't been implemented yet https://eips.ethereum.org/EIPS/eip-7916 - "tests/general/phase0/ssz_generic/progressive_bitlist", - "tests/general/phase0/ssz_generic/basic_progressive_list", - "tests/general/phase0/ssz_generic/containers/.*/ProgressiveBitsStruct.*", - "tests/general/phase0/ssz_generic/containers/.*/ProgressiveTestStruct.*", - "tests/general/phase0/ssz_generic/progressive_containers/.*", - "tests/general/phase0/ssz_generic/compatible_unions/.*", # 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", diff --git a/testing/ef_tests/src/cases/fork_choice.rs b/testing/ef_tests/src/cases/fork_choice.rs index f6405831894..5c20b7b77ad 100644 --- a/testing/ef_tests/src/cases/fork_choice.rs +++ b/testing/ef_tests/src/cases/fork_choice.rs @@ -242,7 +242,13 @@ impl LoadCase for ForkChoiceTest { }) } Step::Attestation { attestation } => { - if fork_name.electra_enabled() { + if fork_name.gloas_enabled() { + ssz_decode_file(&path.join(format!("{}.ssz_snappy", attestation))).map( + |attestation| Step::Attestation { + attestation: Attestation::Gloas(attestation), + }, + ) + } else if fork_name.electra_enabled() { ssz_decode_file(&path.join(format!("{}.ssz_snappy", attestation))).map( |attestation| Step::Attestation { attestation: Attestation::Electra(attestation), @@ -257,7 +263,12 @@ impl LoadCase for ForkChoiceTest { } } Step::AttesterSlashing { attester_slashing } => { - if fork_name.electra_enabled() { + if fork_name.gloas_enabled() { + ssz_decode_file(&path.join(format!("{}.ssz_snappy", attester_slashing))) + .map(|attester_slashing| Step::AttesterSlashing { + attester_slashing: AttesterSlashing::Gloas(attester_slashing), + }) + } else if fork_name.electra_enabled() { ssz_decode_file(&path.join(format!("{}.ssz_snappy", attester_slashing))) .map(|attester_slashing| Step::AttesterSlashing { attester_slashing: AttesterSlashing::Electra(attester_slashing), diff --git a/testing/ef_tests/src/cases/gossip_validation.rs b/testing/ef_tests/src/cases/gossip_validation.rs index 3dbbcae5a72..59adb5c2a4c 100644 --- a/testing/ef_tests/src/cases/gossip_validation.rs +++ b/testing/ef_tests/src/cases/gossip_validation.rs @@ -191,7 +191,9 @@ impl GossipTester { fork_name: ForkName, ) -> Result { let ssz_path = path.join(format!("{}.ssz_snappy", message_meta.message)); - let slashing: AttesterSlashing = if fork_name.electra_enabled() { + let slashing: AttesterSlashing = if fork_name.gloas_enabled() { + ssz_decode_file(&ssz_path).map(AttesterSlashing::Gloas)? + } else if fork_name.electra_enabled() { ssz_decode_file(&ssz_path).map(AttesterSlashing::Electra)? } else { ssz_decode_file(&ssz_path).map(AttesterSlashing::Base)? diff --git a/testing/ef_tests/src/cases/operations.rs b/testing/ef_tests/src/cases/operations.rs index f5c999920dc..82d6ed448fc 100644 --- a/testing/ef_tests/src/cases/operations.rs +++ b/testing/ef_tests/src/cases/operations.rs @@ -168,7 +168,9 @@ impl Operation for AttesterSlashing { } fn decode(path: &Path, fork_name: ForkName, _spec: &ChainSpec) -> Result { - if fork_name.electra_enabled() { + if fork_name.gloas_enabled() { + Ok(Self::Gloas(ssz_decode_file(path)?)) + } else if fork_name.electra_enabled() { Ok(Self::Electra(ssz_decode_file(path)?)) } else { Ok(Self::Base(ssz_decode_file(path)?)) diff --git a/testing/ef_tests/src/cases/ssz_generic.rs b/testing/ef_tests/src/cases/ssz_generic.rs index 1dd37a22eed..429be3a9aec 100644 --- a/testing/ef_tests/src/cases/ssz_generic.rs +++ b/testing/ef_tests/src/cases/ssz_generic.rs @@ -5,8 +5,10 @@ use crate::cases::common::{DecimalU128, DecimalU256, SszStaticType}; use crate::cases::ssz_static::{check_serialization, check_tree_hash}; use crate::decode::{context_yaml_decode_file, log_file_access, snappy_decode_file}; use context_deserialize::{ContextDeserialize, context_deserialize}; -use milhouse::Vector; +use milhouse::{List, ProgressiveList, Vector}; use serde::{Deserialize, Deserializer, de::Error as SerdeError}; +use serde_json::Value as JsonValue; +use ssz::ProgressiveBitList; use ssz_derive::{Decode, Encode}; use ssz_types::{BitList, BitVector, FixedVector, VariableList}; use tree_hash::TreeHash; @@ -14,6 +16,46 @@ use tree_hash_derive::TreeHash; use typenum::*; use types::ForkName; +/// Helper struct for deserializing compatible unions from `{selector, data}` YAML format. +#[derive(Deserialize)] +struct CompatibleUnionYaml { + selector: u8, + data: JsonValue, +} + +/// Implements `Deserialize` for compatible union types to handle the EF test YAML format. +/// +/// Deserialize into `CompatibleUnionYaml` which captures `selector` (`u8`) and +/// `data` (`JsonValue`). +/// Match on the selector to determine which variant to construct. +/// Deserialize the `data` field into the appropriate inner type. +macro_rules! impl_compatible_union_deserialize { + ($type:ty, { $($selector:literal => $variant:ident($inner:ty)),+ $(,)? }) => { + impl<'de> Deserialize<'de> for $type { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let yaml = CompatibleUnionYaml::deserialize(deserializer)?; + match yaml.selector { + $( + $selector => { + let inner: $inner = serde_json::from_value(yaml.data).map_err(D::Error::custom)?; + Ok(<$type>::$variant(inner)) + } + )+ + s => Err(D::Error::custom(format!( + "unknown selector {s} for {}", stringify!($type) + ))), + } + } + } + }; +} + +type U1280 = op!(U128 * U10); +type U1281 = op!(U1280 + U1); + #[derive(Debug, Clone, Deserialize)] #[context_deserialize(ForkName)] struct Metadata { @@ -113,8 +155,15 @@ macro_rules! type_dispatch { "VarTestStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* VarTestStruct>, $($rest)*), "ComplexTestStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* ComplexTestStruct>, $($rest)*), "BitsStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* BitsStruct>, $($rest)*), - // EIP-7916 is still in draft and hasn't been implemented yet https://eips.ethereum.org/EIPS/eip-7916 - "ProgressiveTestStruct" | "ProgressiveBitsStruct" => Err(Error::SkippedKnownFailure), + "ProgressiveBitsStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* ProgressiveBitsStruct>, $($rest)*), + "ProgressiveSingleFieldContainerTestStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* ProgressiveSingleFieldContainerTestStruct>, $($rest)*), + "ProgressiveSingleListContainerTestStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* ProgressiveSingleListContainerTestStruct>, $($rest)*), + "ProgressiveVarTestStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* ProgressiveVarTestStruct>, $($rest)*), + "ProgressiveComplexTestStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* ProgressiveComplexTestStruct>, $($rest)*), + "ProgressiveTestStruct" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* ProgressiveTestStruct>, $($rest)*), + "CompatibleUnionA" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* CompatibleUnionA>, $($rest)*), + "CompatibleUnionBC" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* CompatibleUnionBC>, $($rest)*), + "CompatibleUnionABCA" => type_dispatch!($function, ($($arg),*), $base_ty, <$($param_ty,)* CompatibleUnionABCA>, $($rest)*), _ => Err(Error::FailedToParseTest(format!("unsupported: {}", $value))), } }; @@ -159,6 +208,17 @@ impl Case for SszGeneric { [length => typenum] )?; } + "basic_progressive_list" => { + let elem_ty = parts[1]; + + type_dispatch!( + ssz_generic_test, + (&self.path, fork_name), + ProgressiveList, + <>, + [elem_ty => primitive_type] + )?; + } "bitlist" => { let mut limit = parts[1]; @@ -186,6 +246,14 @@ impl Case for SszGeneric { [length => typenum] )?; } + "progressive_bitlist" => { + type_dispatch!( + ssz_generic_test, + (&self.path, fork_name), + ProgressiveBitList, + <>, + )?; + } "boolean" => { ssz_generic_test::(&self.path, fork_name)?; } @@ -211,6 +279,28 @@ impl Case for SszGeneric { [type_name => test_container] )?; } + "progressive_containers" => { + let type_name = parts[0]; + + type_dispatch!( + ssz_generic_test, + (&self.path, fork_name), + _, + <>, + [type_name => test_container] + )?; + } + "compatible_unions" => { + let type_name = parts[0]; + + type_dispatch!( + ssz_generic_test, + (&self.path, fork_name), + _, + <>, + [type_name => test_container] + )?; + } _ => panic!("unsupported handler: {}", self.handler_name), } Ok(()) @@ -302,6 +392,15 @@ struct ComplexTestStruct { G: Vector, } +#[derive(Debug, Clone, PartialEq, Decode, Encode, TreeHash, Deserialize)] +#[context_deserialize(ForkName)] +struct ProgressiveTestStruct { + A: ProgressiveList, + B: ProgressiveList, + C: ProgressiveList, + D: ProgressiveList>, +} + #[derive(Debug, Clone, PartialEq, Decode, Encode, TreeHash, Deserialize)] #[context_deserialize(ForkName)] struct BitsStruct { @@ -312,6 +411,120 @@ struct BitsStruct { E: BitVector, } +#[derive(Debug, Clone, PartialEq, Decode, Encode, TreeHash, Deserialize)] +#[context_deserialize(ForkName)] +struct ProgressiveBitsStruct { + A: BitVector, + B: BitList, + C: ProgressiveBitList, + D: BitVector, + E: BitList, + F: ProgressiveBitList, + G: BitVector, + H: BitList, + I: ProgressiveBitList, + J: BitVector, + K: BitList, + L: ProgressiveBitList, +} + +#[derive(Debug, Clone, PartialEq, Decode, Encode, TreeHash, Deserialize)] +#[tree_hash(struct_behaviour = "progressive_container", active_fields(1))] +#[context_deserialize(ForkName)] +struct ProgressiveSingleFieldContainerTestStruct { + A: u8, +} + +#[derive(Debug, Clone, PartialEq, Decode, Encode, TreeHash, Deserialize)] +#[tree_hash( + struct_behaviour = "progressive_container", + active_fields(0, 0, 0, 0, 1) +)] +#[context_deserialize(ForkName)] +struct ProgressiveSingleListContainerTestStruct { + C: ProgressiveBitList, +} + +#[derive(Debug, Clone, PartialEq, Decode, Encode, TreeHash, Deserialize)] +#[tree_hash( + struct_behaviour = "progressive_container", + active_fields(1, 0, 1, 0, 1) +)] +#[context_deserialize(ForkName)] +struct ProgressiveVarTestStruct { + A: u8, + B: List, + C: ProgressiveBitList, +} + +#[derive(Debug, Clone, PartialEq, Decode, Encode, TreeHash, Deserialize)] +#[tree_hash( + struct_behaviour = "progressive_container", + active_fields(1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1) +)] +#[context_deserialize(ForkName)] +struct ProgressiveComplexTestStruct { + A: u8, + B: List, + C: ProgressiveBitList, + D: ProgressiveList, + E: ProgressiveList, + F: ProgressiveList>, + G: List, + H: ProgressiveList, +} + +#[derive(Debug, Clone, PartialEq, Decode, Encode, TreeHash)] +#[ssz(enum_behaviour = "compatible_union")] +#[tree_hash(enum_behaviour = "compatible_union")] +#[context_deserialize(ForkName)] +enum CompatibleUnionA { + #[ssz(selector = "1")] + ProgressiveSingleFieldContainerTestStruct(ProgressiveSingleFieldContainerTestStruct), +} + +impl_compatible_union_deserialize!(CompatibleUnionA, { + 1 => ProgressiveSingleFieldContainerTestStruct(ProgressiveSingleFieldContainerTestStruct), +}); + +#[derive(Debug, Clone, PartialEq, Decode, Encode, TreeHash)] +#[ssz(enum_behaviour = "compatible_union")] +#[tree_hash(enum_behaviour = "compatible_union")] +#[context_deserialize(ForkName)] +enum CompatibleUnionBC { + #[ssz(selector = "2")] + ProgressiveSingleListContainerTestStruct(ProgressiveSingleListContainerTestStruct), + #[ssz(selector = "3")] + ProgressiveVarTestStruct(ProgressiveVarTestStruct), +} + +impl_compatible_union_deserialize!(CompatibleUnionBC, { + 2 => ProgressiveSingleListContainerTestStruct(ProgressiveSingleListContainerTestStruct), + 3 => ProgressiveVarTestStruct(ProgressiveVarTestStruct), +}); + +#[derive(Debug, Clone, PartialEq, Decode, Encode, TreeHash)] +#[ssz(enum_behaviour = "compatible_union")] +#[tree_hash(enum_behaviour = "compatible_union")] +#[context_deserialize(ForkName)] +enum CompatibleUnionABCA { + #[ssz(selector = "1")] + A1(ProgressiveSingleFieldContainerTestStruct), + #[ssz(selector = "2")] + B1(ProgressiveSingleListContainerTestStruct), + #[ssz(selector = "3")] + C1(ProgressiveVarTestStruct), + #[ssz(selector = "4")] + A2(ProgressiveSingleFieldContainerTestStruct), +} + +impl_compatible_union_deserialize!(CompatibleUnionABCA, { + 1 => A1(ProgressiveSingleFieldContainerTestStruct), + 2 => B1(ProgressiveSingleListContainerTestStruct), + 3 => C1(ProgressiveVarTestStruct), + 4 => A2(ProgressiveSingleFieldContainerTestStruct), +}); + fn byte_list_from_hex_str<'de, D, N: Unsigned>( deserializer: D, ) -> Result, D::Error> diff --git a/testing/ef_tests/src/handler.rs b/testing/ef_tests/src/handler.rs index df1ece49dd4..76872066d21 100644 --- a/testing/ef_tests/src/handler.rs +++ b/testing/ef_tests/src/handler.rs @@ -329,6 +329,10 @@ impl SszStaticHandler { Self::for_forks(ForkName::list_all()[5..].to_vec()) } + pub fn electra_through_fulu() -> Self { + Self::for_forks(ForkName::list_all()[5..7].to_vec()) + } + pub fn fulu_and_later() -> Self { Self::for_forks(ForkName::list_all()[6..].to_vec()) } @@ -1245,13 +1249,21 @@ impl Handler for SszGenericHandler { // Supported SSZ generic handlers pub struct BasicVector; type_name!(BasicVector, "basic_vector"); +pub struct BasicProgressiveList; +type_name!(BasicProgressiveList, "basic_progressive_list"); pub struct Bitlist; type_name!(Bitlist, "bitlist"); pub struct Bitvector; type_name!(Bitvector, "bitvector"); +pub struct ProgressiveBitlist; +type_name!(ProgressiveBitlist, "progressive_bitlist"); pub struct Boolean; type_name!(Boolean, "boolean"); pub struct Uints; type_name!(Uints, "uints"); pub struct Containers; type_name!(Containers, "containers"); +pub struct ProgressiveContainers; +type_name!(ProgressiveContainers, "progressive_containers"); +pub struct CompatibleUnions; +type_name!(CompatibleUnions, "compatible_unions"); diff --git a/testing/ef_tests/src/type_name.rs b/testing/ef_tests/src/type_name.rs index 18666befaa7..ee52e12f951 100644 --- a/testing/ef_tests/src/type_name.rs +++ b/testing/ef_tests/src/type_name.rs @@ -39,13 +39,16 @@ type_name!(MainnetEthSpec, "mainnet"); type_name_generic!(AggregateAndProof); type_name_generic!(AggregateAndProofBase, "AggregateAndProof"); type_name_generic!(AggregateAndProofElectra, "AggregateAndProof"); +type_name_generic!(AggregateAndProofGloas, "AggregateAndProof"); type_name_generic!(Attestation); type_name_generic!(AttestationBase, "Attestation"); type_name_generic!(AttestationElectra, "Attestation"); +type_name_generic!(AttestationGloas, "Attestation"); type_name!(AttestationData); type_name_generic!(AttesterSlashing); type_name_generic!(AttesterSlashingBase, "AttesterSlashing"); type_name_generic!(AttesterSlashingElectra, "AttesterSlashing"); +type_name_generic!(AttesterSlashingGloas, "AttesterSlashing"); type_name_generic!(BeaconBlock); type_name_generic!(BeaconBlockBody); type_name_generic!(BeaconBlockBodyBase, "BeaconBlockBody"); @@ -93,6 +96,8 @@ type_name_generic!(ExecutionPayloadHeaderFulu, "ExecutionPayloadHeader"); type_name_generic!(ExecutionPayloadBid); type_name_generic!(SignedExecutionPayloadBid); type_name_generic!(ExecutionRequests); +type_name_generic!(ExecutionRequestsElectra, "ExecutionRequests"); +type_name_generic!(ExecutionRequestsGloas, "ExecutionRequests"); type_name_generic!(ExecutionPayloadEnvelope); type_name_generic!(SignedExecutionPayloadEnvelope); type_name_generic!(BlindedPayload, "ExecutionPayloadHeader"); @@ -102,6 +107,7 @@ type_name_generic!(HistoricalBatch); type_name_generic!(IndexedAttestation); type_name_generic!(IndexedAttestationBase, "IndexedAttestation"); type_name_generic!(IndexedAttestationElectra, "IndexedAttestation"); +type_name_generic!(IndexedAttestationGloas, "IndexedAttestation"); type_name_generic!(IndexedPayloadAttestation); type_name_generic!(LightClientBootstrap); type_name_generic!(LightClientBootstrapAltair, "LightClientBootstrap"); @@ -167,6 +173,7 @@ type_name!(SignedProposerPreferences); type_name_generic!(SignedAggregateAndProof); type_name_generic!(SignedAggregateAndProofBase, "SignedAggregateAndProof"); type_name_generic!(SignedAggregateAndProofElectra, "SignedAggregateAndProof"); +type_name_generic!(SignedAggregateAndProofGloas, "SignedAggregateAndProof"); type_name_generic!(SignedBeaconBlock); type_name!(SignedBeaconBlockHeader); type_name_generic!(SignedContributionAndProof); diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index 6e1c4fdc10c..65c58c93838 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -320,9 +320,13 @@ mod ssz_static { fn attestation() { SszStaticHandler::, MinimalEthSpec>::pre_electra().run(); SszStaticHandler::, MainnetEthSpec>::pre_electra().run(); - SszStaticHandler::, MinimalEthSpec>::electra_and_later() + SszStaticHandler::, MinimalEthSpec>::electra_through_fulu() .run(); - SszStaticHandler::, MainnetEthSpec>::electra_and_later() + SszStaticHandler::, MainnetEthSpec>::electra_through_fulu() + .run(); + SszStaticHandler::, MinimalEthSpec>::gloas_and_later() + .run(); + SszStaticHandler::, MainnetEthSpec>::gloas_and_later() .run(); } @@ -338,10 +342,16 @@ mod ssz_static { .run(); SszStaticHandler::, MainnetEthSpec>::pre_electra() .run(); - SszStaticHandler::, MinimalEthSpec>::electra_and_later() + SszStaticHandler::, MinimalEthSpec>::electra_through_fulu() .run(); - SszStaticHandler::, MainnetEthSpec>::electra_and_later() + SszStaticHandler::, MainnetEthSpec>::electra_through_fulu() .run(); + SszStaticHandler::, MinimalEthSpec>::gloas_and_later( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::gloas_and_later( + ) + .run(); } #[test] @@ -350,9 +360,13 @@ mod ssz_static { .run(); SszStaticHandler::, MainnetEthSpec>::pre_electra() .run(); - SszStaticHandler::, MinimalEthSpec>::electra_and_later() + SszStaticHandler::, MinimalEthSpec>::electra_through_fulu() .run(); - SszStaticHandler::, MainnetEthSpec>::electra_and_later() + SszStaticHandler::, MainnetEthSpec>::electra_through_fulu() + .run(); + SszStaticHandler::, MinimalEthSpec>::gloas_and_later() + .run(); + SszStaticHandler::, MainnetEthSpec>::gloas_and_later() .run(); } @@ -364,10 +378,16 @@ mod ssz_static { SszStaticHandler::, MainnetEthSpec>::pre_electra( ) .run(); - SszStaticHandler::, MinimalEthSpec>::electra_and_later( + SszStaticHandler::, MinimalEthSpec>::electra_through_fulu( ) .run(); - SszStaticHandler::, MainnetEthSpec>::electra_and_later( + SszStaticHandler::, MainnetEthSpec>::electra_through_fulu( + ) + .run(); + SszStaticHandler::, MinimalEthSpec>::gloas_and_later( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::gloas_and_later( ) .run(); } @@ -378,10 +398,16 @@ mod ssz_static { .run(); SszStaticHandler::, MainnetEthSpec>::pre_electra() .run(); - SszStaticHandler::, MinimalEthSpec>::electra_and_later( + SszStaticHandler::, MinimalEthSpec>::electra_through_fulu( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::electra_through_fulu( + ) + .run(); + SszStaticHandler::, MinimalEthSpec>::gloas_and_later( ) .run(); - SszStaticHandler::, MainnetEthSpec>::electra_and_later( + SszStaticHandler::, MainnetEthSpec>::gloas_and_later( ) .run(); } @@ -788,9 +814,14 @@ mod ssz_static { #[test] fn execution_requests() { - SszStaticHandler::, MainnetEthSpec>::electra_and_later() + SszStaticHandler::, MainnetEthSpec>::electra_through_fulu() .run(); - SszStaticHandler::, MinimalEthSpec>::electra_and_later() + SszStaticHandler::, MinimalEthSpec>::electra_through_fulu() + .run(); + // [Modified in Gloas:EIP7688] + SszStaticHandler::, MainnetEthSpec>::gloas_and_later() + .run(); + SszStaticHandler::, MinimalEthSpec>::gloas_and_later() .run(); } @@ -880,6 +911,14 @@ fn ssz_generic() { SszGenericHandler::::default().run(); } +#[test] +fn ssz_generic_progressive() { + SszGenericHandler::::default().run(); + SszGenericHandler::::default().run(); + SszGenericHandler::::default().run(); + SszGenericHandler::::default().run(); +} + #[test] fn epoch_processing_justification_and_finalization() { EpochProcessingHandler::::default().run(); diff --git a/testing/state_transition_vectors/src/exit.rs b/testing/state_transition_vectors/src/exit.rs index 3b0fe7d8ec2..24d9419fda2 100644 --- a/testing/state_transition_vectors/src/exit.rs +++ b/testing/state_transition_vectors/src/exit.rs @@ -127,7 +127,7 @@ vectors_and_tests!( block_modifier: Box::new(|_, block| { // Duplicate the exit let exit = block.body().voluntary_exits().first().unwrap().clone(); - block.body_mut().voluntary_exits_mut().push(exit).unwrap(); + block.body_mut().voluntary_exits_push(exit).unwrap(); }), expected: Err(BlockProcessingError::ExitInvalid { index: 1, @@ -145,13 +145,9 @@ vectors_and_tests!( invalid_validator_unknown, ExitTest { block_modifier: Box::new(|_, block| { - block - .body_mut() - .voluntary_exits_mut() - .get_mut(0) - .unwrap() - .message - .validator_index = VALIDATOR_COUNT as u64; + block.body_mut().voluntary_exits_apply(|exit| { + exit.message.validator_index = VALIDATOR_COUNT as u64; + }); }), expected: Err(BlockProcessingError::ExitInvalid { index: 0, @@ -309,13 +305,9 @@ vectors_and_tests!( block_modifier: Box::new(|_, block| { // Shift the validator index by 1 so that it's mismatched from the key that was // used to sign. - block - .body_mut() - .voluntary_exits_mut() - .get_mut(0) - .unwrap() - .message - .validator_index = VALIDATOR_INDEX + 1; + block.body_mut().voluntary_exits_apply(|exit| { + exit.message.validator_index = VALIDATOR_INDEX + 1; + }); }), expected: Err(BlockProcessingError::ExitInvalid { index: 0,