diff --git a/beacon_node/beacon_chain/src/attestation_verification.rs b/beacon_node/beacon_chain/src/attestation_verification.rs index 131932febba..a95160d504d 100644 --- a/beacon_node/beacon_chain/src/attestation_verification.rs +++ b/beacon_node/beacon_chain/src/attestation_verification.rs @@ -471,7 +471,7 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> { if chain .observed_attestations .write() - .is_known_subset(attestation, attestation_data_root) + .is_known_subset(attestation.to_ref(), attestation_data_root) .map_err(|e| Error::BeaconChainError(e.into()))? { metrics::inc_counter(&metrics::AGGREGATED_ATTESTATION_SUBSETS); @@ -559,7 +559,7 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> { return Err(Error::AggregatorNotInCommittee { aggregator_index }); } - get_indexed_attestation(committee.committee, attestation) + get_indexed_attestation(committee.committee, attestation.to_ref()) .map_err(|e| BeaconChainError::from(e).into()) }; @@ -597,7 +597,7 @@ impl<'a, T: BeaconChainTypes> VerifiedAggregatedAttestation<'a, T> { if let ObserveOutcome::Subset = chain .observed_attestations .write() - .observe_item(attestation, Some(attestation_data_root)) + .observe_item(attestation.to_ref(), Some(attestation_data_root)) .map_err(|e| Error::BeaconChainError(e.into()))? { metrics::inc_counter(&metrics::AGGREGATED_ATTESTATION_SUBSETS); @@ -1241,7 +1241,7 @@ pub fn obtain_indexed_attestation_and_committees_per_slot( attestation: &Attestation, ) -> Result<(IndexedAttestation, CommitteesPerSlot), Error> { map_attestation_committee(chain, attestation, |(committee, committees_per_slot)| { - get_indexed_attestation(committee.committee, attestation) + get_indexed_attestation(committee.committee, attestation.to_ref()) .map(|attestation| (attestation, committees_per_slot)) .map_err(Error::Invalid) }) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index d8a546257be..ffd19dca0af 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -2113,7 +2113,7 @@ impl BeaconChain { .fork_choice_write_lock() .on_attestation( self.slot()?, - verified.indexed_attestation(), + verified.indexed_attestation().to_ref(), AttestationFromBlock::False, ) .map_err(Into::into) @@ -2465,7 +2465,7 @@ impl BeaconChain { // Add to fork choice. self.canonical_head .fork_choice_write_lock() - .on_attester_slashing(attester_slashing.as_inner()); + .on_attester_slashing(attester_slashing.as_inner().to_ref()); // Add to the op pool (if we have the ability to propose blocks). if self.eth1_chain.is_some() { @@ -3820,7 +3820,7 @@ impl BeaconChain { continue; } }; - slasher.accept_attestation(indexed_attestation.clone()); + slasher.accept_attestation(indexed_attestation.clone_as_indexed_attestation()); } } } @@ -3839,7 +3839,7 @@ impl BeaconChain { if block.slot() + 2 * T::EthSpec::slots_per_epoch() >= current_slot { metrics::observe( &metrics::OPERATIONS_PER_BLOCK_ATTESTATION, - block.body().attestations().len() as f64, + block.body().attestations().count() as f64, ); if let Ok(sync_aggregate) = block.body().sync_aggregate() { @@ -4859,7 +4859,8 @@ impl BeaconChain { metrics::start_timer(&metrics::BLOCK_PRODUCTION_UNAGGREGATED_TIMES); for attestation in self.naive_aggregation_pool.read().iter() { let import = |attestation: &Attestation| { - let attesting_indices = get_attesting_indices_from_state(&state, attestation)?; + let attesting_indices = + get_attesting_indices_from_state(&state, attestation.to_ref())?; self.op_pool .insert_attestation(attestation.clone(), attesting_indices) }; @@ -4909,7 +4910,7 @@ impl BeaconChain { attestations.retain(|att| { verify_attestation_for_block_inclusion( &state, - att, + att.to_ref(), &mut tmp_ctxt, VerifySignatures::True, &self.spec, @@ -5040,6 +5041,72 @@ impl BeaconChain { bls_to_execution_changes, } = partial_beacon_block; + let (attester_slashings_base, attester_slashings_electra) = + attester_slashings.into_iter().fold( + (Vec::new(), Vec::new()), + |(mut base, mut electra), slashing| { + match slashing { + AttesterSlashing::Base(slashing) => base.push(slashing), + AttesterSlashing::Electra(slashing) => electra.push(slashing), + } + (base, electra) + }, + ); + let (attestations_base, attestations_electra) = attestations.into_iter().fold( + (Vec::new(), Vec::new()), + |(mut base, mut electra), attestation| { + match attestation { + Attestation::Base(attestation) => base.push(attestation), + Attestation::Electra(attestation) => electra.push(attestation), + } + (base, electra) + }, + ); + + // TODO(electra): figure out what should *actually* be done here when we have attestations / attester_slashings of the wrong type + match &state { + BeaconState::Base(_) + | BeaconState::Altair(_) + | BeaconState::Merge(_) + | BeaconState::Capella(_) + | BeaconState::Deneb(_) => { + if !attestations_electra.is_empty() { + error!( + self.log, + "Tried to produce block with attestations of the wrong type"; + "slot" => slot, + "attestations" => attestations_electra.len(), + ); + } + if !attester_slashings_electra.is_empty() { + error!( + self.log, + "Tried to produce block with attester slashings of the wrong type"; + "slot" => slot, + "attester_slashings" => attester_slashings_electra.len(), + ); + } + } + BeaconState::Electra(_) => { + if !attestations_base.is_empty() { + error!( + self.log, + "Tried to produce block with attestations of the wrong type"; + "slot" => slot, + "attestations" => attestations_base.len(), + ); + } + if !attester_slashings_base.is_empty() { + error!( + self.log, + "Tried to produce block with attester slashings of the wrong type"; + "slot" => slot, + "attester_slashings" => attester_slashings_base.len(), + ); + } + } + }; + let (inner_block, maybe_blobs_and_proofs, execution_payload_value) = match &state { BeaconState::Base(_) => ( BeaconBlock::Base(BeaconBlockBase { @@ -5052,8 +5119,8 @@ impl BeaconChain { eth1_data, graffiti, proposer_slashings: proposer_slashings.into(), - attester_slashings: attester_slashings.into(), - attestations: attestations.into(), + attester_slashings: attester_slashings_base.into(), + attestations: attestations_base.into(), deposits: deposits.into(), voluntary_exits: voluntary_exits.into(), _phantom: PhantomData, @@ -5073,8 +5140,8 @@ impl BeaconChain { eth1_data, graffiti, proposer_slashings: proposer_slashings.into(), - attester_slashings: attester_slashings.into(), - attestations: attestations.into(), + attester_slashings: attester_slashings_base.into(), + attestations: attestations_base.into(), deposits: deposits.into(), voluntary_exits: voluntary_exits.into(), sync_aggregate: sync_aggregate @@ -5100,8 +5167,8 @@ impl BeaconChain { eth1_data, graffiti, proposer_slashings: proposer_slashings.into(), - attester_slashings: attester_slashings.into(), - attestations: attestations.into(), + attester_slashings: attester_slashings_base.into(), + attestations: attestations_base.into(), deposits: deposits.into(), voluntary_exits: voluntary_exits.into(), sync_aggregate: sync_aggregate @@ -5132,8 +5199,8 @@ impl BeaconChain { eth1_data, graffiti, proposer_slashings: proposer_slashings.into(), - attester_slashings: attester_slashings.into(), - attestations: attestations.into(), + attester_slashings: attester_slashings_base.into(), + attestations: attestations_base.into(), deposits: deposits.into(), voluntary_exits: voluntary_exits.into(), sync_aggregate: sync_aggregate @@ -5166,8 +5233,8 @@ impl BeaconChain { eth1_data, graffiti, proposer_slashings: proposer_slashings.into(), - attester_slashings: attester_slashings.into(), - attestations: attestations.into(), + attester_slashings: attester_slashings_base.into(), + attestations: attestations_base.into(), deposits: deposits.into(), voluntary_exits: voluntary_exits.into(), sync_aggregate: sync_aggregate @@ -5204,8 +5271,8 @@ impl BeaconChain { eth1_data, graffiti, proposer_slashings: proposer_slashings.into(), - attester_slashings: attester_slashings.into(), - attestations: attestations.into(), + attester_slashings: attester_slashings_electra.into(), + attestations: attestations_electra.into(), deposits: deposits.into(), voluntary_exits: voluntary_exits.into(), sync_aggregate: sync_aggregate @@ -5216,6 +5283,8 @@ impl BeaconChain { bls_to_execution_changes: bls_to_execution_changes.into(), blob_kzg_commitments: kzg_commitments .ok_or(BlockProductionError::InvalidPayloadFork)?, + // TODO(electra): finish consolidations when they're more spec'd out + consolidations: Vec::new().into(), }, }), maybe_blobs_and_proofs, @@ -5321,7 +5390,7 @@ impl BeaconChain { self.log, "Produced beacon block"; "parent" => ?block.parent_root(), - "attestations" => block.body().attestations().len(), + "attestations" => block.body().attestations_len(), "slot" => block.slot() ); diff --git a/beacon_node/beacon_chain/src/block_reward.rs b/beacon_node/beacon_chain/src/block_reward.rs index 44938668fe9..69eecc89b8a 100644 --- a/beacon_node/beacon_chain/src/block_reward.rs +++ b/beacon_node/beacon_chain/src/block_reward.rs @@ -27,10 +27,12 @@ impl BeaconChain { let split_attestations = block .body() .attestations() - .iter() .map(|att| { let attesting_indices = get_attesting_indices_from_state(state, att)?; - Ok(SplitAttestation::new(att.clone(), attesting_indices)) + Ok(SplitAttestation::new( + att.clone_as_attestation(), + attesting_indices, + )) }) .collect::, BeaconChainError>>()?; @@ -86,7 +88,6 @@ impl BeaconChain { block .body() .attestations() - .iter() .map(|a| a.data().clone()) .collect() } else { diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index 866dde5a763..6536e6f4b30 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -1623,7 +1623,7 @@ impl ExecutionPendingBlock { } // Register each attestation in the block with fork choice. - for (i, attestation) in block.message().body().attestations().iter().enumerate() { + for (i, attestation) in block.message().body().attestations().enumerate() { let _fork_choice_attestation_timer = metrics::start_timer(&metrics::FORK_CHOICE_PROCESS_ATTESTATION_TIMES); diff --git a/beacon_node/beacon_chain/src/observed_aggregates.rs b/beacon_node/beacon_chain/src/observed_aggregates.rs index a83eb620788..857b0edb348 100644 --- a/beacon_node/beacon_chain/src/observed_aggregates.rs +++ b/beacon_node/beacon_chain/src/observed_aggregates.rs @@ -10,7 +10,7 @@ use types::consts::altair::{ SYNC_COMMITTEE_SUBNET_COUNT, TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE, }; use types::slot_data::SlotData; -use types::{Attestation, EthSpec, Hash256, Slot, SyncCommitteeContribution}; +use types::{Attestation, AttestationRef, EthSpec, Hash256, Slot, SyncCommitteeContribution}; pub type ObservedSyncContributions = ObservedAggregates< SyncCommitteeContribution, @@ -102,30 +102,30 @@ pub trait SubsetItem { fn root(&self) -> Hash256; } -impl SubsetItem for Attestation { +impl<'a, E: EthSpec> SubsetItem for AttestationRef<'a, E> { type Item = BitList; fn is_subset(&self, other: &Self::Item) -> bool { match self { - Attestation::Base(att) => att.aggregation_bits.is_subset(other), + Self::Base(att) => att.aggregation_bits.is_subset(other), // TODO(electra) implement electra variant - Attestation::Electra(_) => todo!(), + Self::Electra(_) => todo!(), } } fn is_superset(&self, other: &Self::Item) -> bool { match self { - Attestation::Base(att) => other.is_subset(&att.aggregation_bits), + Self::Base(att) => other.is_subset(&att.aggregation_bits), // TODO(electra) implement electra variant - Attestation::Electra(_) => todo!(), + Self::Electra(_) => todo!(), } } /// Returns the sync contribution aggregation bits. fn get_item(&self) -> Self::Item { match self { - Attestation::Base(att) => att.aggregation_bits.clone(), + Self::Base(att) => att.aggregation_bits.clone(), // TODO(electra) implement electra variant - Attestation::Electra(_) => todo!(), + Self::Electra(_) => todo!(), } } @@ -135,7 +135,7 @@ impl SubsetItem for Attestation { } } -impl SubsetItem for SyncCommitteeContribution { +impl<'a, E: EthSpec> SubsetItem for &'a SyncCommitteeContribution { type Item = BitVector; fn is_subset(&self, other: &Self::Item) -> bool { self.aggregation_bits.is_subset(other) @@ -208,7 +208,7 @@ impl SlotHashSet { /// Store the items in self so future observations recognise its existence. pub fn observe_item>( &mut self, - item: &S, + item: S, root: Hash256, ) -> Result { if item.get_slot() != self.slot { @@ -254,7 +254,7 @@ impl SlotHashSet { /// the given root and slot. pub fn is_known_subset>( &self, - item: &S, + item: S, root: Hash256, ) -> Result { if item.get_slot() != self.slot { @@ -276,16 +276,43 @@ impl SlotHashSet { } } +/// Trait for observable items that can be observed from their reference type. +/// +/// This is used to make observations for `Attestation`s from `AttestationRef`s. +pub trait AsReference { + type Reference<'a> + where + Self: 'a; + + fn as_reference(&self) -> Self::Reference<'_>; +} + +impl AsReference for Attestation { + type Reference<'a> = AttestationRef<'a, E>; + + fn as_reference(&self) -> AttestationRef<'_, E> { + self.to_ref() + } +} + +impl AsReference for SyncCommitteeContribution { + type Reference<'a> = &'a Self; + + fn as_reference(&self) -> &Self { + self + } +} + /// Stores the roots of objects for some number of `Slots`, so we can determine if /// these have previously been seen on the network. -pub struct ObservedAggregates { +pub struct ObservedAggregates { lowest_permissible_slot: Slot, sets: Vec>, _phantom_spec: PhantomData, _phantom_tree_hash: PhantomData, } -impl Default for ObservedAggregates { +impl Default for ObservedAggregates { fn default() -> Self { Self { lowest_permissible_slot: Slot::new(0), @@ -296,13 +323,18 @@ impl Default for ObservedAggregates, E: EthSpec, I> ObservedAggregates { +impl ObservedAggregates +where + T: Consts + AsReference, + E: EthSpec, + for<'a> T::Reference<'a>: SubsetItem + SlotData, +{ /// Store `item` in `self` keyed at `root`. /// /// `root` must equal `item.root::()`. pub fn observe_item( &mut self, - item: &T, + item: T::Reference<'_>, root_opt: Option, ) -> Result { let index = self.get_set_index(item.get_slot())?; @@ -319,7 +351,11 @@ impl, E: EthSpec, I> ObservedAggrega /// /// `root` must equal `item.root::()`. #[allow(clippy::wrong_self_convention)] - pub fn is_known_subset(&mut self, item: &T, root: Hash256) -> Result { + pub fn is_known_subset( + &mut self, + item: T::Reference<'_>, + root: Hash256, + ) -> Result { let index = self.get_set_index(item.get_slot())?; self.sets @@ -417,8 +453,8 @@ mod tests { fn get_attestation(slot: Slot, beacon_block_root: u64) -> Attestation { let mut a: Attestation = test_random_instance(); - a.data.slot = slot; - a.data.beacon_block_root = Hash256::from_low_u64_be(beacon_block_root); + a.data_mut().slot = slot; + a.data_mut().beacon_block_root = Hash256::from_low_u64_be(beacon_block_root); a } @@ -444,12 +480,12 @@ mod tests { for a in &items { assert_eq!( - store.is_known_subset(a, a.root()), + store.is_known_subset(a.as_reference(), a.as_reference().root()), Ok(false), "should indicate an unknown attestation is unknown" ); assert_eq!( - store.observe_item(a, None), + store.observe_item(a.as_reference(), None), Ok(ObserveOutcome::New), "should observe new attestation" ); @@ -457,12 +493,12 @@ mod tests { for a in &items { assert_eq!( - store.is_known_subset(a, a.root()), + store.is_known_subset(a.as_reference(), a.as_reference().root()), Ok(true), "should indicate a known attestation is known" ); assert_eq!( - store.observe_item(a, Some(a.root())), + store.observe_item(a.as_reference(), Some(a.as_reference().root())), Ok(ObserveOutcome::Subset), "should acknowledge an existing attestation" ); diff --git a/beacon_node/beacon_chain/src/observed_operations.rs b/beacon_node/beacon_chain/src/observed_operations.rs index a61cc4f74cc..969d03a11b6 100644 --- a/beacon_node/beacon_chain/src/observed_operations.rs +++ b/beacon_node/beacon_chain/src/observed_operations.rs @@ -1,7 +1,6 @@ use derivative::Derivative; use smallvec::{smallvec, SmallVec}; -use ssz::{Decode, Encode}; -use state_processing::{SigVerifiedOp, VerifyOperation, VerifyOperationAt}; +use state_processing::{SigVerifiedOp, TransformPersist, VerifyOperation, VerifyOperationAt}; use std::collections::HashSet; use std::marker::PhantomData; use types::{ @@ -34,7 +33,7 @@ pub struct ObservedOperations, E: EthSpec> { /// Was the observed operation new and valid for further processing, or a useless duplicate? #[derive(Debug, PartialEq, Eq, Clone)] -pub enum ObservationOutcome { +pub enum ObservationOutcome { New(SigVerifiedOp), AlreadyKnown, } @@ -62,12 +61,12 @@ impl ObservableOperation for ProposerSlashing { impl ObservableOperation for AttesterSlashing { fn observed_validators(&self) -> SmallVec<[u64; SMALL_VEC_SIZE]> { let attestation_1_indices = self - .attestation_1 + .attestation_1() .attesting_indices_iter() .copied() .collect::>(); let attestation_2_indices = self - .attestation_2 + .attestation_2() .attesting_indices_iter() .copied() .collect::>(); diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 4a53f64fcac..deefb34ed56 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -1492,7 +1492,8 @@ where ) -> AttesterSlashing { let fork = self.chain.canonical_head.cached_head().head_fork(); - let mut attestation_1 = IndexedAttestation::Base(IndexedAttestationBase { + // TODO(electra): consider making this test fork-agnostic + let mut attestation_1 = IndexedAttestationBase { attesting_indices: VariableList::new(validator_indices).unwrap(), data: AttestationData { slot: Slot::new(0), @@ -1508,36 +1509,37 @@ where }, }, signature: AggregateSignature::infinity(), - }); + }; let mut attestation_2 = attestation_1.clone(); - attestation_2.data_mut().index += 1; - attestation_2.data_mut().source.epoch = source2.unwrap_or(Epoch::new(0)); - attestation_2.data_mut().target.epoch = target2.unwrap_or(fork.epoch); + attestation_2.data.index += 1; + attestation_2.data.source.epoch = source2.unwrap_or(Epoch::new(0)); + attestation_2.data.target.epoch = target2.unwrap_or(fork.epoch); for attestation in &mut [&mut attestation_1, &mut attestation_2] { // TODO(electra) we could explore iter mut here - for i in attestation.attesting_indices_to_vec() { - let sk = &self.validator_keypairs[i as usize].sk; + 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, + attestation.data.target.epoch, Domain::BeaconAttester, &fork, genesis_validators_root, ); - let message = attestation.data().signing_root(domain); + let message = attestation.data.signing_root(domain); - attestation.signature_mut().add_assign(&sk.sign(message)); + attestation.signature.add_assign(&sk.sign(message)); } } - AttesterSlashing { + // TODO(electra): fix this test + AttesterSlashing::Base(AttesterSlashingBase { attestation_1, attestation_2, - } + }) } pub fn make_attester_slashing_different_indices( @@ -1559,43 +1561,45 @@ where }, }; - let mut attestation_1 = IndexedAttestation::Base(IndexedAttestationBase { + // TODO(electra): make this test fork-agnostic + let mut attestation_1 = IndexedAttestationBase { attesting_indices: VariableList::new(validator_indices_1).unwrap(), data: data.clone(), signature: AggregateSignature::infinity(), - }); + }; - let mut attestation_2 = IndexedAttestation::Base(IndexedAttestationBase { + let mut attestation_2 = IndexedAttestationBase { attesting_indices: VariableList::new(validator_indices_2).unwrap(), data, signature: AggregateSignature::infinity(), - }); + }; - attestation_2.data_mut().index += 1; + attestation_2.data.index += 1; let fork = self.chain.canonical_head.cached_head().head_fork(); for attestation in &mut [&mut attestation_1, &mut attestation_2] { - for i in attestation.attesting_indices_to_vec() { - let sk = &self.validator_keypairs[i as usize].sk; + 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, + attestation.data.target.epoch, Domain::BeaconAttester, &fork, genesis_validators_root, ); - let message = attestation.data().signing_root(domain); + let message = attestation.data.signing_root(domain); - attestation.signature_mut().add_assign(&sk.sign(message)); + attestation.signature.add_assign(&sk.sign(message)); } } - AttesterSlashing { + // TODO(electra): fix this test + AttesterSlashing::Base(AttesterSlashingBase { attestation_1, attestation_2, - } + }) } pub fn make_proposer_slashing(&self, validator_index: u64) -> ProposerSlashing { diff --git a/beacon_node/beacon_chain/src/validator_monitor.rs b/beacon_node/beacon_chain/src/validator_monitor.rs index 70f86ac138e..ea0fe46caeb 100644 --- a/beacon_node/beacon_chain/src/validator_monitor.rs +++ b/beacon_node/beacon_chain/src/validator_monitor.rs @@ -25,9 +25,10 @@ use types::consts::altair::{ TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX, }; use types::{ - Attestation, AttestationData, AttesterSlashing, BeaconBlockRef, BeaconState, BeaconStateError, - ChainSpec, Epoch, EthSpec, Hash256, IndexedAttestation, ProposerSlashing, PublicKeyBytes, - SignedAggregateAndProof, SignedContributionAndProof, Slot, SyncCommitteeMessage, VoluntaryExit, + Attestation, AttestationData, AttesterSlashingRef, BeaconBlockRef, BeaconState, + BeaconStateError, ChainSpec, Epoch, EthSpec, Hash256, IndexedAttestation, + IndexedAttestationRef, ProposerSlashing, PublicKeyBytes, SignedAggregateAndProof, + SignedContributionAndProof, Slot, SyncCommitteeMessage, VoluntaryExit, }; /// Used for Prometheus labels. @@ -1410,7 +1411,7 @@ impl ValidatorMonitor { /// Note: Blocks that get orphaned will skew the inclusion distance calculation. pub fn register_attestation_in_block( &self, - indexed_attestation: &IndexedAttestation, + indexed_attestation: IndexedAttestationRef<'_, E>, parent_slot: Slot, spec: &ChainSpec, ) { @@ -1783,30 +1784,30 @@ impl ValidatorMonitor { } /// Register an attester slashing from the gossip network. - pub fn register_gossip_attester_slashing(&self, slashing: &AttesterSlashing) { + pub fn register_gossip_attester_slashing(&self, slashing: AttesterSlashingRef<'_, E>) { self.register_attester_slashing("gossip", slashing) } /// Register an attester slashing from the HTTP API. - pub fn register_api_attester_slashing(&self, slashing: &AttesterSlashing) { + pub fn register_api_attester_slashing(&self, slashing: AttesterSlashingRef<'_, E>) { self.register_attester_slashing("api", slashing) } /// Register an attester slashing included in a *valid* `BeaconBlock`. - pub fn register_block_attester_slashing(&self, slashing: &AttesterSlashing) { + pub fn register_block_attester_slashing(&self, slashing: AttesterSlashingRef<'_, E>) { self.register_attester_slashing("block", slashing) } - fn register_attester_slashing(&self, src: &str, slashing: &AttesterSlashing) { - let data = slashing.attestation_1.data(); + fn register_attester_slashing(&self, src: &str, slashing: AttesterSlashingRef<'_, E>) { + let data = slashing.attestation_1().data(); let attestation_1_indices: HashSet = slashing - .attestation_1 + .attestation_1() .attesting_indices_iter() .copied() .collect(); slashing - .attestation_2 + .attestation_2() .attesting_indices_iter() .filter(|index| attestation_1_indices.contains(index)) .filter_map(|index| self.get_validator(*index)) diff --git a/beacon_node/beacon_chain/tests/attestation_production.rs b/beacon_node/beacon_chain/tests/attestation_production.rs index ff83b253205..a58cfd9f2d1 100644 --- a/beacon_node/beacon_chain/tests/attestation_production.rs +++ b/beacon_node/beacon_chain/tests/attestation_production.rs @@ -8,7 +8,9 @@ use beacon_chain::{metrics, StateSkipConfig, WhenSlotSkipped}; use lazy_static::lazy_static; use std::sync::Arc; use tree_hash::TreeHash; -use types::{AggregateSignature, EthSpec, Keypair, MainnetEthSpec, RelativeEpoch, Slot}; +use types::{ + AggregateSignature, Attestation, EthSpec, Keypair, MainnetEthSpec, RelativeEpoch, Slot, +}; pub const VALIDATOR_COUNT: usize = 16; @@ -188,20 +190,35 @@ async fn produces_attestations() { .produce_unaggregated_attestation(slot, index) .expect("should produce attestation"); - let data = &attestation.data; + match &attestation { + Attestation::Base(att) => { + assert_eq!( + att.aggregation_bits.len(), + committee_len, + "bad committee len" + ); + assert!( + att.aggregation_bits.is_zero(), + "some committee bits are set" + ); + } + Attestation::Electra(att) => { + assert_eq!( + att.aggregation_bits.len(), + committee_len, + "bad committee len" + ); + assert!( + att.aggregation_bits.is_zero(), + "some committee bits are set" + ); + } + } + let data = attestation.data(); assert_eq!( - attestation.aggregation_bits.len(), - committee_len, - "bad committee len" - ); - assert!( - attestation.aggregation_bits.is_zero(), - "some committee bits are set" - ); - assert_eq!( - attestation.signature, - AggregateSignature::empty(), + attestation.signature(), + &AggregateSignature::empty(), "bad signature" ); assert_eq!(data.index, index, "bad index"); @@ -329,10 +346,10 @@ async fn early_attester_cache_old_request() { .produce_unaggregated_attestation(attest_slot, 0) .unwrap(); - assert_eq!(attestation.data.slot, attest_slot); + assert_eq!(attestation.data().slot, attest_slot); let attested_block = harness .chain - .get_blinded_block(&attestation.data.beacon_block_root) + .get_blinded_block(&attestation.data().beacon_block_root) .unwrap() .unwrap(); assert_eq!(attested_block.slot(), attest_slot); diff --git a/beacon_node/beacon_chain/tests/attestation_verification.rs b/beacon_node/beacon_chain/tests/attestation_verification.rs index 3432604cc93..02caf831fe4 100644 --- a/beacon_node/beacon_chain/tests/attestation_verification.rs +++ b/beacon_node/beacon_chain/tests/attestation_verification.rs @@ -125,7 +125,7 @@ fn get_valid_unaggregated_attestation( let validator_committee_index = 0; let validator_index = *head .beacon_state - .get_beacon_committee(current_slot, valid_attestation.data.index) + .get_beacon_committee(current_slot, valid_attestation.data().index) .expect("should get committees") .committee .get(validator_committee_index) @@ -144,7 +144,7 @@ fn get_valid_unaggregated_attestation( .expect("should sign attestation"); let subnet_id = SubnetId::compute_subnet_for_attestation_data::( - &valid_attestation.data, + valid_attestation.data(), head.beacon_state .get_committee_count_at_slot(current_slot) .expect("should get committee count"), @@ -170,7 +170,7 @@ fn get_valid_aggregated_attestation( let current_slot = chain.slot().expect("should get slot"); let committee = state - .get_beacon_committee(current_slot, aggregate.data.index) + .get_beacon_committee(current_slot, aggregate.data().index) .expect("should get committees"); let committee_len = committee.committee.len(); @@ -181,7 +181,7 @@ fn get_valid_aggregated_attestation( let aggregator_sk = generate_deterministic_keypair(val_index).sk; let proof = SelectionProof::new::( - aggregate.data.slot, + aggregate.data().slot, &aggregator_sk, &state.fork(), chain.genesis_validators_root, @@ -220,7 +220,7 @@ fn get_non_aggregator( let current_slot = chain.slot().expect("should get slot"); let committee = state - .get_beacon_committee(current_slot, aggregate.data.index) + .get_beacon_committee(current_slot, aggregate.data().index) .expect("should get committees"); let committee_len = committee.committee.len(); @@ -231,7 +231,7 @@ fn get_non_aggregator( let aggregator_sk = generate_deterministic_keypair(val_index).sk; let proof = SelectionProof::new::( - aggregate.data.slot, + aggregate.data().slot, &aggregator_sk, &state.fork(), chain.genesis_validators_root, @@ -301,7 +301,7 @@ impl GossipTester { get_valid_aggregated_attestation(&harness.chain, valid_attestation.clone()); let mut invalid_attestation = valid_attestation.clone(); - invalid_attestation.data.beacon_block_root = Hash256::repeat_byte(13); + invalid_attestation.data_mut().beacon_block_root = Hash256::repeat_byte(13); let (mut invalid_aggregate, _, _) = get_valid_aggregated_attestation(&harness.chain, invalid_attestation.clone()); @@ -490,7 +490,7 @@ async fn aggregated_gossip_verification() { */ .inspect_aggregate_err( "aggregate from future slot", - |tester, a| a.message.aggregate.data.slot = tester.slot() + 1, + |tester, a| a.message.aggregate.data_mut().slot = tester.slot() + 1, |tester, err| { assert!(matches!( err, @@ -504,8 +504,9 @@ async fn aggregated_gossip_verification() { "aggregate from past slot", |tester, a| { let too_early_slot = tester.earliest_valid_attestation_slot() - 1; - a.message.aggregate.data.slot = too_early_slot; - a.message.aggregate.data.target.epoch = too_early_slot.epoch(E::slots_per_epoch()); + a.message.aggregate.data_mut().slot = too_early_slot; + a.message.aggregate.data_mut().target.epoch = + too_early_slot.epoch(E::slots_per_epoch()); }, |tester, err| { let valid_early_slot = tester.earliest_valid_attestation_slot(); @@ -529,7 +530,7 @@ async fn aggregated_gossip_verification() { */ .inspect_aggregate_err( "attestation with invalid target epoch", - |_, a| a.message.aggregate.data.target.epoch += 1, + |_, a| a.message.aggregate.data_mut().target.epoch += 1, |_, err| assert!(matches!(err, AttnError::InvalidTargetEpoch { .. })), ) /* @@ -538,7 +539,7 @@ async fn aggregated_gossip_verification() { */ .inspect_aggregate_err( "attestation with invalid target root", - |_, a| a.message.aggregate.data.target.root = Hash256::repeat_byte(42), + |_, a| a.message.aggregate.data_mut().target.root = Hash256::repeat_byte(42), |_, err| assert!(matches!(err, AttnError::InvalidTargetRoot { .. })), ) /* @@ -548,7 +549,7 @@ async fn aggregated_gossip_verification() { */ .inspect_aggregate_err( "aggregate with unknown head block", - |_, a| a.message.aggregate.data.beacon_block_root = Hash256::repeat_byte(42), + |_, a| a.message.aggregate.data_mut().beacon_block_root = Hash256::repeat_byte(42), |_, err| { assert!(matches!( err, @@ -567,10 +568,19 @@ async fn aggregated_gossip_verification() { .inspect_aggregate_err( "aggregate with no participants", |_, a| { - let aggregation_bits = &mut a.message.aggregate.aggregation_bits; - aggregation_bits.difference_inplace(&aggregation_bits.clone()); - assert!(aggregation_bits.is_zero()); - a.message.aggregate.signature = AggregateSignature::infinity(); + match &mut a.message.aggregate { + Attestation::Base(ref mut att) => { + let aggregation_bits = &mut att.aggregation_bits; + aggregation_bits.difference_inplace(&aggregation_bits.clone()); + assert!(aggregation_bits.is_zero()); + } + Attestation::Electra(ref mut att) => { + let aggregation_bits = &mut att.aggregation_bits; + aggregation_bits.difference_inplace(&aggregation_bits.clone()); + assert!(aggregation_bits.is_zero()); + } + } + *a.message.aggregate.signature_mut() = AggregateSignature::infinity(); }, |_, err| assert!(matches!(err, AttnError::EmptyAggregationBitfield)), ) @@ -598,7 +608,7 @@ async fn aggregated_gossip_verification() { .chain .head_snapshot() .beacon_state - .get_beacon_committee(tester.slot(), a.message.aggregate.data.index) + .get_beacon_committee(tester.slot(), a.message.aggregate.data().index) .expect("should get committees") .committee .len(); @@ -634,7 +644,7 @@ async fn aggregated_gossip_verification() { |tester, a| { let mut agg_sig = AggregateSignature::infinity(); agg_sig.add_assign(&tester.aggregator_sk.sign(Hash256::repeat_byte(42))); - a.message.aggregate.signature = agg_sig; + *a.message.aggregate.signature_mut() = agg_sig; }, |_, err| assert!(matches!(err, AttnError::InvalidSignature)), ) @@ -729,7 +739,7 @@ async fn aggregated_gossip_verification() { assert!(matches!( err, AttnError::AttestationSupersetKnown(hash) - if hash == tester.valid_aggregate.message.aggregate.data.tree_hash_root() + if hash == tester.valid_aggregate.message.aggregate.data().tree_hash_root() )) }, ) @@ -741,7 +751,7 @@ async fn aggregated_gossip_verification() { */ .inspect_aggregate_err( "aggregate from aggregator that has already been seen", - |_, a| a.message.aggregate.data.beacon_block_root = Hash256::repeat_byte(42), + |_, a| a.message.aggregate.data_mut().beacon_block_root = Hash256::repeat_byte(42), |tester, err| { assert!(matches!( err, @@ -766,12 +776,12 @@ async fn unaggregated_gossip_verification() { .inspect_unaggregate_err( "attestation with invalid committee index", |tester, a, _| { - a.data.index = tester + a.data_mut().index = tester .harness .chain .head_snapshot() .beacon_state - .get_committee_count_at_slot(a.data.slot) + .get_committee_count_at_slot(a.data().slot) .unwrap() }, |_, err| assert!(matches!(err, AttnError::NoCommitteeForSlotAndIndex { .. })), @@ -806,7 +816,7 @@ async fn unaggregated_gossip_verification() { */ .inspect_unaggregate_err( "attestation from future slot", - |tester, a, _| a.data.slot = tester.slot() + 1, + |tester, a, _| a.data_mut().slot = tester.slot() + 1, |tester, err| { assert!(matches!( err, @@ -822,8 +832,8 @@ async fn unaggregated_gossip_verification() { "attestation from past slot", |tester, a, _| { let too_early_slot = tester.earliest_valid_attestation_slot() - 1; - a.data.slot = too_early_slot; - a.data.target.epoch = too_early_slot.epoch(E::slots_per_epoch()); + a.data_mut().slot = too_early_slot; + a.data_mut().target.epoch = too_early_slot.epoch(E::slots_per_epoch()); }, |tester, err| { let valid_early_slot = tester.earliest_valid_attestation_slot(); @@ -847,7 +857,7 @@ async fn unaggregated_gossip_verification() { */ .inspect_unaggregate_err( "attestation with invalid target epoch", - |_, a, _| a.data.target.epoch += 1, + |_, a, _| a.data_mut().target.epoch += 1, |_, err| { assert!(matches!( err, @@ -863,15 +873,29 @@ async fn unaggregated_gossip_verification() { */ .inspect_unaggregate_err( "attestation without any aggregation bits set", - |tester, a, _| { - a.aggregation_bits - .set(tester.attester_committee_index, false) - .expect("should unset aggregation bit"); - assert_eq!( - a.aggregation_bits.num_set_bits(), - 0, - "test requires no set bits" - ); + |tester, mut a, _| { + match &mut a { + Attestation::Base(ref mut att) => { + att.aggregation_bits + .set(tester.attester_committee_index, false) + .expect("should unset aggregation bit"); + assert_eq!( + att.aggregation_bits.num_set_bits(), + 0, + "test requires no set bits" + ); + } + Attestation::Electra(ref mut att) => { + att.aggregation_bits + .set(tester.attester_committee_index, false) + .expect("should unset aggregation bit"); + assert_eq!( + att.aggregation_bits.num_set_bits(), + 0, + "test requires no set bits" + ); + } + } }, |_, err| { assert!(matches!( @@ -882,10 +906,19 @@ async fn unaggregated_gossip_verification() { ) .inspect_unaggregate_err( "attestation with two aggregation bits set", - |tester, a, _| { - a.aggregation_bits - .set(tester.attester_committee_index + 1, true) - .expect("should set second aggregation bit"); + |tester, mut a, _| { + match &mut a { + Attestation::Base(ref mut att) => { + att.aggregation_bits + .set(tester.attester_committee_index + 1, true) + .expect("should set second aggregation bit"); + } + Attestation::Electra(ref mut att) => { + att.aggregation_bits + .set(tester.attester_committee_index + 1, true) + .expect("should set second aggregation bit"); + } + } }, |_, err| { assert!(matches!( @@ -903,11 +936,22 @@ async fn unaggregated_gossip_verification() { */ .inspect_unaggregate_err( "attestation with invalid bitfield", - |_, a, _| { - let bits = a.aggregation_bits.iter().collect::>(); - a.aggregation_bits = BitList::with_capacity(bits.len() + 1).unwrap(); - for (i, bit) in bits.into_iter().enumerate() { - a.aggregation_bits.set(i, bit).unwrap(); + |_, mut a, _| { + match &mut a { + Attestation::Base(ref mut att) => { + let bits = att.aggregation_bits.iter().collect::>(); + att.aggregation_bits = BitList::with_capacity(bits.len() + 1).unwrap(); + for (i, bit) in bits.into_iter().enumerate() { + att.aggregation_bits.set(i, bit).unwrap(); + } + } + Attestation::Electra(ref mut att) => { + let bits = att.aggregation_bits.iter().collect::>(); + att.aggregation_bits = BitList::with_capacity(bits.len() + 1).unwrap(); + for (i, bit) in bits.into_iter().enumerate() { + att.aggregation_bits.set(i, bit).unwrap(); + } + } } }, |_, err| { @@ -927,7 +971,7 @@ async fn unaggregated_gossip_verification() { .inspect_unaggregate_err( "attestation with unknown head block", |_, a, _| { - a.data.beacon_block_root = Hash256::repeat_byte(42); + a.data_mut().beacon_block_root = Hash256::repeat_byte(42); }, |_, err| { assert!(matches!( @@ -949,7 +993,7 @@ async fn unaggregated_gossip_verification() { .inspect_unaggregate_err( "attestation with invalid target root", |_, a, _| { - a.data.target.root = Hash256::repeat_byte(42); + a.data_mut().target.root = Hash256::repeat_byte(42); }, |_, err| { assert!(matches!( @@ -968,7 +1012,7 @@ async fn unaggregated_gossip_verification() { |tester, a, _| { let mut agg_sig = AggregateSignature::infinity(); agg_sig.add_assign(&tester.attester_sk.sign(Hash256::repeat_byte(42))); - a.signature = agg_sig; + *a.signature_mut() = agg_sig; }, |_, err| { assert!(matches!( @@ -1055,7 +1099,7 @@ async fn attestation_that_skips_epochs() { .cloned() .expect("should have at least one attestation in committee"); - let block_root = attestation.data.beacon_block_root; + let block_root = attestation.data().beacon_block_root; let block_slot = harness .chain .store @@ -1066,7 +1110,7 @@ async fn attestation_that_skips_epochs() { .slot(); assert!( - attestation.data.slot - block_slot > E::slots_per_epoch() * 2, + attestation.data().slot - block_slot > E::slots_per_epoch() * 2, "the attestation must skip more than two epochs" ); @@ -1228,7 +1272,7 @@ async fn attestation_to_finalized_block() { .first() .cloned() .expect("should have at least one attestation in committee"); - assert_eq!(attestation.data.beacon_block_root, earlier_block_root); + assert_eq!(attestation.data().beacon_block_root, earlier_block_root); // Attestation should be rejected for attesting to a pre-finalization block. let res = harness @@ -1281,7 +1325,7 @@ async fn verify_aggregate_for_gossip_doppelganger_detection() { .verify_aggregated_attestation_for_gossip(&valid_aggregate) .expect("should verify aggregate attestation"); - let epoch = valid_aggregate.message.aggregate.data.target.epoch; + let epoch = valid_aggregate.message.aggregate.data().target.epoch; let index = valid_aggregate.message.aggregator_index as usize; assert!(harness.chain.validator_seen_at_epoch(index, epoch)); @@ -1338,7 +1382,7 @@ async fn verify_attestation_for_gossip_doppelganger_detection() { .verify_unaggregated_attestation_for_gossip(&valid_attestation, Some(subnet_id)) .expect("should verify attestation"); - let epoch = valid_attestation.data.target.epoch; + let epoch = valid_attestation.data().target.epoch; assert!(harness.chain.validator_seen_at_epoch(index, epoch)); // Check the correct beacon cache is populated diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index 98a112daffe..7b2c1addd28 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -664,7 +664,7 @@ async fn invalid_signature_attester_slashing() { for &block_index in BLOCK_INDICES { let harness = get_invalid_sigs_harness(&chain_segment).await; let mut snapshots = chain_segment.clone(); - let indexed_attestation = IndexedAttestation { + let indexed_attestation = IndexedAttestationBase { attesting_indices: vec![0].into(), data: AttestationData { slot: Slot::new(0), @@ -681,7 +681,7 @@ async fn invalid_signature_attester_slashing() { }, signature: junk_aggregate_signature(), }; - let attester_slashing = AttesterSlashing { + let attester_slashing = AttesterSlashingBase { attestation_1: indexed_attestation.clone(), attestation_2: indexed_attestation, }; @@ -690,11 +690,36 @@ async fn invalid_signature_attester_slashing() { .as_ref() .clone() .deconstruct(); - block - .body_mut() - .attester_slashings_mut() - .push(attester_slashing) - .expect("should update attester slashing"); + match &mut block.body_mut() { + BeaconBlockBodyRefMut::Base(ref mut blk) => { + blk.attester_slashings + .push(attester_slashing) + .expect("should update attester slashing"); + } + BeaconBlockBodyRefMut::Altair(ref mut blk) => { + blk.attester_slashings + .push(attester_slashing) + .expect("should update attester slashing"); + } + BeaconBlockBodyRefMut::Merge(ref mut blk) => { + blk.attester_slashings + .push(attester_slashing) + .expect("should update attester slashing"); + } + BeaconBlockBodyRefMut::Capella(ref mut blk) => { + blk.attester_slashings + .push(attester_slashing) + .expect("should update attester slashing"); + } + BeaconBlockBodyRefMut::Deneb(ref mut blk) => { + blk.attester_slashings + .push(attester_slashing) + .expect("should update attester slashing"); + } + BeaconBlockBodyRefMut::Electra(_) => { + panic!("electra test not implemented!"); + } + } snapshots[block_index].beacon_block = Arc::new(SignedBeaconBlock::from_block(block, signature)); update_parent_roots(&mut snapshots, &mut chain_segment_blobs); @@ -724,8 +749,34 @@ async fn invalid_signature_attestation() { .as_ref() .clone() .deconstruct(); - if let Some(attestation) = block.body_mut().attestations_mut().get_mut(0) { - attestation.signature = junk_aggregate_signature(); + match &mut block.body_mut() { + BeaconBlockBodyRefMut::Base(ref mut blk) => blk + .attestations + .get_mut(0) + .map(|att| att.signature = junk_aggregate_signature()), + BeaconBlockBodyRefMut::Altair(ref mut blk) => blk + .attestations + .get_mut(0) + .map(|att| att.signature = junk_aggregate_signature()), + BeaconBlockBodyRefMut::Merge(ref mut blk) => blk + .attestations + .get_mut(0) + .map(|att| att.signature = junk_aggregate_signature()), + BeaconBlockBodyRefMut::Capella(ref mut blk) => blk + .attestations + .get_mut(0) + .map(|att| att.signature = junk_aggregate_signature()), + BeaconBlockBodyRefMut::Deneb(ref mut blk) => blk + .attestations + .get_mut(0) + .map(|att| att.signature = junk_aggregate_signature()), + BeaconBlockBodyRefMut::Electra(ref mut blk) => blk + .attestations + .get_mut(0) + .map(|att| att.signature = junk_aggregate_signature()), + }; + + if block.body().attestations_len() > 0 { snapshots[block_index].beacon_block = Arc::new(SignedBeaconBlock::from_block(block, signature)); update_parent_roots(&mut snapshots, &mut chain_segment_blobs); @@ -1187,9 +1238,13 @@ async fn verify_block_for_gossip_doppelganger_detection() { let state = harness.get_current_state(); let ((block, _), _) = harness.make_block(state.clone(), Slot::new(1)).await; - + let attestations = block + .message() + .body() + .attestations() + .map(|att| att.clone_as_attestation()) + .collect::>(); let verified_block = harness.chain.verify_block_for_gossip(block).await.unwrap(); - let attestations = verified_block.block.message().body().attestations().clone(); harness .chain .process_block( @@ -1202,37 +1257,69 @@ async fn verify_block_for_gossip_doppelganger_detection() { .unwrap(); for att in attestations.iter() { - let epoch = att.data.target.epoch; + let epoch = att.data().target.epoch; let committee = state - .get_beacon_committee(att.data.slot, att.data.index) + .get_beacon_committee(att.data().slot, att.data().index) .unwrap(); - let indexed_attestation = get_indexed_attestation(committee.committee, att).unwrap(); - - for &index in &indexed_attestation.attesting_indices { - let index = index as usize; - - assert!(harness.chain.validator_seen_at_epoch(index, epoch)); - - // Check the correct beacon cache is populated - assert!(harness - .chain - .observed_block_attesters - .read() - .validator_has_been_observed(epoch, index) - .expect("should check if block attester was observed")); - assert!(!harness - .chain - .observed_gossip_attesters - .read() - .validator_has_been_observed(epoch, index) - .expect("should check if gossip attester was observed")); - assert!(!harness - .chain - .observed_aggregators - .read() - .validator_has_been_observed(epoch, index) - .expect("should check if gossip aggregator was observed")); - } + let indexed_attestation = + get_indexed_attestation(committee.committee, att.to_ref()).unwrap(); + + match indexed_attestation { + IndexedAttestation::Base(indexed_attestation) => { + for &index in &indexed_attestation.attesting_indices { + let index = index as usize; + + assert!(harness.chain.validator_seen_at_epoch(index, epoch)); + + // Check the correct beacon cache is populated + assert!(harness + .chain + .observed_block_attesters + .read() + .validator_has_been_observed(epoch, index) + .expect("should check if block attester was observed")); + assert!(!harness + .chain + .observed_gossip_attesters + .read() + .validator_has_been_observed(epoch, index) + .expect("should check if gossip attester was observed")); + assert!(!harness + .chain + .observed_aggregators + .read() + .validator_has_been_observed(epoch, index) + .expect("should check if gossip aggregator was observed")); + } + } + IndexedAttestation::Electra(indexed_attestation) => { + for &index in &indexed_attestation.attesting_indices { + let index = index as usize; + + assert!(harness.chain.validator_seen_at_epoch(index, epoch)); + + // Check the correct beacon cache is populated + assert!(harness + .chain + .observed_block_attesters + .read() + .validator_has_been_observed(epoch, index) + .expect("should check if block attester was observed")); + assert!(!harness + .chain + .observed_gossip_attesters + .read() + .validator_has_been_observed(epoch, index) + .expect("should check if gossip attester was observed")); + assert!(!harness + .chain + .observed_aggregators + .read() + .validator_has_been_observed(epoch, index) + .expect("should check if gossip aggregator was observed")); + } + } + }; } } diff --git a/beacon_node/beacon_chain/tests/payload_invalidation.rs b/beacon_node/beacon_chain/tests/payload_invalidation.rs index 0ef348319af..c1c770cfe4e 100644 --- a/beacon_node/beacon_chain/tests/payload_invalidation.rs +++ b/beacon_node/beacon_chain/tests/payload_invalidation.rs @@ -1191,9 +1191,17 @@ async fn attesting_to_optimistic_head() { .produce_unaggregated_attestation(Slot::new(0), 0) .unwrap(); - attestation.aggregation_bits.set(0, true).unwrap(); - attestation.data.slot = slot; - attestation.data.beacon_block_root = root; + match &mut attestation { + Attestation::Base(ref mut att) => { + att.aggregation_bits.set(0, true).unwrap(); + } + Attestation::Electra(ref mut att) => { + att.aggregation_bits.set(0, true).unwrap(); + } + } + + attestation.data_mut().slot = slot; + attestation.data_mut().beacon_block_root = root; rig.harness .chain @@ -1214,15 +1222,15 @@ async fn attesting_to_optimistic_head() { let get_aggregated = || { rig.harness .chain - .get_aggregated_attestation(&attestation.data) + .get_aggregated_attestation(attestation.data()) }; let get_aggregated_by_slot_and_root = || { rig.harness .chain .get_aggregated_attestation_by_slot_and_root( - attestation.data.slot, - &attestation.data.tree_hash_root(), + attestation.data().slot, + &attestation.data().tree_hash_root(), ) }; diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index ba8a6bf7016..8c0464187f3 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -606,7 +606,7 @@ async fn epoch_boundary_state_attestation_processing() { for (attestation, subnet_id) in late_attestations.into_iter().flatten() { // load_epoch_boundary_state is idempotent! - let block_root = attestation.data.beacon_block_root; + let block_root = attestation.data().beacon_block_root; let block = store .get_blinded_block(&block_root) .unwrap() @@ -629,7 +629,7 @@ async fn epoch_boundary_state_attestation_processing() { .verify_unaggregated_attestation_for_gossip(&attestation, Some(subnet_id)); let current_slot = harness.chain.slot().expect("should get slot"); - let expected_attestation_slot = attestation.data.slot; + let expected_attestation_slot = attestation.data().slot; // Extra -1 to handle gossip clock disparity. let expected_earliest_permissible_slot = current_slot - E::slots_per_epoch() - 1; @@ -1028,8 +1028,7 @@ async fn multiple_attestations_per_block() { .as_ref() .message() .body() - .attestations() - .len() as u64, + .attestations_len() as u64, if slot <= 1 { 0 } else { committees_per_slot } ); } diff --git a/beacon_node/beacon_chain/tests/tests.rs b/beacon_node/beacon_chain/tests/tests.rs index e27180a002c..3219611155e 100644 --- a/beacon_node/beacon_chain/tests/tests.rs +++ b/beacon_node/beacon_chain/tests/tests.rs @@ -573,7 +573,7 @@ async fn attestations_with_increasing_slots() { .verify_unaggregated_attestation_for_gossip(&attestation, Some(subnet_id)); let current_slot = harness.chain.slot().expect("should get slot"); - let expected_attestation_slot = attestation.data.slot; + let expected_attestation_slot = attestation.data().slot; let expected_earliest_permissible_slot = current_slot - MinimalEthSpec::slots_per_epoch() - 1; diff --git a/beacon_node/http_api/src/block_packing_efficiency.rs b/beacon_node/http_api/src/block_packing_efficiency.rs index 3e511c25dff..43dee5b6eb4 100644 --- a/beacon_node/http_api/src/block_packing_efficiency.rs +++ b/beacon_node/http_api/src/block_packing_efficiency.rs @@ -10,8 +10,8 @@ use std::collections::{HashMap, HashSet}; use std::marker::PhantomData; use std::sync::Arc; use types::{ - Attestation, BeaconCommittee, BeaconState, BeaconStateError, BlindedPayload, ChainSpec, Epoch, - EthSpec, Hash256, OwnedBeaconCommittee, RelativeEpoch, SignedBeaconBlock, Slot, + AttestationRef, BeaconCommittee, BeaconState, BeaconStateError, BlindedPayload, ChainSpec, + Epoch, EthSpec, Hash256, OwnedBeaconCommittee, RelativeEpoch, SignedBeaconBlock, Slot, }; use warp_utils::reject::{beacon_chain_error, custom_bad_request, custom_server_error}; @@ -111,9 +111,9 @@ impl PackingEfficiencyHandler { let attestations = block_body.attestations(); let mut attestations_in_block = HashMap::new(); - for attestation in attestations.iter() { + for attestation in attestations { match attestation { - Attestation::Base(attn) => { + AttestationRef::Base(attn) => { for (position, voted) in attn.aggregation_bits.iter().enumerate() { if voted { let unique_attestation = UniqueAttestation { @@ -133,7 +133,7 @@ impl PackingEfficiencyHandler { } } // TODO(electra) implement electra variant - Attestation::Electra(_) => { + AttestationRef::Electra(_) => { todo!() } } diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 87f7df07310..cc680c523ef 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -1635,7 +1635,12 @@ pub fn serve( let (block, execution_optimistic, finalized) = block_id.blinded_block(&chain)?; Ok(api_types::GenericResponse::from( - block.message().body().attestations().clone(), + block + .message() + .body() + .attestations() + .map(|att| att.clone_as_attestation()) + .collect::>(), ) .add_execution_optimistic_finalized(execution_optimistic, finalized)) }) @@ -1833,7 +1838,7 @@ pub fn serve( chain .validator_monitor .read() - .register_api_attester_slashing(&slashing); + .register_api_attester_slashing(slashing.to_ref()); if let ObservationOutcome::New(slashing) = outcome { publish_pubsub_message( diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 21d93f22f33..87d6a8405d1 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -1633,7 +1633,13 @@ impl ApiTester { let expected = block_id.full_block(&self.chain).await.ok().map( |(block, _execution_optimistic, _finalized)| { - block.message().body().attestations().clone().into() + block + .message() + .body() + .attestations() + .map(|att| att.clone_as_attestation()) + .collect::>() + .into() }, ); @@ -1793,7 +1799,14 @@ impl ApiTester { pub async fn test_post_beacon_pool_attester_slashings_invalid(mut self) -> Self { let mut slashing = self.attester_slashing.clone(); - slashing.attestation_1.data_mut().slot += 1; + match &mut slashing { + AttesterSlashing::Base(ref mut slashing) => { + slashing.attestation_1.data.slot += 1; + } + AttesterSlashing::Electra(ref mut slashing) => { + slashing.attestation_1.data.slot += 1; + } + } self.client .post_beacon_pool_attester_slashings(&slashing) @@ -3183,8 +3196,10 @@ impl ApiTester { .head_beacon_block() .message() .body() - .attestations()[0] - .clone(); + .attestations() + .next() + .unwrap() + .clone_as_attestation(); let result = self .client diff --git a/beacon_node/lighthouse_network/src/types/pubsub.rs b/beacon_node/lighthouse_network/src/types/pubsub.rs index 653e264349e..68864c76638 100644 --- a/beacon_node/lighthouse_network/src/types/pubsub.rs +++ b/beacon_node/lighthouse_network/src/types/pubsub.rs @@ -7,12 +7,12 @@ use ssz::{Decode, Encode}; use std::io::{Error, ErrorKind}; use std::sync::Arc; use types::{ - Attestation, AttesterSlashing, BlobSidecar, EthSpec, ForkContext, ForkName, - LightClientFinalityUpdate, LightClientOptimisticUpdate, ProposerSlashing, - SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, - SignedBeaconBlockCapella, SignedBeaconBlockDeneb, SignedBeaconBlockElectra, - SignedBeaconBlockMerge, SignedBlsToExecutionChange, SignedContributionAndProof, - SignedVoluntaryExit, SubnetId, SyncCommitteeMessage, SyncSubnetId, + Attestation, AttesterSlashing, AttesterSlashingBase, AttesterSlashingElectra, BlobSidecar, + EthSpec, ForkContext, ForkName, LightClientFinalityUpdate, LightClientOptimisticUpdate, + ProposerSlashing, SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockAltair, + SignedBeaconBlockBase, SignedBeaconBlockCapella, SignedBeaconBlockDeneb, + SignedBeaconBlockElectra, SignedBeaconBlockMerge, SignedBlsToExecutionChange, + SignedContributionAndProof, SignedVoluntaryExit, SubnetId, SyncCommitteeMessage, SyncSubnetId, }; #[derive(Debug, Clone, PartialEq)] @@ -239,8 +239,28 @@ impl PubsubMessage { Ok(PubsubMessage::ProposerSlashing(Box::new(proposer_slashing))) } GossipKind::AttesterSlashing => { - let attester_slashing = AttesterSlashing::from_ssz_bytes(data) - .map_err(|e| format!("{:?}", e))?; + // TODO(electra): could an older attester slashing still be floating around during the fork transition? + let attester_slashing = + match fork_context.from_context_bytes(gossip_topic.fork_digest) { + Some(ForkName::Base) + | Some(ForkName::Altair) + | Some(ForkName::Merge) + | Some(ForkName::Capella) + | Some(ForkName::Deneb) => AttesterSlashing::Base( + AttesterSlashingBase::from_ssz_bytes(data) + .map_err(|e| format!("{:?}", e))?, + ), + Some(ForkName::Electra) => AttesterSlashing::Electra( + AttesterSlashingElectra::from_ssz_bytes(data) + .map_err(|e| format!("{:?}", e))?, + ), + None => { + return Err(format!( + "Unknown gossipsub fork digest: {:?}", + gossip_topic.fork_digest + )) + } + }; Ok(PubsubMessage::AttesterSlashing(Box::new(attester_slashing))) } GossipKind::SignedContributionAndProof => { diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 772c2efc944..dab5310f2b5 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -1428,7 +1428,7 @@ impl NetworkBeaconProcessor { self.chain .validator_monitor .read() - .register_gossip_attester_slashing(slashing.as_inner()); + .register_gossip_attester_slashing(slashing.as_inner().to_ref()); self.chain.import_attester_slashing(slashing); debug!(self.log, "Successfully imported attester slashing"); diff --git a/beacon_node/operation_pool/src/attestation_storage.rs b/beacon_node/operation_pool/src/attestation_storage.rs index 90fdf3cdb81..927e8c9b12c 100644 --- a/beacon_node/operation_pool/src/attestation_storage.rs +++ b/beacon_node/operation_pool/src/attestation_storage.rs @@ -2,8 +2,9 @@ use crate::AttestationStats; use itertools::Itertools; use std::collections::HashMap; use types::{ - attestation::{AttestationBase, AttestationElectra}, superstruct, AggregateSignature, Attestation, AttestationData, - BeaconState, BitList, BitVector, Checkpoint, Epoch, EthSpec, Hash256, Slot, + attestation::{AttestationBase, AttestationElectra}, + superstruct, AggregateSignature, Attestation, AttestationData, BeaconState, BitList, BitVector, + Checkpoint, Epoch, EthSpec, Hash256, Slot, }; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] @@ -121,12 +122,14 @@ impl<'a, E: EthSpec> AttestationRef<'a, E> { data: self.attestation_data(), signature: indexed_att.signature.clone(), }), - CompactIndexedAttestation::Electra(indexed_att) => Attestation::Electra(AttestationElectra { - aggregation_bits: indexed_att.aggregation_bits.clone(), - data: self.attestation_data(), - signature: indexed_att.signature.clone(), - committee_bits: indexed_att.committee_bits.clone(), - }), + CompactIndexedAttestation::Electra(indexed_att) => { + Attestation::Electra(AttestationElectra { + aggregation_bits: indexed_att.aggregation_bits.clone(), + data: self.attestation_data(), + signature: indexed_att.signature.clone(), + committee_bits: indexed_att.committee_bits.clone(), + }) + } } } } diff --git a/beacon_node/operation_pool/src/attester_slashing.rs b/beacon_node/operation_pool/src/attester_slashing.rs index 725d4d2a857..c2411d4d726 100644 --- a/beacon_node/operation_pool/src/attester_slashing.rs +++ b/beacon_node/operation_pool/src/attester_slashing.rs @@ -1,17 +1,17 @@ use crate::max_cover::MaxCover; use state_processing::per_block_processing::get_slashable_indices_modular; use std::collections::{HashMap, HashSet}; -use types::{AttesterSlashing, BeaconState, EthSpec}; +use types::{AttesterSlashing, AttesterSlashingRef, BeaconState, EthSpec}; #[derive(Debug, Clone)] pub struct AttesterSlashingMaxCover<'a, E: EthSpec> { - slashing: &'a AttesterSlashing, + slashing: AttesterSlashingRef<'a, E>, effective_balances: HashMap, } impl<'a, E: EthSpec> AttesterSlashingMaxCover<'a, E> { pub fn new( - slashing: &'a AttesterSlashing, + slashing: AttesterSlashingRef<'a, E>, proposer_slashing_indices: &HashSet, state: &BeaconState, ) -> Option { @@ -39,16 +39,16 @@ impl<'a, E: EthSpec> AttesterSlashingMaxCover<'a, E> { impl<'a, E: EthSpec> MaxCover for AttesterSlashingMaxCover<'a, E> { /// The result type, of which we would eventually like a collection of maximal quality. type Object = AttesterSlashing; - type Intermediate = AttesterSlashing; + type Intermediate = AttesterSlashingRef<'a, E>; /// The type used to represent sets. type Set = HashMap; - fn intermediate(&self) -> &AttesterSlashing { - self.slashing + fn intermediate(&self) -> &AttesterSlashingRef<'a, E> { + &self.slashing } - fn convert_to_object(slashing: &AttesterSlashing) -> AttesterSlashing { - slashing.clone() + fn convert_to_object(slashing: &AttesterSlashingRef<'a, E>) -> AttesterSlashing { + slashing.clone_as_attester_slashing() } /// Get the set of elements covered. @@ -58,7 +58,7 @@ impl<'a, E: EthSpec> MaxCover for AttesterSlashingMaxCover<'a, E> { /// Update the set of items covered, for the inclusion of some object in the solution. fn update_covering_set( &mut self, - _best_slashing: &AttesterSlashing, + _best_slashing: &AttesterSlashingRef<'a, E>, covered_validator_indices: &HashMap, ) { self.effective_balances diff --git a/beacon_node/operation_pool/src/lib.rs b/beacon_node/operation_pool/src/lib.rs index 4b5fc259507..f700eecf6bd 100644 --- a/beacon_node/operation_pool/src/lib.rs +++ b/beacon_node/operation_pool/src/lib.rs @@ -428,7 +428,7 @@ impl OperationPool { let relevant_attester_slashings = reader.iter().flat_map(|slashing| { if slashing.signature_is_still_valid(&state.fork()) { - AttesterSlashingMaxCover::new(slashing.as_inner(), to_be_slashed, state) + AttesterSlashingMaxCover::new(slashing.as_inner().to_ref(), to_be_slashed, state) } else { None } @@ -442,7 +442,7 @@ impl OperationPool { .into_iter() .map(|cover| { to_be_slashed.extend(cover.covering_set().keys()); - cover.intermediate().clone() + AttesterSlashingMaxCover::convert_to_object(cover.intermediate()) }) .collect() } @@ -463,16 +463,19 @@ impl OperationPool { // Check that the attestation's signature is still valid wrt the fork version. let signature_ok = slashing.signature_is_still_valid(&head_state.fork()); // Slashings that don't slash any validators can also be dropped. - let slashing_ok = - get_slashable_indices_modular(head_state, slashing.as_inner(), |_, validator| { + let slashing_ok = get_slashable_indices_modular( + head_state, + slashing.as_inner().to_ref(), + |_, validator| { // Declare that a validator is still slashable if they have not exited prior // to the finalized epoch. // // We cannot check the `slashed` field since the `head` is not finalized and // a fork could un-slash someone. validator.exit_epoch > head_state.finalized_checkpoint().epoch - }) - .map_or(false, |indices| !indices.is_empty()); + }, + ) + .map_or(false, |indices| !indices.is_empty()); signature_ok && slashing_ok }); @@ -907,8 +910,8 @@ mod release_tests { }) .unwrap(); - let att1_indices = get_attesting_indices_from_state(&state, &att1).unwrap(); - let att2_indices = get_attesting_indices_from_state(&state, &att2).unwrap(); + let att1_indices = get_attesting_indices_from_state(&state, att1.to_ref()).unwrap(); + let att2_indices = get_attesting_indices_from_state(&state, att2.to_ref()).unwrap(); let att1_split = SplitAttestation::new(att1.clone(), att1_indices); let att2_split = SplitAttestation::new(att2.clone(), att2_indices); @@ -981,7 +984,8 @@ mod release_tests { for (atts, _) in attestations { for (att, _) in atts { - let attesting_indices = get_attesting_indices_from_state(&state, &att).unwrap(); + let attesting_indices = + get_attesting_indices_from_state(&state, att.to_ref()).unwrap(); op_pool.insert_attestation(att, attesting_indices).unwrap(); } } @@ -1051,7 +1055,7 @@ mod release_tests { for (_, aggregate) in attestations { let att = aggregate.unwrap().message.aggregate; - let attesting_indices = get_attesting_indices_from_state(&state, &att).unwrap(); + let attesting_indices = get_attesting_indices_from_state(&state, att.to_ref()).unwrap(); op_pool .insert_attestation(att.clone(), attesting_indices.clone()) .unwrap(); @@ -1139,7 +1143,8 @@ mod release_tests { .collect::>(); for att in aggs1.into_iter().chain(aggs2.into_iter()) { - let attesting_indices = get_attesting_indices_from_state(&state, &att).unwrap(); + let attesting_indices = + get_attesting_indices_from_state(&state, att.to_ref()).unwrap(); op_pool.insert_attestation(att, attesting_indices).unwrap(); } } @@ -1211,7 +1216,8 @@ mod release_tests { .collect::>(); for att in aggs { - let attesting_indices = get_attesting_indices_from_state(&state, &att).unwrap(); + let attesting_indices = + get_attesting_indices_from_state(&state, att.to_ref()).unwrap(); op_pool.insert_attestation(att, attesting_indices).unwrap(); } }; @@ -1306,7 +1312,8 @@ mod release_tests { .collect::>(); for att in aggs { - let attesting_indices = get_attesting_indices_from_state(&state, &att).unwrap(); + let attesting_indices = + get_attesting_indices_from_state(&state, att.to_ref()).unwrap(); op_pool.insert_attestation(att, attesting_indices).unwrap(); } }; @@ -1349,7 +1356,7 @@ mod release_tests { reward_cache.update(&state).unwrap(); for att in best_attestations { - let attesting_indices = get_attesting_indices_from_state(&state, &att).unwrap(); + let attesting_indices = get_attesting_indices_from_state(&state, att.to_ref()).unwrap(); let split_attestation = SplitAttestation::new(att, attesting_indices); let mut fresh_validators_rewards = AttMaxCover::new( split_attestation.as_ref(), diff --git a/beacon_node/operation_pool/src/persistence.rs b/beacon_node/operation_pool/src/persistence.rs index 8e4e5e2898d..6fd8a6cc3cc 100644 --- a/beacon_node/operation_pool/src/persistence.rs +++ b/beacon_node/operation_pool/src/persistence.rs @@ -39,9 +39,7 @@ pub struct PersistedOperationPool { pub attestations: Vec<(Attestation, Vec)>, /// Mapping from sync contribution ID to sync contributions and aggregate. pub sync_contributions: PersistedSyncContributions, - /// [DEPRECATED] Attester slashings. - #[superstruct(only(V5))] - pub attester_slashings_v5: Vec<(AttesterSlashing, ForkVersion)>, + /// TODO(electra): we've made a DB change here!!! /// Attester slashings. #[superstruct(only(V12, V14, V15))] pub attester_slashings: Vec, E>>, diff --git a/consensus/fork_choice/src/fork_choice.rs b/consensus/fork_choice/src/fork_choice.rs index f7836fb2fb4..147fdb01d02 100644 --- a/consensus/fork_choice/src/fork_choice.rs +++ b/consensus/fork_choice/src/fork_choice.rs @@ -14,8 +14,8 @@ use std::marker::PhantomData; use std::time::Duration; use types::{ consts::merge::INTERVALS_PER_SLOT, AbstractExecPayload, AttestationShufflingId, - AttesterSlashing, BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, Checkpoint, Epoch, - EthSpec, ExecPayload, ExecutionBlockHash, Hash256, IndexedAttestation, RelativeEpoch, + AttesterSlashingRef, BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, Checkpoint, + Epoch, EthSpec, ExecPayload, ExecutionBlockHash, Hash256, IndexedAttestationRef, RelativeEpoch, SignedBeaconBlock, Slot, }; @@ -238,8 +238,8 @@ pub struct QueuedAttestation { target_epoch: Epoch, } -impl From<&IndexedAttestation> for QueuedAttestation { - fn from(a: &IndexedAttestation) -> Self { +impl<'a, E: EthSpec> From> for QueuedAttestation { + fn from(a: IndexedAttestationRef<'a, E>) -> Self { Self { slot: a.data().slot, attesting_indices: a.attesting_indices_to_vec(), @@ -940,7 +940,7 @@ where /// https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/fork-choice.md#validate_on_attestation fn validate_on_attestation( &self, - indexed_attestation: &IndexedAttestation, + indexed_attestation: IndexedAttestationRef, is_from_block: AttestationFromBlock, ) -> Result<(), InvalidAttestation> { // There is no point in processing an attestation with an empty bitfield. Reject @@ -1037,7 +1037,7 @@ where pub fn on_attestation( &mut self, system_time_current_slot: Slot, - attestation: &IndexedAttestation, + attestation: IndexedAttestationRef, is_from_block: AttestationFromBlock, ) -> Result<(), Error> { self.update_time(system_time_current_slot)?; @@ -1086,14 +1086,14 @@ where /// Apply an attester slashing to fork choice. /// /// We assume that the attester slashing provided to this function has already been verified. - pub fn on_attester_slashing(&mut self, slashing: &AttesterSlashing) { - let attesting_indices_set = |att: &IndexedAttestation| { + pub fn on_attester_slashing(&mut self, slashing: AttesterSlashingRef<'_, E>) { + let attesting_indices_set = |att: IndexedAttestationRef<'_, E>| { att.attesting_indices_iter() .copied() .collect::>() }; - let att1_indices = attesting_indices_set(&slashing.attestation_1); - let att2_indices = attesting_indices_set(&slashing.attestation_2); + let att1_indices = attesting_indices_set(slashing.attestation_1()); + let att2_indices = attesting_indices_set(slashing.attestation_2()); self.fc_store .extend_equivocating_indices(att1_indices.intersection(&att2_indices).copied()); } diff --git a/consensus/state_processing/src/common/get_attesting_indices.rs b/consensus/state_processing/src/common/get_attesting_indices.rs index 6b8ce6f9372..e798f55f844 100644 --- a/consensus/state_processing/src/common/get_attesting_indices.rs +++ b/consensus/state_processing/src/common/get_attesting_indices.rs @@ -25,14 +25,14 @@ pub fn get_attesting_indices( /// 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: &Attestation, + att: AttestationRef, ) -> Result, BeaconStateError> { let committee = state.get_beacon_committee(att.data().slot, att.data().index)?; match att { - Attestation::Base(att) => { + AttestationRef::Base(att) => { get_attesting_indices::(committee.committee, &att.aggregation_bits) } // TODO(electra) implement get_attesting_indices for electra - Attestation::Electra(_) => todo!(), + AttestationRef::Electra(_) => todo!(), } } diff --git a/consensus/state_processing/src/common/get_indexed_attestation.rs b/consensus/state_processing/src/common/get_indexed_attestation.rs index d6c7512961c..a04d9198c68 100644 --- a/consensus/state_processing/src/common/get_indexed_attestation.rs +++ b/consensus/state_processing/src/common/get_indexed_attestation.rs @@ -9,12 +9,12 @@ type Result = std::result::Result>; /// Spec v0.12.1 pub fn get_indexed_attestation( committee: &[usize], - attestation: &Attestation, + attestation: AttestationRef, ) -> Result> { let attesting_indices = match attestation { - Attestation::Base(att) => get_attesting_indices::(committee, &att.aggregation_bits)?, + AttestationRef::Base(att) => get_attesting_indices::(committee, &att.aggregation_bits)?, // TODO(electra) implement get_attesting_indices for electra - Attestation::Electra(_) => todo!(), + AttestationRef::Electra(_) => todo!(), }; Ok(IndexedAttestation::Base(IndexedAttestationBase { diff --git a/consensus/state_processing/src/consensus_context.rs b/consensus/state_processing/src/consensus_context.rs index b1196c942d3..b28f218fc8a 100644 --- a/consensus/state_processing/src/consensus_context.rs +++ b/consensus/state_processing/src/consensus_context.rs @@ -4,8 +4,9 @@ use crate::EpochCacheError; use std::collections::{hash_map::Entry, HashMap}; use tree_hash::TreeHash; use types::{ - AbstractExecPayload, Attestation, AttestationData, BeaconState, BeaconStateError, BitList, - ChainSpec, Epoch, EthSpec, Hash256, IndexedAttestation, SignedBeaconBlock, Slot, + AbstractExecPayload, AttestationData, AttestationRef, BeaconState, BeaconStateError, BitList, + ChainSpec, Epoch, EthSpec, Hash256, IndexedAttestation, IndexedAttestationRef, + SignedBeaconBlock, Slot, }; #[derive(Debug, PartialEq, Clone)] @@ -153,13 +154,13 @@ impl ConsensusContext { } } - pub fn get_indexed_attestation( - &mut self, + pub fn get_indexed_attestation<'a>( + &'a mut self, state: &BeaconState, - attestation: &Attestation, - ) -> Result<&IndexedAttestation, BlockOperationError> { + attestation: AttestationRef<'a, E>, + ) -> Result, BlockOperationError> { let aggregation_bits = match attestation { - Attestation::Base(attn) => { + AttestationRef::Base(attn) => { let mut extended_aggregation_bits: BitList = BitList::with_capacity(attn.aggregation_bits.len()) .map_err(BeaconStateError::from)?; @@ -171,7 +172,7 @@ impl ConsensusContext { } extended_aggregation_bits } - Attestation::Electra(attn) => attn.aggregation_bits.clone(), + AttestationRef::Electra(attn) => attn.aggregation_bits.clone(), }; let key = (attestation.data().clone(), aggregation_bits); @@ -186,6 +187,7 @@ impl ConsensusContext { Ok(vacant.insert(indexed_attestation)) } } + .map(|indexed_attestation| (*indexed_attestation).to_ref()) } pub fn num_cached_indexed_attestations(&self) -> usize { diff --git a/consensus/state_processing/src/lib.rs b/consensus/state_processing/src/lib.rs index 74f9d84bb11..adabf6862d3 100644 --- a/consensus/state_processing/src/lib.rs +++ b/consensus/state_processing/src/lib.rs @@ -45,4 +45,4 @@ pub use per_epoch_processing::{ }; pub use per_slot_processing::{per_slot_processing, Error as SlotProcessingError}; pub use types::{EpochCache, EpochCacheError, EpochCacheKey}; -pub use verify_operation::{SigVerifiedOp, VerifyOperation, VerifyOperationAt}; +pub use verify_operation::{SigVerifiedOp, TransformPersist, VerifyOperation, VerifyOperationAt}; diff --git a/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs b/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs index 884fe8305ba..74477f5e481 100644 --- a/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs +++ b/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs @@ -247,13 +247,12 @@ where ) -> Result<()> { self.sets .sets - .reserve(block.message().body().attester_slashings().len() * 2); + .reserve(block.message().body().attester_slashings_len() * 2); block .message() .body() .attester_slashings() - .iter() .try_for_each(|attester_slashing| { let (set_1, set_2) = attester_slashing_signature_sets( self.state, @@ -277,13 +276,12 @@ where ) -> Result<()> { self.sets .sets - .reserve(block.message().body().attestations().len()); + .reserve(block.message().body().attestations_len()); block .message() .body() .attestations() - .iter() .try_for_each(|attestation| { let indexed_attestation = ctxt.get_indexed_attestation(self.state, attestation)?; 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 7c7c9e474ac..4bad3315cc4 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 @@ -13,7 +13,7 @@ fn error(reason: Invalid) -> BlockOperationError { /// Verify an `IndexedAttestation`. pub fn is_valid_indexed_attestation( state: &BeaconState, - indexed_attestation: &IndexedAttestation, + indexed_attestation: IndexedAttestationRef, verify_signatures: VerifySignatures, spec: &ChainSpec, ) -> Result<()> { 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 cb677f5514f..fdccf5af205 100644 --- a/consensus/state_processing/src/per_block_processing/process_operations.rs +++ b/consensus/state_processing/src/per_block_processing/process_operations.rs @@ -46,13 +46,16 @@ pub mod base { /// /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns /// an `Err` describing the invalid object or cause of failure. - pub fn process_attestations( + pub fn process_attestations<'a, E: EthSpec, I>( state: &mut BeaconState, - attestations: &[Attestation], + attestations: I, verify_signatures: VerifySignatures, ctxt: &mut ConsensusContext, spec: &ChainSpec, - ) -> Result<(), BlockProcessingError> { + ) -> Result<(), BlockProcessingError> + where + I: Iterator>, + { // Ensure required caches are all built. These should be no-ops during regular operation. state.build_committee_cache(RelativeEpoch::Current, spec)?; state.build_committee_cache(RelativeEpoch::Previous, spec)?; @@ -63,7 +66,7 @@ pub mod base { let proposer_index = ctxt.get_proposer_index(state, spec)?; // Verify and apply each attestation. - for (i, attestation) in attestations.iter().enumerate() { + for (i, attestation) in attestations.enumerate() { verify_attestation_for_block_inclusion( state, attestation, @@ -74,7 +77,7 @@ pub mod base { .map_err(|e| e.into_with_index(i))?; match attestation { - Attestation::Base(att) => { + AttestationRef::Base(att) => { let pending_attestation = PendingAttestation { aggregation_bits: att.aggregation_bits.clone(), data: att.data.clone(), @@ -94,7 +97,7 @@ pub mod base { .push(pending_attestation)?; } } - Attestation::Electra(_) => { + AttestationRef::Electra(_) => { // TODO(electra) pending attestations are only phase 0 // so we should just raise a relevant error here todo!() @@ -110,24 +113,24 @@ pub mod altair_deneb { use super::*; use crate::common::update_progressive_balances_cache::update_progressive_balances_on_attestation; - pub fn process_attestations( + pub fn process_attestations<'a, E: EthSpec, I>( state: &mut BeaconState, - attestations: &[Attestation], + attestations: I, verify_signatures: VerifySignatures, ctxt: &mut ConsensusContext, spec: &ChainSpec, - ) -> Result<(), BlockProcessingError> { - attestations - .iter() - .enumerate() - .try_for_each(|(i, attestation)| { - process_attestation(state, attestation, i, ctxt, verify_signatures, spec) - }) + ) -> Result<(), BlockProcessingError> + where + I: Iterator>, + { + attestations.enumerate().try_for_each(|(i, attestation)| { + process_attestation(state, attestation, i, ctxt, verify_signatures, spec) + }) } pub fn process_attestation( state: &mut BeaconState, - attestation: &Attestation, + attestation: AttestationRef, att_index: usize, ctxt: &mut ConsensusContext, verify_signatures: VerifySignatures, @@ -238,16 +241,19 @@ pub fn process_proposer_slashings( /// /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns /// an `Err` describing the invalid object or cause of failure. -pub fn process_attester_slashings( +pub fn process_attester_slashings<'a, E: EthSpec, I>( state: &mut BeaconState, - attester_slashings: &[AttesterSlashing], + attester_slashings: I, verify_signatures: VerifySignatures, ctxt: &mut ConsensusContext, spec: &ChainSpec, -) -> Result<(), BlockProcessingError> { +) -> Result<(), BlockProcessingError> +where + I: Iterator>, +{ state.build_slashings_cache()?; - for (i, attester_slashing) in attester_slashings.iter().enumerate() { + for (i, attester_slashing) in attester_slashings.enumerate() { let slashable_indices = verify_attester_slashing(state, attester_slashing, verify_signatures, spec) .map_err(|e| e.into_with_index(i))?; diff --git a/consensus/state_processing/src/per_block_processing/signature_sets.rs b/consensus/state_processing/src/per_block_processing/signature_sets.rs index 19351a2c2f8..f19714dc193 100644 --- a/consensus/state_processing/src/per_block_processing/signature_sets.rs +++ b/consensus/state_processing/src/per_block_processing/signature_sets.rs @@ -7,10 +7,10 @@ use ssz::DecodeError; use std::borrow::Cow; use tree_hash::TreeHash; use types::{ - AbstractExecPayload, AggregateSignature, AttesterSlashing, BeaconBlockRef, BeaconState, + AbstractExecPayload, AggregateSignature, AttesterSlashingRef, BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, DepositData, Domain, Epoch, EthSpec, Fork, Hash256, - InconsistentFork, IndexedAttestation, ProposerSlashing, PublicKey, PublicKeyBytes, Signature, - SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockHeader, + InconsistentFork, IndexedAttestation, IndexedAttestationRef, ProposerSlashing, PublicKey, + PublicKeyBytes, Signature, SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockHeader, SignedBlsToExecutionChange, SignedContributionAndProof, SignedRoot, SignedVoluntaryExit, SigningData, Slot, SyncAggregate, SyncAggregatorSelectionData, Unsigned, }; @@ -272,7 +272,7 @@ pub fn indexed_attestation_signature_set<'a, 'b, E, F>( state: &'a BeaconState, get_pubkey: F, signature: &'a AggregateSignature, - indexed_attestation: &'b IndexedAttestation, + indexed_attestation: IndexedAttestationRef<'b, E>, spec: &'a ChainSpec, ) -> Result> where @@ -335,7 +335,7 @@ where pub fn attester_slashing_signature_sets<'a, E, F>( state: &'a BeaconState, get_pubkey: F, - attester_slashing: &'a AttesterSlashing, + attester_slashing: AttesterSlashingRef<'a, E>, spec: &'a ChainSpec, ) -> Result<(SignatureSet<'a>, SignatureSet<'a>)> where @@ -346,15 +346,15 @@ where indexed_attestation_signature_set( state, get_pubkey.clone(), - attester_slashing.attestation_1.signature(), - &attester_slashing.attestation_1, + attester_slashing.attestation_1().signature(), + attester_slashing.attestation_1(), spec, )?, indexed_attestation_signature_set( state, get_pubkey, - attester_slashing.attestation_2.signature(), - &attester_slashing.attestation_2, + attester_slashing.attestation_2().signature(), + attester_slashing.attestation_2(), spec, )?, )) diff --git a/consensus/state_processing/src/per_block_processing/tests.rs b/consensus/state_processing/src/per_block_processing/tests.rs index 85bb740a6cd..2774dd3d87f 100644 --- a/consensus/state_processing/src/per_block_processing/tests.rs +++ b/consensus/state_processing/src/per_block_processing/tests.rs @@ -388,7 +388,12 @@ async fn invalid_attestation_no_committee_for_index() { .clone() .deconstruct() .0; - head_block.to_mut().body_mut().attestations_mut()[0] + head_block + .to_mut() + .body_mut() + .attestations_mut() + .next() + .unwrap() .data_mut() .index += 1; let mut ctxt = ConsensusContext::new(state.slot()); @@ -423,10 +428,21 @@ async fn invalid_attestation_wrong_justified_checkpoint() { .clone() .deconstruct() .0; - let old_justified_checkpoint = head_block.body().attestations()[0].data().source; + let old_justified_checkpoint = head_block + .body() + .attestations() + .next() + .unwrap() + .data() + .source; let mut new_justified_checkpoint = old_justified_checkpoint; new_justified_checkpoint.epoch += Epoch::new(1); - head_block.to_mut().body_mut().attestations_mut()[0] + head_block + .to_mut() + .body_mut() + .attestations_mut() + .next() + .unwrap() .data_mut() .source = new_justified_checkpoint; @@ -467,7 +483,12 @@ async fn invalid_attestation_bad_aggregation_bitfield_len() { .clone() .deconstruct() .0; - *head_block.to_mut().body_mut().attestations_mut()[0] + *head_block + .to_mut() + .body_mut() + .attestations_mut() + .next() + .unwrap() .aggregation_bits_base_mut() .unwrap() = Bitfield::with_capacity(spec.target_committee_size).unwrap(); @@ -502,8 +523,13 @@ async fn invalid_attestation_bad_signature() { .clone() .deconstruct() .0; - *head_block.to_mut().body_mut().attestations_mut()[0].signature_mut() = - AggregateSignature::empty(); + *head_block + .to_mut() + .body_mut() + .attestations_mut() + .next() + .unwrap() + .signature_mut() = AggregateSignature::empty(); let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attestations( @@ -538,9 +564,14 @@ async fn invalid_attestation_included_too_early() { .clone() .deconstruct() .0; - let new_attesation_slot = head_block.body().attestations()[0].data().slot + let new_attesation_slot = head_block.body().attestations().next().unwrap().data().slot + Slot::new(MainnetEthSpec::slots_per_epoch()); - head_block.to_mut().body_mut().attestations_mut()[0] + head_block + .to_mut() + .body_mut() + .attestations_mut() + .next() + .unwrap() .data_mut() .slot = new_attesation_slot; @@ -581,9 +612,14 @@ async fn invalid_attestation_included_too_late() { .clone() .deconstruct() .0; - let new_attesation_slot = head_block.body().attestations()[0].data().slot + let new_attesation_slot = head_block.body().attestations().next().unwrap().data().slot - Slot::new(MainnetEthSpec::slots_per_epoch()); - head_block.to_mut().body_mut().attestations_mut()[0] + head_block + .to_mut() + .body_mut() + .attestations_mut() + .next() + .unwrap() .data_mut() .slot = new_attesation_slot; @@ -621,7 +657,12 @@ async fn invalid_attestation_target_epoch_slot_mismatch() { .clone() .deconstruct() .0; - head_block.to_mut().body_mut().attestations_mut()[0] + head_block + .to_mut() + .body_mut() + .attestations_mut() + .next() + .unwrap() .data_mut() .target .epoch += Epoch::new(1); @@ -657,7 +698,7 @@ async fn valid_insert_attester_slashing() { let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attester_slashings( &mut state, - &[attester_slashing], + [attester_slashing.to_ref()].into_iter(), VerifySignatures::True, &mut ctxt, &spec, @@ -673,13 +714,20 @@ async fn invalid_attester_slashing_not_slashable() { let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT).await; let mut attester_slashing = harness.make_attester_slashing(vec![1, 2]); - attester_slashing.attestation_1 = attester_slashing.attestation_2.clone(); + match &mut attester_slashing { + AttesterSlashing::Base(ref mut attester_slashing) => { + attester_slashing.attestation_1 = attester_slashing.attestation_2.clone(); + } + AttesterSlashing::Electra(ref mut attester_slashing) => { + attester_slashing.attestation_1 = attester_slashing.attestation_2.clone(); + } + } let mut state = harness.get_current_state(); let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attester_slashings( &mut state, - &[attester_slashing], + [attester_slashing.to_ref()].into_iter(), VerifySignatures::True, &mut ctxt, &spec, @@ -701,16 +749,20 @@ async fn invalid_attester_slashing_1_invalid() { let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT).await; let mut attester_slashing = harness.make_attester_slashing(vec![1, 2]); - *attester_slashing - .attestation_1 - .attesting_indices_base_mut() - .unwrap() = VariableList::from(vec![2, 1]); + match &mut attester_slashing { + AttesterSlashing::Base(ref mut attester_slashing) => { + attester_slashing.attestation_1.attesting_indices = VariableList::from(vec![2, 1]); + } + AttesterSlashing::Electra(ref mut attester_slashing) => { + attester_slashing.attestation_1.attesting_indices = VariableList::from(vec![2, 1]); + } + } let mut state = harness.get_current_state(); let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attester_slashings( &mut state, - &[attester_slashing], + [attester_slashing.to_ref()].into_iter(), VerifySignatures::True, &mut ctxt, &spec, @@ -735,16 +787,20 @@ async fn invalid_attester_slashing_2_invalid() { let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT).await; let mut attester_slashing = harness.make_attester_slashing(vec![1, 2]); - *attester_slashing - .attestation_2 - .attesting_indices_base_mut() - .unwrap() = VariableList::from(vec![2, 1]); + match &mut attester_slashing { + AttesterSlashing::Base(ref mut attester_slashing) => { + attester_slashing.attestation_2.attesting_indices = VariableList::from(vec![2, 1]); + } + AttesterSlashing::Electra(ref mut attester_slashing) => { + attester_slashing.attestation_2.attesting_indices = VariableList::from(vec![2, 1]); + } + } let mut state = harness.get_current_state(); let mut ctxt = ConsensusContext::new(state.slot()); let result = process_operations::process_attester_slashings( &mut state, - &[attester_slashing], + [attester_slashing.to_ref()].into_iter(), VerifySignatures::True, &mut ctxt, &spec, 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 8369f988f73..df9bbc855c1 100644 --- a/consensus/state_processing/src/per_block_processing/verify_attestation.rs +++ b/consensus/state_processing/src/per_block_processing/verify_attestation.rs @@ -17,11 +17,11 @@ fn error(reason: Invalid) -> BlockOperationError { /// Optionally verifies the aggregate signature, depending on `verify_signatures`. pub fn verify_attestation_for_block_inclusion<'ctxt, E: EthSpec>( state: &BeaconState, - attestation: &Attestation, + attestation: AttestationRef<'ctxt, E>, ctxt: &'ctxt mut ConsensusContext, verify_signatures: VerifySignatures, spec: &ChainSpec, -) -> Result<&'ctxt IndexedAttestation> { +) -> Result> { let data = attestation.data(); verify!( @@ -61,11 +61,11 @@ pub fn verify_attestation_for_block_inclusion<'ctxt, E: EthSpec>( /// Spec v0.12.1 pub fn verify_attestation_for_state<'ctxt, E: EthSpec>( state: &BeaconState, - attestation: &Attestation, + attestation: AttestationRef<'ctxt, E>, ctxt: &'ctxt mut ConsensusContext, verify_signatures: VerifySignatures, spec: &ChainSpec, -) -> Result<&'ctxt IndexedAttestation> { +) -> Result> { let data = attestation.data(); verify!( @@ -87,7 +87,7 @@ pub fn verify_attestation_for_state<'ctxt, E: EthSpec>( /// /// Spec v0.12.1 fn verify_casper_ffg_vote( - attestation: &Attestation, + attestation: AttestationRef, state: &BeaconState, ) -> Result<()> { let data = attestation.data(); diff --git a/consensus/state_processing/src/per_block_processing/verify_attester_slashing.rs b/consensus/state_processing/src/per_block_processing/verify_attester_slashing.rs index 7fb784a968e..7fe4c8bc08b 100644 --- a/consensus/state_processing/src/per_block_processing/verify_attester_slashing.rs +++ b/consensus/state_processing/src/per_block_processing/verify_attester_slashing.rs @@ -18,12 +18,12 @@ fn error(reason: Invalid) -> BlockOperationError { /// invalidity. pub fn verify_attester_slashing( state: &BeaconState, - attester_slashing: &AttesterSlashing, + attester_slashing: AttesterSlashingRef<'_, E>, verify_signatures: VerifySignatures, spec: &ChainSpec, ) -> Result> { - let attestation_1 = &attester_slashing.attestation_1; - let attestation_2 = &attester_slashing.attestation_2; + let attestation_1 = attester_slashing.attestation_1(); + let attestation_2 = attester_slashing.attestation_2(); // Spec: is_slashable_attestation_data verify!( @@ -45,7 +45,7 @@ pub fn verify_attester_slashing( /// Returns Ok(indices) if `indices.len() > 0` pub fn get_slashable_indices( state: &BeaconState, - attester_slashing: &AttesterSlashing, + attester_slashing: AttesterSlashingRef<'_, E>, ) -> Result> { get_slashable_indices_modular(state, attester_slashing, |_, validator| { validator.is_slashable_at(state.current_epoch()) @@ -56,14 +56,14 @@ pub fn get_slashable_indices( /// for determining whether a given validator should be considered slashable. pub fn get_slashable_indices_modular( state: &BeaconState, - attester_slashing: &AttesterSlashing, + attester_slashing: AttesterSlashingRef<'_, E>, is_slashable: F, ) -> Result> where F: Fn(u64, &Validator) -> bool, { - let attestation_1 = &attester_slashing.attestation_1; - let attestation_2 = &attester_slashing.attestation_2; + let attestation_1 = attester_slashing.attestation_1(); + let attestation_2 = attester_slashing.attestation_2(); let attesting_indices_1 = attestation_1 .attesting_indices_iter() diff --git a/consensus/state_processing/src/verify_operation.rs b/consensus/state_processing/src/verify_operation.rs index 4a2e5e22c56..c12467fbb57 100644 --- a/consensus/state_processing/src/verify_operation.rs +++ b/consensus/state_processing/src/verify_operation.rs @@ -12,30 +12,132 @@ use smallvec::{smallvec, SmallVec}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use std::marker::PhantomData; +use types::{AttesterSlashing, AttesterSlashingOnDisk, AttesterSlashingRefOnDisk}; use types::{ - AttesterSlashing, BeaconState, ChainSpec, Epoch, EthSpec, Fork, ForkVersion, ProposerSlashing, + BeaconState, ChainSpec, Epoch, EthSpec, Fork, ForkVersion, ProposerSlashing, SignedBlsToExecutionChange, SignedVoluntaryExit, }; const MAX_FORKS_VERIFIED_AGAINST: usize = 2; +pub trait TransformPersist { + type Persistable: Encode + Decode; + type PersistableRef<'a>: Encode + where + Self: 'a; + + /// Returns a reference to the object in a form that implements `Encode` + fn as_persistable_ref(&self) -> Self::PersistableRef<'_>; + + /// Converts the object back into its original form. + fn from_persistable(persistable: Self::Persistable) -> Self; +} + /// Wrapper around an operation type that acts as proof that its signature has been checked. /// /// The inner `op` field is private, meaning instances of this type can only be constructed /// by calling `validate`. -#[derive(Derivative, Debug, Clone, Encode, Decode)] +#[derive(Derivative, Debug, Clone)] #[derivative( PartialEq, Eq, - Hash(bound = "T: Encode + Decode + std::hash::Hash, E: EthSpec") + Hash(bound = "T: TransformPersist + std::hash::Hash, E: EthSpec") )] -pub struct SigVerifiedOp { +pub struct SigVerifiedOp { op: T, verified_against: VerifiedAgainst, - #[ssz(skip_serializing, skip_deserializing)] + //#[ssz(skip_serializing, skip_deserializing)] _phantom: PhantomData, } +impl Encode for SigVerifiedOp { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + && ::is_ssz_fixed_len() + } + + #[allow(clippy::expect_used)] + fn ssz_fixed_len() -> usize { + if ::is_ssz_fixed_len() { + ::ssz_fixed_len() + .checked_add(::ssz_fixed_len()) + .expect("encode ssz_fixed_len length overflow") + } else { + ssz::BYTES_PER_LENGTH_OFFSET + } + } + + #[allow(clippy::expect_used)] + fn ssz_bytes_len(&self) -> usize { + if ::is_ssz_fixed_len() { + ::ssz_fixed_len() + } else { + let persistable = self.op.as_persistable_ref(); + persistable + .ssz_bytes_len() + .checked_add(self.verified_against.ssz_bytes_len()) + .expect("ssz_bytes_len length overflow") + } + } + + fn ssz_append(&self, buf: &mut Vec) { + let mut encoder = ssz::SszEncoder::container(buf, ::ssz_fixed_len()); + let persistable = self.op.as_persistable_ref(); + encoder.append(&persistable); + encoder.append(&self.verified_against); + encoder.finalize(); + } +} + +impl Decode for SigVerifiedOp { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + && ::is_ssz_fixed_len() + } + + #[allow(clippy::expect_used)] + fn ssz_fixed_len() -> usize { + if ::is_ssz_fixed_len() { + ::ssz_fixed_len() + .checked_add(::ssz_fixed_len()) + .expect("decode ssz_fixed_len length overflow") + } else { + ssz::BYTES_PER_LENGTH_OFFSET + } + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + let mut builder = ssz::SszDecoderBuilder::new(bytes); + + // Register types based on whether they are fixed or variable length + if ::is_ssz_fixed_len() { + builder.register_type::()?; + } else { + builder.register_anonymous_variable_length_item()?; + } + + if ::is_ssz_fixed_len() { + builder.register_type::()?; + } else { + builder.register_anonymous_variable_length_item()?; + } + + let mut decoder = builder.build()?; + // Decode each component + let persistable: T::Persistable = decoder.decode_next()?; + let verified_against: VerifiedAgainst = decoder.decode_next()?; + + // Use TransformPersist to convert persistable back into the original type + let op = T::from_persistable(persistable); + + Ok(SigVerifiedOp { + op, + verified_against, + _phantom: PhantomData, + }) + } +} + /// Information about the fork versions that this message was verified against. /// /// In general it is not safe to assume that a `SigVerifiedOp` constructed at some point in the past @@ -109,7 +211,7 @@ where } /// Trait for operations that can be verified and transformed into a `SigVerifiedOp`. -pub trait VerifyOperation: Encode + Decode + Sized { +pub trait VerifyOperation: TransformPersist + Sized { type Error; fn validate( @@ -152,15 +254,15 @@ impl VerifyOperation for AttesterSlashing { state: &BeaconState, spec: &ChainSpec, ) -> Result, Self::Error> { - verify_attester_slashing(state, &self, VerifySignatures::True, spec)?; + verify_attester_slashing(state, self.to_ref(), VerifySignatures::True, spec)?; Ok(SigVerifiedOp::new(self, state)) } #[allow(clippy::arithmetic_side_effects)] fn verification_epochs(&self) -> SmallVec<[Epoch; MAX_FORKS_VERIFIED_AGAINST]> { smallvec![ - self.attestation_1.data().target.epoch, - self.attestation_2.data().target.epoch + self.attestation_1().data().target.epoch, + self.attestation_2().data().target.epoch ] } } @@ -237,3 +339,55 @@ impl VerifyOperationAt for SignedVoluntaryExit { Ok(SigVerifiedOp::new(self, state)) } } + +impl TransformPersist for SignedVoluntaryExit { + type Persistable = Self; + type PersistableRef<'a> = &'a Self; + + fn as_persistable_ref(&self) -> Self::PersistableRef<'_> { + self + } + + fn from_persistable(persistable: Self::Persistable) -> Self { + persistable + } +} + +impl TransformPersist for AttesterSlashing { + type Persistable = AttesterSlashingOnDisk; + type PersistableRef<'a> = AttesterSlashingRefOnDisk<'a, E>; + + fn as_persistable_ref(&self) -> Self::PersistableRef<'_> { + self.to_ref().into() + } + + fn from_persistable(persistable: Self::Persistable) -> Self { + persistable.into() + } +} + +impl TransformPersist for ProposerSlashing { + type Persistable = Self; + type PersistableRef<'a> = &'a Self; + + fn as_persistable_ref(&self) -> Self::PersistableRef<'_> { + self + } + + fn from_persistable(persistable: Self::Persistable) -> Self { + persistable + } +} + +impl TransformPersist for SignedBlsToExecutionChange { + type Persistable = Self; + type PersistableRef<'a> = &'a Self; + + fn as_persistable_ref(&self) -> Self::PersistableRef<'_> { + self + } + + fn from_persistable(persistable: Self::Persistable) -> Self { + persistable + } +} diff --git a/consensus/types/src/attestation.rs b/consensus/types/src/attestation.rs index e036f958fff..0d3f29ae289 100644 --- a/consensus/types/src/attestation.rs +++ b/consensus/types/src/attestation.rs @@ -212,6 +212,15 @@ impl Attestation { } } +impl<'a, E: EthSpec> AttestationRef<'a, E> { + pub fn clone_as_attestation(self) -> Attestation { + match self { + AttestationRef::Base(att) => Attestation::Base(att.clone()), + AttestationRef::Electra(att) => Attestation::Electra(att.clone()), + } + } +} + impl AttestationElectra { /// Are the aggregation bitfields of these attestations disjoint? pub fn signers_disjoint_from(&self, other: &Self) -> bool { @@ -363,6 +372,12 @@ impl SlotData for Attestation { } } +impl<'a, E: EthSpec> SlotData for AttestationRef<'a, E> { + fn get_slot(&self) -> Slot { + self.data().slot + } +} + #[cfg(test)] mod tests { use super::*; @@ -394,5 +409,6 @@ mod tests { ); } - ssz_and_tree_hash_tests!(Attestation); + // TODO(electra): can we do this with both variants or should we? + ssz_and_tree_hash_tests!(AttestationBase); } diff --git a/consensus/types/src/attester_slashing.rs b/consensus/types/src/attester_slashing.rs index 5ad5297d0ce..4ad19f477ca 100644 --- a/consensus/types/src/attester_slashing.rs +++ b/consensus/types/src/attester_slashing.rs @@ -1,32 +1,168 @@ -use crate::{test_utils::TestRandom, EthSpec, IndexedAttestation}; - +use crate::indexed_attestation::{ + IndexedAttestationBase, IndexedAttestationElectra, IndexedAttestationRef, +}; +use crate::{test_utils::TestRandom, EthSpec}; use derivative::Derivative; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; +use superstruct::superstruct; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; -/// Two conflicting attestations. -/// -/// Spec v0.12.1 +#[superstruct( + variants(Base, Electra), + variant_attributes( + derive( + Derivative, + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, + arbitrary::Arbitrary + ), + derivative(PartialEq, Eq, Hash(bound = "E: EthSpec")), + serde(bound = "E: EthSpec"), + arbitrary(bound = "E: EthSpec") + ), + ref_attributes(derive(Debug)) +)] #[derive( - Derivative, - Debug, - Clone, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - TestRandom, - arbitrary::Arbitrary, + Debug, Clone, Serialize, Encode, Deserialize, TreeHash, Derivative, arbitrary::Arbitrary, )] #[derivative(PartialEq, Eq, Hash(bound = "E: EthSpec"))] -#[serde(bound = "E: EthSpec")] +#[serde(bound = "E: EthSpec", untagged)] #[arbitrary(bound = "E: EthSpec")] +#[ssz(enum_behaviour = "transparent")] +#[tree_hash(enum_behaviour = "transparent")] pub struct AttesterSlashing { - pub attestation_1: IndexedAttestation, - pub attestation_2: IndexedAttestation, + // TODO(electra) change this to `#[superstruct(flatten)]` when 0.8 is out.. + #[superstruct(only(Base), partial_getter(rename = "attestation_1_base"))] + pub attestation_1: IndexedAttestationBase, + #[superstruct(only(Electra), partial_getter(rename = "attestation_1_electra"))] + pub attestation_1: IndexedAttestationElectra, + #[superstruct(only(Base), partial_getter(rename = "attestation_2_base"))] + pub attestation_2: IndexedAttestationBase, + #[superstruct(only(Electra), partial_getter(rename = "attestation_2_electra"))] + pub attestation_2: IndexedAttestationElectra, +} + +/// This is a copy of the `AttesterSlashing` enum but with `Encode` and `Decode` derived +/// using the `union` behavior for the purposes of persistence on disk. We use a separate +/// type so that we don't accidentally use this non-spec encoding in consensus objects. +#[derive(Debug, Clone, Encode, Decode, Derivative)] +#[derivative(PartialEq, Eq, Hash(bound = "E: EthSpec"))] +#[ssz(enum_behaviour = "union")] +pub enum AttesterSlashingOnDisk { + Base(AttesterSlashingBase), + Electra(AttesterSlashingElectra), +} + +#[derive(Debug, Clone, Encode)] +#[ssz(enum_behaviour = "union")] +pub enum AttesterSlashingRefOnDisk<'a, E: EthSpec> { + Base(&'a AttesterSlashingBase), + Electra(&'a AttesterSlashingElectra), +} + +impl From> for AttesterSlashingOnDisk { + fn from(attester_slashing: AttesterSlashing) -> Self { + match attester_slashing { + AttesterSlashing::Base(attester_slashing) => Self::Base(attester_slashing), + AttesterSlashing::Electra(attester_slashing) => Self::Electra(attester_slashing), + } + } +} + +impl From> for AttesterSlashing { + fn from(attester_slashing: AttesterSlashingOnDisk) -> Self { + match attester_slashing { + AttesterSlashingOnDisk::Base(attester_slashing) => Self::Base(attester_slashing), + AttesterSlashingOnDisk::Electra(attester_slashing) => Self::Electra(attester_slashing), + } + } +} + +impl<'a, E: EthSpec> From> for AttesterSlashingRef<'a, E> { + fn from(attester_slashing: AttesterSlashingRefOnDisk<'a, E>) -> Self { + match attester_slashing { + AttesterSlashingRefOnDisk::Base(attester_slashing) => Self::Base(attester_slashing), + AttesterSlashingRefOnDisk::Electra(attester_slashing) => { + Self::Electra(attester_slashing) + } + } + } +} + +impl<'a, E: EthSpec> From> for AttesterSlashingRefOnDisk<'a, E> { + fn from(attester_slashing: AttesterSlashingRef<'a, E>) -> Self { + match attester_slashing { + AttesterSlashingRef::Base(attester_slashing) => Self::Base(attester_slashing), + AttesterSlashingRef::Electra(attester_slashing) => Self::Electra(attester_slashing), + } + } +} + +impl<'a, E: EthSpec> AttesterSlashingRef<'a, E> { + pub fn clone_as_attester_slashing(self) -> AttesterSlashing { + match self { + AttesterSlashingRef::Base(attester_slashing) => { + AttesterSlashing::Base(attester_slashing.clone()) + } + AttesterSlashingRef::Electra(attester_slashing) => { + AttesterSlashing::Electra(attester_slashing.clone()) + } + } + } + + pub fn attestation_1(&self) -> IndexedAttestationRef<'a, E> { + match self { + AttesterSlashingRef::Base(attester_slashing) => { + IndexedAttestationRef::Base(&attester_slashing.attestation_1) + } + AttesterSlashingRef::Electra(attester_slashing) => { + IndexedAttestationRef::Electra(&attester_slashing.attestation_1) + } + } + } + + pub fn attestation_2(&self) -> IndexedAttestationRef<'a, E> { + match self { + AttesterSlashingRef::Base(attester_slashing) => { + IndexedAttestationRef::Base(&attester_slashing.attestation_2) + } + AttesterSlashingRef::Electra(attester_slashing) => { + IndexedAttestationRef::Electra(&attester_slashing.attestation_2) + } + } + } +} + +impl AttesterSlashing { + pub fn attestation_1(&self) -> IndexedAttestationRef { + match self { + AttesterSlashing::Base(attester_slashing) => { + IndexedAttestationRef::Base(&attester_slashing.attestation_1) + } + AttesterSlashing::Electra(attester_slashing) => { + IndexedAttestationRef::Electra(&attester_slashing.attestation_1) + } + } + } + + pub fn attestation_2(&self) -> IndexedAttestationRef { + match self { + AttesterSlashing::Base(attester_slashing) => { + IndexedAttestationRef::Base(&attester_slashing.attestation_2) + } + AttesterSlashing::Electra(attester_slashing) => { + IndexedAttestationRef::Electra(&attester_slashing.attestation_2) + } + } + } } #[cfg(test)] @@ -34,5 +170,6 @@ mod tests { use super::*; use crate::*; - ssz_and_tree_hash_tests!(AttesterSlashing); + // TODO(electra): should this be done for both variants? + ssz_and_tree_hash_tests!(AttesterSlashingBase); } diff --git a/consensus/types/src/beacon_block.rs b/consensus/types/src/beacon_block.rs index dd1a12abba1..045cfb0ef5c 100644 --- a/consensus/types/src/beacon_block.rs +++ b/consensus/types/src/beacon_block.rs @@ -11,7 +11,7 @@ use test_random_derive::TestRandom; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; -use self::indexed_attestation::IndexedAttestationBase; +use self::indexed_attestation::{IndexedAttestationBase, IndexedAttestationElectra}; /// A block of the `BeaconChain`. #[superstruct( @@ -327,16 +327,15 @@ impl> BeaconBlockBase { message: header, signature: Signature::empty(), }; - let indexed_attestation: IndexedAttestation = - IndexedAttestation::Base(IndexedAttestationBase { - attesting_indices: VariableList::new(vec![ - 0_u64; - E::MaxValidatorsPerCommittee::to_usize() - ]) - .unwrap(), - data: AttestationData::default(), - signature: AggregateSignature::empty(), - }); + let indexed_attestation = IndexedAttestationBase { + attesting_indices: VariableList::new(vec![ + 0_u64; + E::MaxValidatorsPerCommittee::to_usize() + ]) + .unwrap(), + data: AttestationData::default(), + signature: AggregateSignature::empty(), + }; let deposit_data = DepositData { pubkey: PublicKeyBytes::empty(), @@ -349,17 +348,17 @@ impl> BeaconBlockBase { signed_header_2: signed_header, }; - let attester_slashing = AttesterSlashing { + let attester_slashing = AttesterSlashingBase { attestation_1: indexed_attestation.clone(), attestation_2: indexed_attestation, }; - let attestation: Attestation = Attestation::Base(AttestationBase { + let attestation = AttestationBase { aggregation_bits: BitList::with_capacity(E::MaxValidatorsPerCommittee::to_usize()) .unwrap(), data: AttestationData::default(), signature: AggregateSignature::empty(), - }); + }; let deposit = Deposit { proof: FixedVector::from_elem(Hash256::zero()), @@ -607,6 +606,42 @@ impl> BeaconBlockElectra /// Return a Electra block where the block has maximum size. pub fn full(spec: &ChainSpec) -> Self { let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec); + // TODO(electra): check this + let indexed_attestation: IndexedAttestationElectra = IndexedAttestationElectra { + attesting_indices: VariableList::new(vec![ + 0_u64; + E::MaxValidatorsPerCommitteePerSlot::to_usize( + ) + ]) + .unwrap(), + data: AttestationData::default(), + signature: AggregateSignature::empty(), + }; + // TODO(electra): fix this so we calculate this size correctly + let attester_slashings = vec![ + AttesterSlashingElectra { + attestation_1: indexed_attestation.clone(), + attestation_2: indexed_attestation, + }; + E::max_attester_slashings_electra() + ] + .into(); + // TODO(electra): check this + let attestation = AttestationElectra { + aggregation_bits: BitList::with_capacity( + E::MaxValidatorsPerCommitteePerSlot::to_usize(), + ) + .unwrap(), + data: AttestationData::default(), + signature: AggregateSignature::empty(), + // TODO(electra): does this actually allocate the size correctly? + committee_bits: BitVector::new(), + }; + let mut attestations_electra = vec![]; + for _ in 0..E::MaxAttestationsElectra::to_usize() { + attestations_electra.push(attestation.clone()); + } + let bls_to_execution_changes = vec![ SignedBlsToExecutionChange { message: BlsToExecutionChange { @@ -630,8 +665,8 @@ impl> BeaconBlockElectra state_root: Hash256::zero(), body: BeaconBlockBodyElectra { proposer_slashings: base_block.body.proposer_slashings, - attester_slashings: base_block.body.attester_slashings, - attestations: base_block.body.attestations, + attester_slashings, + attestations: attestations_electra.into(), deposits: base_block.body.deposits, voluntary_exits: base_block.body.voluntary_exits, bls_to_execution_changes, @@ -645,6 +680,7 @@ impl> BeaconBlockElectra graffiti: Graffiti::default(), execution_payload: Payload::Electra::default(), blob_kzg_commitments: VariableList::empty(), + consolidations: VariableList::empty(), }, } } @@ -675,6 +711,7 @@ impl> EmptyBlock for BeaconBlockElec execution_payload: Payload::Electra::default(), bls_to_execution_changes: VariableList::empty(), blob_kzg_commitments: VariableList::empty(), + consolidations: VariableList::empty(), }, } } diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index a55c16b80d5..9fdffd0736a 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -63,8 +63,21 @@ pub struct BeaconBlockBody = FullPay pub eth1_data: Eth1Data, pub graffiti: Graffiti, pub proposer_slashings: VariableList, - pub attester_slashings: VariableList, E::MaxAttesterSlashings>, - pub attestations: VariableList, E::MaxAttestations>, + #[superstruct( + only(Base, Altair, Merge, Capella, Deneb), + partial_getter(rename = "attester_slashings_base") + )] + pub attester_slashings: VariableList, E::MaxAttesterSlashings>, + #[superstruct(only(Electra), partial_getter(rename = "attester_slashings_electra"))] + pub attester_slashings: + VariableList, E::MaxAttesterSlashingsElectra>, + #[superstruct( + only(Base, Altair, Merge, Capella, Deneb), + partial_getter(rename = "attestations_base") + )] + pub attestations: VariableList, E::MaxAttestations>, + #[superstruct(only(Electra), partial_getter(rename = "attestations_electra"))] + pub attestations: VariableList, E::MaxAttestationsElectra>, pub deposits: VariableList, pub voluntary_exits: VariableList, #[superstruct(only(Altair, Merge, Capella, Deneb, Electra))] @@ -89,6 +102,8 @@ pub struct BeaconBlockBody = FullPay VariableList, #[superstruct(only(Deneb, Electra))] pub blob_kzg_commitments: KzgCommitments, + #[superstruct(only(Electra))] + pub consolidations: VariableList, #[superstruct(only(Base, Altair))] #[ssz(skip_serializing, skip_deserializing)] #[tree_hash(skip_hashing)] @@ -253,6 +268,99 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, self.blob_kzg_commitments() .map_or(false, |blobs| !blobs.is_empty()) } + + pub fn attestations_len(&self) -> usize { + match self { + Self::Base(body) => body.attestations.len(), + Self::Altair(body) => body.attestations.len(), + Self::Merge(body) => body.attestations.len(), + Self::Capella(body) => body.attestations.len(), + Self::Deneb(body) => body.attestations.len(), + Self::Electra(body) => body.attestations.len(), + } + } + + pub fn attester_slashings_len(&self) -> usize { + match self { + Self::Base(body) => body.attester_slashings.len(), + Self::Altair(body) => body.attester_slashings.len(), + Self::Merge(body) => body.attester_slashings.len(), + Self::Capella(body) => body.attester_slashings.len(), + Self::Deneb(body) => body.attester_slashings.len(), + Self::Electra(body) => body.attester_slashings.len(), + } + } + + pub fn attestations(&self) -> Box> + 'a> { + match self { + Self::Base(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)), + Self::Altair(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)), + Self::Merge(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)), + Self::Capella(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)), + Self::Deneb(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)), + Self::Electra(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)), + } + } + + pub fn attester_slashings(&self) -> Box> + 'a> { + match self { + Self::Base(body) => Box::new( + body.attester_slashings + .iter() + .map(AttesterSlashingRef::Base), + ), + Self::Altair(body) => Box::new( + body.attester_slashings + .iter() + .map(AttesterSlashingRef::Base), + ), + Self::Merge(body) => Box::new( + body.attester_slashings + .iter() + .map(AttesterSlashingRef::Base), + ), + Self::Capella(body) => Box::new( + body.attester_slashings + .iter() + .map(AttesterSlashingRef::Base), + ), + Self::Deneb(body) => Box::new( + body.attester_slashings + .iter() + .map(AttesterSlashingRef::Base), + ), + Self::Electra(body) => Box::new( + body.attester_slashings + .iter() + .map(AttesterSlashingRef::Electra), + ), + } + } +} + +impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRefMut<'a, E, Payload> { + pub fn attestations_mut( + &'a mut self, + ) -> Box> + 'a> { + match self { + Self::Base(body) => Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base)), + Self::Altair(body) => { + Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base)) + } + Self::Merge(body) => { + Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base)) + } + Self::Capella(body) => { + Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base)) + } + Self::Deneb(body) => { + Box::new(body.attestations.iter_mut().map(AttestationRefMut::Base)) + } + Self::Electra(body) => { + Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra)) + } + } + } } impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Payload> { @@ -553,6 +661,7 @@ impl From>> execution_payload: FullPayloadElectra { execution_payload }, bls_to_execution_changes, blob_kzg_commitments, + consolidations, } = body; ( @@ -571,6 +680,7 @@ impl From>> }, bls_to_execution_changes, blob_kzg_commitments: blob_kzg_commitments.clone(), + consolidations, }, Some(execution_payload), ) @@ -709,6 +819,7 @@ impl BeaconBlockBodyElectra> { execution_payload: FullPayloadElectra { execution_payload }, bls_to_execution_changes, blob_kzg_commitments, + consolidations, } = self; BeaconBlockBodyElectra { @@ -726,6 +837,7 @@ impl BeaconBlockBodyElectra> { }, bls_to_execution_changes: bls_to_execution_changes.clone(), blob_kzg_commitments: blob_kzg_commitments.clone(), + consolidations: consolidations.clone(), } } } @@ -759,13 +871,31 @@ impl BeaconBlockBody { _ => return Err(Error::IndexNotSupported(generalized_index)), }; + let attestations_root = match self { + BeaconBlockBody::Base(_) + | BeaconBlockBody::Altair(_) + | BeaconBlockBody::Merge(_) + | BeaconBlockBody::Capella(_) + | BeaconBlockBody::Deneb(_) => self.attestations_base()?.tree_hash_root(), + BeaconBlockBody::Electra(_) => self.attestations_electra()?.tree_hash_root(), + }; + + let attester_slashings_root = match self { + BeaconBlockBody::Base(_) + | BeaconBlockBody::Altair(_) + | BeaconBlockBody::Merge(_) + | BeaconBlockBody::Capella(_) + | BeaconBlockBody::Deneb(_) => self.attester_slashings_base()?.tree_hash_root(), + BeaconBlockBody::Electra(_) => self.attester_slashings_electra()?.tree_hash_root(), + }; + let mut leaves = vec![ self.randao_reveal().tree_hash_root(), self.eth1_data().tree_hash_root(), self.graffiti().tree_hash_root(), self.proposer_slashings().tree_hash_root(), - self.attester_slashings().tree_hash_root(), - self.attestations().tree_hash_root(), + attester_slashings_root, + attestations_root, self.deposits().tree_hash_root(), self.voluntary_exits().tree_hash_root(), ]; diff --git a/consensus/types/src/indexed_attestation.rs b/consensus/types/src/indexed_attestation.rs index 4477b236fad..d17d00a3fa8 100644 --- a/consensus/types/src/indexed_attestation.rs +++ b/consensus/types/src/indexed_attestation.rs @@ -69,15 +69,16 @@ impl IndexedAttestation { /// /// Spec v0.12.1 pub fn is_double_vote(&self, other: &Self) -> bool { - self.data().target.epoch == other.data().target.epoch && self.data() != other.data() + // reuse the ref implementation to ensure logic is the same + self.to_ref().is_double_vote(other.to_ref()) } /// Check if ``attestation_data_1`` surrounds ``attestation_data_2``. /// /// Spec v0.12.1 pub fn is_surround_vote(&self, other: &Self) -> bool { - self.data().source.epoch < other.data().source.epoch - && other.data().target.epoch < self.data().target.epoch + // reuse the ref implementation to ensure logic is the same + self.to_ref().is_surround_vote(other.to_ref()) } pub fn attesting_indices_len(&self) -> usize { @@ -116,6 +117,59 @@ impl IndexedAttestation { } } +impl<'a, E: EthSpec> IndexedAttestationRef<'a, E> { + pub fn is_double_vote(&self, other: Self) -> bool { + self.data().target.epoch == other.data().target.epoch && self.data() != other.data() + } + + pub fn is_surround_vote(&self, other: Self) -> bool { + self.data().source.epoch < other.data().source.epoch + && other.data().target.epoch < self.data().target.epoch + } + + pub fn attesting_indices_len(&self) -> usize { + match self { + IndexedAttestationRef::Base(att) => att.attesting_indices.len(), + IndexedAttestationRef::Electra(att) => att.attesting_indices.len(), + } + } + + pub fn attesting_indices_to_vec(&self) -> Vec { + match self { + IndexedAttestationRef::Base(att) => att.attesting_indices.to_vec(), + IndexedAttestationRef::Electra(att) => att.attesting_indices.to_vec(), + } + } + + pub fn attesting_indices_is_empty(&self) -> bool { + match self { + IndexedAttestationRef::Base(att) => att.attesting_indices.is_empty(), + IndexedAttestationRef::Electra(att) => att.attesting_indices.is_empty(), + } + } + + pub fn attesting_indices_iter(&self) -> Iter<'_, u64> { + match self { + IndexedAttestationRef::Base(att) => att.attesting_indices.iter(), + IndexedAttestationRef::Electra(att) => att.attesting_indices.iter(), + } + } + + pub fn attesting_indices_first(&self) -> Option<&u64> { + match self { + IndexedAttestationRef::Base(att) => att.attesting_indices.first(), + IndexedAttestationRef::Electra(att) => att.attesting_indices.first(), + } + } + + pub fn clone_as_indexed_attestation(self) -> IndexedAttestation { + match self { + IndexedAttestationRef::Base(att) => IndexedAttestation::Base(att.clone()), + IndexedAttestationRef::Electra(att) => IndexedAttestation::Electra(att.clone()), + } + } +} + impl Decode for IndexedAttestation { fn is_ssz_fixed_len() -> bool { false diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index feefbc48946..0349e776943 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -115,10 +115,16 @@ use ethereum_types::{H160, H256}; pub use crate::activation_queue::ActivationQueue; pub use crate::aggregate_and_proof::AggregateAndProof; -pub use crate::attestation::{Attestation, Error as AttestationError}; +pub use crate::attestation::{ + Attestation, AttestationBase, AttestationElectra, AttestationRef, AttestationRefMut, + Error as AttestationError, +}; pub use crate::attestation_data::AttestationData; pub use crate::attestation_duty::AttestationDuty; -pub use crate::attester_slashing::AttesterSlashing; +pub use crate::attester_slashing::{ + AttesterSlashing, AttesterSlashingBase, AttesterSlashingElectra, AttesterSlashingOnDisk, + AttesterSlashingRef, AttesterSlashingRefOnDisk, +}; pub use crate::beacon_block::{ BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockCapella, BeaconBlockDeneb, BeaconBlockElectra, BeaconBlockMerge, BeaconBlockRef, BeaconBlockRefMut, BlindedBeaconBlock, @@ -169,7 +175,9 @@ pub use crate::fork_name::{ForkName, InconsistentFork}; pub use crate::fork_versioned_response::{ForkVersionDeserialize, ForkVersionedResponse}; pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN}; pub use crate::historical_batch::HistoricalBatch; -pub use crate::indexed_attestation::IndexedAttestation; +pub use crate::indexed_attestation::{ + IndexedAttestation, IndexedAttestationBase, IndexedAttestationElectra, IndexedAttestationRef, +}; pub use crate::light_client_bootstrap::{ LightClientBootstrap, LightClientBootstrapAltair, LightClientBootstrapCapella, LightClientBootstrapDeneb, diff --git a/consensus/types/src/signed_beacon_block.rs b/consensus/types/src/signed_beacon_block.rs index da4ac392362..6c12277700a 100644 --- a/consensus/types/src/signed_beacon_block.rs +++ b/consensus/types/src/signed_beacon_block.rs @@ -498,6 +498,7 @@ impl SignedBeaconBlockElectra> { execution_payload: BlindedPayloadElectra { .. }, bls_to_execution_changes, blob_kzg_commitments, + consolidations, }, }, signature, @@ -521,6 +522,7 @@ impl SignedBeaconBlockElectra> { execution_payload: FullPayloadElectra { execution_payload }, bls_to_execution_changes, blob_kzg_commitments, + consolidations, }, }, signature, diff --git a/consensus/types/src/sync_committee_contribution.rs b/consensus/types/src/sync_committee_contribution.rs index 2c20daa9702..81de83ba3be 100644 --- a/consensus/types/src/sync_committee_contribution.rs +++ b/consensus/types/src/sync_committee_contribution.rs @@ -110,6 +110,12 @@ impl SlotData for SyncCommitteeContribution { } } +impl SlotData for &SyncCommitteeContribution { + fn get_slot(&self) -> Slot { + self.slot + } +} + impl SlotData for SyncContributionData { fn get_slot(&self) -> Slot { self.slot diff --git a/lcli/src/indexed_attestations.rs b/lcli/src/indexed_attestations.rs index 4c8b06a5088..8d932ba7600 100644 --- a/lcli/src/indexed_attestations.rs +++ b/lcli/src/indexed_attestations.rs @@ -35,7 +35,7 @@ pub fn run(matches: &ArgMatches) -> Result<(), String> { .into_iter() .map(|att| { let committee = state.get_beacon_committee(att.data().slot, att.data().index)?; - get_indexed_attestation(committee.committee, &att) + get_indexed_attestation(committee.committee, att.to_ref()) }) .collect::, _>>() .map_err(|e| format!("Error constructing indexed attestation: {:?}", e))?; diff --git a/lcli/src/transition_blocks.rs b/lcli/src/transition_blocks.rs index 77fd352829f..5c7231a9eda 100644 --- a/lcli/src/transition_blocks.rs +++ b/lcli/src/transition_blocks.rs @@ -390,7 +390,7 @@ fn do_transition( // Signature verification should prime the indexed attestation cache. assert_eq!( ctxt.num_cached_indexed_attestations(), - block.message().body().attestations().len() + block.message().body().attestations_len() ); } diff --git a/slasher/src/lib.rs b/slasher/src/lib.rs index 45cbef84f21..2577829577e 100644 --- a/slasher/src/lib.rs +++ b/slasher/src/lib.rs @@ -28,7 +28,8 @@ pub use database::{ }; pub use error::Error; -use types::{AttesterSlashing, EthSpec, IndexedAttestation, ProposerSlashing}; +use types::{AttesterSlashing, AttesterSlashingBase, AttesterSlashingElectra}; +use types::{EthSpec, IndexedAttestation, ProposerSlashing}; #[derive(Debug, PartialEq)] pub enum AttesterSlashingStatus { @@ -59,14 +60,48 @@ impl AttesterSlashingStatus { match self { NotSlashable => None, AlreadyDoubleVoted => None, - DoubleVote(existing) | SurroundedByExisting(existing) => Some(AttesterSlashing { - attestation_1: *existing, - attestation_2: new_attestation.clone(), - }), - SurroundsExisting(existing) => Some(AttesterSlashing { + DoubleVote(existing) | SurroundedByExisting(existing) => { + match (*existing, new_attestation) { + // TODO(electra) - determine when we would convert a Base attestation to Electra / how to handle mismatched attestations here + (IndexedAttestation::Base(existing_att), IndexedAttestation::Base(new_att)) => { + Some(AttesterSlashing::Base(AttesterSlashingBase { + attestation_1: existing_att, + attestation_2: new_att.clone(), + })) + } + ( + IndexedAttestation::Electra(existing_att), + IndexedAttestation::Electra(new_att), + ) => Some(AttesterSlashing::Electra(AttesterSlashingElectra { + attestation_1: existing_att, + attestation_2: new_att.clone(), + })), + _ => panic!("attestations must be of the same type"), + } + } + // TODO(electra): fix this once we superstruct IndexedAttestation (return the correct type) + SurroundsExisting(existing) => match (*existing, new_attestation) { + (IndexedAttestation::Base(existing_att), IndexedAttestation::Base(new_att)) => { + Some(AttesterSlashing::Base(AttesterSlashingBase { + attestation_1: new_att.clone(), + attestation_2: existing_att, + })) + } + ( + IndexedAttestation::Electra(existing_att), + IndexedAttestation::Electra(new_att), + ) => Some(AttesterSlashing::Electra(AttesterSlashingElectra { + attestation_1: new_att.clone(), + attestation_2: existing_att, + })), + _ => panic!("attestations must be of the same type"), + }, + /* + Some(AttesterSlashing::Base(AttesterSlashingBase { attestation_1: new_attestation.clone(), attestation_2: *existing, - }), + })), + */ } } } diff --git a/slasher/src/slasher.rs b/slasher/src/slasher.rs index 40b49fbbc66..fc8e8453c89 100644 --- a/slasher/src/slasher.rs +++ b/slasher/src/slasher.rs @@ -299,7 +299,7 @@ impl Slasher { self.log, "Found double-vote slashing"; "validator_index" => validator_index, - "epoch" => slashing.attestation_1.data().target.epoch, + "epoch" => slashing.attestation_1().data().target.epoch, ); slashings.insert(slashing); } diff --git a/slasher/src/test_utils.rs b/slasher/src/test_utils.rs index c46b2f39492..634b4d52113 100644 --- a/slasher/src/test_utils.rs +++ b/slasher/src/test_utils.rs @@ -1,8 +1,8 @@ use std::collections::HashSet; use types::{ indexed_attestation::IndexedAttestationBase, AggregateSignature, AttestationData, - AttesterSlashing, BeaconBlockHeader, Checkpoint, Epoch, Hash256, IndexedAttestation, - MainnetEthSpec, Signature, SignedBeaconBlockHeader, Slot, + AttesterSlashing, AttesterSlashingBase, AttesterSlashingElectra, BeaconBlockHeader, Checkpoint, + Epoch, Hash256, IndexedAttestation, MainnetEthSpec, Signature, SignedBeaconBlockHeader, Slot, }; pub type E = MainnetEthSpec; @@ -37,9 +37,21 @@ pub fn att_slashing( attestation_1: &IndexedAttestation, attestation_2: &IndexedAttestation, ) -> AttesterSlashing { - AttesterSlashing { - attestation_1: attestation_1.clone(), - attestation_2: attestation_2.clone(), + // TODO(electra): fix this one we superstruct IndexedAttestation (return the correct type) + match (attestation_1, attestation_2) { + (IndexedAttestation::Base(att1), IndexedAttestation::Base(att2)) => { + AttesterSlashing::Base(AttesterSlashingBase { + attestation_1: att1.clone(), + attestation_2: att2.clone(), + }) + } + (IndexedAttestation::Electra(att1), IndexedAttestation::Electra(att2)) => { + AttesterSlashing::Electra(AttesterSlashingElectra { + attestation_1: att1.clone(), + attestation_2: att2.clone(), + }) + } + _ => panic!("attestations must be of the same type"), } } @@ -61,8 +73,8 @@ pub fn slashed_validators_from_slashings(slashings: &HashSet slashings .iter() .flat_map(|slashing| { - let att1 = &slashing.attestation_1; - let att2 = &slashing.attestation_2; + let att1 = slashing.attestation_1(); + let att2 = slashing.attestation_2(); assert!( att1.is_double_vote(att2) || att1.is_surround_vote(att2), "invalid slashing: {:#?}", diff --git a/testing/ef_tests/src/cases/fork_choice.rs b/testing/ef_tests/src/cases/fork_choice.rs index f0749c3c7e4..bcc1c3aa25b 100644 --- a/testing/ef_tests/src/cases/fork_choice.rs +++ b/testing/ef_tests/src/cases/fork_choice.rs @@ -23,8 +23,9 @@ use state_processing::state_advance::complete_state_advance; use std::future::Future; use std::sync::Arc; use std::time::Duration; +use types::AttesterSlashingBase; use types::{ - Attestation, AttesterSlashing, BeaconBlock, BeaconState, BlobSidecar, BlobsList, Checkpoint, + Attestation, AttesterSlashingRef, BeaconBlock, BeaconState, BlobSidecar, BlobsList, Checkpoint, ExecutionBlockHash, Hash256, IndexedAttestation, KzgProof, ProposerPreparationData, SignedBeaconBlock, Slot, Uint256, }; @@ -131,8 +132,9 @@ pub struct ForkChoiceTest { pub anchor_state: BeaconState, pub anchor_block: BeaconBlock, #[allow(clippy::type_complexity)] + // TODO(electra): these tests will need to be updated to use new types pub steps: Vec< - Step, BlobsList, Attestation, AttesterSlashing, PowBlock>, + Step, BlobsList, Attestation, AttesterSlashingBase, PowBlock>, >, } @@ -249,7 +251,7 @@ impl Case for ForkChoiceTest { } => tester.process_block(block.clone(), blobs.clone(), proofs.clone(), *valid)?, Step::Attestation { attestation } => tester.process_attestation(attestation)?, Step::AttesterSlashing { attester_slashing } => { - tester.process_attester_slashing(attester_slashing) + tester.process_attester_slashing(AttesterSlashingRef::Base(attester_slashing)) } Step::PowBlock { pow_block } => tester.process_pow_block(pow_block), Step::OnPayloadInfo { @@ -591,7 +593,7 @@ impl Tester { .map_err(|e| Error::InternalError(format!("attestation import failed with {:?}", e))) } - pub fn process_attester_slashing(&self, attester_slashing: &AttesterSlashing) { + pub fn process_attester_slashing(&self, attester_slashing: AttesterSlashingRef) { self.harness .chain .canonical_head diff --git a/testing/ef_tests/src/cases/operations.rs b/testing/ef_tests/src/cases/operations.rs index a2f50896a57..15de13610fe 100644 --- a/testing/ef_tests/src/cases/operations.rs +++ b/testing/ef_tests/src/cases/operations.rs @@ -93,7 +93,7 @@ impl Operation for Attestation { match state { BeaconState::Base(_) => base::process_attestations( state, - &[self.clone()], + [self.clone().to_ref()].into_iter(), VerifySignatures::True, &mut ctxt, spec, @@ -106,7 +106,7 @@ impl Operation for Attestation { initialize_progressive_balances_cache(state, spec)?; altair_deneb::process_attestation( state, - self, + self.to_ref(), 0, &mut ctxt, VerifySignatures::True, @@ -123,7 +123,7 @@ impl Operation for AttesterSlashing { } fn decode(path: &Path, _fork_name: ForkName, _spec: &ChainSpec) -> Result { - ssz_decode_file(path) + Ok(Self::Base(ssz_decode_file(path)?)) } fn apply_to( @@ -136,7 +136,7 @@ impl Operation for AttesterSlashing { initialize_progressive_balances_cache(state, spec)?; process_attester_slashings( state, - &[self.clone()], + [self.clone().to_ref()].into_iter(), VerifySignatures::True, &mut ctxt, spec, diff --git a/validator_client/src/block_service.rs b/validator_client/src/block_service.rs index 06d484a52bb..cb3636135c1 100644 --- a/validator_client/src/block_service.rs +++ b/validator_client/src/block_service.rs @@ -919,8 +919,8 @@ impl SignedBlock { } pub fn num_attestations(&self) -> usize { match self { - SignedBlock::Full(block) => block.signed_block().message().body().attestations().len(), - SignedBlock::Blinded(block) => block.message().body().attestations().len(), + SignedBlock::Full(block) => block.signed_block().message().body().attestations_len(), + SignedBlock::Blinded(block) => block.message().body().attestations_len(), } } } diff --git a/watch/src/database/mod.rs b/watch/src/database/mod.rs index 59547c303ab..7d8c4c68cc2 100644 --- a/watch/src/database/mod.rs +++ b/watch/src/database/mod.rs @@ -141,7 +141,7 @@ pub fn insert_beacon_block( let parent_root = WatchHash::from_hash(block.parent_root()); let proposer_index = block_message.proposer_index() as i32; let graffiti = block_message.body().graffiti().as_utf8_lossy(); - let attestation_count = block_message.body().attestations().len() as i32; + let attestation_count = block_message.body().attestations_len() as i32; let full_payload = block_message.execution_payload().ok();