diff --git a/Cargo.lock b/Cargo.lock index 416a56cd29d..19612748111 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -683,7 +683,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -694,7 +694,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1457,7 +1457,7 @@ dependencies = [ "bitflags 2.10.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.12.1", "lazy_static", "lazycell", "proc-macro2", @@ -1477,7 +1477,7 @@ dependencies = [ "bitflags 2.10.0", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.12.1", "log", "prettyplease", "proc-macro2", @@ -2058,7 +2058,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]] @@ -3203,7 +3203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3371,8 +3371,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#43766ccc42b5f6b9ead9e4fe37a3f5a8011dd520" dependencies = [ "alloy-primitives", "arbitrary", @@ -3387,9 +3386,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#43766ccc42b5f6b9ead9e4fe37a3f5a8011dd520" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -4813,6 +4811,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -5897,8 +5904,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", @@ -6323,7 +6329,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7161,7 +7167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.117", @@ -7174,7 +7180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.117", @@ -7233,9 +7239,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quinn" -version = "0.11.11" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", @@ -7254,9 +7260,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.15" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", "fastbloom", @@ -7830,7 +7836,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -8545,7 +8551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -8579,8 +8585,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#9203d56ad2d7bc5f12133d043e085843f81edcd6" dependencies = [ "arbitrary", "context_deserialize", @@ -8890,7 +8895,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -9440,8 +9445,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#5e272f38aa290179f62552820ce6edb0ba43fc20" dependencies = [ "alloy-primitives", "ethereum_hashing", @@ -9453,8 +9457,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#5e272f38aa290179f62552820ce6edb0ba43fc20" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -10246,7 +10249,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3cbe113d522..8f50ac0d9df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -277,6 +277,13 @@ 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" } # Temporary patch until the axum migration is complete warp = { git = "https://github.com/macladson/warp", rev = "6f5f21beab6a240e59470caaab56afd46d46b709" } diff --git a/beacon_node/beacon_chain/src/attestation_verification.rs b/beacon_node/beacon_chain/src/attestation_verification.rs index 90ac7d68cf9..bf0d29a0b82 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::{ @@ -688,6 +689,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; @@ -724,6 +735,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()) + } } }; @@ -1562,6 +1580,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 7eff6fb99a3..b06c839e0c8 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1259,7 +1259,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" ); } @@ -1756,6 +1756,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)?, + ), } } @@ -5823,6 +5829,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) }, @@ -5833,6 +5842,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) }, @@ -6091,11 +6103,6 @@ impl BeaconChain { blob_kzg_commitments: kzg_commitments .ok_or(BlockProductionError::InvalidPayloadFork)?, execution_requests: maybe_requests - .map(|r| ExecutionRequestsElectra { - deposits: r.deposits().clone(), - withdrawals: r.withdrawals().clone(), - consolidations: r.consolidations().clone(), - }) .ok_or(BlockProductionError::MissingExecutionRequests)?, }, }), @@ -6150,11 +6157,6 @@ impl BeaconChain { blob_kzg_commitments: kzg_commitments .ok_or(BlockProductionError::InvalidPayloadFork)?, execution_requests: maybe_requests - .map(|r| ExecutionRequestsElectra { - deposits: r.deposits().clone(), - withdrawals: r.withdrawals().clone(), - consolidations: r.consolidations().clone(), - }) .ok_or(BlockProductionError::MissingExecutionRequests)?, }, }), diff --git a/beacon_node/beacon_chain/src/block_production/gloas.rs b/beacon_node/beacon_chain/src/block_production/gloas.rs index f84ad65e12a..fc8d9b5f8a6 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, ExecutionRequestsGloas, 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, @@ -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::>(); @@ -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 list 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,12 @@ impl BeaconChain { should_override_builder, } = block_proposal_contents; + // [Modified in Gloas:EIP7688] convert the EL-derived blob commitments to their progressive + // representation. The bid's requests root commits to the progressive merkleization, and the + // execution requests are already carried as their progressive `Gloas` representation. + 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 +840,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 @@ -1139,7 +1156,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; @@ -1163,11 +1180,12 @@ mod tests { consolidations: Vec, ) -> ExecutionRequestsGloas { ExecutionRequestsGloas { - deposits: VariableList::empty(), - withdrawals: VariableList::new(withdrawals).unwrap(), - consolidations: VariableList::new(consolidations).unwrap(), - builder_deposits: VariableList::empty(), - builder_exits: VariableList::empty(), + deposits: ProgressiveVariableList::empty(), + withdrawals: ProgressiveVariableList::new(withdrawals), + consolidations: ProgressiveVariableList::new(consolidations), + builder_deposits: ProgressiveVariableList::empty(), + builder_exits: ProgressiveVariableList::empty(), + _phantom: PhantomData, } } diff --git a/beacon_node/beacon_chain/src/data_availability_checker.rs b/beacon_node/beacon_chain/src/data_availability_checker.rs index d129072ff9c..9db74a963aa 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker.rs @@ -618,13 +618,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) @@ -1216,7 +1216,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..314aa477b54 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,8 @@ 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:?}"))?, + 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 c88ec5813a5..d01e6734703 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 @@ -341,7 +341,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, @@ -449,7 +449,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 d198b56d1b6..e60fb900d91 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::{ @@ -660,7 +660,7 @@ fn invalid_blob_kzg_commitments() { value: 0, parent_block_root: ctx.genesis_block_root, prev_randao: ctx.expected_prev_randao(), - 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 341c7c00f02..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,7 +312,7 @@ 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, @@ -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(), + 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 bb0a1a124d3..22b4c301964 100644 --- a/beacon_node/beacon_chain/src/pending_payload_cache/mod.rs +++ b/beacon_node/beacon_chain/src/pending_payload_cache/mod.rs @@ -217,7 +217,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)?; @@ -308,7 +308,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| { 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..ee7ba780334 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,8 @@ 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()?, + column: ProgressiveVariableList::from_iter(column), + kzg_proofs: ProgressiveVariableList::from_iter(kzg_proofs), slot, beacon_block_root, }))) diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index ce51a82cec9..16c8b7440ea 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -51,6 +51,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; @@ -1564,6 +1566,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) @@ -1902,6 +1910,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() = { @@ -2557,6 +2568,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)); + } + } } } @@ -2669,6 +2697,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)); + } + } } } @@ -2826,7 +2871,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. @@ -4131,7 +4176,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)), @@ -4212,9 +4258,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 ad369c79ee6..ea458fd9eaa 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 bf8d7cdae0a..1b765169537 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -25,7 +25,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; @@ -352,12 +352,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() @@ -885,8 +885,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)); @@ -1006,9 +1005,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 = @@ -1128,8 +1131,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)); @@ -1177,8 +1179,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, @@ -1653,11 +1654,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 c89be0f5dda..b88eb2c02bd 100644 --- a/beacon_node/beacon_chain/tests/payload_invalidation.rs +++ b/beacon_node/beacon_chain/tests/payload_invalidation.rs @@ -1130,6 +1130,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 4a2b8121a43..452bc5b576b 100644 --- a/beacon_node/beacon_chain/tests/prepare_payload.rs +++ b/beacon_node/beacon_chain/tests/prepare_payload.rs @@ -9,11 +9,12 @@ use bls::Keypair; use eth2::types::ProposerPreparationData; use fork_choice::PayloadStatus; use logging::create_test_tracing_subscriber; -use ssz_types::VariableList; +use ssz_types::ProgressiveVariableList; use state_processing::{ per_block_processing::{apply_parent_execution_payload, withdrawals::get_expected_withdrawals}, state_advance::complete_state_advance, }; +use std::marker::PhantomData; use std::sync::{Arc, LazyLock}; use store::database::interface::BeaconNodeBackend; use store::{HotColdDB, StoreConfig}; @@ -185,11 +186,12 @@ async fn prepare_payload_generic( let consolidation_request = harness.make_switch_to_compounding_request(1); let execution_requests = ExecutionRequests::Gloas(ExecutionRequestsGloas:: { - deposits: VariableList::empty(), - withdrawals: VariableList::empty(), - consolidations: VariableList::new(vec![consolidation_request]).unwrap(), - builder_deposits: VariableList::empty(), - builder_exits: VariableList::empty(), + deposits: ProgressiveVariableList::empty(), + withdrawals: ProgressiveVariableList::empty(), + consolidations: ProgressiveVariableList::new(vec![consolidation_request]), + builder_deposits: ProgressiveVariableList::empty(), + builder_exits: ProgressiveVariableList::empty(), + _phantom: PhantomData, }); // Inject the execution requests into the mock EL so the next payload includes them. diff --git a/beacon_node/execution_layer/src/block_hash.rs b/beacon_node/execution_layer/src/block_hash.rs index 69a6324b239..1f0c0df4241 100644 --- a/beacon_node/execution_layer/src/block_hash.rs +++ b/beacon_node/execution_layer/src/block_hash.rs @@ -22,9 +22,7 @@ pub fn calculate_execution_block_hash( // 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() { diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index cb5168b6f81..2f47e3794ce 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -1663,7 +1663,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 dfa1c25282f..45f059c09ff 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -2,13 +2,14 @@ use super::*; use alloy_rlp::RlpEncodable; use serde::{Deserialize, Serialize}; use ssz::{Decode, 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::{ - BuilderDepositRequests, BuilderExitRequests, ConsolidationRequests, DepositRequests, - ExecutionRequestsElectra, ExecutionRequestsGloas, RequestType, WithdrawalRequests, + BlockAccessList, BuilderDepositRequests, BuilderExitRequests, ConsolidationRequests, + DepositRequests, ExecutionRequestsElectra, ExecutionRequestsGloas, ProgressiveTransactions, + RequestType, WithdrawalRequests, }; use types::kzg_ext::KzgCommitments; use types::{Blob, KzgProof}; @@ -100,10 +101,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::prog_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, @@ -111,8 +124,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, @@ -258,7 +271,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, @@ -433,7 +446,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, @@ -624,12 +637,15 @@ impl TryFrom for ExecutionRequestsGloas { fn try_from(value: JsonExecutionRequests) -> Result { let (deposits, withdrawals, consolidations, builder_deposits, builder_exits) = parse_execution_requests::(value)?; + // [Modified in Gloas:EIP7688] the Gloas variant stores progressive (unbounded) lists, so + // re-type the parsed bounded lists. Ok(ExecutionRequestsGloas { - deposits, - withdrawals, - consolidations, - builder_deposits, - builder_exits, + deposits: deposits.iter().cloned().collect(), + withdrawals: withdrawals.iter().cloned().collect(), + consolidations: consolidations.iter().cloned().collect(), + builder_deposits: builder_deposits.iter().cloned().collect(), + builder_exits: builder_exits.iter().cloned().collect(), + _phantom: std::marker::PhantomData, }) } } @@ -1247,6 +1263,10 @@ mod tests { VariableList::try_from(vec![x.clone()]).unwrap() } + fn singleton_progressive_list(x: &T) -> ProgressiveVariableList { + ProgressiveVariableList::new(vec![x.clone()]) + } + /// Tests all error conditions except ssz decoding errors /// /// *** @@ -1465,11 +1485,12 @@ mod tests { ])) .unwrap(), ExecutionRequestsGloas { - deposits: singleton_list(&deposit_request), - withdrawals: singleton_list(&withdrawal_request), - consolidations: singleton_list(&consolidation_request), - builder_deposits: singleton_list(&builder_deposit_request), - builder_exits: singleton_list(&builder_exit_request), + deposits: singleton_progressive_list(&deposit_request), + withdrawals: singleton_progressive_list(&withdrawal_request), + consolidations: singleton_progressive_list(&consolidation_request), + builder_deposits: singleton_progressive_list(&builder_deposit_request), + builder_exits: singleton_progressive_list(&builder_exit_request), + _phantom: std::marker::PhantomData, } ); @@ -1480,11 +1501,12 @@ mod tests { ])) .unwrap(), ExecutionRequestsGloas { - deposits: singleton_list(&deposit_request), + deposits: singleton_progressive_list(&deposit_request), withdrawals: Default::default(), consolidations: Default::default(), builder_deposits: Default::default(), builder_exits: Default::default(), + _phantom: std::marker::PhantomData, } ); @@ -1502,8 +1524,9 @@ mod tests { deposits: Default::default(), withdrawals: Default::default(), consolidations: Default::default(), - builder_deposits: singleton_list(&builder_deposit_request), - builder_exits: singleton_list(&builder_exit_request), + builder_deposits: singleton_progressive_list(&builder_deposit_request), + builder_exits: singleton_progressive_list(&builder_exit_request), + _phantom: std::marker::PhantomData, } ); 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 d6b7f018aa5..646e08d5bb9 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 @@ -53,6 +53,7 @@ pub struct NewPayloadRequest<'block, E: EthSpec> { partial_getter(rename = "execution_requests_electra") )] pub execution_requests: &'block ExecutionRequestsElectra, + // [Modified in Gloas:EIP7688] #[superstruct(only(Gloas), partial_getter(rename = "execution_requests_gloas"))] pub execution_requests: &'block ExecutionRequestsGloas, } diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 523f06b7ffd..4e5a7720d96 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -47,15 +47,14 @@ use types::builder::BuilderBid; use types::execution::BlockProductionVersion; use types::kzg_ext::KzgCommitments; use types::{ - AbstractExecPayload, BlobsList, ExecutionPayloadDeneb, ExecutionRequests, KzgProofs, - SignedBlindedBeaconBlock, + AbstractExecPayload, BlobsList, ExecutionPayloadDeneb, ExecutionRequests, + ExecutionRequestsElectra, ExecutionRequestsGloas, KzgProofs, SignedBlindedBeaconBlock, }; use types::{ BeaconStateError, BlindedPayload, ChainSpec, Epoch, ExecPayload, ExecutionPayloadBellatrix, - ExecutionPayloadCapella, ExecutionPayloadElectra, ExecutionPayloadFulu, FullPayload, - ProposerPreparationData, Slot, + ExecutionPayloadCapella, ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, + FullPayload, ProposerPreparationData, Slot, }; -use types::{ExecutionPayloadGloas, ExecutionRequestsGloas}; mod block_hash; mod engine_api; @@ -118,14 +117,14 @@ impl TryFrom> for ProvenancedPayload BlockProposalContents::PayloadAndBlobs { payload: ExecutionPayloadHeader::Fulu(builder_bid.header).into(), block_value: builder_bid.value, kzg_commitments: builder_bid.blob_kzg_commitments, blobs_and_proofs: None, - requests: Some(builder_bid.execution_requests.into()), + requests: Some(builder_bid.execution_requests), }, }; Ok(ProvenancedPayload::Builder( @@ -236,7 +235,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>, }, } @@ -282,7 +281,14 @@ impl> TryFrom> block_value, kzg_commitments: bundle.commitments, blobs_and_proofs: Some((bundle.blobs, bundle.proofs)), - requests: maybe_requests, + // Gloas payloads are handled via `BlockProposalContentsGloas`, not this path. + requests: match maybe_requests { + Some(ExecutionRequests::Electra(requests)) => Some(requests), + Some(ExecutionRequests::Gloas(_)) => { + return Err(Error::InvalidPayloadConversion); + } + None => None, + }, }), None => Ok(Self::Payload { payload: execution_payload.into(), @@ -311,7 +317,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..139515b4cda 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; @@ -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 10eaefaabaf..fedb8b5e775 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -720,8 +720,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 e02107e1166..9eff3dbee46 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -607,13 +607,10 @@ impl MockBuilder { .unwrap_or_default(), value: self.get_bid_value(value), pubkey: self.builder_sk.public_key().compress(), - execution_requests: maybe_requests - .map(|r| ExecutionRequestsElectra { - deposits: r.deposits().clone(), - withdrawals: r.withdrawals().clone(), - consolidations: r.consolidations().clone(), - }) - .unwrap_or_default(), + execution_requests: match maybe_requests { + Some(ExecutionRequests::Electra(requests)) => requests, + _ => ExecutionRequestsElectra::default(), + }, }), ForkName::Electra => BuilderBid::Electra(BuilderBidElectra { header: payload @@ -625,13 +622,10 @@ impl MockBuilder { .unwrap_or_default(), value: self.get_bid_value(value), pubkey: self.builder_sk.public_key().compress(), - execution_requests: maybe_requests - .map(|r| ExecutionRequestsElectra { - deposits: r.deposits().clone(), - withdrawals: r.withdrawals().clone(), - consolidations: r.consolidations().clone(), - }) - .unwrap_or_default(), + execution_requests: match maybe_requests { + Some(ExecutionRequests::Electra(requests)) => requests, + _ => ExecutionRequestsElectra::default(), + }, }), ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb { header: payload 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..6cd253f6b81 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 0aabf0ccfe2..a9070acdd34 100644 --- a/beacon_node/http_api/src/beacon/states.rs +++ b/beacon_node/http_api/src/beacon/states.rs @@ -52,7 +52,7 @@ pub fn get_beacon_state_pending_consolidations( }; Ok(( - consolidations.clone(), + consolidations.to_owned_list(), execution_optimistic, finalized, state.fork_name_unchecked(), @@ -98,7 +98,7 @@ pub fn get_beacon_state_pending_partial_withdrawals( }; Ok(( - withdrawals.clone(), + withdrawals.to_owned_list(), execution_optimistic, finalized, state.fork_name_unchecked(), @@ -144,7 +144,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 b37bd7d37db..c62ebd7d30a 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, @@ -987,19 +988,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 bd2a6f17559..3e0178fe378 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 @@ -3082,6 +3084,7 @@ impl ApiTester { execution_payment: 0, blob_kzg_commitments: Default::default(), execution_requests_root: Hash256::zero(), + _phantom: std::marker::PhantomData, }; let signed = SignedExecutionPayloadBid { @@ -5274,6 +5277,9 @@ impl ApiTester { SignedAggregateAndProof::Electra(aggregate) => { aggregate.message.aggregate.data.slot += 1; } + SignedAggregateAndProof::Gloas(aggregate) => { + aggregate.message.aggregate.data.slot += 1; + } } self.client @@ -5311,6 +5317,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 00b2c42629a..296637f1f4d 100644 --- a/beacon_node/lighthouse_network/src/types/pubsub.rs +++ b/beacon_node/lighthouse_network/src/types/pubsub.rs @@ -7,17 +7,17 @@ use ssz::{Decode, Encode}; use std::io::{Error, ErrorKind}; use std::sync::Arc; use types::{ - AttesterSlashing, AttesterSlashingBase, AttesterSlashingElectra, CellBitmap, DataColumnSidecar, - DataColumnSubnetId, EthSpec, ForkContext, ForkName, Hash256, LightClientFinalityUpdate, - LightClientOptimisticUpdate, PartialDataColumn, PartialDataColumnHeader, - PartialDataColumnSidecar, PayloadAttestationMessage, ProposerSlashing, SignedAggregateAndProof, - SignedAggregateAndProofBase, SignedAggregateAndProofElectra, SignedBeaconBlock, - SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockBellatrix, - SignedBeaconBlockCapella, SignedBeaconBlockDeneb, SignedBeaconBlockElectra, - SignedBeaconBlockFulu, SignedBeaconBlockGloas, SignedBlsToExecutionChange, - SignedContributionAndProof, SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope, - SignedProposerPreferences, SignedVoluntaryExit, SingleAttestation, SubnetId, - SyncCommitteeMessage, SyncSubnetId, + AttesterSlashing, AttesterSlashingBase, AttesterSlashingElectra, AttesterSlashingGloas, + CellBitmap, DataColumnSidecar, DataColumnSubnetId, EthSpec, ForkContext, ForkName, Hash256, + LightClientFinalityUpdate, LightClientOptimisticUpdate, PartialDataColumn, + PartialDataColumnHeader, PartialDataColumnSidecar, PayloadAttestationMessage, ProposerSlashing, + SignedAggregateAndProof, SignedAggregateAndProofBase, SignedAggregateAndProofElectra, + SignedAggregateAndProofGloas, SignedBeaconBlock, SignedBeaconBlockAltair, + SignedBeaconBlockBase, SignedBeaconBlockBellatrix, SignedBeaconBlockCapella, + SignedBeaconBlockDeneb, SignedBeaconBlockElectra, SignedBeaconBlockFulu, + SignedBeaconBlockGloas, SignedBlsToExecutionChange, SignedContributionAndProof, + SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope, SignedProposerPreferences, + SignedVoluntaryExit, SingleAttestation, SubnetId, SyncCommitteeMessage, SyncSubnetId, }; #[derive(Debug, Clone, PartialEq)] @@ -200,7 +200,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))?, @@ -309,7 +317,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 f5c557a130c..25b3acd17d4 100644 --- a/beacon_node/network/src/network_beacon_processor/tests.rs +++ b/beacon_node/network/src/network_beacon_processor/tests.rs @@ -43,11 +43,11 @@ use std::time::Duration; use tokio::sync::mpsc; use types::{ AttesterSlashing, ChainSpec, DataColumnSidecarList, DataColumnSubnetId, Epoch, EthSpec, - ExecutionPayloadEnvelope, ExecutionPayloadGloas, Hash256, MainnetEthSpec, ProposerSlashing, - SignedAggregateAndProof, SignedBeaconBlock, SignedExecutionPayloadEnvelope, - SignedVoluntaryExit, SingleAttestation, Slot, SubnetId, + ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequestsGloas, Hash256, + MainnetEthSpec, ProposerSlashing, SignedAggregateAndProof, SignedBeaconBlock, + SignedExecutionPayloadEnvelope, SignedVoluntaryExit, SingleAttestation, Slot, SubnetId, + data::BlobIdentifier, }; -use types::{ExecutionRequestsGloas, data::BlobIdentifier}; type E = MainnetEthSpec; type T = EphemeralHarnessType; @@ -1408,7 +1408,7 @@ async fn requeue_unknown_block_gossip_aggregated_attestation_without_import() { .await; assert_eq!( - rig.chain.naive_aggregation_pool.read().num_items(), + rig.chain.op_pool.num_attestations(), initial_attns, "Attestation should not have been included." ); diff --git a/beacon_node/network/src/sync/tests/lookups.rs b/beacon_node/network/src/sync/tests/lookups.rs index f9afb910d9c..d800faca67a 100644 --- a/beacon_node/network/src/sync/tests/lookups.rs +++ b/beacon_node/network/src/sync/tests/lookups.rs @@ -1249,8 +1249,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 9b3f5d8857c..88e0df54e4f 100644 --- a/consensus/fork_choice/tests/tests.rs +++ b/consensus/fork_choice/tests/tests.rs @@ -784,6 +784,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 fc215942fae..8f24e71018e 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -599,6 +599,43 @@ pub fn apply_parent_execution_payload( 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. + // [New in Gloas:EIP8282] the builder request lists must be checked as well. + let request_checks: [(&str, usize, usize); 5] = [ + ( + "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(), + ), + ( + "builder_deposit_requests", + requests.builder_deposits.len(), + E::MaxBuilderDepositRequestsPerPayload::to_usize(), + ), + ( + "builder_exit_requests", + requests.builder_exits.len(), + E::MaxBuilderExitRequestsPerPayload::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(state, &requests.deposits, spec)?; process_operations::process_withdrawal_requests(state, &requests.withdrawals, spec)?; diff --git a/consensus/state_processing/src/per_block_processing/errors.rs b/consensus/state_processing/src/per_block_processing/errors.rs index 2679cdf9d57..ad4645a17bc 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 8d9962c4783..cca9a8bb052 100644 --- a/consensus/state_processing/src/per_block_processing/process_operations.rs +++ b/consensus/state_processing/src/per_block_processing/process_operations.rs @@ -18,9 +18,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, @@ -33,11 +39,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() { @@ -62,6 +78,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::*; @@ -184,7 +256,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, @@ -304,7 +376,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, @@ -668,7 +740,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, @@ -701,7 +773,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, @@ -827,7 +899,7 @@ pub fn process_deposit_requests( 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, diff --git a/consensus/state_processing/src/per_block_processing/tests.rs b/consensus/state_processing/src/per_block_processing/tests.rs index 593a2557e86..63f33548fb2 100644 --- a/consensus/state_processing/src/per_block_processing/tests.rs +++ b/consensus/state_processing/src/per_block_processing/tests.rs @@ -12,7 +12,7 @@ use crate::{ }; use beacon_chain::test_utils::{BeaconChainHarness, EphemeralHarnessType}; use bls::{AggregateSignature, Keypair, PublicKeyBytes, Signature, SignatureBytes}; -use ssz_types::Bitfield; +use ssz_types::BitList; use ssz_types::VariableList; use std::sync::{Arc, LazyLock}; use test_utils::generate_deterministic_keypairs; @@ -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(())); @@ -396,7 +438,7 @@ async fn invalid_attestation_no_committee_for_index() { .attestations_mut() .next() .unwrap() - .data_mut() + .into_data_mut() .index += 1; let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attestations( @@ -445,7 +487,7 @@ async fn invalid_attestation_wrong_justified_checkpoint() { .attestations_mut() .next() .unwrap() - .data_mut() + .into_data_mut() .source = new_justified_checkpoint; let mut ctxt = ConsensusContext::new(state.slot()); @@ -486,14 +528,21 @@ async fn invalid_attestation_bad_aggregation_bitfield_len() { .deconstruct() .0; // Use Electra method since harness runs at Electra fork - *head_block + if let AttestationRefMut::Electra(att) = head_block .to_mut() .body_mut() .attestations_mut() .next() .unwrap() - .aggregation_bits_electra_mut() - .unwrap() = Bitfield::with_capacity(spec.target_committee_size).unwrap(); + { + att.aggregation_bits = + BitList::<::MaxValidatorsPerSlot>::with_capacity( + spec.target_committee_size, + ) + .unwrap(); + } else { + panic!("harness should produce Electra attestations"); + } let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attestations( @@ -533,7 +582,7 @@ async fn invalid_attestation_bad_signature() { .attestations_mut() .next() .unwrap() - .signature_mut() = AggregateSignature::empty(); + .into_signature_mut() = AggregateSignature::empty(); let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attestations( @@ -576,7 +625,7 @@ async fn invalid_attestation_included_too_early() { .attestations_mut() .next() .unwrap() - .data_mut() + .into_data_mut() .slot = new_attesation_slot; let mut ctxt = ConsensusContext::new(state.slot()); @@ -626,7 +675,7 @@ async fn invalid_attestation_target_epoch_slot_mismatch() { .attestations_mut() .next() .unwrap() - .data_mut() + .into_data_mut() .target .epoch += Epoch::new(1); @@ -684,6 +733,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 +773,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 +817,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 43d6606f07e..7a94aec352a 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, @@ -1262,9 +1261,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)?; @@ -1302,7 +1301,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 6e32bd00bc3..54d5c9ce181 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::is_valid_deposit_signature; 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, FixedVector}; use std::{collections::HashMap, mem}; @@ -8,7 +8,7 @@ use tree_hash::TreeHash; use typenum::Unsigned; use types::{ BeaconState, BeaconStateError as Error, BeaconStateGloas, BuilderPendingPayment, ChainSpec, - DepositData, EthSpec, ExecutionPayloadBid, ExecutionRequestsGloas, Fork, + DepositData, EthSpec, ExecutionPayloadBid, ExecutionRequestsGloas, Fork, PendingDeposit, consts::gloas::PAYLOAD_BUILDER_VERSION, is_builder_withdrawal_credential, }; @@ -55,22 +55,26 @@ 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), + 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 + 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), + 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(), @@ -92,12 +96,16 @@ 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(), + 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)] @@ -106,9 +114,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, @@ -166,9 +174,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 @@ -182,7 +190,7 @@ 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; } @@ -190,14 +198,14 @@ fn onboard_builders_from_pending_deposits( None => { // 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; } @@ -232,7 +240,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..49486b85f3a 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,9 @@ impl From for Error { arbitrary(bound = "E: EthSpec") ) ), + specific_variant_attributes(Gloas( + 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 +83,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 +100,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 +118,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 +182,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 +231,13 @@ impl Attestation { genesis_validators_root, spec, ), + Attestation::Gloas(att) => att.sign( + secret_key, + committee_position, + fork, + genesis_validators_root, + spec, + ), } } @@ -213,6 +250,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 +258,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 +266,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 +274,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 +282,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 +290,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 +301,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 +309,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 +340,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 +348,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 +356,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 +364,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 +384,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 +493,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 +693,7 @@ impl SlotData for AttestationRef<'_, E> { pub enum AttestationOnDisk { Base(AttestationBase), Electra(AttestationElectra), + Gloas(AttestationGloas), } impl AttestationOnDisk { @@ -524,6 +701,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 +711,7 @@ impl AttestationOnDisk { pub enum AttestationRefOnDisk<'a, E: EthSpec> { Base(&'a AttestationBase), Electra(&'a AttestationElectra), + Gloas(&'a AttestationGloas), } impl From> for AttestationOnDisk { @@ -540,6 +719,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 +729,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 +739,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 +749,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 +759,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 +814,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..8a464b064b6 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,10 @@ use crate::{attestation::AttestationData, core::EthSpec, fork::ForkName}; derive(arbitrary::Arbitrary), arbitrary(bound = "E: EthSpec"), ), - ) + ), + specific_variant_attributes(Gloas( + tree_hash(struct_behaviour = "progressive_container", active_fields(1, 1, 1)) + )) )] #[cfg_attr( feature = "arbitrary", @@ -62,8 +65,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 +102,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 +110,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 +118,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 +126,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 +134,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 +147,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 +197,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 +205,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 +213,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 +221,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 +229,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 +237,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 +252,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/indexed_payload_attestation.rs b/consensus/types/src/attestation/indexed_payload_attestation.rs index 67fdf77bdf1..693173b25cd 100644 --- a/consensus/types/src/attestation/indexed_payload_attestation.rs +++ b/consensus/types/src/attestation/indexed_payload_attestation.rs @@ -11,6 +11,7 @@ use tree_hash_derive::TreeHash; #[serde(bound = "E: EthSpec", deny_unknown_fields)] #[cfg_attr(feature = "arbitrary", arbitrary(bound = "E: EthSpec"))] #[context_deserialize(ForkName)] +#[tree_hash(struct_behaviour = "progressive_container", active_fields(1, 1, 1))] pub struct IndexedPayloadAttestation { #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] pub attesting_indices: VariableList, 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/payload_attestation.rs b/consensus/types/src/attestation/payload_attestation.rs index d5e76f941b5..be82e3e5ed2 100644 --- a/consensus/types/src/attestation/payload_attestation.rs +++ b/consensus/types/src/attestation/payload_attestation.rs @@ -14,6 +14,7 @@ use tree_hash_derive::TreeHash; #[cfg_attr(feature = "arbitrary", arbitrary(bound = "E: EthSpec"))] #[educe(PartialEq, Hash)] #[context_deserialize(ForkName)] +#[tree_hash(struct_behaviour = "progressive_container", active_fields(1, 1, 1))] pub struct PayloadAttestation { pub aggregation_bits: BitVector, pub data: PayloadAttestationData, 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 a307c9bd954..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; @@ -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(), + 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 f8e6ad6ae72..c9d7717628a 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}, @@ -34,7 +35,8 @@ use crate::{ 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, @@ -86,7 +88,13 @@ 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)))), + 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", @@ -111,30 +119,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, @@ -158,9 +185,17 @@ 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))] @@ -168,7 +203,7 @@ pub struct BeaconBlockBody = FullPay #[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: ExecutionRequestsGloas, #[superstruct(only(Base, Altair, Gloas))] @@ -185,6 +220,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() @@ -344,6 +400,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)), @@ -353,7 +466,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)), } } @@ -397,7 +510,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Self::Gloas(body) => Box::new( body.attester_slashings .iter() - .map(AttesterSlashingRef::Electra), + .map(AttesterSlashingRef::Gloas), ), } } @@ -428,10 +541,185 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRefMut<'a, Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra)) } Self::Gloas(body) => { - Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra)) + Box::new(body.attestations.iter_mut().map(AttestationRefMut::Gloas)) + } + } + } + + /// 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) => { + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; + } + Self::Altair(body) => { + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; + } + Self::Bellatrix(body) => { + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; + } + Self::Capella(body) => { + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; + } + Self::Deneb(body) => { + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; + } + Self::Electra(body) => { + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; + } + Self::Fulu(body) => { + body.deposits = VariableList::new(deposits.into_iter().collect()) + .map_err(BeaconStateError::SszTypesError)?; + } + Self::Gloas(body) => { + body.deposits = deposits.into_iter().collect(); + } + } + Ok(()) + } + + /// Mutable slice over the block body's voluntary exits. + pub fn voluntary_exits_mut(&mut self) -> &mut [SignedVoluntaryExit] { + match self { + Self::Base(body) => &mut body.voluntary_exits, + Self::Altair(body) => &mut body.voluntary_exits, + Self::Bellatrix(body) => &mut body.voluntary_exits, + Self::Capella(body) => &mut body.voluntary_exits, + Self::Deneb(body) => &mut body.voluntary_exits, + Self::Electra(body) => &mut body.voluntary_exits, + Self::Fulu(body) => &mut body.voluntary_exits, + Self::Gloas(body) => &mut body.voluntary_exits, + } + } } impl> BeaconBlockBodyRef<'_, E, Payload> { @@ -1136,4 +1424,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/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..dd0d5815a88 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::prog_list_of_hex_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..64d453f9d89 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,12 @@ pub type Transactions = VariableList< arbitrary(bound = "E: EthSpec"), ), ), + specific_variant_attributes(Gloas( + 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 +188,22 @@ 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))] + #[serde(with = "ssz_types::serde_utils::prog_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 +211,9 @@ pub struct ExecutionPayload { #[serde(with = "serde_utils::quoted_u64")] pub excess_blob_gas: u64, /// EIP-7928: Block access list + #[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 +226,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..09b2329c568 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)] @@ -16,6 +17,10 @@ use tree_hash_derive::TreeHash; #[serde(bound = "E: EthSpec")] #[context_deserialize(ForkName)] // https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#executionpayloadbid +#[tree_hash( + struct_behaviour = "progressive_container", + active_fields(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) +)] pub struct ExecutionPayloadBid { pub parent_block_hash: ExecutionBlockHash, pub parent_block_root: Hash256, @@ -32,8 +37,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 c24665f41a6..1984df38f65 100644 --- a/consensus/types/src/execution/execution_payload_envelope.rs +++ b/consensus/types/src/execution/execution_payload_envelope.rs @@ -17,8 +17,13 @@ use tree_hash_derive::TreeHash; #[educe(PartialEq, Hash(bound(E: EthSpec)))] #[context_deserialize(ForkName)] #[serde(bound = "E: EthSpec")] +#[tree_hash( + struct_behaviour = "progressive_container", + active_fields(1, 1, 1, 1, 1) +)] pub struct ExecutionPayloadEnvelope { pub payload: ExecutionPayloadGloas, + // [Modified in Gloas:EIP7688] pub execution_requests: ExecutionRequestsGloas, #[serde(with = "serde_utils::quoted_u64")] pub builder_index: u64, diff --git a/consensus/types/src/execution/execution_requests.rs b/consensus/types/src/execution/execution_requests.rs index dc14b71cd5d..f7c88c66d82 100644 --- a/consensus/types/src/execution/execution_requests.rs +++ b/consensus/types/src/execution/execution_requests.rs @@ -1,11 +1,12 @@ 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 ssz::Encode; +use serde::{Deserialize, Deserializer, Serialize}; +use ssz::{Decode, 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; @@ -18,7 +19,6 @@ use crate::{ state::BeaconStateError, withdrawal::WithdrawalRequest, }; -use ssz::Decode; pub type DepositRequests = VariableList::MaxDepositRequestsPerPayload>; @@ -31,12 +31,19 @@ pub type BuilderDepositRequests = pub type BuilderExitRequests = VariableList::MaxBuilderExitRequestsPerPayload>; +/// 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. The `builder_deposits` and `builder_exits` lists are +/// \[New in Gloas:EIP8282\]. #[superstruct( variants(Electra, Gloas), variant_attributes( derive( - Default, Debug, + Default, Clone, Serialize, Deserialize, @@ -45,8 +52,8 @@ pub type BuilderExitRequests = TreeHash, Educe, ), - context_deserialize(ForkName), educe(PartialEq, Eq, Hash(bound(E: EthSpec))), + context_deserialize(ForkName), serde(bound = "E: EthSpec"), cfg_attr( feature = "arbitrary", @@ -54,6 +61,10 @@ pub type BuilderExitRequests = arbitrary(bound = "E: EthSpec"), ), ), + specific_variant_attributes(Gloas(tree_hash( + struct_behaviour = "progressive_container", + active_fields(1, 1, 1, 1, 1) + ))), cast_error( ty = "BeaconStateError", expr = "BeaconStateError::IncorrectStateVariant" @@ -68,97 +79,58 @@ pub type BuilderExitRequests = derive(arbitrary::Arbitrary), arbitrary(bound = "E: EthSpec") )] -#[derive(Debug, Educe, Clone, Serialize, Deserialize, Encode, TreeHash)] -#[serde(bound = "E: EthSpec", untagged)] +#[derive(Debug, Clone, Serialize, Encode, TreeHash, Educe)] #[educe(PartialEq, Eq, Hash(bound(E: EthSpec)))] -#[tree_hash(enum_behaviour = "transparent")] +#[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, + // [New in Gloas:EIP8282] The builder request lists are only present on the Gloas variant. #[superstruct(only(Gloas))] - pub builder_deposits: BuilderDepositRequests, + pub builder_deposits: ProgressiveVariableList, #[superstruct(only(Gloas))] - pub builder_exits: BuilderExitRequests, + pub builder_exits: 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<'a, E: EthSpec> ExecutionRequestsRef<'a, E> { - /// 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()), - )); - } - // [New in Gloas:EIP8282] The builder request lists are only present on the Gloas variant. - if let Ok(builder_deposits) = self.builder_deposits() - && !builder_deposits.is_empty() - { - requests_list.push(Bytes::from_iter( - [RequestType::BuilderDeposit.to_u8()] - .into_iter() - .chain(builder_deposits.as_ssz_bytes()), - )); - } - if let Ok(builder_exits) = self.builder_exits() - && !builder_exits.is_empty() - { - requests_list.push(Bytes::from_iter( - [RequestType::BuilderExit.to_u8()] - .into_iter() - .chain(builder_exits.as_ssz_bytes()), - )); - } - 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(); - - 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(); - - hasher.update(&request_hash); - } - - hasher.finalize().into() +impl Default for ExecutionRequests { + fn default() -> Self { + Self::Electra(ExecutionRequestsElectra::default()) } } -impl ExecutionRequests { - /// 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 { - self.to_ref().get_execution_requests_list() - } - - /// Generate the execution layer `requests_hash` based on EIP-7685. - pub fn requests_hash(&self) -> Hash256 { - self.to_ref().requests_hash() +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) + } } } @@ -181,6 +153,144 @@ impl ForkVersionDecode for ExecutionRequests { } } +/// Build the EIP-7685 requests list from each request kind's `(request_type, is_empty, ssz_bytes)`. +fn build_execution_requests_list(encoded: Vec<(RequestType, bool, Vec)>) -> 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. + pub fn get_execution_requests_list(&self) -> Vec { + match self { + Self::Electra(r) => r.get_execution_requests_list(), + Self::Gloas(r) => r.get_execution_requests_list(), + } + } + + /// Generate the execution layer `requests_hash` based on EIP-7685. + pub fn requests_hash(&self) -> Hash256 { + execution_requests_hash(&self.get_execution_requests_list()) + } +} + +impl<'a, E: EthSpec> ExecutionRequestsRef<'a, E> { + /// 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 { + match self { + Self::Electra(r) => r.get_execution_requests_list(), + Self::Gloas(r) => r.get_execution_requests_list(), + } + } + + /// Generate the execution layer `requests_hash` based on EIP-7685. + pub fn requests_hash(&self) -> Hash256 { + execution_requests_hash(&self.get_execution_requests_list()) + } +} + +impl ExecutionRequestsElectra { + /// 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(vec![ + ( + 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(), + ), + ]) + } + + /// EIP-7685 `requests_hash`. + pub fn requests_hash(&self) -> Hash256 { + execution_requests_hash(&self.get_execution_requests_list()) + } +} + +impl ExecutionRequestsGloas { + /// 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(vec![ + ( + 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(), + ), + // [New in Gloas:EIP8282] + ( + RequestType::BuilderDeposit, + self.builder_deposits.is_empty(), + self.builder_deposits.as_ssz_bytes(), + ), + // [New in Gloas:EIP8282] + ( + RequestType::BuilderExit, + self.builder_exits.is_empty(), + self.builder_exits.as_ssz_bytes(), + ), + ]) + } + + /// EIP-7685 `requests_hash`. + pub fn requests_hash(&self) -> Hash256 { + execution_requests_hash(&self.get_execution_requests_list()) + } +} + +impl From<&ExecutionRequestsElectra> for ExecutionRequestsGloas { + /// Re-type the bounded (Electra) requests as the progressive Gloas variant. Infallible: the + /// progressive lists have no capacity limit. The Gloas-only builder request lists start empty. + fn from(requests: &ExecutionRequestsElectra) -> Self { + Self { + deposits: requests.deposits.iter().cloned().collect(), + withdrawals: requests.withdrawals.iter().cloned().collect(), + consolidations: requests.consolidations.iter().cloned().collect(), + builder_deposits: ProgressiveVariableList::default(), + builder_exits: ProgressiveVariableList::default(), + _phantom: PhantomData, + } + } +} + /// The prefix types for `ExecutionRequest` objects. #[derive(Debug, Copy, Clone)] pub enum RequestType { diff --git a/consensus/types/src/execution/mod.rs b/consensus/types/src/execution/mod.rs index befc27ceab6..ee9e897cbdd 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; 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 181245c7477..85ead609601 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,29 @@ 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()), - )) + 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 +504,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 +546,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 +588,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 +680,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 +726,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 +746,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 +756,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 +809,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`. @@ -1786,22 +2008,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)] @@ -1809,90 +2039,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. @@ -1909,7 +2256,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)) } @@ -1961,7 +2308,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)) } @@ -1994,13 +2341,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)?; } @@ -2081,7 +2428,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)) } @@ -2111,7 +2458,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, )) @@ -2285,35 +2632,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) } @@ -2839,7 +3169,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)?; @@ -2864,7 +3194,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; @@ -3416,6 +3746,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`: @@ -3431,6 +3767,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`: @@ -3446,6 +3788,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 c81f94d8e97..7baeb807bc2 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 9edc5b85c8d..c1b5edaaf89 100644 --- a/testing/ef_tests/src/cases/fork_choice.rs +++ b/testing/ef_tests/src/cases/fork_choice.rs @@ -252,7 +252,14 @@ impl LoadCase for ForkChoiceTest { }) } Step::Attestation { attestation, valid } => { - 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), + valid, + }, + ) + } else if fork_name.electra_enabled() { ssz_decode_file(&path.join(format!("{}.ssz_snappy", attestation))).map( |attestation| Step::Attestation { attestation: Attestation::Electra(attestation), @@ -269,7 +276,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 81c8b7bc128..350d52dcf2a 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..05262d58f70 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,42 @@ 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 a compatible-union type by reading the `{selector, data}` EF-test +/// YAML and dispatching on the selector to construct the matching variant. +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 +151,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 +204,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 +242,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 +275,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 +388,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 +407,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 a438fcc91c1..7583bd86c6e 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()) } @@ -1242,13 +1246,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 199de1d5ab1..7a0206e4d2f 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"); @@ -105,6 +108,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"); @@ -170,6 +174,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 5222634c906..8f35cfe4eb9 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -333,9 +333,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(); } @@ -351,10 +355,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] @@ -363,9 +373,13 @@ 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(); } @@ -377,10 +391,16 @@ mod ssz_static { SszStaticHandler::, MainnetEthSpec>::pre_electra( ) .run(); - SszStaticHandler::, MinimalEthSpec>::electra_and_later( + SszStaticHandler::, MinimalEthSpec>::electra_through_fulu( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::electra_through_fulu( ) .run(); - SszStaticHandler::, MainnetEthSpec>::electra_and_later( + SszStaticHandler::, MinimalEthSpec>::gloas_and_later( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::gloas_and_later( ) .run(); } @@ -391,10 +411,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(); } @@ -815,6 +841,7 @@ mod ssz_static { .run(); SszStaticHandler::, MinimalEthSpec>::fulu_only() .run(); + // [Modified in Gloas:EIP7688] SszStaticHandler::, MainnetEthSpec>::gloas_only() .run(); SszStaticHandler::, MinimalEthSpec>::gloas_only() @@ -919,6 +946,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..336cff902bd 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; + for exit in block.body_mut().voluntary_exits_mut() { + 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; + for exit in block.body_mut().voluntary_exits_mut() { + exit.message.validator_index = VALIDATOR_INDEX + 1; + } }), expected: Err(BlockProcessingError::ExitInvalid { index: 0,