diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 279f2eff18..981d5750f3 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -2004,6 +2004,18 @@ impl BeaconState { } } + /// Get the PTC + /// Requires the committee cache to be initialized. + /// TODO(EIP-7732): definitely gonna have to cache this.. + /// TODO(EIP-7732): check this implementation against the latest spec + // https://ethereum.github.io/consensus-specs/specs/_features/eip7732/beacon-chain/#new-get_ptc + pub fn get_ptc(&self, slot: Slot) -> Result, Error> { + let committee_cache = self.committee_cache_at_slot(slot)?; + let committees = committee_cache.get_beacon_committees_at_slot(slot)?; + + PTC::from_committees(&committees) + } + /// Build all caches (except the tree hash cache), if they need to be built. #[instrument(skip_all, level = "debug")] pub fn build_caches(&mut self, spec: &ChainSpec) -> Result<(), Error> { diff --git a/consensus/types/src/chain_spec.rs b/consensus/types/src/chain_spec.rs index b2a93c24d8..03fee0fda7 100644 --- a/consensus/types/src/chain_spec.rs +++ b/consensus/types/src/chain_spec.rs @@ -2483,9 +2483,9 @@ mod tests { spec.domain_aggregate_and_proof, &spec, ); + test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec); test_domain(Domain::BeaconBuilder, spec.domain_beacon_builder, &spec); test_domain(Domain::PTCAttester, spec.domain_ptc_attester, &spec); - test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec); // The builder domain index is zero let builder_domain_pre_mask = [0; 4]; diff --git a/consensus/types/src/eth_spec.rs b/consensus/types/src/eth_spec.rs index 9aa111c30e..03bfffd8fc 100644 --- a/consensus/types/src/eth_spec.rs +++ b/consensus/types/src/eth_spec.rs @@ -405,10 +405,12 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + Self::ProposerLookaheadSlots::to_usize() } + /// Returns the `PTCSize` constant for this specification. fn ptc_size() -> usize { Self::PTCSize::to_usize() } + /// Returns the `MaxPayloadAttestations` constant for this specification. fn max_payload_attestations() -> usize { Self::MaxPayloadAttestations::to_usize() } @@ -484,7 +486,7 @@ impl EthSpec for MainnetEthSpec { type MaxWithdrawalRequestsPerPayload = U16; type MaxPendingDepositsPerEpoch = U16; type PTCSize = U512; - type MaxPayloadAttestations = U2; + type MaxPayloadAttestations = U4; fn default_spec() -> ChainSpec { ChainSpec::mainnet() diff --git a/consensus/types/src/fork_context.rs b/consensus/types/src/fork_context.rs index 66617326e1..6c1fb74a09 100644 --- a/consensus/types/src/fork_context.rs +++ b/consensus/types/src/fork_context.rs @@ -180,6 +180,7 @@ mod tests { spec.deneb_fork_epoch = Some(Epoch::new(4)); spec.electra_fork_epoch = Some(Epoch::new(5)); spec.fulu_fork_epoch = Some(Epoch::new(6)); + spec.gloas_fork_epoch = Some(Epoch::new(7)); spec.blob_schedule = BlobSchedule::new(blob_parameters); spec } @@ -194,6 +195,7 @@ mod tests { assert!(context.fork_exists(ForkName::Electra)); assert!(context.fork_exists(ForkName::Fulu)); + assert!(context.fork_exists(ForkName::Gloas)); } #[test] diff --git a/consensus/types/src/indexed_payload_attestation.rs b/consensus/types/src/indexed_payload_attestation.rs index 45b3de95bc..ddc2d00185 100644 --- a/consensus/types/src/indexed_payload_attestation.rs +++ b/consensus/types/src/indexed_payload_attestation.rs @@ -12,6 +12,7 @@ use tree_hash_derive::TreeHash; #[cfg_attr(feature = "arbitrary", arbitrary(bound = "E: EthSpec"))] #[context_deserialize(ForkName)] pub struct IndexedPayloadAttestation { + #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] pub attesting_indices: VariableList, pub data: PayloadAttestationData, pub signature: AggregateSignature, diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index e4cb84e291..68651eb869 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -69,6 +69,7 @@ pub mod pending_deposit; pub mod pending_partial_withdrawal; pub mod proposer_preparation_data; pub mod proposer_slashing; +pub mod ptc; pub mod relative_epoch; pub mod selection_proof; pub mod shuffling_id; @@ -249,6 +250,7 @@ pub use crate::preset::{ }; pub use crate::proposer_preparation_data::ProposerPreparationData; pub use crate::proposer_slashing::ProposerSlashing; +pub use crate::ptc::PTC; pub use crate::relative_epoch::{Error as RelativeEpochError, RelativeEpoch}; pub use crate::runtime_fixed_vector::RuntimeFixedVector; pub use crate::runtime_var_list::RuntimeVariableList; diff --git a/consensus/types/src/ptc.rs b/consensus/types/src/ptc.rs new file mode 100644 index 0000000000..26cc1c2ca1 --- /dev/null +++ b/consensus/types/src/ptc.rs @@ -0,0 +1,57 @@ +use crate::*; +use safe_arith::SafeArith; + +/// TODO(EIP-7732): is it easier to return u64 or usize? +#[derive(Clone, Debug, PartialEq)] +pub struct PTC(FixedVector); + +// TODO(EIP-7732): check this implementation against the latest spec +// https://ethereum.github.io/consensus-specs/specs/_features/eip7732/beacon-chain/#new-get_ptc +impl PTC { + pub fn from_committees(committees: &[BeaconCommittee]) -> Result { + // this function is only used here and + // I have no idea where else to put it + fn bit_floor(n: u64) -> u64 { + if n == 0 { + 0 + } else { + 1 << (n.leading_zeros() as u64 ^ 63) + } + } + + let committee_count_per_slot = committees.len() as u64; + let committees_per_slot = bit_floor(std::cmp::min( + committee_count_per_slot, + E::PTCSize::to_u64(), + )) as usize; + let members_per_committee = E::PTCSize::to_usize().safe_div(committees_per_slot)?; + + let mut ptc = Vec::with_capacity(E::PTCSize::to_usize()); + for idx in 0..committees_per_slot { + let beacon_committee = committees + .get(idx as usize) + .ok_or_else(|| Error::InvalidCommitteeIndex(idx as u64))?; + ptc.extend_from_slice(&beacon_committee.committee[..members_per_committee]); + } + + Ok(Self(FixedVector::from(ptc))) + } +} + +impl<'a, E: EthSpec> IntoIterator for &'a PTC { + type Item = &'a usize; + type IntoIter = std::slice::Iter<'a, usize>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl IntoIterator for PTC { + type Item = usize; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} diff --git a/consensus/types/src/signed_execution_payload_envelope.rs b/consensus/types/src/signed_execution_payload_envelope.rs index 6d5c6d1c12..6cab667d14 100644 --- a/consensus/types/src/signed_execution_payload_envelope.rs +++ b/consensus/types/src/signed_execution_payload_envelope.rs @@ -55,7 +55,36 @@ impl SignedExecutionPayloadEnvelope { } } - // todo(eip-7732): implement verify_signature since spec calls for verify_execution_payload_envelope_signature + /// Verify `self.signature`. + /// + /// The `parent_state` is the post-state of the beacon block with + /// block_root = self.message.beacon_block_root + pub fn verify_signature( + &self, + parent_state: &BeaconState, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> Result { + let domain = spec.get_domain( + parent_state.current_epoch(), + Domain::BeaconBuilder, + &parent_state.fork(), + genesis_validators_root, + ); + let pubkey = parent_state + .validators() + .get(self.message().builder_index() as usize) + .and_then(|v| { + let pk: Option = v.pubkey.decompress().ok(); + pk + }) + .ok_or_else(|| { + BeaconStateError::UnknownValidator(self.message().builder_index() as usize) + })?; + let message = self.message().signing_root(domain); + + Ok(self.signature().verify(&pubkey, message)) + } } impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for SignedExecutionPayloadEnvelope {