Skip to content

Commit

Permalink
WIP: Add single attestation as a variant to combined attestation
Browse files Browse the repository at this point in the history
  • Loading branch information
povi committed Feb 19, 2025
1 parent a850e35 commit 9426f51
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 71 deletions.
3 changes: 3 additions & 0 deletions factory/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,9 @@ fn block<P: Preset>(
.partition_map(|attestation| match attestation {
Attestation::Phase0(attestation) => Either::Left(attestation),
Attestation::Electra(attestation) => Either::Right(attestation),
Attestation::Single(_) => {
unreachable!("block should not contain SingleAttestation type attestations")
}
});

ensure!(
Expand Down
2 changes: 1 addition & 1 deletion fork_choice_store/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1279,7 +1279,7 @@ impl<P: Preset> Store<P> {

let index = aggregate
.committee_bits()
.and_then(|bits| misc::get_committee_indices::<P>(*bits).next())
.and_then(|bits| misc::get_committee_indices::<P>(bits).next())
.unwrap_or(index);

let committee = accessors::beacon_committee(&target_state, slot, index)?;
Expand Down
3 changes: 3 additions & 0 deletions fork_choice_store/src/supersets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ impl<P: Preset> MultiPhaseAggregateAndProofSets<P> {
self.electra_supersets
.check(&data, &attestation.aggregation_bits)
}
Attestation::Single(_) => {
unreachable!("single attestations should not be validated with methods meant for aggregate and proofs only")
}
}
}

Expand Down
1 change: 1 addition & 0 deletions helper_functions/src/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,7 @@ pub fn committee_index<P: Preset>(attestation: &Attestation<P>) -> CommitteeInde
Attestation::Electra(attestation) => get_committee_indices::<P>(attestation.committee_bits)
.next()
.unwrap_or_default(),
Attestation::Single(attestation) => attestation.committee_index,
}
}

Expand Down
78 changes: 22 additions & 56 deletions operation_pools/src/attestation_agg_pool/conversion.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use anyhow::{ensure, Error as AnyhowError, Result};
use anyhow::{Error as AnyhowError, Result};
use eth1_api::{ApiController, RealController};
use fork_choice_control::Wait;
use helper_functions::{accessors, misc};
use ssz::{BitList, BitVector, ReadError};
use ssz::ReadError;
use thiserror::Error;
use typenum::Unsigned as _;
use types::{
combined::Attestation,
electra::containers::{Attestation as ElectraAttestation, SingleAttestation},
Expand All @@ -20,7 +19,8 @@ pub enum Error {
InvalidAggregationBits(#[source] ReadError),
}

pub fn convert_attestation_for_pool<P: Preset>(
pub fn convert_attestation_for_pool<P: Preset, W: Wait>(
controller: &ApiController<P, W>,
attestation: Attestation<P>,
) -> Result<Phase0Attestation<P>> {
let attestation = match attestation {
Expand All @@ -47,6 +47,16 @@ pub fn convert_attestation_for_pool<P: Preset>(
signature,
}
}
Attestation::Single(attestation) => {
let slot = attestation.data.slot;
let state = controller
.state_at_slot(slot)?
.ok_or_else(|| AnyhowError::msg(format!("state not available at slot: {slot:?}")))?
.value;
let committee = accessors::beacon_committee(&state, slot, attestation.committee_index)?;

attestation.try_into_phase0_attestation(committee)?
}
};

Ok(attestation)
Expand All @@ -55,24 +65,7 @@ pub fn convert_attestation_for_pool<P: Preset>(
pub fn convert_to_electra_attestation<P: Preset>(
attestation: Phase0Attestation<P>,
) -> Result<ElectraAttestation<P>> {
let Phase0Attestation {
aggregation_bits,
data,
signature,
} = attestation;

let aggregation_bits: Vec<u8> = aggregation_bits.into();
let mut committee_bits = BitVector::default();
committee_bits.set(data.index.try_into()?, true);

Ok(ElectraAttestation {
aggregation_bits: aggregation_bits
.try_into()
.map_err(Error::InvalidAggregationBits)?,
data: AttestationData { index: 0, ..data },
committee_bits,
signature,
})
attestation.try_into()
}

// TODO(feature/electra): properly refactor attestations
Expand Down Expand Up @@ -116,42 +109,15 @@ pub fn try_convert_to_attestation<P: Preset, W: Wait>(
controller: &ApiController<P, W>,
single_attestation: SingleAttestation,
) -> Result<Attestation<P>> {
let SingleAttestation {
committee_index,
attester_index,
data,
signature,
} = single_attestation;

ensure!(
committee_index < P::MaxCommitteesPerSlot::U64,
AnyhowError::msg(format!("invalid committee_index: {committee_index}"))
);

let mut committee_bits = BitVector::default();
let index = committee_index.try_into()?;
committee_bits.set(index, true);

let slot = single_attestation.data.slot;
let state = controller
.state_at_slot(data.slot)?
.ok_or_else(|| AnyhowError::msg(format!("state not available at slot: {:?}", data.slot)))?
.state_at_slot(slot)?
.ok_or_else(|| AnyhowError::msg(format!("state not available at slot: {slot:?}")))?
.value;

let committee = accessors::beacon_committee(&state, data.slot, committee_index)?;

let mut aggregation_bits = BitList::with_length(committee.len());

let position = committee
.into_iter()
.position(|index| index == attester_index)
.ok_or_else(|| AnyhowError::msg(format!("{attester_index} not in committee")))?;
let committee = accessors::beacon_committee(&state, slot, single_attestation.committee_index)?;

aggregation_bits.set(position, true);

Ok(Attestation::Electra(ElectraAttestation {
aggregation_bits,
data,
signature,
committee_bits,
}))
Ok(Attestation::Electra(
single_attestation.try_into_electra_attestation(committee)?,
))
}
2 changes: 1 addition & 1 deletion operation_pools/src/attestation_agg_pool/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ impl<P: Preset, W: Wait> Manager<P, W> {
}

pub fn insert_attestation(&self, wait_group: W, attestation: &CombinedAttestation<P>) {
match convert_attestation_for_pool((*attestation).clone()) {
match convert_attestation_for_pool(&self.controller, (*attestation).clone()) {
Ok(attestation) => {
self.spawn_detached(InsertAttestationTask {
wait_group,
Expand Down
27 changes: 17 additions & 10 deletions types/src/combined.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,7 @@ impl<'list, P: Preset> IntoIterator for &'list AttestingIndices<P> {
pub enum Attestation<P: Preset> {
Phase0(Phase0Attestation<P>),
Electra(ElectraAttestation<P>),
Single(ElectraSingleAttestation),
}

impl<P: Preset> SszSize for Attestation<P> {
Expand Down Expand Up @@ -1477,6 +1478,7 @@ impl<P: Preset> SszWrite for Attestation<P> {
match self {
Self::Phase0(attestation) => attestation.write_variable(bytes),
Self::Electra(attestation) => attestation.write_variable(bytes),
Self::Single(attestation) => attestation.write_variable(bytes),
}
}
}
Expand All @@ -1488,6 +1490,7 @@ impl<P: Preset> SszHash for Attestation<P> {
match self {
Self::Phase0(attestation) => attestation.hash_tree_root(),
Self::Electra(attestation) => attestation.hash_tree_root(),
Self::Single(attestation) => attestation.hash_tree_root(),
}
}
}
Expand All @@ -1497,39 +1500,43 @@ impl<P: Preset> Attestation<P> {
match self {
Self::Phase0(attestation) => attestation.data,
Self::Electra(attestation) => attestation.data,
Self::Single(attestation) => attestation.data,
}
}

pub const fn committee_bits(&self) -> Option<&BitVector<P::MaxCommitteesPerSlot>> {
pub fn committee_bits(&self) -> Option<BitVector<P::MaxCommitteesPerSlot>> {
match self {
Self::Phase0(_) => None,
Self::Electra(attestation) => Some(&attestation.committee_bits),
Self::Electra(attestation) => Some(attestation.committee_bits),
Self::Single(attestation) => {
let committee_index: usize = attestation
.committee_index
.try_into()
.expect("committee index should fit in usize");
let mut bits = BitVector::default();
bits.set(committee_index, true);
Some(bits)
}
}
}

pub fn count_aggregation_bits(&self) -> usize {
match self {
Self::Phase0(attestation) => attestation.aggregation_bits.count_ones(),
Self::Electra(attestation) => attestation.aggregation_bits.count_ones(),
Self::Single(_) => 1,
}
}

#[cfg(test)]
pub const fn phase(&self) -> Phase {
match self {
Self::Phase0(_) => Phase::Phase0,
Self::Electra(_) => Phase::Electra,
Self::Electra(_) | Self::Single(_) => Phase::Electra,
}
}
}

#[derive(Clone, PartialEq, Eq, Debug, From, Deserialize)]
#[serde(bound = "", untagged)]
pub enum SingleAttestation<P: Preset> {
Phase0(Phase0Attestation<P>),
Electra(ElectraSingleAttestation),
}

#[derive(Clone, PartialEq, Eq, Debug, From, Serialize)]
#[serde(bound = "", untagged)]
pub enum AttesterSlashing<P: Preset> {
Expand Down
121 changes: 118 additions & 3 deletions types/src/electra/container_impls.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
use ssz::ContiguousList;
use anyhow::{ensure, Error as AnyhowError, Result};
use ssz::{BitList, BitVector, ContiguousList, ReadError};
use thiserror::Error;
use typenum::Unsigned as _;

use crate::{
cache::IndexSlice,
deneb::{
containers::{ExecutionPayload, ExecutionPayloadHeader},
primitives::KzgCommitment,
},
electra::containers::{
BeaconBlock, BeaconBlockBody, BlindedBeaconBlock, BlindedBeaconBlockBody, ExecutionRequests,
Attestation, BeaconBlock, BeaconBlockBody, BlindedBeaconBlock, BlindedBeaconBlockBody,
ExecutionRequests, SingleAttestation,
},
phase0::{
containers::{Attestation as Phase0Attestation, AttestationData},
primitives::H256,
},
phase0::primitives::H256,
preset::Preset,
};

#[derive(Debug, Error)]
pub enum AttestationError {
#[error("invalid aggregation bits for conversion")]
InvalidAggregationBits(#[source] ReadError),
}

impl<P: Preset> BeaconBlock<P> {
pub fn with_execution_payload_header_and_kzg_commitments(
self,
Expand Down Expand Up @@ -124,3 +138,104 @@ impl<P: Preset> BlindedBeaconBlock<P> {
self
}
}

impl<P: Preset> TryFrom<Phase0Attestation<P>> for Attestation<P> {
type Error = AnyhowError;

fn try_from(phase0_attestation: Phase0Attestation<P>) -> Result<Self> {
let Phase0Attestation {
aggregation_bits,
data,
signature,
} = phase0_attestation;

let aggregation_bits: Vec<u8> = aggregation_bits.into();
let mut committee_bits = BitVector::default();
committee_bits.set(data.index.try_into()?, true);

Ok(Self {
aggregation_bits: aggregation_bits
.try_into()
.map_err(AttestationError::InvalidAggregationBits)?,
data: AttestationData { index: 0, ..data },
committee_bits,
signature,
})
}
}

impl SingleAttestation {
pub fn try_into_electra_attestation<P: Preset>(
self,
beacon_committee: IndexSlice,
) -> Result<Attestation<P>> {
let Self {
committee_index,
attester_index,
data,
signature,
} = self;

ensure!(
committee_index < P::MaxCommitteesPerSlot::U64,
AnyhowError::msg(format!("invalid committee_index: {committee_index}"))
);

let mut committee_bits = BitVector::default();
let index = committee_index.try_into()?;
committee_bits.set(index, true);

let mut aggregation_bits = BitList::with_length(beacon_committee.len());

let position = beacon_committee
.into_iter()
.position(|index| index == attester_index)
.ok_or_else(|| AnyhowError::msg(format!("{attester_index} not in committee")))?;

aggregation_bits.set(position, true);

Ok(Attestation {
aggregation_bits,
data,
signature,
committee_bits,
})
}

pub fn try_into_phase0_attestation<P: Preset>(
self,
beacon_committee: IndexSlice,
) -> Result<Phase0Attestation<P>> {
let Self {
committee_index,
attester_index,
data,
signature,
} = self;

let data = AttestationData {
index: committee_index,
..data
};

ensure!(
committee_index < P::MaxCommitteesPerSlot::U64,
AnyhowError::msg(format!("invalid committee_index: {committee_index}"))
);

let mut aggregation_bits = BitList::with_length(beacon_committee.len());

let position = beacon_committee
.into_iter()
.position(|index| index == attester_index)
.ok_or_else(|| AnyhowError::msg(format!("{attester_index} not in committee")))?;

aggregation_bits.set(position, true);

Ok(Phase0Attestation {
aggregation_bits,
data,
signature,
})
}
}

0 comments on commit 9426f51

Please sign in to comment.