diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 522c6414ea..c37bd5e16e 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -30,6 +30,7 @@ pub use reqwest::{StatusCode, Url}; pub use sensitive_url::{SensitiveError, SensitiveUrl}; use serde::{de::DeserializeOwned, Serialize}; use ssz::Encode; +use types::attestation::SingleAttestation; use std::fmt; use std::future::Future; use std::path::PathBuf; @@ -1316,7 +1317,7 @@ impl BeaconNodeHttpClient { /// `POST v2/beacon/pool/attestations` pub async fn post_beacon_pool_attestations_v2( &self, - attestations: &[Attestation], + attestations: &[SingleAttestation], fork_name: ForkName, ) -> Result<(), Error> { let mut path = self.eth_path(V2)?; diff --git a/consensus/types/src/attestation.rs b/consensus/types/src/attestation.rs index 59536cc6bf..144ce0e105 100644 --- a/consensus/types/src/attestation.rs +++ b/consensus/types/src/attestation.rs @@ -232,6 +232,13 @@ impl Attestation { Attestation::Electra(att) => att.aggregation_bits.get(index), } } + + pub fn to_single_attestation_with_attester_index(&self, attester_index: usize) -> Result { + match self { + Self::Base(_) => Err(Error::IncorrectStateVariant), + Self::Electra(attn) => attn.to_single_attestation_with_attester_index(attester_index) + } + } } impl AttestationRef<'_, E> { @@ -288,6 +295,14 @@ impl AttestationElectra { self.get_committee_indices().first().cloned() } + pub fn get_aggregation_bits(&self) -> Vec { + self.aggregation_bits + .iter() + .enumerate() + .filter_map(|(index, bit)| if bit { Some(index as u64) } else { None }) + .collect() + } + pub fn get_committee_indices(&self) -> Vec { self.committee_bits .iter() @@ -352,12 +367,26 @@ impl AttestationElectra { } } - pub fn get_aggregation_bits(&self) -> Vec { - self.aggregation_bits - .iter() - .enumerate() - .filter_map(|(index, bit)| if bit { Some(index as u64) } else { None }) - .collect() + pub fn to_single_attestation_with_attester_index( + &self, + attester_index: usize + ) -> Result { + let committee_indices = self.get_committee_indices(); + + if committee_indices.len() != 1 { + return Err(Error::InvalidCommitteeLength); + } + + let committee_index = *committee_indices + .first() + .ok_or(Error::InvalidCommitteeIndex)?; + + Ok(SingleAttestation { + committee_index: committee_index as usize, + attester_index, + data: self.data.clone(), + signature: self.signature.clone() + }) } pub fn to_single_attestation( @@ -493,71 +522,6 @@ impl SlotData for AttestationRef<'_, E> { } } -#[derive( - Debug, - Clone, - Serialize, - Deserialize, - Decode, - Encode, - TestRandom, - Derivative, - arbitrary::Arbitrary, - TreeHash, - PartialEq, -)] -pub struct SingleAttestation { - pub committee_index: usize, - pub attester_index: usize, - pub data: AttestationData, - pub signature: AggregateSignature, -} - -impl SingleAttestation { - pub fn to_attestation( - &self, - committees: &[BeaconCommittee], - ) -> Result, Error> { - let beacon_committee = committees - .get(self.committee_index) - .ok_or(Error::InvalidAggregationBit)?; - let aggregation_bits = beacon_committee - .committee - .iter() - .enumerate() - .filter_map(|(i, &validator_index)| { - if self.attester_index == validator_index { - return Some(i); - } - None - }) - .collect::>(); - - if aggregation_bits.len() != 1 { - return Err(Error::InvalidAggregationBit); - } - - let aggregation_bit = aggregation_bits.first().unwrap(); - - let mut committee_bits: BitVector = BitVector::default(); - committee_bits - .set(self.committee_index, true) - .map_err(|_| Error::InvalidCommitteeIndex)?; - - let mut aggregation_bits = BitList::with_capacity(beacon_committee.committee.len()) - .map_err(|_| Error::InvalidCommitteeLength)?; - - aggregation_bits.set(*aggregation_bit, true)?; - - Ok(Attestation::Electra(AttestationElectra { - aggregation_bits, - committee_bits, - data: self.data.clone(), - signature: self.signature.clone(), - })) - } -} - #[derive(Debug, Clone, Encode, Decode, PartialEq)] #[ssz(enum_behaviour = "union")] pub enum AttestationOnDisk { @@ -657,6 +621,99 @@ impl ForkVersionDeserialize for Vec> { } } +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Decode, + Encode, + TestRandom, + Derivative, + arbitrary::Arbitrary, + TreeHash, + PartialEq, +)] +pub struct SingleAttestation { + pub committee_index: usize, + pub attester_index: usize, + pub data: AttestationData, + pub signature: AggregateSignature, +} + +impl SingleAttestation { + /// Produces a `SingleAttestation` with empty signature and empty attester index. + /// ONLY USE IN ELECTRA + pub fn empty_for_signing( + committee_index: usize, + slot: Slot, + beacon_block_root: Hash256, + source: Checkpoint, + target: Checkpoint, + ) -> Self { + Self { + committee_index, + attester_index: 0, + data: AttestationData { + slot, + index: 0, + beacon_block_root, + source, + target, + }, + signature: AggregateSignature::infinity(), + } + } + + pub fn add_signature(&mut self, signature: &AggregateSignature, committee_position: usize) { + self.attester_index = committee_position; + self.signature = signature.clone(); + } + + pub fn to_attestation( + &self, + committees: &[BeaconCommittee], + ) -> Result, Error> { + let beacon_committee = committees + .get(self.committee_index) + .ok_or(Error::InvalidAggregationBit)?; + let aggregation_bits = beacon_committee + .committee + .iter() + .enumerate() + .filter_map(|(i, &validator_index)| { + if self.attester_index == validator_index { + return Some(i); + } + None + }) + .collect::>(); + + if aggregation_bits.len() != 1 { + return Err(Error::InvalidAggregationBit); + } + + let aggregation_bit = aggregation_bits.first().unwrap(); + + let mut committee_bits: BitVector = BitVector::default(); + committee_bits + .set(self.committee_index, true) + .map_err(|_| Error::InvalidCommitteeIndex)?; + + let mut aggregation_bits = BitList::with_capacity(beacon_committee.committee.len()) + .map_err(|_| Error::InvalidCommitteeLength)?; + + aggregation_bits.set(*aggregation_bit, true)?; + + Ok(Attestation::Electra(AttestationElectra { + aggregation_bits, + committee_bits, + data: self.data.clone(), + signature: self.signature.clone(), + })) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/validator_client/validator_services/src/attestation_service.rs b/validator_client/validator_services/src/attestation_service.rs index e31ad4f661..b8b33416c7 100644 --- a/validator_client/validator_services/src/attestation_service.rs +++ b/validator_client/validator_services/src/attestation_service.rs @@ -457,8 +457,11 @@ impl AttestationService { &[validator_metrics::ATTESTATIONS_HTTP_POST], ); if fork_name.electra_enabled() { + let single_attestations = attestations.iter().zip(validator_indices).filter_map(|(a, i)| { + a.to_single_attestation_with_attester_index(*i as usize).ok() + }).collect::>(); beacon_node - .post_beacon_pool_attestations_v2(attestations, fork_name) + .post_beacon_pool_attestations_v2::(&single_attestations, fork_name) .await } else { beacon_node