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 21, 2025
1 parent fb0b005 commit b151709
Show file tree
Hide file tree
Showing 15 changed files with 241 additions and 125 deletions.
16 changes: 16 additions & 0 deletions attestation_verifier/src/attestation_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use std_ext::ArcExt as _;
use types::{
combined::{Attestation, BeaconState, SignedAggregateAndProof},
config::Config,
electra::containers::IndexedAttestation as ElectraIndexedAttestation,
preset::Preset,
};

Expand Down Expand Up @@ -469,6 +470,21 @@ fn attestation_batch_triples<'a, P: Preset>(
&mut triple,
)?;

triple
}
Attestation::Single(attestation) => {
let indexed_attestation: ElectraIndexedAttestation<P> =
(*attestation).try_into()?;

let mut triple = Triple::default();

predicates::validate_constructed_indexed_attestation(
config,
state,
&indexed_attestation,
&mut triple,
)?;

triple
}
};
Expand Down
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
20 changes: 19 additions & 1 deletion fork_choice_store/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use types::{
containers::{BlobIdentifier, BlobSidecar},
primitives::{BlobIndex, KzgCommitment},
},
electra::containers::IndexedAttestation as ElectraIndexedAttestation,
nonstandard::{BlobSidecarWithId, PayloadStatus, Phase, WithStatus},
phase0::{
consts::{ATTESTATION_PROPAGATION_SLOT_RANGE, GENESIS_EPOCH, GENESIS_SLOT},
Expand Down Expand Up @@ -1279,7 +1280,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 Expand Up @@ -1644,6 +1645,23 @@ impl<P: Preset> Store<P> {
)?;
}

Ok(AttestingIndices::Electra(
indexed_attestation.attesting_indices,
))
}
Attestation::Single(attestation) => {
let indexed_attestation: ElectraIndexedAttestation<P> =
(*attestation).try_into()?;

if validate_indexed {
predicates::validate_constructed_indexed_attestation(
&self.chain_config,
target_state,
&indexed_attestation,
SingleVerifier,
)?;
}

Ok(AttestingIndices::Electra(
indexed_attestation.attesting_indices,
))
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
14 changes: 1 addition & 13 deletions http_api/src/extractors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use serde_with::{As, DisplayFromStr};
use ssz::SszRead;
use types::{
altair::containers::SignedContributionAndProof,
combined::{Attestation, AttesterSlashing, SignedAggregateAndProof, SingleAttestation},
combined::{Attestation, AttesterSlashing, SignedAggregateAndProof},
config::Config,
nonstandard::Phase,
phase0::{
Expand Down Expand Up @@ -233,18 +233,6 @@ impl<S: Sync, P: Preset> FromRequest<S, Body> for EthJson<Vec<Arc<Attestation<P>
}
}

impl<S: Sync, P: Preset> FromRequest<S, Body> for EthJson<Vec<SingleAttestation<P>>> {
type Rejection = Error;

async fn from_request(request: Request<Body>, _state: &S) -> Result<Self, Self::Rejection> {
request
.extract()
.await
.map(|Json(attestation)| Self(attestation))
.map_err(Error::InvalidJsonBody)
}
}

impl<S: Sync> FromRequest<S, Body> for EthJson<Vec<Value>> {
type Rejection = Error;

Expand Down
18 changes: 2 additions & 16 deletions http_api/src/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ use types::{
capella::containers::{SignedBlsToExecutionChange, Withdrawal},
combined::{
Attestation, AttesterSlashing, BeaconBlock, BeaconState, SignedAggregateAndProof,
SignedBeaconBlock, SignedBlindedBeaconBlock, SingleAttestation,
SignedBeaconBlock, SignedBlindedBeaconBlock,
},
config::Config as ChainConfig,
deneb::{
Expand Down Expand Up @@ -1554,22 +1554,8 @@ pub async fn submit_pool_attestations<P: Preset, W: Wait>(
pub async fn submit_pool_attestations_v2<P: Preset, W: Wait>(
State(controller): State<ApiController<P, W>>,
State(api_to_p2p_tx): State<UnboundedSender<ApiToP2p<P>>>,
EthJson(attestations): EthJson<Vec<SingleAttestation<P>>>,
EthJson(attestations): EthJson<Vec<Arc<Attestation<P>>>>,
) -> Result<(), Error> {
let attestations = attestations
.into_iter()
.map(|attestation| {
let attestation = match attestation {
SingleAttestation::Phase0(attestatation) => Attestation::Phase0(attestatation),
SingleAttestation::Electra(single_attestation) => {
operation_pools::try_convert_to_attestation(&controller, single_attestation)?
}
};

Ok(Arc::new(attestation))
})
.collect::<Result<Vec<_>>>()?;

submit_attestations_to_pool(controller, api_to_p2p_tx, attestations).await
}

Expand Down
9 changes: 9 additions & 0 deletions liveness_tracker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use prometheus_metrics::Metrics;
use types::{
altair::containers::SyncCommitteeMessage,
combined::{Attestation, BeaconState, SignedBeaconBlock},
electra::containers::IndexedAttestation as ElectraIndexedAttestation,
phase0::primitives::{Epoch, ValidatorIndex},
preset::Preset,
traits::SignedBeaconBlock as _,
Expand Down Expand Up @@ -152,6 +153,14 @@ impl<P: Preset, W: Wait> LivenessTracker<P, W> {
Attestation::Electra(attestation) => {
let indexed_attestation = electra::get_indexed_attestation(state, attestation)?;

for validator_index in indexed_attestation.attesting_indices {
self.set(epoch, validator_index)?;
}
}
Attestation::Single(attestation) => {
let indexed_attestation: ElectraIndexedAttestation<P> =
(*attestation).try_into()?;

for validator_index in indexed_attestation.attesting_indices {
self.set(epoch, validator_index)?;
}
Expand Down
81 changes: 15 additions & 66 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 @@ -111,47 +104,3 @@ pub fn try_convert_to_single_attestation<P: Preset>(
signature: *signature,
})
}

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 state = controller
.state_at_slot(data.slot)?
.ok_or_else(|| AnyhowError::msg(format!("state not available at slot: {:?}", data.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")))?;

aggregation_bits.set(position, true);

Ok(Attestation::Electra(ElectraAttestation {
aggregation_bits,
data,
signature,
committee_bits,
}))
}
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 @@ -150,7 +150,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
4 changes: 2 additions & 2 deletions operation_pools/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pub use crate::{
attestation_agg_pool::{
convert_attestation_for_pool, convert_to_electra_attestation, try_convert_to_attestation,
convert_attestation_for_pool, convert_to_electra_attestation,
try_convert_to_single_attestation, AttestationPacker, Manager as AttestationAggPool,
},
bls_to_execution_change_pool::{
Expand All @@ -15,7 +15,7 @@ pub use crate::{
mod attestation_agg_pool {
pub use attestation_packer::AttestationPacker;
pub use conversion::{
convert_attestation_for_pool, convert_to_electra_attestation, try_convert_to_attestation,
convert_attestation_for_pool, convert_to_electra_attestation,
try_convert_to_single_attestation,
};
pub use manager::Manager;
Expand Down
17 changes: 7 additions & 10 deletions p2p/src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,12 @@ impl<P: Preset> Network<P> {
single_attestation,
));
}
Attestation::Single(single_attestation) => {
self.publish(PubsubMessage::SingleAttestation(
subnet_id,
*single_attestation,
));
}
}
}

Expand Down Expand Up @@ -1439,16 +1445,7 @@ impl<P: Preset> Network<P> {
{single_attestation:?} from {source}",
);

let attestation = match operation_pools::try_convert_to_attestation(
&self.controller,
single_attestation,
) {
Ok(attestation) => Arc::new(attestation),
Err(error) => {
warn!("cannot convert single attestation to attestation: {error:?}");
return;
}
};
let attestation = Arc::new(Attestation::Single(single_attestation));

if let Some(network_to_slasher_tx) = &self.channels.network_to_slasher_tx {
P2pToSlasher::Attestation(attestation.clone_arc()).send(network_to_slasher_tx);
Expand Down
3 changes: 2 additions & 1 deletion slasher/src/slasher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ impl<P: Preset> Slasher<P> {
fn process_attestation(&self, attestation: &Attestation<P>) -> Result<()> {
let attestation = match attestation {
Attestation::Phase0(attestation) => attestation,
Attestation::Electra(_) => return Ok(()),
// TODO:
Attestation::Electra(_) | Attestation::Single(_) => return Ok(()),
};

let target = attestation.data.target;
Expand Down
Loading

0 comments on commit b151709

Please sign in to comment.