mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 04:37:13 +00:00
Single attestation "Full" implementation (#7444)
#6970 This allows for us to receive `SingleAttestation` over gossip and process it without converting. There is still a conversion to `Attestation` as a final step in the attestation verification process, but by then the `SingleAttestation` is fully verified. I've also fully removed the `submitPoolAttestationsV1` endpoint as its been deprecated I've also pre-emptively deprecated supporting `Attestation` in `submitPoolAttestationsV2` endpoint. See here for more info: https://github.com/ethereum/beacon-APIs/pull/531 I tried to the minimize the diff here by only making the "required" changes. There are some unnecessary complexities with the way we manage the different attestation verification wrapper types. We could probably consolidate this to one wrapper type and refactor this even further. We could leave that to a separate PR if we feel like cleaning things up in the future. Note that I've also updated the test harness to always submit `SingleAttestation` regardless of fork variant. I don't see a problem in that approach and it allows us to delete more code :)
This commit is contained in:
@@ -14,7 +14,6 @@ use beacon_chain::{
|
||||
light_client_finality_update_verification::Error as LightClientFinalityUpdateError,
|
||||
light_client_optimistic_update_verification::Error as LightClientOptimisticUpdateError,
|
||||
observed_operations::ObservationOutcome,
|
||||
single_attestation::single_attestation_to_attestation,
|
||||
sync_committee_verification::{self, Error as SyncCommitteeError},
|
||||
validator_monitor::{get_block_delay_ms, get_slot_delay_ms},
|
||||
AvailabilityProcessingStatus, BeaconChainError, BeaconChainTypes, BlockError, ForkChoiceError,
|
||||
@@ -84,8 +83,8 @@ impl<T: BeaconChainTypes> VerifiedAttestation<T> for VerifiedUnaggregate<T> {
|
||||
}
|
||||
|
||||
/// An attestation that failed validation by the `BeaconChain`.
|
||||
struct RejectedUnaggregate<E: EthSpec> {
|
||||
attestation: Box<Attestation<E>>,
|
||||
struct RejectedUnaggregate {
|
||||
attestation: Box<SingleAttestation>,
|
||||
error: AttnError,
|
||||
}
|
||||
|
||||
@@ -126,16 +125,11 @@ struct RejectedAggregate<E: EthSpec> {
|
||||
/// Data for an aggregated or unaggregated attestation that failed verification.
|
||||
enum FailedAtt<E: EthSpec> {
|
||||
Unaggregate {
|
||||
attestation: Box<Attestation<E>>,
|
||||
attestation: Box<SingleAttestation>,
|
||||
subnet_id: SubnetId,
|
||||
should_import: bool,
|
||||
seen_timestamp: Duration,
|
||||
},
|
||||
// This variant is just a dummy variant for now, as SingleAttestation reprocessing is handled
|
||||
// separately.
|
||||
SingleUnaggregate {
|
||||
attestation: Box<SingleAttestation>,
|
||||
},
|
||||
Aggregate {
|
||||
attestation: Box<SignedAggregateAndProof<E>>,
|
||||
seen_timestamp: Duration,
|
||||
@@ -150,15 +144,13 @@ impl<E: EthSpec> FailedAtt<E> {
|
||||
pub fn kind(&self) -> &'static str {
|
||||
match self {
|
||||
FailedAtt::Unaggregate { .. } => "unaggregated",
|
||||
FailedAtt::SingleUnaggregate { .. } => "unaggregated",
|
||||
FailedAtt::Aggregate { .. } => "aggregated",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attestation_data(&self) -> &AttestationData {
|
||||
match self {
|
||||
FailedAtt::Unaggregate { attestation, .. } => attestation.data(),
|
||||
FailedAtt::SingleUnaggregate { attestation, .. } => &attestation.data,
|
||||
FailedAtt::Unaggregate { attestation, .. } => &attestation.data,
|
||||
FailedAtt::Aggregate { attestation, .. } => attestation.message().aggregate().data(),
|
||||
}
|
||||
}
|
||||
@@ -210,7 +202,7 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
|
||||
self: Arc<Self>,
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
attestation: Box<Attestation<T::EthSpec>>,
|
||||
attestation: Box<SingleAttestation>,
|
||||
subnet_id: SubnetId,
|
||||
should_import: bool,
|
||||
reprocess_tx: Option<mpsc::Sender<ReprocessQueueMessage>>,
|
||||
@@ -220,10 +212,14 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
|
||||
.chain
|
||||
.verify_unaggregated_attestation_for_gossip(&attestation, Some(subnet_id))
|
||||
{
|
||||
Ok(verified_attestation) => Ok(VerifiedUnaggregate {
|
||||
indexed_attestation: verified_attestation.into_indexed_attestation(),
|
||||
attestation,
|
||||
}),
|
||||
Ok(verified_attestation) => {
|
||||
let attestation =
|
||||
Box::new(verified_attestation.attestation().clone_as_attestation());
|
||||
Ok(VerifiedUnaggregate {
|
||||
indexed_attestation: verified_attestation.into_indexed_attestation(),
|
||||
attestation,
|
||||
})
|
||||
}
|
||||
Err(error) => Err(RejectedUnaggregate { attestation, error }),
|
||||
};
|
||||
|
||||
@@ -240,7 +236,7 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
|
||||
|
||||
pub fn process_gossip_attestation_batch(
|
||||
self: Arc<Self>,
|
||||
packages: GossipAttestationBatch<T::EthSpec>,
|
||||
packages: GossipAttestationBatch,
|
||||
reprocess_tx: Option<mpsc::Sender<ReprocessQueueMessage>>,
|
||||
) {
|
||||
let attestations_and_subnets = packages
|
||||
@@ -277,14 +273,19 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
|
||||
#[allow(clippy::needless_collect)] // The clippy suggestion fails the borrow checker.
|
||||
let results = results
|
||||
.into_iter()
|
||||
.map(|result| result.map(|verified| verified.into_indexed_attestation()))
|
||||
.map(|result| {
|
||||
result.map(|verified| {
|
||||
let attestation = verified.attestation().clone_as_attestation();
|
||||
(verified.into_indexed_attestation(), attestation)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (result, package) in results.into_iter().zip(packages.into_iter()) {
|
||||
let result = match result {
|
||||
Ok(indexed_attestation) => Ok(VerifiedUnaggregate {
|
||||
Ok((indexed_attestation, attestation)) => Ok(VerifiedUnaggregate {
|
||||
indexed_attestation,
|
||||
attestation: package.attestation,
|
||||
attestation: Box::new(attestation),
|
||||
}),
|
||||
Err(error) => Err(RejectedUnaggregate {
|
||||
attestation: package.attestation,
|
||||
@@ -309,7 +310,7 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn process_gossip_attestation_result(
|
||||
self: &Arc<Self>,
|
||||
result: Result<VerifiedUnaggregate<T>, RejectedUnaggregate<T::EthSpec>>,
|
||||
result: Result<VerifiedUnaggregate<T>, RejectedUnaggregate>,
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
subnet_id: SubnetId,
|
||||
@@ -405,147 +406,6 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Process an unaggregated attestation requiring conversion.
|
||||
///
|
||||
/// This function performs the conversion, and if successfull queues a new message to be
|
||||
/// processed by `process_gossip_attestation`. If unsuccessful due to block unavailability,
|
||||
/// a retry message will be pushed to the `reprocess_tx` if it is `Some`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn process_gossip_attestation_to_convert(
|
||||
self: Arc<Self>,
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
single_attestation: Box<SingleAttestation>,
|
||||
subnet_id: SubnetId,
|
||||
should_import: bool,
|
||||
reprocess_tx: Option<mpsc::Sender<ReprocessQueueMessage>>,
|
||||
seen_timestamp: Duration,
|
||||
) {
|
||||
let conversion_result = self.chain.with_committee_cache(
|
||||
single_attestation.data.target.root,
|
||||
single_attestation
|
||||
.data
|
||||
.slot
|
||||
.epoch(T::EthSpec::slots_per_epoch()),
|
||||
|committee_cache, _| {
|
||||
let slot = single_attestation.data.slot;
|
||||
let committee_index = single_attestation.committee_index;
|
||||
let Some(committee) = committee_cache.get_beacon_committee(slot, committee_index)
|
||||
else {
|
||||
return Ok(Err(AttnError::NoCommitteeForSlotAndIndex {
|
||||
slot,
|
||||
index: committee_index,
|
||||
}));
|
||||
};
|
||||
|
||||
Ok(single_attestation_to_attestation(
|
||||
&single_attestation,
|
||||
committee.committee,
|
||||
))
|
||||
},
|
||||
);
|
||||
|
||||
match conversion_result {
|
||||
Ok(Ok(attestation)) => {
|
||||
let slot = attestation.data().slot;
|
||||
if let Err(e) = self.send_unaggregated_attestation(
|
||||
message_id.clone(),
|
||||
peer_id,
|
||||
attestation,
|
||||
subnet_id,
|
||||
should_import,
|
||||
seen_timestamp,
|
||||
) {
|
||||
error!(
|
||||
error = %e,
|
||||
%slot,
|
||||
"Unable to queue converted SingleAttestation"
|
||||
);
|
||||
self.propagate_validation_result(
|
||||
message_id,
|
||||
peer_id,
|
||||
MessageAcceptance::Ignore,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Outermost error (from `with_committee_cache`) indicating that the block is not known
|
||||
// and that this conversion should be retried.
|
||||
Err(BeaconChainError::MissingBeaconBlock(beacon_block_root)) => {
|
||||
if let Some(sender) = reprocess_tx {
|
||||
metrics::inc_counter(
|
||||
&metrics::BEACON_PROCESSOR_UNAGGREGATED_ATTESTATION_REQUEUED_TOTAL,
|
||||
);
|
||||
// We don't know the block, get the sync manager to handle the block lookup, and
|
||||
// send the attestation to be scheduled for re-processing.
|
||||
self.sync_tx
|
||||
.send(SyncMessage::UnknownBlockHashFromAttestation(
|
||||
peer_id,
|
||||
beacon_block_root,
|
||||
))
|
||||
.unwrap_or_else(|_| {
|
||||
warn!(msg = "UnknownBlockHash", "Failed to send to sync service")
|
||||
});
|
||||
let processor = self.clone();
|
||||
// Do not allow this attestation to be re-processed beyond this point.
|
||||
let reprocess_msg =
|
||||
ReprocessQueueMessage::UnknownBlockUnaggregate(QueuedUnaggregate {
|
||||
beacon_block_root,
|
||||
process_fn: Box::new(move || {
|
||||
processor.process_gossip_attestation_to_convert(
|
||||
message_id,
|
||||
peer_id,
|
||||
single_attestation,
|
||||
subnet_id,
|
||||
should_import,
|
||||
None,
|
||||
seen_timestamp,
|
||||
)
|
||||
}),
|
||||
});
|
||||
if sender.try_send(reprocess_msg).is_err() {
|
||||
error!("Failed to send attestation for re-processing")
|
||||
}
|
||||
} else {
|
||||
// We shouldn't make any further attempts to process this attestation.
|
||||
//
|
||||
// Don't downscore the peer since it's not clear if we requested this head
|
||||
// block from them or not.
|
||||
self.propagate_validation_result(
|
||||
message_id,
|
||||
peer_id,
|
||||
MessageAcceptance::Ignore,
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(Err(error)) => {
|
||||
// We already handled reprocessing above so do not attempt it in the error handler.
|
||||
self.handle_attestation_verification_failure(
|
||||
peer_id,
|
||||
message_id,
|
||||
FailedAtt::SingleUnaggregate {
|
||||
attestation: single_attestation,
|
||||
},
|
||||
None,
|
||||
error,
|
||||
seen_timestamp,
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
// We already handled reprocessing above so do not attempt it in the error handler.
|
||||
self.handle_attestation_verification_failure(
|
||||
peer_id,
|
||||
message_id,
|
||||
FailedAtt::SingleUnaggregate {
|
||||
attestation: single_attestation,
|
||||
},
|
||||
None,
|
||||
AttnError::BeaconChainError(Box::new(error)),
|
||||
seen_timestamp,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Process the aggregated attestation received from the gossip network and:
|
||||
///
|
||||
/// - If it passes gossip propagation criteria, tell the network thread to forward it.
|
||||
@@ -2530,16 +2390,6 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
|
||||
}),
|
||||
})
|
||||
}
|
||||
FailedAtt::SingleUnaggregate { .. } => {
|
||||
// This should never happen, as we handle the unknown head block case
|
||||
// for `SingleAttestation`s separately and should not be able to hit
|
||||
// an `UnknownHeadBlock` error.
|
||||
error!(
|
||||
block_root = ?beacon_block_root,
|
||||
"Dropping SingleAttestation instead of requeueing"
|
||||
);
|
||||
return;
|
||||
}
|
||||
FailedAtt::Unaggregate {
|
||||
attestation,
|
||||
subnet_id,
|
||||
@@ -2635,19 +2485,6 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
|
||||
"attn_no_committee",
|
||||
);
|
||||
}
|
||||
AttnError::NotExactlyOneAggregationBitSet(_) => {
|
||||
/*
|
||||
* The unaggregated attestation doesn't have only one signature.
|
||||
*
|
||||
* The peer has published an invalid consensus message.
|
||||
*/
|
||||
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject);
|
||||
self.gossip_penalize_peer(
|
||||
peer_id,
|
||||
PeerAction::LowToleranceError,
|
||||
"attn_too_many_agg_bits",
|
||||
);
|
||||
}
|
||||
AttnError::NotExactlyOneCommitteeBitSet(_) => {
|
||||
/*
|
||||
* The attestation doesn't have only one committee bit set.
|
||||
|
||||
@@ -75,20 +75,21 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
|
||||
self.beacon_processor_send.try_send(event)
|
||||
}
|
||||
|
||||
/// Create a new `Work` event for some `SingleAttestation`.
|
||||
pub fn send_single_attestation(
|
||||
/// Create a new `Work` event for some unaggregated attestation.
|
||||
pub fn send_unaggregated_attestation(
|
||||
self: &Arc<Self>,
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
single_attestation: SingleAttestation,
|
||||
attestation: SingleAttestation,
|
||||
subnet_id: SubnetId,
|
||||
should_import: bool,
|
||||
seen_timestamp: Duration,
|
||||
) -> Result<(), Error<T::EthSpec>> {
|
||||
// Define a closure for processing individual attestations.
|
||||
let processor = self.clone();
|
||||
let process_individual = move |package: GossipAttestationPackage<SingleAttestation>| {
|
||||
let reprocess_tx = processor.reprocess_tx.clone();
|
||||
processor.process_gossip_attestation_to_convert(
|
||||
processor.process_gossip_attestation(
|
||||
package.message_id,
|
||||
package.peer_id,
|
||||
package.attestation,
|
||||
@@ -99,48 +100,6 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
|
||||
)
|
||||
};
|
||||
|
||||
self.try_send(BeaconWorkEvent {
|
||||
drop_during_sync: true,
|
||||
work: Work::GossipAttestationToConvert {
|
||||
attestation: Box::new(GossipAttestationPackage {
|
||||
message_id,
|
||||
peer_id,
|
||||
attestation: Box::new(single_attestation),
|
||||
subnet_id,
|
||||
should_import,
|
||||
seen_timestamp,
|
||||
}),
|
||||
process_individual: Box::new(process_individual),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new `Work` event for some unaggregated attestation.
|
||||
pub fn send_unaggregated_attestation(
|
||||
self: &Arc<Self>,
|
||||
message_id: MessageId,
|
||||
peer_id: PeerId,
|
||||
attestation: Attestation<T::EthSpec>,
|
||||
subnet_id: SubnetId,
|
||||
should_import: bool,
|
||||
seen_timestamp: Duration,
|
||||
) -> Result<(), Error<T::EthSpec>> {
|
||||
// Define a closure for processing individual attestations.
|
||||
let processor = self.clone();
|
||||
let process_individual =
|
||||
move |package: GossipAttestationPackage<Attestation<T::EthSpec>>| {
|
||||
let reprocess_tx = processor.reprocess_tx.clone();
|
||||
processor.process_gossip_attestation(
|
||||
package.message_id,
|
||||
package.peer_id,
|
||||
package.attestation,
|
||||
package.subnet_id,
|
||||
package.should_import,
|
||||
Some(reprocess_tx),
|
||||
package.seen_timestamp,
|
||||
)
|
||||
};
|
||||
|
||||
// Define a closure for processing batches of attestations.
|
||||
let processor = self.clone();
|
||||
let process_batch = move |attestations| {
|
||||
|
||||
@@ -36,9 +36,9 @@ use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
use types::blob_sidecar::FixedBlobSidecarList;
|
||||
use types::{
|
||||
Attestation, AttesterSlashing, BlobSidecar, BlobSidecarList, ChainSpec, DataColumnSidecarList,
|
||||
AttesterSlashing, BlobSidecar, BlobSidecarList, ChainSpec, DataColumnSidecarList,
|
||||
DataColumnSubnetId, Epoch, Hash256, MainnetEthSpec, ProposerSlashing, SignedAggregateAndProof,
|
||||
SignedBeaconBlock, SignedVoluntaryExit, Slot, SubnetId,
|
||||
SignedBeaconBlock, SignedVoluntaryExit, SingleAttestation, Slot, SubnetId,
|
||||
};
|
||||
|
||||
type E = MainnetEthSpec;
|
||||
@@ -60,8 +60,8 @@ struct TestRig {
|
||||
next_block: Arc<SignedBeaconBlock<E>>,
|
||||
next_blobs: Option<BlobSidecarList<E>>,
|
||||
next_data_columns: Option<DataColumnSidecarList<E>>,
|
||||
attestations: Vec<(Attestation<E>, SubnetId)>,
|
||||
next_block_attestations: Vec<(Attestation<E>, SubnetId)>,
|
||||
attestations: Vec<(SingleAttestation, SubnetId)>,
|
||||
next_block_attestations: Vec<(SingleAttestation, SubnetId)>,
|
||||
next_block_aggregate_attestations: Vec<SignedAggregateAndProof<E>>,
|
||||
attester_slashing: AttesterSlashing<E>,
|
||||
proposer_slashing: ProposerSlashing,
|
||||
@@ -143,7 +143,7 @@ impl TestRig {
|
||||
|
||||
let head_state_root = head.beacon_state_root();
|
||||
let attestations = harness
|
||||
.get_unaggregated_attestations(
|
||||
.get_single_attestations(
|
||||
&AttestationStrategy::AllValidators,
|
||||
&head.beacon_state,
|
||||
head_state_root,
|
||||
@@ -160,7 +160,7 @@ impl TestRig {
|
||||
);
|
||||
|
||||
let next_block_attestations = harness
|
||||
.get_unaggregated_attestations(
|
||||
.get_single_attestations(
|
||||
&AttestationStrategy::AllValidators,
|
||||
&next_state,
|
||||
next_block_tuple.0.state_root(),
|
||||
|
||||
@@ -354,17 +354,6 @@ impl<T: BeaconChainTypes> Router<T> {
|
||||
timestamp_now(),
|
||||
),
|
||||
),
|
||||
PubsubMessage::SingleAttestation(subnet_attestation) => self
|
||||
.handle_beacon_processor_send_result(
|
||||
self.network_beacon_processor.send_single_attestation(
|
||||
message_id,
|
||||
peer_id,
|
||||
subnet_attestation.1,
|
||||
subnet_attestation.0,
|
||||
should_process,
|
||||
timestamp_now(),
|
||||
),
|
||||
),
|
||||
PubsubMessage::BeaconBlock(block) => self.handle_beacon_processor_send_result(
|
||||
self.network_beacon_processor.send_gossip_beacon_block(
|
||||
message_id,
|
||||
|
||||
@@ -554,23 +554,7 @@ impl<T: BeaconChainTypes> NetworkService<T> {
|
||||
// the attestation, else we just just propagate the Attestation.
|
||||
let should_process = self.subnet_service.should_process_attestation(
|
||||
Subnet::Attestation(subnet_id),
|
||||
attestation.data(),
|
||||
);
|
||||
self.send_to_router(RouterMessage::PubsubMessage(
|
||||
id,
|
||||
source,
|
||||
message,
|
||||
should_process,
|
||||
));
|
||||
}
|
||||
PubsubMessage::SingleAttestation(ref subnet_and_attestation) => {
|
||||
let subnet_id = subnet_and_attestation.0;
|
||||
let single_attestation = &subnet_and_attestation.1;
|
||||
// checks if we have an aggregator for the slot. If so, we should process
|
||||
// the attestation, else we just just propagate the Attestation.
|
||||
let should_process = self.subnet_service.should_process_attestation(
|
||||
Subnet::Attestation(subnet_id),
|
||||
&single_attestation.data,
|
||||
&attestation.data,
|
||||
);
|
||||
self.send_to_router(RouterMessage::PubsubMessage(
|
||||
id,
|
||||
|
||||
Reference in New Issue
Block a user