Instrument attestation signing. (#8508)

We noticed attestation signing taking 2+ seconds on some of our hoodi nodes and the current traces doesn't provide enough details. This PR adds a few more spans to the `attestation_duty_cycle` code path in the VC.

Before:
<img width="842" height="154" alt="image" src="https://github.com/user-attachments/assets/cfc5c8c0-e6f2-4f56-a8e4-65001af4a005" />

After:
<img width="496" height="217" alt="image" src="https://github.com/user-attachments/assets/c91cfa88-af1b-456e-8c64-625809eb6881" />


  


Co-Authored-By: Jimmy Chen <jchen.tc@gmail.com>
This commit is contained in:
Jimmy Chen
2025-12-02 16:17:13 +11:00
committed by GitHub
parent 4fbe517491
commit 7ef9501ff6
6 changed files with 77 additions and 66 deletions

1
Cargo.lock generated
View File

@@ -8336,6 +8336,7 @@ dependencies = [
"reqwest", "reqwest",
"serde", "serde",
"task_executor", "task_executor",
"tracing",
"types", "types",
"url", "url",
"validator_metrics", "validator_metrics",

View File

@@ -15,7 +15,7 @@ use std::marker::PhantomData;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use task_executor::TaskExecutor; use task_executor::TaskExecutor;
use tracing::{error, info, warn}; use tracing::{error, info, instrument, warn};
use types::{ use types::{
AbstractExecPayload, Address, AggregateAndProof, Attestation, BeaconBlock, BlindedPayload, AbstractExecPayload, Address, AggregateAndProof, Attestation, BeaconBlock, BlindedPayload,
ChainSpec, ContributionAndProof, Domain, Epoch, EthSpec, Fork, Graffiti, Hash256, ChainSpec, ContributionAndProof, Domain, Epoch, EthSpec, Fork, Graffiti, Hash256,
@@ -242,6 +242,7 @@ impl<T: SlotClock + 'static, E: EthSpec> LighthouseValidatorStore<T, E> {
/// Returns a `SigningMethod` for `validator_pubkey` *only if* that validator is considered safe /// Returns a `SigningMethod` for `validator_pubkey` *only if* that validator is considered safe
/// by doppelganger protection. /// by doppelganger protection.
#[instrument(skip_all, level = "debug")]
fn doppelganger_checked_signing_method( fn doppelganger_checked_signing_method(
&self, &self,
validator_pubkey: PublicKeyBytes, validator_pubkey: PublicKeyBytes,
@@ -745,6 +746,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore for LighthouseValidatorS
} }
} }
#[instrument(skip_all)]
async fn sign_attestation( async fn sign_attestation(
&self, &self,
validator_pubkey: PublicKeyBytes, validator_pubkey: PublicKeyBytes,

View File

@@ -12,6 +12,7 @@ parking_lot = { workspace = true }
reqwest = { workspace = true } reqwest = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
task_executor = { workspace = true } task_executor = { workspace = true }
tracing = { workspace = true }
types = { workspace = true } types = { workspace = true }
url = { workspace = true } url = { workspace = true }
validator_metrics = { workspace = true } validator_metrics = { workspace = true }

View File

@@ -10,6 +10,7 @@ use reqwest::{Client, header::ACCEPT};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use task_executor::TaskExecutor; use task_executor::TaskExecutor;
use tracing::instrument;
use types::*; use types::*;
use url::Url; use url::Url;
use web3signer::{ForkInfo, MessageType, SigningRequest, SigningResponse}; use web3signer::{ForkInfo, MessageType, SigningRequest, SigningResponse};
@@ -131,6 +132,7 @@ impl SigningMethod {
} }
/// Return the signature of `signable_message`, with respect to the `signing_context`. /// Return the signature of `signable_message`, with respect to the `signing_context`.
#[instrument(skip_all, level = "debug")]
pub async fn get_signature<E: EthSpec, Payload: AbstractExecPayload<E>>( pub async fn get_signature<E: EthSpec, Payload: AbstractExecPayload<E>>(
&self, &self,
signable_message: SignableMessage<'_, E, Payload>, signable_message: SignableMessage<'_, E, Payload>,

View File

@@ -11,6 +11,7 @@ use rusqlite::{OptionalExtension, Transaction, TransactionBehavior, params};
use std::fs::File; use std::fs::File;
use std::path::Path; use std::path::Path;
use std::time::Duration; use std::time::Duration;
use tracing::instrument;
use types::{AttestationData, BeaconBlockHeader, Epoch, Hash256, PublicKeyBytes, SignedRoot, Slot}; use types::{AttestationData, BeaconBlockHeader, Epoch, Hash256, PublicKeyBytes, SignedRoot, Slot};
type Pool = r2d2::Pool<SqliteConnectionManager>; type Pool = r2d2::Pool<SqliteConnectionManager>;
@@ -639,6 +640,7 @@ impl SlashingDatabase {
/// to prevent concurrent checks and inserts from resulting in slashable data being inserted. /// to prevent concurrent checks and inserts from resulting in slashable data being inserted.
/// ///
/// This is the safe, externally-callable interface for checking attestations. /// This is the safe, externally-callable interface for checking attestations.
#[instrument(skip_all, level = "debug")]
pub fn check_and_insert_attestation( pub fn check_and_insert_attestation(
&self, &self,
validator_pubkey: &PublicKeyBytes, validator_pubkey: &PublicKeyBytes,

View File

@@ -8,7 +8,7 @@ use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use task_executor::TaskExecutor; use task_executor::TaskExecutor;
use tokio::time::{Duration, Instant, sleep, sleep_until}; use tokio::time::{Duration, Instant, sleep, sleep_until};
use tracing::{Instrument, debug, error, info, info_span, instrument, trace, warn}; use tracing::{Instrument, Span, debug, error, info, info_span, instrument, trace, warn};
use tree_hash::TreeHash; use tree_hash::TreeHash;
use types::{Attestation, AttestationData, ChainSpec, CommitteeIndex, EthSpec, Slot}; use types::{Attestation, AttestationData, ChainSpec, CommitteeIndex, EthSpec, Slot};
use validator_store::{Error as ValidatorStoreError, ValidatorStore}; use validator_store::{Error as ValidatorStoreError, ValidatorStore};
@@ -369,79 +369,82 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> AttestationService<S,
// Create futures to produce signed `Attestation` objects. // Create futures to produce signed `Attestation` objects.
let attestation_data_ref = &attestation_data; let attestation_data_ref = &attestation_data;
let signing_futures = validator_duties.iter().map(|duty_and_proof| async move { let signing_futures = validator_duties.iter().map(|duty_and_proof| {
let duty = &duty_and_proof.duty; async move {
let attestation_data = attestation_data_ref; let duty = &duty_and_proof.duty;
let attestation_data = attestation_data_ref;
// Ensure that the attestation matches the duties. // Ensure that the attestation matches the duties.
if !duty.match_attestation_data::<S::E>(attestation_data, &self.chain_spec) { if !duty.match_attestation_data::<S::E>(attestation_data, &self.chain_spec) {
crit!(
validator = ?duty.pubkey,
duty_slot = %duty.slot,
attestation_slot = %attestation_data.slot,
duty_index = duty.committee_index,
attestation_index = attestation_data.index,
"Inconsistent validator duties during signing"
);
return None;
}
let mut attestation = match Attestation::empty_for_signing(
duty.committee_index,
duty.committee_length as usize,
attestation_data.slot,
attestation_data.beacon_block_root,
attestation_data.source,
attestation_data.target,
&self.chain_spec,
) {
Ok(attestation) => attestation,
Err(err) => {
crit!( crit!(
validator = ?duty.pubkey, validator = ?duty.pubkey,
?duty, duty_slot = %duty.slot,
?err, attestation_slot = %attestation_data.slot,
"Invalid validator duties during signing" duty_index = duty.committee_index,
attestation_index = attestation_data.index,
"Inconsistent validator duties during signing"
); );
return None; return None;
} }
};
match self let mut attestation = match Attestation::empty_for_signing(
.validator_store duty.committee_index,
.sign_attestation( duty.committee_length as usize,
duty.pubkey, attestation_data.slot,
duty.validator_committee_index as usize, attestation_data.beacon_block_root,
&mut attestation, attestation_data.source,
current_epoch, attestation_data.target,
) &self.chain_spec,
.await ) {
{ Ok(attestation) => attestation,
Ok(()) => Some((attestation, duty.validator_index)), Err(err) => {
Err(ValidatorStoreError::UnknownPubkey(pubkey)) => { crit!(
// A pubkey can be missing when a validator was recently validator = ?duty.pubkey,
// removed via the API. ?duty,
warn!( ?err,
info = "a validator may have recently been removed from this VC", "Invalid validator duties during signing"
pubkey = ?pubkey, );
validator = ?duty.pubkey, return None;
committee_index = committee_index, }
slot = slot.as_u64(), };
"Missing pubkey for attestation"
); match self
None .validator_store
} .sign_attestation(
Err(e) => { duty.pubkey,
crit!( duty.validator_committee_index as usize,
error = ?e, &mut attestation,
validator = ?duty.pubkey, current_epoch,
committee_index, )
slot = slot.as_u64(), .await
"Failed to sign attestation" {
); Ok(()) => Some((attestation, duty.validator_index)),
None Err(ValidatorStoreError::UnknownPubkey(pubkey)) => {
// A pubkey can be missing when a validator was recently
// removed via the API.
warn!(
info = "a validator may have recently been removed from this VC",
pubkey = ?pubkey,
validator = ?duty.pubkey,
committee_index = committee_index,
slot = slot.as_u64(),
"Missing pubkey for attestation"
);
None
}
Err(e) => {
crit!(
error = ?e,
validator = ?duty.pubkey,
committee_index,
slot = slot.as_u64(),
"Failed to sign attestation"
);
None
}
} }
} }
.instrument(Span::current())
}); });
// Execute all the futures in parallel, collecting any successful results. // Execute all the futures in parallel, collecting any successful results.