Skip to content

Commit 5e271ab

Browse files
Removed the conversion step so validator attestation service constructs SingleAttestation directly
1 parent 90ff643 commit 5e271ab

File tree

4 files changed

+160
-61
lines changed

4 files changed

+160
-61
lines changed

consensus/types/src/attestation.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,28 @@ impl SingleAttestation {
640640
})
641641
}
642642
}
643+
644+
pub fn empty_for_signing(
645+
committee_index: u64,
646+
attester_index: u64,
647+
slot: Slot,
648+
beacon_block_root: Hash256,
649+
source: Checkpoint,
650+
target: Checkpoint,
651+
) -> Self {
652+
Self {
653+
committee_index,
654+
attester_index,
655+
data: AttestationData {
656+
slot,
657+
index: committee_index,
658+
beacon_block_root,
659+
source,
660+
target,
661+
},
662+
signature: AggregateSignature::infinity(),
663+
}
664+
}
643665
}
644666

645667
#[cfg(test)]

validator_client/lighthouse_validator_store/src/lib.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use types::{
2525
SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData,
2626
VoluntaryExit,
2727
};
28+
use types::{AggregateSignature, SingleAttestation};
2829
use validator_store::{
2930
DoppelgangerStatus, Error as ValidatorStoreError, ProposalData, SignedBlock, UnsignedBlock,
3031
ValidatorStore,
@@ -840,6 +841,99 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore for LighthouseValidatorS
840841
}
841842
}
842843

844+
async fn sign_single_attestation(
845+
&self,
846+
validator_pubkey: PublicKeyBytes,
847+
single_attestation: &mut SingleAttestation,
848+
current_epoch: Epoch,
849+
) -> Result<(), Error> {
850+
// Make sure the target epoch is not higher than the current epoch to avoid potential attacks.
851+
if single_attestation.data.target.epoch > current_epoch {
852+
return Err(Error::GreaterThanCurrentEpoch {
853+
epoch: single_attestation.data.target.epoch,
854+
current_epoch,
855+
});
856+
}
857+
858+
// Get the signing method and check doppelganger protection.
859+
let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?;
860+
861+
// Checking for slashing conditions.
862+
let signing_epoch = single_attestation.data.target.epoch;
863+
let signing_context = self.signing_context(Domain::BeaconAttester, signing_epoch);
864+
let domain_hash = signing_context.domain_hash(&self.spec);
865+
866+
let slashing_status = if signing_method
867+
.requires_local_slashing_protection(self.enable_web3signer_slashing_protection)
868+
{
869+
self.slashing_protection.check_and_insert_attestation(
870+
&validator_pubkey,
871+
&single_attestation.data,
872+
domain_hash,
873+
)
874+
} else {
875+
Ok(Safe::Valid)
876+
};
877+
878+
match slashing_status {
879+
// We can safely sign this attestation.
880+
Ok(Safe::Valid) => {
881+
let signature = signing_method
882+
.get_signature::<E, BlindedPayload<E>>(
883+
SignableMessage::AttestationData(&single_attestation.data),
884+
signing_context,
885+
&self.spec,
886+
&self.task_executor,
887+
)
888+
.await?;
889+
890+
// Create an aggregate signature from the individual signature
891+
let mut aggregate_signature = AggregateSignature::empty();
892+
aggregate_signature.add_assign(&signature);
893+
single_attestation.signature = aggregate_signature;
894+
895+
validator_metrics::inc_counter_vec(
896+
&validator_metrics::SIGNED_ATTESTATIONS_TOTAL,
897+
&[validator_metrics::SUCCESS],
898+
);
899+
900+
Ok(())
901+
}
902+
Ok(Safe::SameData) => {
903+
warn!("Skipping signing of previously signed attestation");
904+
validator_metrics::inc_counter_vec(
905+
&validator_metrics::SIGNED_ATTESTATIONS_TOTAL,
906+
&[validator_metrics::SAME_DATA],
907+
);
908+
Err(Error::SameData)
909+
}
910+
Err(NotSafe::UnregisteredValidator(pk)) => {
911+
warn!(
912+
msg = "Carefully consider running with --init-slashing-protection (see --help)",
913+
public_key = format!("{:?}", pk),
914+
"Not signing attestation for unregistered validator"
915+
);
916+
validator_metrics::inc_counter_vec(
917+
&validator_metrics::SIGNED_ATTESTATIONS_TOTAL,
918+
&[validator_metrics::UNREGISTERED],
919+
);
920+
Err(Error::Slashable(NotSafe::UnregisteredValidator(pk)))
921+
}
922+
Err(e) => {
923+
crit!(
924+
attestation = format!("{:?}", single_attestation.data),
925+
error = format!("{:?}", e),
926+
"Not signing slashable attestation"
927+
);
928+
validator_metrics::inc_counter_vec(
929+
&validator_metrics::SIGNED_ATTESTATIONS_TOTAL,
930+
&[validator_metrics::SLASHABLE],
931+
);
932+
Err(Error::Slashable(e))
933+
}
934+
}
935+
}
936+
843937
async fn sign_validator_registration_data(
844938
&self,
845939
validator_registration_data: ValidatorRegistrationData,

validator_client/validator_services/src/attestation_service.rs

Lines changed: 32 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use task_executor::TaskExecutor;
1010
use tokio::time::{sleep, sleep_until, Duration, Instant};
1111
use tracing::{debug, error, info, trace, warn};
1212
use tree_hash::TreeHash;
13-
use types::{Attestation, AttestationData, ChainSpec, CommitteeIndex, EthSpec, Slot};
13+
use types::{AttestationData, ChainSpec, CommitteeIndex, EthSpec, SingleAttestation, Slot};
1414
use validator_store::{Error as ValidatorStoreError, ValidatorStore};
1515

1616
/// Builds an `AttestationService`.
@@ -379,41 +379,23 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> AttestationService<S,
379379
return None;
380380
}
381381

382-
let mut attestation = match Attestation::empty_for_signing(
382+
let mut single_attestation = SingleAttestation::empty_for_signing(
383383
duty.committee_index,
384-
duty.committee_length as usize,
384+
duty.validator_index,
385385
attestation_data.slot,
386386
attestation_data.beacon_block_root,
387387
attestation_data.source,
388388
attestation_data.target,
389-
&self.chain_spec,
390-
) {
391-
Ok(attestation) => attestation,
392-
Err(err) => {
393-
crit!(
394-
validator = ?duty.pubkey,
395-
?duty,
396-
?err,
397-
"Invalid validator duties during signing"
398-
);
399-
return None;
400-
}
401-
};
389+
);
402390

391+
// Sign the SingleAttestation
403392
match self
404393
.validator_store
405-
.sign_attestation(
406-
duty.pubkey,
407-
duty.validator_committee_index as usize,
408-
&mut attestation,
409-
current_epoch,
410-
)
394+
.sign_single_attestation(duty.pubkey, &mut single_attestation, current_epoch)
411395
.await
412396
{
413-
Ok(()) => Some((attestation, duty.validator_index)),
397+
Ok(()) => Some(single_attestation),
414398
Err(ValidatorStoreError::UnknownPubkey(pubkey)) => {
415-
// A pubkey can be missing when a validator was recently
416-
// removed via the API.
417399
warn!(
418400
info = "a validator may have recently been removed from this VC",
419401
pubkey = ?pubkey,
@@ -438,59 +420,50 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> AttestationService<S,
438420
});
439421

440422
// Execute all the futures in parallel, collecting any successful results.
441-
let (ref attestations, ref validator_indices): (Vec<_>, Vec<_>) = join_all(signing_futures)
423+
let single_attestations: Vec<SingleAttestation> = join_all(signing_futures)
442424
.await
443425
.into_iter()
444426
.flatten()
445-
.unzip();
427+
.collect();
446428

447-
if attestations.is_empty() {
429+
if single_attestations.is_empty() {
448430
warn!("No attestations were published");
449431
return Ok(None);
450432
}
433+
434+
// Extract validator indices BEFORE moving single_attestations into the closure
435+
let validator_indices: Vec<u64> = single_attestations
436+
.iter()
437+
.map(|a| a.attester_index)
438+
.collect();
439+
451440
let fork_name = self
452441
.chain_spec
453442
.fork_name_at_slot::<S::E>(attestation_data.slot);
454443

444+
// Clone single_attestations before using it in the closure
445+
let attestations_to_send = single_attestations.clone();
446+
455447
// Post the attestations to the BN.
456448
match self
457449
.beacon_nodes
458-
.request(ApiTopic::Attestations, |beacon_node| async move {
459-
let _timer = validator_metrics::start_timer_vec(
460-
&validator_metrics::ATTESTATION_SERVICE_TIMES,
461-
&[validator_metrics::ATTESTATIONS_HTTP_POST],
462-
);
463-
464-
let single_attestations = attestations
465-
.iter()
466-
.zip(validator_indices)
467-
.filter_map(|(a, i)| {
468-
match a.to_single_attestation_with_attester_index(*i) {
469-
Ok(a) => Some(a),
470-
Err(e) => {
471-
// This shouldn't happen unless BN and VC are out of sync with
472-
// respect to the Electra fork.
473-
error!(
474-
error = ?e,
475-
committee_index = attestation_data.index,
476-
slot = slot.as_u64(),
477-
"type" = "unaggregated",
478-
"Unable to convert to SingleAttestation"
479-
);
480-
None
481-
}
482-
}
483-
})
484-
.collect::<Vec<_>>();
450+
.request(ApiTopic::Attestations, |beacon_node| {
451+
let attestations = attestations_to_send.clone();
452+
async move {
453+
let _timer = validator_metrics::start_timer_vec(
454+
&validator_metrics::ATTESTATION_SERVICE_TIMES,
455+
&[validator_metrics::ATTESTATIONS_HTTP_POST],
456+
);
485457

486-
beacon_node
487-
.post_beacon_pool_attestations_v2::<S::E>(single_attestations, fork_name)
488-
.await
458+
beacon_node
459+
.post_beacon_pool_attestations_v2::<S::E>(attestations, fork_name)
460+
.await
461+
}
489462
})
490463
.await
491464
{
492465
Ok(()) => info!(
493-
count = attestations.len(),
466+
count = single_attestations.len(),
494467
validator_indices = ?validator_indices,
495468
head_block = ?attestation_data.beacon_block_root,
496469
committee_index = attestation_data.index,

validator_client/validator_store/src/lib.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use std::sync::Arc;
66
use types::{
77
Address, Attestation, AttestationError, BlindedBeaconBlock, Epoch, EthSpec, Graffiti, Hash256,
88
PublicKeyBytes, SelectionProof, Signature, SignedAggregateAndProof, SignedBlindedBeaconBlock,
9-
SignedContributionAndProof, SignedValidatorRegistrationData, Slot, SyncCommitteeContribution,
10-
SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData,
9+
SignedContributionAndProof, SignedValidatorRegistrationData, SingleAttestation, Slot,
10+
SyncCommitteeContribution, SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId,
11+
ValidatorRegistrationData,
1112
};
1213

1314
#[derive(Debug, PartialEq, Clone)]
@@ -20,6 +21,7 @@ pub enum Error<T> {
2021
GreaterThanCurrentSlot { slot: Slot, current_slot: Slot },
2122
GreaterThanCurrentEpoch { epoch: Epoch, current_epoch: Epoch },
2223
UnableToSignAttestation(AttestationError),
24+
ValidatorNotEnabled(PublicKeyBytes),
2325
SpecificError(T),
2426
}
2527

@@ -169,6 +171,14 @@ pub trait ValidatorStore: Send + Sync {
169171
/// `ProposalData` fields include defaulting logic described in `get_fee_recipient_defaulting`,
170172
/// `get_gas_limit_defaulting`, and `get_builder_proposals_defaulting`.
171173
fn proposal_data(&self, pubkey: &PublicKeyBytes) -> Option<ProposalData>;
174+
/// Signs a SingleAttestation for a given validator.
175+
///
176+
fn sign_single_attestation(
177+
&self,
178+
validator_pubkey: PublicKeyBytes,
179+
single_attestation: &mut SingleAttestation,
180+
current_epoch: Epoch,
181+
) -> impl Future<Output = Result<(), Error<Self::Error>>> + Send;
172182
}
173183

174184
#[derive(Debug)]

0 commit comments

Comments
 (0)