diff --git a/crates/e2e-tests/src/lib.rs b/crates/e2e-tests/src/lib.rs index 1c4baff5f..9e91b74e4 100644 --- a/crates/e2e-tests/src/lib.rs +++ b/crates/e2e-tests/src/lib.rs @@ -655,9 +655,15 @@ mod tests { .map(|node| { let signing_manager = node.hashi().signing_manager(); let onchain_state = node.hashi().onchain_state().clone(); - let p2p_channel = hashi::mpc::rpc::RpcP2PChannel::new(onchain_state, epoch); + let p2p_channel = hashi::mpc::rpc::RpcP2PChannel::new( + onchain_state, + epoch, + hashi::metrics::MPC_LABEL_SIGNING, + node.hashi().metrics.clone(), + ); let beacon = beacon_value; let message = message.to_vec(); + let metrics = node.hashi().metrics.clone(); async move { hashi::mpc::SigningManager::sign( &signing_manager, @@ -668,6 +674,7 @@ mod tests { &beacon, None, SIGNING_TIMEOUT, + &metrics, ) .await } diff --git a/crates/hashi/src/grpc/client.rs b/crates/hashi/src/grpc/client.rs index f8e383e52..8f2da1129 100644 --- a/crates/hashi/src/grpc/client.rs +++ b/crates/hashi/src/grpc/client.rs @@ -4,6 +4,7 @@ use std::time::Duration; use axum::http; +use prost::Message as _; use tonic::Response; use tonic_rustls::Channel; use tonic_rustls::Endpoint; @@ -105,34 +106,41 @@ impl Client { &self, epoch: u64, request: &SendMessagesRequest, - ) -> Result { + ) -> Result<(SendMessagesResponse, usize)> { let proto_request = request.to_proto(epoch); + let request_size = proto_request.encoded_len(); let response = self .mpc_service_client() .send_messages(proto_request) .await?; - SendMessagesResponse::try_from(response.get_ref()) - .map_err(|e| tonic::Status::internal(e.to_string())) + let resp = SendMessagesResponse::try_from(response.get_ref()) + .map_err(|e| tonic::Status::internal(e.to_string()))?; + Ok((resp, request_size)) } pub async fn retrieve_messages( &self, request: &RetrieveMessagesRequest, - ) -> Result { + ) -> Result<(RetrieveMessagesResponse, usize, usize)> { let proto_request = request.to_proto(); + let request_size = proto_request.encoded_len(); let response = self .mpc_service_client() .retrieve_messages(proto_request) .await?; - RetrieveMessagesResponse::try_from(response.get_ref()) - .map_err(|e| tonic::Status::internal(e.to_string())) + let response_size = response.get_ref().encoded_len(); + let resp = RetrieveMessagesResponse::try_from(response.get_ref()) + .map_err(|e| tonic::Status::internal(e.to_string()))?; + Ok((resp, request_size, response_size)) } - pub async fn complain(&self, request: &ComplainRequest) -> Result { + pub async fn complain(&self, request: &ComplainRequest) -> Result<(ComplaintResponses, usize)> { let proto_request = request.to_proto(); + let request_size = proto_request.encoded_len(); let response = self.mpc_service_client().complain(proto_request).await?; - ComplaintResponses::try_from(response.get_ref()) - .map_err(|e| tonic::Status::internal(e.to_string())) + let resp = ComplaintResponses::try_from(response.get_ref()) + .map_err(|e| tonic::Status::internal(e.to_string()))?; + Ok((resp, request_size)) } pub async fn get_public_mpc_output( @@ -152,14 +160,16 @@ impl Client { &self, epoch: u64, request: &GetPartialSignaturesRequest, - ) -> Result { + ) -> Result<(GetPartialSignaturesResponse, usize)> { let proto_request = request.to_proto(epoch); + let request_size = proto_request.encoded_len(); let response = self .mpc_service_client() .get_partial_signatures(proto_request) .await?; - GetPartialSignaturesResponse::try_from(response.get_ref()) - .map_err(|e| tonic::Status::internal(e.to_string())) + let resp = GetPartialSignaturesResponse::try_from(response.get_ref()) + .map_err(|e| tonic::Status::internal(e.to_string()))?; + Ok((resp, request_size)) } pub async fn get_reconfig_completion_signature(&self, epoch: u64) -> Result>> { diff --git a/crates/hashi/src/grpc/mod.rs b/crates/hashi/src/grpc/mod.rs index e9115f7cb..dcc431969 100644 --- a/crates/hashi/src/grpc/mod.rs +++ b/crates/hashi/src/grpc/mod.rs @@ -39,6 +39,10 @@ impl HttpService { Self { inner: hashi } } + pub(crate) fn metrics(&self) -> &crate::metrics::Metrics { + &self.inner.metrics + } + pub async fn start(self) -> (std::net::SocketAddr, Service) { let router = { let max_decoding_message_size = self.inner.config.grpc_max_decoding_message_size(); diff --git a/crates/hashi/src/metrics.rs b/crates/hashi/src/metrics.rs index 61256e0de..b2d1f6782 100644 --- a/crates/hashi/src/metrics.rs +++ b/crates/hashi/src/metrics.rs @@ -73,6 +73,30 @@ pub struct Metrics { pub mpc_sign_duration_seconds: HistogramVec, pub mpc_sign_failures_total: IntCounterVec, + + // MPC profiling metrics + pub mpc_reconfig_total_duration_seconds: HistogramVec, + pub mpc_end_reconfig_duration_seconds: HistogramVec, + pub mpc_prepare_signing_duration_seconds: HistogramVec, + pub mpc_total_duration_seconds: HistogramVec, + pub mpc_dealer_crypto_duration_seconds: HistogramVec, + pub mpc_p2p_broadcast_duration_seconds: HistogramVec, + pub mpc_cert_publish_duration_seconds: HistogramVec, + pub mpc_tob_poll_duration_seconds: HistogramVec, + pub mpc_cert_verify_duration_seconds: HistogramVec, + pub mpc_message_process_duration_seconds: HistogramVec, + pub mpc_message_retrieval_duration_seconds: HistogramVec, + pub mpc_complaint_recovery_duration_seconds: HistogramVec, + pub mpc_completion_duration_seconds: HistogramVec, + pub mpc_presig_conversion_duration_seconds: HistogramVec, + pub mpc_rotation_prepare_previous_duration_seconds: HistogramVec, + pub mpc_sign_partial_gen_duration_seconds: HistogramVec, + pub mpc_sign_collection_duration_seconds: HistogramVec, + pub mpc_sign_aggregation_duration_seconds: HistogramVec, + pub mpc_rpc_handler_process_duration_seconds: HistogramVec, + pub mpc_bytes_sent_total: IntCounterVec, + pub mpc_bytes_received_total: IntCounterVec, + pub mpc_p2p_message_size_bytes: HistogramVec, } const LATENCY_SEC_BUCKETS: &[f64] = &[ @@ -81,6 +105,19 @@ const LATENCY_SEC_BUCKETS: &[f64] = &[ const MPC_SIGN_DURATION_BUCKETS: &[f64] = &[0.1, 0.25, 0.5, 1., 1.5, 2., 2.5, 3., 4., 5., 7.5, 10.]; +pub const MPC_LABEL_DKG: &str = "dkg"; +pub const MPC_LABEL_KEY_ROTATION: &str = "key_rotation"; +pub const MPC_LABEL_NONCE_GEN: &str = "nonce_gen"; +pub const MPC_LABEL_SIGNING: &str = "signing"; + +const MPC_PROTOCOL_DURATION_BUCKETS: &[f64] = &[0.1, 0.25, 0.5, 1., 2., 5., 10., 20., 30., 60.]; + +const MPC_PHASE_DURATION_BUCKETS: &[f64] = + &[0.001, 0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 1., 2., 5., 10.]; + +const MPC_MESSAGE_SIZE_BUCKETS: &[f64] = + &[1024., 4096., 16384., 65536., 262144., 1048576., 4194304.]; + impl Metrics { pub fn new_default() -> Self { Self::new(prometheus::default_registry()) @@ -343,6 +380,190 @@ impl Metrics { registry, ) .unwrap(), + + // MPC profiling: reconfig-level + mpc_reconfig_total_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_reconfig_total_duration_seconds", + "Duration of full handle_reconfig", + &["protocol"], + MPC_PROTOCOL_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_end_reconfig_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_end_reconfig_duration_seconds", + "Duration of submit_end_reconfig", + &["protocol"], + MPC_PROTOCOL_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_prepare_signing_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_prepare_signing_duration_seconds", + "Duration of prepare_signing", + &["protocol"], + MPC_PROTOCOL_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + + // MPC profiling: per-phase (labeled by protocol) + mpc_total_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_total_duration_seconds", + "End-to-end duration of MPC protocol", + &["protocol"], + MPC_PROTOCOL_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_dealer_crypto_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_dealer_crypto_duration_seconds", + "Duration of dealer crypto", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_p2p_broadcast_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_p2p_broadcast_duration_seconds", + "Duration of send_to_many", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_cert_publish_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_cert_publish_duration_seconds", + "Duration of tob_channel.publish", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_tob_poll_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_tob_poll_duration_seconds", + "Duration of tob_channel.receive", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_cert_verify_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_cert_verify_duration_seconds", + "Duration of BLS certificate signature verification", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_message_process_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_message_process_duration_seconds", + "Duration of AVSS message processing", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_message_retrieval_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_message_retrieval_duration_seconds", + "Duration of retrieve_dealer_message", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_complaint_recovery_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_complaint_recovery_duration_seconds", + "Duration of complaint recovery", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_completion_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_completion_duration_seconds", + "Duration of final aggregation", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_presig_conversion_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_presig_conversion_duration_seconds", + "Duration of Presignatures::new", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_rotation_prepare_previous_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_rotation_prepare_previous_duration_seconds", + "Duration of prepare_previous_output", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + + // MPC profiling: signing phase breakdown + mpc_sign_partial_gen_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_sign_partial_gen_duration_seconds", + "Duration of generate_partial_signatures", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_sign_collection_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_sign_collection_duration_seconds", + "Duration of P2P partial signature collection from peers", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_sign_aggregation_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_sign_aggregation_duration_seconds", + "Duration of aggregate_signatures / RS recovery", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + + // MPC profiling: RPC handler + mpc_rpc_handler_process_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_rpc_handler_process_duration_seconds", + "Duration of process_message in RPC handler", + &["protocol"], + MPC_PHASE_DURATION_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + + // MPC profiling: communication volume + mpc_bytes_sent_total: register_int_counter_vec_with_registry!( + "hashi_mpc_bytes_sent_total", + "Total bytes sent in MPC P2P messages", + &["protocol"], + registry, + ) + .unwrap(), + mpc_bytes_received_total: register_int_counter_vec_with_registry!( + "hashi_mpc_bytes_received_total", + "Total bytes received in MPC P2P messages", + &["protocol"], + registry, + ) + .unwrap(), + mpc_p2p_message_size_bytes: register_histogram_vec_with_registry!( + "hashi_mpc_p2p_message_size_bytes", + "Size of each MPC P2P message sent (bytes)", + &["protocol"], + MPC_MESSAGE_SIZE_BUCKETS.to_vec(), + registry, + ) + .unwrap(), } } diff --git a/crates/hashi/src/mpc/mpc_except_signing.rs b/crates/hashi/src/mpc/mpc_except_signing.rs index e939b4a6e..177691fe4 100644 --- a/crates/hashi/src/mpc/mpc_except_signing.rs +++ b/crates/hashi/src/mpc/mpc_except_signing.rs @@ -7,6 +7,10 @@ use crate::communication::send_to_many; use crate::communication::with_timeout_and_retry; use crate::constants::SUI_MAINNET_CHAIN_ID; use crate::constants::SUI_TESTNET_CHAIN_ID; +use crate::metrics::MPC_LABEL_DKG; +use crate::metrics::MPC_LABEL_KEY_ROTATION; +use crate::metrics::MPC_LABEL_NONCE_GEN; +use crate::metrics::Metrics; use crate::mpc::types::CertificateV1; pub use crate::mpc::types::ComplainRequest; pub use crate::mpc::types::ComplaintResponses; @@ -68,6 +72,7 @@ use std::collections::HashSet; use std::sync::Arc; use std::sync::LazyLock; use std::sync::RwLock; +use std::time::Instant; use sui_sdk_types::Address; const ERR_PUBLISH_CERT_FAILED: &str = "Failed to publish certificate"; @@ -492,6 +497,7 @@ impl MpcManager { mpc_manager: &Arc>, p2p_channel: &impl P2PChannel, tob_channel: &mut impl OrderedBroadcastChannel, + metrics: &Metrics, ) -> MpcResult { let certified = tob_channel.certified_dealers().await; let (certified_reduced_weight, threshold) = { @@ -510,11 +516,12 @@ impl MpcManager { (weight, mgr.mpc_config.threshold as u32) }; if certified_reduced_weight < threshold - && let Err(e) = Self::run_dkg_as_dealer(mpc_manager, p2p_channel, tob_channel).await + && let Err(e) = + Self::run_dkg_as_dealer(mpc_manager, p2p_channel, tob_channel, metrics).await { tracing::error!("Dealer phase failed: {}. Continuing as party only.", e); } - Self::run_dkg_as_party(mpc_manager, p2p_channel, tob_channel).await + Self::run_dkg_as_party(mpc_manager, p2p_channel, tob_channel, metrics).await } pub async fn run_key_rotation( @@ -522,9 +529,15 @@ impl MpcManager { previous_certificates: &[CertificateV1], p2p_channel: &impl P2PChannel, ordered_broadcast_channel: &mut impl OrderedBroadcastChannel, + metrics: &Metrics, ) -> MpcResult { + let prepare_prev_start = Instant::now(); let (previous, is_member_of_previous_committee) = Self::prepare_previous_output(mpc_manager, previous_certificates, p2p_channel).await?; + metrics + .mpc_rotation_prepare_previous_duration_seconds + .with_label_values(&[MPC_LABEL_KEY_ROTATION]) + .observe(prepare_prev_start.elapsed().as_secs_f64()); { let mut mgr = mpc_manager.write().unwrap(); mgr.previous_output = Some(previous.clone()); @@ -565,6 +578,7 @@ impl MpcManager { &previous, p2p_channel, ordered_broadcast_channel, + metrics, ) .await { @@ -578,6 +592,7 @@ impl MpcManager { &previous, p2p_channel, ordered_broadcast_channel, + metrics, ) .await } @@ -587,6 +602,7 @@ impl MpcManager { batch_index: u32, p2p_channel: &impl P2PChannel, tob_channel: &mut impl OrderedBroadcastChannel, + metrics: &Metrics, ) -> MpcResult> { // Clear stale state from previous batch. { @@ -614,8 +630,14 @@ impl MpcManager { (weight, mgr.required_nonce_weight()) }; if certified_reduced_weight < required_reduced_weight - && let Err(e) = - Self::run_as_nonce_dealer(mpc_manager, batch_index, p2p_channel, tob_channel).await + && let Err(e) = Self::run_as_nonce_dealer( + mpc_manager, + batch_index, + p2p_channel, + tob_channel, + metrics, + ) + .await { tracing::error!( "Nonce dealer phase failed: {}. Continuing as party only.", @@ -623,7 +645,8 @@ impl MpcManager { ); } let certified = - Self::run_as_nonce_party(mpc_manager, batch_index, p2p_channel, tob_channel).await?; + Self::run_as_nonce_party(mpc_manager, batch_index, p2p_channel, tob_channel, metrics) + .await?; let mut mgr = mpc_manager.write().unwrap(); // Keep only the outputs selected by the party phase. The RPC handler's // `try_sign_nonce_message` may have inserted additional outputs @@ -709,8 +732,10 @@ impl MpcManager { mpc_manager: &Arc>, p2p_channel: &impl P2PChannel, tob_channel: &mut impl OrderedBroadcastChannel, + metrics: &Metrics, ) -> MpcResult<()> { // TODO(Optimization): Skip dealer phase if certificate is already on TOB + let crypto_start = Instant::now(); let dealer_data = { let mgr = Arc::clone(mpc_manager); spawn_blocking(move || { @@ -720,6 +745,10 @@ impl MpcManager { }) .await? }; + metrics + .mpc_dealer_crypto_duration_seconds + .with_label_values(&[MPC_LABEL_DKG]) + .observe(crypto_start.elapsed().as_secs_f64()); let mut aggregator = BlsSignatureAggregator::new_with_reduced_weights( &dealer_data.committee, dealer_data.messages_hash.clone(), @@ -728,12 +757,17 @@ impl MpcManager { aggregator .add_signature(dealer_data.my_signature) .expect("first signature should always be valid"); + let broadcast_start = Instant::now(); let results = send_to_many( dealer_data.recipients.iter().copied(), dealer_data.request, |addr, req| async move { p2p_channel.send_messages(&addr, &req).await }, ) .await; + metrics + .mpc_p2p_broadcast_duration_seconds + .with_label_values(&[MPC_LABEL_DKG]) + .observe(broadcast_start.elapsed().as_secs_f64()); for (addr, result) in results { match result { Ok(response) => { @@ -749,11 +783,16 @@ impl MpcManager { .finish() .expect("signatures should always be valid"); let cert = CertificateV1::Dkg(dkg_cert); + let publish_start = Instant::now(); with_timeout_and_retry(|| tob_channel.publish(cert.clone())) .await .map_err(|e| { MpcError::BroadcastError(format!("{}: {}", ERR_PUBLISH_CERT_FAILED, e)) })?; + metrics + .mpc_cert_publish_duration_seconds + .with_label_values(&[MPC_LABEL_DKG]) + .observe(publish_start.elapsed().as_secs_f64()); } Ok(()) } @@ -762,6 +801,7 @@ impl MpcManager { mpc_manager: &Arc>, p2p_channel: &impl P2PChannel, tob_channel: &mut impl OrderedBroadcastChannel, + metrics: &Metrics, ) -> MpcResult { let threshold = { let mgr = mpc_manager.read().unwrap(); @@ -773,10 +813,15 @@ impl MpcManager { if dealer_weight_sum >= threshold { break; } + let poll_start = Instant::now(); let cert = tob_channel .receive() .await .map_err(|e| MpcError::BroadcastError(e.to_string()))?; + metrics + .mpc_tob_poll_duration_seconds + .with_label_values(&[MPC_LABEL_DKG]) + .observe(poll_start.elapsed().as_secs_f64()); let CertificateV1::Dkg(dkg_cert) = cert else { continue; }; @@ -786,6 +831,7 @@ impl MpcManager { continue; } { + let verify_start = Instant::now(); let mgr = Arc::clone(mpc_manager); let cert = dkg_cert.clone(); let verified = spawn_blocking(move || { @@ -793,6 +839,10 @@ impl MpcManager { mgr.committee.verify_signature(&cert) }) .await; + metrics + .mpc_cert_verify_duration_seconds + .with_label_values(&[MPC_LABEL_DKG]) + .observe(verify_start.elapsed().as_secs_f64()); if let Err(e) = verified { tracing::info!("Invalid certificate signature from {:?}: {}", &dealer, e); continue; @@ -813,6 +863,7 @@ impl MpcManager { "Certificate from dealer {:?} received but message missing or hash mismatch, retrieving from signers", &dealer ); + let retrieval_start = Instant::now(); Self::retrieve_dealer_message(mpc_manager, message, &dkg_cert, p2p_channel) .await .map_err(|e| { @@ -823,6 +874,10 @@ impl MpcManager { ); e })?; + metrics + .mpc_message_retrieval_duration_seconds + .with_label_values(&[MPC_LABEL_DKG]) + .observe(retrieval_start.elapsed().as_secs_f64()); // Delete stale output from the RPC handler so the party phase // reprocesses with the retrieved (certified) message. mpc_manager @@ -831,6 +886,7 @@ impl MpcManager { .dealer_outputs .remove(&DealerOutputsKey::Dkg(dealer)); } + let process_start = Instant::now(); let has_complaint = { let mgr = Arc::clone(mpc_manager); spawn_blocking(move || { @@ -851,18 +907,23 @@ impl MpcManager { }) .await? }; + metrics + .mpc_message_process_duration_seconds + .with_label_values(&[MPC_LABEL_DKG]) + .observe(process_start.elapsed().as_secs_f64()); if has_complaint { tracing::info!( "DKG complaint detected for dealer {:?}, recovering via Complain RPC", dealer ); - let signers = { + let complaint_start = Instant::now(); + let (signers, epoch) = { let mgr = mpc_manager.read().unwrap(); - dkg_cert + let signers = dkg_cert .signers(&mgr.committee) - .expect("certificate verified above") + .expect("certificate verified above"); + (signers, mgr.mpc_config.epoch) }; - let epoch = mpc_manager.read().unwrap().mpc_config.epoch; Self::recover_shares_via_complaint( mpc_manager, &dealer, @@ -871,6 +932,10 @@ impl MpcManager { epoch, ) .await?; + metrics + .mpc_complaint_recovery_duration_seconds + .with_label_values(&[MPC_LABEL_DKG]) + .observe(complaint_start.elapsed().as_secs_f64()); } let dealer_weight = { let mgr = mpc_manager.read().unwrap(); @@ -894,6 +959,7 @@ impl MpcManager { dealer_weight_sum += dealer_weight as u32; certified_dealers.insert(dealer); } + let completion_start = Instant::now(); let output = { let mgr = Arc::clone(mpc_manager); spawn_blocking(move || { @@ -902,6 +968,10 @@ impl MpcManager { }) .await? }; + metrics + .mpc_completion_duration_seconds + .with_label_values(&[MPC_LABEL_DKG]) + .observe(completion_start.elapsed().as_secs_f64()); Ok(output) } @@ -910,8 +980,10 @@ impl MpcManager { previous: &MpcOutput, p2p_channel: &impl P2PChannel, ordered_broadcast_channel: &mut impl OrderedBroadcastChannel, + metrics: &Metrics, ) -> MpcResult<()> { // TODO(Optimization): Skip dealer phase if certificate is already on TOB + let crypto_start = Instant::now(); let dealer_data = { let mgr = Arc::clone(mpc_manager); let previous = previous.clone(); @@ -922,6 +994,10 @@ impl MpcManager { }) .await? }; + metrics + .mpc_dealer_crypto_duration_seconds + .with_label_values(&[MPC_LABEL_KEY_ROTATION]) + .observe(crypto_start.elapsed().as_secs_f64()); let mut aggregator = BlsSignatureAggregator::new_with_reduced_weights( &dealer_data.committee, dealer_data.messages_hash.clone(), @@ -930,12 +1006,17 @@ impl MpcManager { aggregator .add_signature(dealer_data.my_signature) .expect("first signature should always be valid"); + let broadcast_start = Instant::now(); let results = send_to_many( dealer_data.recipients.iter().copied(), dealer_data.request, |addr, req| async move { p2p_channel.send_messages(&addr, &req).await }, ) .await; + metrics + .mpc_p2p_broadcast_duration_seconds + .with_label_values(&[MPC_LABEL_KEY_ROTATION]) + .observe(broadcast_start.elapsed().as_secs_f64()); for (addr, result) in results { match result { Ok(response) => { @@ -954,11 +1035,16 @@ impl MpcManager { .finish() .expect("signatures should always be valid"); let cert = CertificateV1::Rotation(rotation_cert); + let publish_start = Instant::now(); with_timeout_and_retry(|| ordered_broadcast_channel.publish(cert.clone())) .await .map_err(|e| { MpcError::BroadcastError(format!("{}: {}", ERR_PUBLISH_CERT_FAILED, e)) })?; + metrics + .mpc_cert_publish_duration_seconds + .with_label_values(&[MPC_LABEL_KEY_ROTATION]) + .observe(publish_start.elapsed().as_secs_f64()); } Ok(()) } @@ -968,6 +1054,7 @@ impl MpcManager { previous: &MpcOutput, p2p_channel: &impl P2PChannel, ordered_broadcast_channel: &mut impl OrderedBroadcastChannel, + metrics: &Metrics, ) -> MpcResult { let mut certified_share_indices: Vec = Vec::new(); let mut certified_dealers = HashSet::new(); @@ -975,10 +1062,15 @@ impl MpcManager { if certified_share_indices.len() >= previous.threshold as usize { break; } + let poll_start = Instant::now(); let cert = ordered_broadcast_channel .receive() .await .map_err(|e| MpcError::BroadcastError(e.to_string()))?; + metrics + .mpc_tob_poll_duration_seconds + .with_label_values(&[MPC_LABEL_KEY_ROTATION]) + .observe(poll_start.elapsed().as_secs_f64()); let CertificateV1::Rotation(rotation_cert) = cert else { continue; }; @@ -988,6 +1080,7 @@ impl MpcManager { continue; } { + let verify_start = Instant::now(); let mgr = Arc::clone(mpc_manager); let cert = rotation_cert.clone(); let verified = spawn_blocking(move || { @@ -995,6 +1088,10 @@ impl MpcManager { mgr.committee.verify_signature(&cert) }) .await; + metrics + .mpc_cert_verify_duration_seconds + .with_label_values(&[MPC_LABEL_KEY_ROTATION]) + .observe(verify_start.elapsed().as_secs_f64()); if let Err(e) = verified { tracing::info!( "Invalid rotation certificate signature from {:?}: {}", @@ -1040,6 +1137,7 @@ impl MpcManager { "Rotation messages from dealer {:?} not available or hash mismatch, retrieving from signers", dealer ); + let retrieval_start = Instant::now(); Self::retrieve_rotation_messages(mpc_manager, message, &rotation_cert, p2p_channel) .await .map_err(|e| { @@ -1050,6 +1148,10 @@ impl MpcManager { ); e })?; + metrics + .mpc_message_retrieval_duration_seconds + .with_label_values(&[MPC_LABEL_KEY_ROTATION]) + .observe(retrieval_start.elapsed().as_secs_f64()); // Delete stale outputs from the RPC handler so the party phase // reprocesses with the retrieved (certified) messages. { @@ -1060,6 +1162,7 @@ impl MpcManager { } } { + let process_start = Instant::now(); let mgr = Arc::clone(mpc_manager); let previous = previous.clone(); let share_indices = dealer_share_indices.clone(); @@ -1077,14 +1180,19 @@ impl MpcManager { Ok::<_, MpcError>(()) }) .await?; + metrics + .mpc_message_process_duration_seconds + .with_label_values(&[MPC_LABEL_KEY_ROTATION]) + .observe(process_start.elapsed().as_secs_f64()); } - let signers = { + let (signers, epoch) = { let mgr = mpc_manager.read().unwrap(); - rotation_cert + let signers = rotation_cert .signers(&mgr.committee) - .expect("certificate verified above") + .expect("certificate verified above"); + (signers, mgr.mpc_config.epoch) }; - let epoch = mpc_manager.read().unwrap().mpc_config.epoch; + let complaint_start = Instant::now(); Self::recover_rotation_shares_via_complaints( mpc_manager, &dealer, @@ -1094,6 +1202,10 @@ impl MpcManager { epoch, ) .await?; + metrics + .mpc_complaint_recovery_duration_seconds + .with_label_values(&[MPC_LABEL_KEY_ROTATION]) + .observe(complaint_start.elapsed().as_secs_f64()); // Only add indices that have outputs (avoids adding indices for // dealers with empty rotation messages, e.g. a node that rejoined // with no shares from the new-member fallback). @@ -1111,6 +1223,7 @@ impl MpcManager { } certified_dealers.insert(dealer); } + let completion_start = Instant::now(); let output = { let mgr = Arc::clone(mpc_manager); let previous = previous.clone(); @@ -1120,6 +1233,10 @@ impl MpcManager { }) .await? }; + metrics + .mpc_completion_duration_seconds + .with_label_values(&[MPC_LABEL_KEY_ROTATION]) + .observe(completion_start.elapsed().as_secs_f64()); Ok(output) } @@ -1128,7 +1245,9 @@ impl MpcManager { batch_index: u32, p2p_channel: &impl P2PChannel, tob_channel: &mut impl OrderedBroadcastChannel, + metrics: &Metrics, ) -> MpcResult<()> { + let crypto_start = Instant::now(); let dealer_data = { let mgr = Arc::clone(mpc_manager); spawn_blocking(move || { @@ -1138,6 +1257,10 @@ impl MpcManager { }) .await? }; + metrics + .mpc_dealer_crypto_duration_seconds + .with_label_values(&[MPC_LABEL_NONCE_GEN]) + .observe(crypto_start.elapsed().as_secs_f64()); let mut aggregator = BlsSignatureAggregator::new_with_reduced_weights( &dealer_data.committee, dealer_data.messages_hash.clone(), @@ -1146,12 +1269,17 @@ impl MpcManager { aggregator .add_signature(dealer_data.my_signature) .expect("first signature should always be valid"); + let broadcast_start = Instant::now(); let results = send_to_many( dealer_data.recipients.iter().copied(), dealer_data.request, |addr, req| async move { p2p_channel.send_messages(&addr, &req).await }, ) .await; + metrics + .mpc_p2p_broadcast_duration_seconds + .with_label_values(&[MPC_LABEL_NONCE_GEN]) + .observe(broadcast_start.elapsed().as_secs_f64()); for (addr, result) in results { match result { Ok(response) => { @@ -1170,11 +1298,16 @@ impl MpcManager { batch_index, cert: nonce_cert, }; + let publish_start = Instant::now(); with_timeout_and_retry(|| tob_channel.publish(cert.clone())) .await .map_err(|e| { MpcError::BroadcastError(format!("{}: {}", ERR_PUBLISH_CERT_FAILED, e)) })?; + metrics + .mpc_cert_publish_duration_seconds + .with_label_values(&[MPC_LABEL_NONCE_GEN]) + .observe(publish_start.elapsed().as_secs_f64()); } Ok(()) } @@ -1184,6 +1317,7 @@ impl MpcManager { batch_index: u32, p2p_channel: &impl P2PChannel, tob_channel: &mut impl OrderedBroadcastChannel, + metrics: &Metrics, ) -> MpcResult> { let required_weight = { let mgr = mpc_manager.read().unwrap(); @@ -1195,10 +1329,15 @@ impl MpcManager { if dealer_weight_sum >= required_weight { break; } + let poll_start = Instant::now(); let cert = tob_channel .receive() .await .map_err(|e| MpcError::BroadcastError(e.to_string()))?; + metrics + .mpc_tob_poll_duration_seconds + .with_label_values(&[MPC_LABEL_NONCE_GEN]) + .observe(poll_start.elapsed().as_secs_f64()); let CertificateV1::NonceGeneration { cert: nonce_cert, .. } = cert @@ -1211,6 +1350,7 @@ impl MpcManager { continue; } { + let verify_start = Instant::now(); let mgr = Arc::clone(mpc_manager); let cert = nonce_cert.clone(); let verified = spawn_blocking(move || { @@ -1218,6 +1358,10 @@ impl MpcManager { mgr.committee.verify_signature(&cert) }) .await; + metrics + .mpc_cert_verify_duration_seconds + .with_label_values(&[MPC_LABEL_NONCE_GEN]) + .observe(verify_start.elapsed().as_secs_f64()); if let Err(e) = verified { tracing::info!( "Invalid nonce certificate signature from {:?}: {}", @@ -1236,6 +1380,7 @@ impl MpcManager { "Nonce message for dealer {:?} not found in memory or DB, retrieving from signers", &dealer ); + let retrieval_start = Instant::now(); Self::retrieve_nonce_message( mpc_manager, message, @@ -1252,6 +1397,10 @@ impl MpcManager { ); e })?; + metrics + .mpc_message_retrieval_duration_seconds + .with_label_values(&[MPC_LABEL_NONCE_GEN]) + .observe(retrieval_start.elapsed().as_secs_f64()); // Delete stale output from the RPC handler so the party phase // reprocesses with the retrieved (certified) message. mpc_manager @@ -1260,6 +1409,7 @@ impl MpcManager { .dealer_nonce_outputs .remove(&dealer); } + let process_start = Instant::now(); let has_complaint = { let mgr = Arc::clone(mpc_manager); spawn_blocking(move || { @@ -1278,18 +1428,23 @@ impl MpcManager { }) .await? }; + metrics + .mpc_message_process_duration_seconds + .with_label_values(&[MPC_LABEL_NONCE_GEN]) + .observe(process_start.elapsed().as_secs_f64()); if has_complaint { tracing::info!( "Nonce gen complaint detected for dealer {:?}, recovering via Complain RPC", dealer ); - let signers = { + let complaint_start = Instant::now(); + let (signers, epoch) = { let mgr = mpc_manager.read().unwrap(); - nonce_cert + let signers = nonce_cert .signers(&mgr.committee) - .expect("certificate verified above") + .expect("certificate verified above"); + (signers, mgr.mpc_config.epoch) }; - let epoch = mpc_manager.read().unwrap().mpc_config.epoch; Self::recover_nonce_shares_via_complaint( mpc_manager, &dealer, @@ -1298,6 +1453,10 @@ impl MpcManager { epoch, ) .await?; + metrics + .mpc_complaint_recovery_duration_seconds + .with_label_values(&[MPC_LABEL_NONCE_GEN]) + .observe(complaint_start.elapsed().as_secs_f64()); } let dealer_weight = { let mgr = mpc_manager.read().unwrap(); diff --git a/crates/hashi/src/mpc/mpc_except_signing_tests.rs b/crates/hashi/src/mpc/mpc_except_signing_tests.rs index c4361e527..e18bc5358 100644 --- a/crates/hashi/src/mpc/mpc_except_signing_tests.rs +++ b/crates/hashi/src/mpc/mpc_except_signing_tests.rs @@ -3,6 +3,11 @@ use super::*; use crate::communication::ChannelResult; +use crate::metrics::Metrics; + +fn test_metrics() -> Metrics { + Metrics::new(&prometheus::Registry::new()) +} use crate::mpc::types::GetPartialSignaturesRequest; use crate::mpc::types::GetPartialSignaturesResponse; use crate::mpc::types::ProtocolType; @@ -1396,12 +1401,13 @@ async fn test_run_dkg() { let test_manager = Arc::new(RwLock::new(test_manager)); // Call run_as_dealer() and run_as_party() for validator 0 - MpcManager::run_dkg_as_dealer(&test_manager, &mock_p2p, &mut mock_tob) - .await - .unwrap(); - let output = MpcManager::run_dkg_as_party(&test_manager, &mock_p2p, &mut mock_tob) + MpcManager::run_dkg_as_dealer(&test_manager, &mock_p2p, &mut mock_tob, &test_metrics()) .await .unwrap(); + let output = + MpcManager::run_dkg_as_party(&test_manager, &mock_p2p, &mut mock_tob, &test_metrics()) + .await + .unwrap(); // Verify validator 0 received the correct number of key shares based on its weight assert_eq!( @@ -1514,12 +1520,13 @@ async fn test_run_dkg_with_complaint_recovery() { let test_manager = Arc::new(RwLock::new(test_manager)); - MpcManager::run_dkg_as_dealer(&test_manager, &mock_p2p, &mut mock_tob) - .await - .unwrap(); - let output = MpcManager::run_dkg_as_party(&test_manager, &mock_p2p, &mut mock_tob) + MpcManager::run_dkg_as_dealer(&test_manager, &mock_p2p, &mut mock_tob, &test_metrics()) .await .unwrap(); + let output = + MpcManager::run_dkg_as_party(&test_manager, &mock_p2p, &mut mock_tob, &test_metrics()) + .await + .unwrap(); // Verify output is valid despite cheating dealer assert_eq!( @@ -1615,9 +1622,14 @@ async fn test_run_triggers_dealer_phase() { let mut mock_tob = MockOrderedBroadcastChannel::new(setup.certificates) .with_override_certified_dealers(vec![]); - let output = MpcManager::run_dkg(&setup.test_manager, &setup.mock_p2p, &mut mock_tob) - .await - .unwrap(); + let output = MpcManager::run_dkg( + &setup.test_manager, + &setup.mock_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await + .unwrap(); // Verify dealer published a certificate assert!( @@ -1637,9 +1649,14 @@ async fn test_run_skips_dealer_phase() { // With 4 certificates and threshold = 2, existing_weight = 4 >= 2, dealer skips let mut mock_tob = MockOrderedBroadcastChannel::new(setup.certificates); - let output = MpcManager::run_dkg(&setup.test_manager, &setup.mock_p2p, &mut mock_tob) - .await - .unwrap(); + let output = MpcManager::run_dkg( + &setup.test_manager, + &setup.mock_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await + .unwrap(); // Verify dealer did NOT publish (skipped) assert_eq!( @@ -1663,9 +1680,14 @@ async fn test_run_dealer_failure_party_still_executes() { .with_override_certified_dealers(vec![]) .with_fail_on_publish("simulated publish failure"); - let output = MpcManager::run_dkg(&setup.test_manager, &setup.mock_p2p, &mut mock_tob) - .await - .unwrap(); + let output = MpcManager::run_dkg( + &setup.test_manager, + &setup.mock_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await + .unwrap(); // Verify DKG completed successfully (party phase executed despite dealer failure) assert_eq!(output.key_shares.shares.len(), 1); @@ -1692,7 +1714,9 @@ async fn test_run_as_dealer_success() { let mut mock_tob = MockOrderedBroadcastChannel::new(Vec::new()); // Call run_as_dealer() - let result = MpcManager::run_dkg_as_dealer(&test_manager, &mock_p2p, &mut mock_tob).await; + let result = + MpcManager::run_dkg_as_dealer(&test_manager, &mock_p2p, &mut mock_tob, &test_metrics()) + .await; // Verify success assert!(result.is_ok()); @@ -1774,9 +1798,10 @@ async fn test_run_as_party_success() { .map(|(idx, mgr)| (setup.address(idx + 1), mgr)) .collect(); let mock_p2p = MockP2PChannel::new(other_managers, setup.address(0)); - let output = MpcManager::run_dkg_as_party(&test_manager, &mock_p2p, &mut mock_tob) - .await - .unwrap(); + let output = + MpcManager::run_dkg_as_party(&test_manager, &mock_p2p, &mut mock_tob, &test_metrics()) + .await + .unwrap(); // Verify output structure assert_eq!(output.key_shares.shares.len(), 1); // weight = 1 @@ -1892,9 +1917,10 @@ async fn test_run_as_party_recovers_shares_via_complaint() { let party_manager = Arc::new(RwLock::new(party_manager)); // Run as party - should recover shares via complaint - let output = MpcManager::run_dkg_as_party(&party_manager, &mock_p2p, &mut mock_tob) - .await - .unwrap(); + let output = + MpcManager::run_dkg_as_party(&party_manager, &mock_p2p, &mut mock_tob, &test_metrics()) + .await + .unwrap(); // Verify complaint was resolved // DKG: complaints keyed by dealer address @@ -2004,9 +2030,10 @@ async fn test_run_as_party_recovers_from_hash_mismatch() { .map(|(idx, mgr)| (setup.address(idx + 1), mgr)) .collect(); let mock_p2p = MockP2PChannel::new(other_managers, setup.address(0)); - let output = MpcManager::run_dkg_as_party(&test_manager, &mock_p2p, &mut mock_tob) - .await - .unwrap(); + let output = + MpcManager::run_dkg_as_party(&test_manager, &mock_p2p, &mut mock_tob, &test_metrics()) + .await + .unwrap(); // The critical assertion: test_manager must produce the same vk as other nodes. // Without the delete-on-mismatch fix, the stale output from wrong_msg_0 would @@ -2075,9 +2102,10 @@ async fn test_run_as_party_requires_different_dealers() { }) .collect(); let mock_p2p = MockP2PChannel::new(other_managers, setup.address(2)); - let output = MpcManager::run_dkg_as_party(&test_manager, &mock_p2p, &mut mock_tob) - .await - .unwrap(); + let output = + MpcManager::run_dkg_as_party(&test_manager, &mock_p2p, &mut mock_tob, &test_metrics()) + .await + .unwrap(); // Verify it correctly waited for 2 different dealers assert_eq!(output.key_shares.shares.len(), 1); // weight = 1 @@ -2098,7 +2126,9 @@ async fn test_run_as_dealer_p2p_send_error() { }; let mut mock_tob = MockOrderedBroadcastChannel::new(Vec::new()); - let result = MpcManager::run_dkg_as_dealer(&test_manager, &failing_p2p, &mut mock_tob).await; + let result = + MpcManager::run_dkg_as_dealer(&test_manager, &failing_p2p, &mut mock_tob, &test_metrics()) + .await; assert!(result.is_ok()); assert_eq!(mock_tob.published_count(), 0); @@ -2130,8 +2160,13 @@ async fn test_run_as_dealer_tob_publish_error() { fail_on_receive: false, }; - let result = - MpcManager::run_dkg_as_dealer(&test_manager, &succeeding_p2p, &mut failing_tob).await; + let result = MpcManager::run_dkg_as_dealer( + &test_manager, + &succeeding_p2p, + &mut failing_tob, + &test_metrics(), + ) + .await; assert!(result.is_err()); let err = result.unwrap_err(); @@ -2164,8 +2199,13 @@ async fn test_run_as_dealer_partial_failures_still_collects_enough() { let mut mock_tob = MockOrderedBroadcastChannel::new(Vec::new()); - let result = - MpcManager::run_dkg_as_dealer(&test_manager, &partially_failing_p2p, &mut mock_tob).await; + let result = MpcManager::run_dkg_as_dealer( + &test_manager, + &partially_failing_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await; assert!(result.is_ok()); // Verify that a certificate was published @@ -2193,8 +2233,13 @@ async fn test_run_as_dealer_partial_failures_insufficient_signatures() { let mut mock_tob = MockOrderedBroadcastChannel::new(Vec::new()); - let result = - MpcManager::run_dkg_as_dealer(&test_manager, &partially_failing_p2p, &mut mock_tob).await; + let result = MpcManager::run_dkg_as_dealer( + &test_manager, + &partially_failing_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await; assert!(result.is_ok()); assert_eq!(mock_tob.published_count(), 0); @@ -2222,7 +2267,9 @@ async fn test_run_as_dealer_includes_own_signature() { let mut mock_tob = MockOrderedBroadcastChannel::new(Vec::new()); // Run as dealer - let result = MpcManager::run_dkg_as_dealer(&test_manager, &mock_p2p, &mut mock_tob).await; + let result = + MpcManager::run_dkg_as_dealer(&test_manager, &mock_p2p, &mut mock_tob, &test_metrics()) + .await; assert!(result.is_ok()); @@ -2265,7 +2312,9 @@ async fn test_run_as_party_tob_receive_error() { }; let mock_p2p = MockP2PChannel::new(HashMap::new(), setup.address(0)); - let result = MpcManager::run_dkg_as_party(&test_manager, &mock_p2p, &mut failing_tob).await; + let result = + MpcManager::run_dkg_as_party(&test_manager, &mock_p2p, &mut failing_tob, &test_metrics()) + .await; assert!(result.is_err()); let err = result.unwrap_err(); @@ -2377,7 +2426,9 @@ async fn setup_party_and_run( // Run party collection let mock_p2p = MockP2PChannel::new(HashMap::new(), party_addr); let party_manager = Arc::new(RwLock::new(party_manager)); - let result = MpcManager::run_dkg_as_party(&party_manager, &mock_p2p, &mut mock_tob).await; + let result = + MpcManager::run_dkg_as_party(&party_manager, &mock_p2p, &mut mock_tob, &test_metrics()) + .await; (result, mock_tob) } @@ -2519,7 +2570,9 @@ async fn test_run_as_party_skips_duplicate_dealers() { // Run party collection let mock_p2p = MockP2PChannel::new(HashMap::new(), party_addr); let party_manager = Arc::new(RwLock::new(party_manager)); - let result = MpcManager::run_dkg_as_party(&party_manager, &mock_p2p, &mut mock_tob).await; + let result = + MpcManager::run_dkg_as_party(&party_manager, &mock_p2p, &mut mock_tob, &test_metrics()) + .await; assert!(result.is_ok()); // Verify behavior: @@ -2605,7 +2658,9 @@ async fn test_run_as_party_retrieves_missing_dealer_messages() { let party_manager = Arc::new(RwLock::new(party_manager)); // Run as party - should retrieve missing messages via P2P - let result = MpcManager::run_dkg_as_party(&party_manager, &mock_p2p, &mut mock_tob).await; + let result = + MpcManager::run_dkg_as_party(&party_manager, &mock_p2p, &mut mock_tob, &test_metrics()) + .await; assert!(result.is_ok()); let mgr = party_manager.read().unwrap(); @@ -2708,7 +2763,9 @@ async fn test_run_as_party_aborts_on_retrieval_failure() { let party_manager = Arc::new(RwLock::new(party_manager)); // Run as party - should process dealer1 successfully, then ABORT on dealer2 retrieval failure - let result = MpcManager::run_dkg_as_party(&party_manager, &mock_p2p, &mut mock_tob).await; + let result = + MpcManager::run_dkg_as_party(&party_manager, &mock_p2p, &mut mock_tob, &test_metrics()) + .await; // Should fail with PairwiseCommunicationError (could not retrieve message from any signer) assert!(result.is_err()); @@ -2803,7 +2860,9 @@ async fn test_run_as_party_aborts_on_failed_recovery() { // Run as party - should ABORT on dealer0 recovery failure // With retry logic, failed signers are skipped, so we get ProtocolFailed - let result = MpcManager::run_dkg_as_party(&party_manager, &mock_p2p, &mut mock_tob).await; + let result = + MpcManager::run_dkg_as_party(&party_manager, &mock_p2p, &mut mock_tob, &test_metrics()) + .await; // Should fail with ProtocolFailed (all signers failed, not enough responses) assert!(result.is_err(), "Expected error, got: {:?}", result); @@ -4607,7 +4666,13 @@ async fn test_restart_dealer_reuses_stored_message() { let restarted_manager = Arc::new(RwLock::new(restarted_manager)); // Run run_as_dealer - should reuse stored message - let result = MpcManager::run_dkg_as_dealer(&restarted_manager, &mock_p2p, &mut mock_tob).await; + let result = MpcManager::run_dkg_as_dealer( + &restarted_manager, + &mock_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await; assert!(result.is_ok()); // Verify store_dealer_message was NOT called (message already existed) @@ -4721,7 +4786,13 @@ async fn test_restart_party_uses_stored_messages_without_retrieval() { let party_manager = Arc::new(RwLock::new(party_manager)); // Run as party - let result = MpcManager::run_dkg_as_party(&party_manager, &tracking_p2p, &mut mock_tob).await; + let result = MpcManager::run_dkg_as_party( + &party_manager, + &tracking_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await; assert!(result.is_ok()); // Verify retrieve_message was NOT called (messages were already in memory) @@ -5257,6 +5328,7 @@ async fn test_run_key_rotation() { &rotation_setup.certificates(), &mock_p2p, &mut mock_tob, + &test_metrics(), ) .await .unwrap(); @@ -5386,6 +5458,7 @@ async fn test_run_key_rotation_skips_dealer_phase() { &rotation_setup.certificates(), &mock_p2p, &mut mock_tob, + &test_metrics(), ) .await .unwrap(); @@ -5592,6 +5665,7 @@ async fn test_run_key_rotation_recovers_from_hash_mismatch() { &rotation_setup.certificates(), &mock_p2p, &mut mock_tob, + &test_metrics(), ) .await .unwrap(); @@ -5725,6 +5799,7 @@ async fn test_run_key_rotation_with_complaint_recovery() { &rotation_setup.certificates(), &mock_p2p, &mut mock_tob, + &test_metrics(), ) .await .unwrap(); @@ -8215,12 +8290,24 @@ async fn test_run_nonce_generation() { let test_manager = Arc::new(RwLock::new(test_manager)); - MpcManager::run_as_nonce_dealer(&test_manager, batch_index, &mock_p2p, &mut mock_tob) - .await - .unwrap(); - MpcManager::run_as_nonce_party(&test_manager, batch_index, &mock_p2p, &mut mock_tob) - .await - .unwrap(); + MpcManager::run_as_nonce_dealer( + &test_manager, + batch_index, + &mock_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await + .unwrap(); + MpcManager::run_as_nonce_party( + &test_manager, + batch_index, + &mock_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await + .unwrap(); // Verify validator 0 has nonce outputs from enough dealers let mgr = test_manager.read().unwrap(); @@ -8359,9 +8446,15 @@ async fn test_run_as_nonce_party_recovers_from_hash_mismatch() { let mut mock_tob = MockOrderedBroadcastChannel::new(all_certs); let test_manager = Arc::new(RwLock::new(test_manager)); - MpcManager::run_as_nonce_party(&test_manager, batch_index, &mock_p2p, &mut mock_tob) - .await - .unwrap(); + MpcManager::run_as_nonce_party( + &test_manager, + batch_index, + &mock_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await + .unwrap(); // Verify the nonce output for dealer 0 matches what a clean node has. // Without the delete-on-mismatch fix, the stale output from the wrong @@ -8431,10 +8524,15 @@ async fn test_run_nonce_generation_skips_dealer_phase() { let test_manager = Arc::new(RwLock::new(test_manager)); let mut mock_tob = MockOrderedBroadcastChannel::new(certificates); - let outputs = - MpcManager::run_nonce_generation(&test_manager, batch_index, &mock_p2p, &mut mock_tob) - .await - .unwrap(); + let outputs = MpcManager::run_nonce_generation( + &test_manager, + batch_index, + &mock_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await + .unwrap(); // Verify dealer did NOT publish (skipped) assert_eq!( @@ -8513,10 +8611,15 @@ async fn test_run_as_nonce_party_loads_from_store_after_restart() { // run_as_nonce_party should succeed by loading messages from the store, // not from P2P (which would fail since mock_p2p has no managers). - let certified = - MpcManager::run_as_nonce_party(&test_manager, batch_index, &mock_p2p, &mut mock_tob) - .await - .unwrap(); + let certified = MpcManager::run_as_nonce_party( + &test_manager, + batch_index, + &mock_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await + .unwrap(); let mgr = test_manager.read().unwrap(); assert!( @@ -8700,12 +8803,24 @@ async fn test_run_nonce_generation_with_complaint_recovery() { let test_manager = Arc::new(RwLock::new(test_manager)); - MpcManager::run_as_nonce_dealer(&test_manager, batch_index, &mock_p2p, &mut mock_tob) - .await - .unwrap(); - MpcManager::run_as_nonce_party(&test_manager, batch_index, &mock_p2p, &mut mock_tob) - .await - .unwrap(); + MpcManager::run_as_nonce_dealer( + &test_manager, + batch_index, + &mock_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await + .unwrap(); + MpcManager::run_as_nonce_party( + &test_manager, + batch_index, + &mock_p2p, + &mut mock_tob, + &test_metrics(), + ) + .await + .unwrap(); // Verify enough nonce outputs collected let mgr = test_manager.read().unwrap(); diff --git a/crates/hashi/src/mpc/rpc/p2p_channel.rs b/crates/hashi/src/mpc/rpc/p2p_channel.rs index 9aadc2ed3..9510cbfca 100644 --- a/crates/hashi/src/mpc/rpc/p2p_channel.rs +++ b/crates/hashi/src/mpc/rpc/p2p_channel.rs @@ -1,10 +1,13 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use std::sync::Arc; + use crate::communication::ChannelError; use crate::communication::ChannelResult; use crate::communication::P2PChannel; use crate::grpc::Client; +use crate::metrics::Metrics; use crate::mpc::types::ComplainRequest; use crate::mpc::types::ComplaintResponses; use crate::mpc::types::GetPartialSignaturesRequest; @@ -22,13 +25,22 @@ use sui_sdk_types::Address; pub struct RpcP2PChannel { onchain_state: OnchainState, epoch: u64, + metrics: Arc, + protocol_label: &'static str, } impl RpcP2PChannel { - pub fn new(onchain_state: OnchainState, epoch: u64) -> Self { + pub fn new( + onchain_state: OnchainState, + epoch: u64, + protocol_label: &'static str, + metrics: Arc, + ) -> Self { Self { onchain_state, epoch, + metrics, + protocol_label, } } @@ -49,10 +61,20 @@ impl P2PChannel for RpcP2PChannel { recipient: &Address, request: &SendMessagesRequest, ) -> ChannelResult { - self.get_client(recipient)? + let (response, request_size) = self + .get_client(recipient)? .send_messages(self.epoch, request) .await - .map_err(|e| ChannelError::RequestFailed(e.to_string())) + .map_err(|e| ChannelError::RequestFailed(e.to_string()))?; + self.metrics + .mpc_p2p_message_size_bytes + .with_label_values(&[self.protocol_label]) + .observe(request_size as f64); + self.metrics + .mpc_bytes_sent_total + .with_label_values(&[self.protocol_label]) + .inc_by(request_size as u64); + Ok(response) } async fn retrieve_messages( @@ -60,10 +82,20 @@ impl P2PChannel for RpcP2PChannel { party: &Address, request: &RetrieveMessagesRequest, ) -> ChannelResult { - self.get_client(party)? + let (response, request_size, response_size) = self + .get_client(party)? .retrieve_messages(request) .await - .map_err(|e| ChannelError::RequestFailed(e.to_string())) + .map_err(|e| ChannelError::RequestFailed(e.to_string()))?; + self.metrics + .mpc_bytes_sent_total + .with_label_values(&[self.protocol_label]) + .inc_by(request_size as u64); + self.metrics + .mpc_bytes_received_total + .with_label_values(&[self.protocol_label]) + .inc_by(response_size as u64); + Ok(response) } async fn complain( @@ -71,10 +103,16 @@ impl P2PChannel for RpcP2PChannel { party: &Address, request: &ComplainRequest, ) -> ChannelResult { - self.get_client(party)? + let (response, request_size) = self + .get_client(party)? .complain(request) .await - .map_err(|e| ChannelError::RequestFailed(e.to_string())) + .map_err(|e| ChannelError::RequestFailed(e.to_string()))?; + self.metrics + .mpc_bytes_sent_total + .with_label_values(&[self.protocol_label]) + .inc_by(request_size as u64); + Ok(response) } async fn get_public_mpc_output( @@ -93,9 +131,15 @@ impl P2PChannel for RpcP2PChannel { party: &Address, request: &GetPartialSignaturesRequest, ) -> ChannelResult { - self.get_client(party)? + let (response, request_size) = self + .get_client(party)? .get_partial_signatures(self.epoch, request) .await - .map_err(|e| ChannelError::RequestFailed(e.to_string())) + .map_err(|e| ChannelError::RequestFailed(e.to_string()))?; + self.metrics + .mpc_bytes_sent_total + .with_label_values(&[self.protocol_label]) + .inc_by(request_size as u64); + Ok(response) } } diff --git a/crates/hashi/src/mpc/rpc/service.rs b/crates/hashi/src/mpc/rpc/service.rs index 0dadeb075..7d665d3f4 100644 --- a/crates/hashi/src/mpc/rpc/service.rs +++ b/crates/hashi/src/mpc/rpc/service.rs @@ -19,6 +19,7 @@ use hashi_types::proto::RetrieveMessagesResponse; use hashi_types::proto::SendMessagesRequest; use hashi_types::proto::SendMessagesResponse; use hashi_types::proto::mpc_service_server::MpcService; +use std::time::Instant; use sui_sdk_types::Address; use tonic::Status; @@ -33,7 +34,13 @@ impl MpcService for HttpService { let external_request = request.into_inner(); let internal_request = types::SendMessagesRequest::try_from(&external_request) .map_err(|e| Status::invalid_argument(e.to_string()))?; + let label = match &internal_request.messages { + types::Messages::Dkg(_) => crate::metrics::MPC_LABEL_DKG, + types::Messages::Rotation(_) => crate::metrics::MPC_LABEL_KEY_ROTATION, + types::Messages::NonceGeneration(_) => crate::metrics::MPC_LABEL_NONCE_GEN, + }; let mpc_manager = self.mpc_manager()?; + let process_start = Instant::now(); let response = spawn_blocking(move || -> Result<_, Status> { let mut mgr = mpc_manager.write().unwrap(); validate_epoch(mgr.mpc_config.epoch, external_request.epoch)?; @@ -48,6 +55,10 @@ impl MpcService for HttpService { }) }) .await?; + self.metrics() + .mpc_rpc_handler_process_duration_seconds + .with_label_values(&[label]) + .observe(process_start.elapsed().as_secs_f64()); Ok(tonic::Response::new(SendMessagesResponse::from(&response))) } diff --git a/crates/hashi/src/mpc/service.rs b/crates/hashi/src/mpc/service.rs index 3457064a3..a9316e365 100644 --- a/crates/hashi/src/mpc/service.rs +++ b/crates/hashi/src/mpc/service.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use std::time::Duration; +use std::time::Instant; use fastcrypto::serde_helpers::ToFromByteArray; use fastcrypto::traits::ToFromBytes; @@ -20,6 +21,9 @@ use crate::Hashi; use crate::communication::SuiTobChannel; use crate::communication::fetch_certificates; use crate::constants::PRESIG_REFILL_DIVISOR; +use crate::metrics::MPC_LABEL_DKG; +use crate::metrics::MPC_LABEL_KEY_ROTATION; +use crate::metrics::MPC_LABEL_NONCE_GEN; use crate::mpc::MpcManager; use crate::mpc::MpcOutput; use crate::mpc::SigningManager; @@ -281,7 +285,12 @@ impl MpcService { .mpc_manager() .expect("MpcManager must be set before run_dkg"); let signer = self.inner.config.operator_private_key()?; - let p2p_channel = RpcP2PChannel::new(onchain_state.clone(), target_epoch); + let p2p_channel = RpcP2PChannel::new( + onchain_state.clone(), + target_epoch, + MPC_LABEL_DKG, + self.inner.metrics.clone(), + ); let mut tob_channel = SuiTobChannel::new( self.inner.config.hashi_ids(), onchain_state, @@ -289,9 +298,14 @@ impl MpcService { None, signer, ); - let output = MpcManager::run_dkg(&mpc_manager, &p2p_channel, &mut tob_channel) - .await - .map_err(|e| anyhow::anyhow!("DKG failed: {e}"))?; + let output = MpcManager::run_dkg( + &mpc_manager, + &p2p_channel, + &mut tob_channel, + &self.inner.metrics, + ) + .await + .map_err(|e| anyhow::anyhow!("DKG failed: {e}"))?; Ok(output) } @@ -314,7 +328,12 @@ impl MpcService { .mpc_manager() .ok_or_else(|| anyhow::anyhow!("MpcManager not initialized"))?; let signer = self.inner.config.operator_private_key()?; - let p2p_channel = RpcP2PChannel::new(onchain_state.clone(), epoch); + let p2p_channel = RpcP2PChannel::new( + onchain_state.clone(), + epoch, + MPC_LABEL_NONCE_GEN, + self.inner.metrics.clone(), + ); let mut tob_channel = SuiTobChannel::new( self.inner.config.hashi_ids(), onchain_state, @@ -322,14 +341,22 @@ impl MpcService { Some(batch_index), signer, ); - let nonce_outputs = MpcManager::run_nonce_generation( + let metrics = &self.inner.metrics; + let nonce_start = Instant::now(); + let nonce_result = MpcManager::run_nonce_generation( &mpc_manager, batch_index, &p2p_channel, &mut tob_channel, + metrics, ) - .await - .map_err(|e| anyhow::anyhow!("Nonce generation failed: {e}"))?; + .await; + metrics + .mpc_total_duration_seconds + .with_label_values(&[MPC_LABEL_NONCE_GEN]) + .observe(nonce_start.elapsed().as_secs_f64()); + let nonce_outputs = + nonce_result.map_err(|e| anyhow::anyhow!("Nonce generation failed: {e}"))?; let (batch_size_per_weight, f) = { let mgr = mpc_manager.read().unwrap(); ( @@ -337,8 +364,13 @@ impl MpcService { mgr.mpc_config.max_faulty as usize, ) }; + let presig_start = Instant::now(); let presignatures = Presignatures::new(nonce_outputs, batch_size_per_weight, f) .map_err(|e| anyhow::anyhow!("Failed to create presignatures: {e}"))?; + metrics + .mpc_presig_conversion_duration_seconds + .with_label_values(&[MPC_LABEL_NONCE_GEN]) + .observe(presig_start.elapsed().as_secs_f64()); Ok((committee, presignatures)) } @@ -461,7 +493,12 @@ impl MpcService { "No nonce gen certificates on TOB for epoch {epoch} batch {batch_index}" ) })?; - let p2p_channel = RpcP2PChannel::new(self.inner.onchain_state().clone(), epoch); + let p2p_channel = RpcP2PChannel::new( + self.inner.onchain_state().clone(), + epoch, + MPC_LABEL_NONCE_GEN, + self.inner.metrics.clone(), + ); let outputs = MpcManager::reconstruct_presignatures_with_complaint_recovery( mpc_manager, epoch, @@ -524,6 +561,7 @@ impl MpcService { } async fn handle_reconfig(&self, target_epoch: u64) { + let reconfig_start = Instant::now(); // Determine whether this is an initial DKG or a key rotation // based on if we already have a committed mpc_public_key. let run_dkg = self @@ -554,15 +592,26 @@ impl MpcService { return; } + let protocol_label = if run_dkg { + MPC_LABEL_DKG + } else { + MPC_LABEL_KEY_ROTATION + }; + let metrics = &self.inner.metrics; let output = loop { if self.get_pending_epoch_change() != Some(target_epoch) { return; } + let protocol_start = Instant::now(); let result = if run_dkg { self.run_dkg(target_epoch).await } else { self.run_key_rotation(target_epoch).await }; + metrics + .mpc_total_duration_seconds + .with_label_values(&[protocol_label]) + .observe(protocol_start.elapsed().as_secs_f64()); match result { Ok(output) => break output, Err(e) => { @@ -576,6 +625,7 @@ impl MpcService { }; let _ = self.key_ready_tx.send(Some(output.public_key)); info!("MPC key ready for epoch {target_epoch}, submitting end_reconfig"); + let end_reconfig_start = Instant::now(); loop { if self.get_pending_epoch_change() != Some(target_epoch) { break; @@ -591,7 +641,12 @@ impl MpcService { } } } + metrics + .mpc_end_reconfig_duration_seconds + .with_label_values(&[protocol_label]) + .observe(end_reconfig_start.elapsed().as_secs_f64()); info!("end_reconfig complete for epoch {target_epoch}, running prepare_signing"); + let prepare_signing_start = Instant::now(); for attempt in 1..=MAX_PROTOCOL_ATTEMPTS { match self.prepare_signing(target_epoch, &output).await { Ok(()) => break, @@ -611,6 +666,14 @@ impl MpcService { } } } + metrics + .mpc_prepare_signing_duration_seconds + .with_label_values(&[protocol_label]) + .observe(prepare_signing_start.elapsed().as_secs_f64()); + metrics + .mpc_reconfig_total_duration_seconds + .with_label_values(&[protocol_label]) + .observe(reconfig_start.elapsed().as_secs_f64()); } fn setup_initial_dkg(&self, target_epoch: u64) -> anyhow::Result<()> { @@ -642,7 +705,12 @@ impl MpcService { let previous_certs: Vec = previous_certs.into_iter().map(|(_, cert)| cert).collect(); let signer = self.inner.config.operator_private_key()?; - let p2p_channel = RpcP2PChannel::new(onchain_state.clone(), target_epoch); + let p2p_channel = RpcP2PChannel::new( + onchain_state.clone(), + target_epoch, + MPC_LABEL_KEY_ROTATION, + self.inner.metrics.clone(), + ); let mut tob_channel = SuiTobChannel::new( self.inner.config.hashi_ids(), onchain_state, @@ -655,6 +723,7 @@ impl MpcService { &previous_certs, &p2p_channel, &mut tob_channel, + &self.inner.metrics, ) .await .map_err(|e| anyhow::anyhow!("Key rotation failed: {e}"))?; diff --git a/crates/hashi/src/mpc/signing.rs b/crates/hashi/src/mpc/signing.rs index 21165a4d3..73cd6f798 100644 --- a/crates/hashi/src/mpc/signing.rs +++ b/crates/hashi/src/mpc/signing.rs @@ -26,6 +26,8 @@ use tokio::time::Instant; use crate::communication::P2PChannel; use crate::communication::send_to_many; +use crate::metrics::MPC_LABEL_SIGNING; +use crate::metrics::Metrics; use crate::mpc::types::GetPartialSignaturesRequest; use crate::mpc::types::GetPartialSignaturesResponse; use crate::mpc::types::PartialSigningOutput; @@ -180,6 +182,7 @@ impl SigningManager { beacon_value: &S, derivation_address: Option<&DerivationAddress>, timeout: Duration, + metrics: &Metrics, ) -> SigningResult { let (public_nonce, partial_sigs, threshold, address, committee, verifying_key) = { let mut mgr = signing_manager.write().unwrap(); @@ -253,6 +256,7 @@ impl SigningManager { batch_index={used_batch_index}, \ position={target_position})", ); + let gen_start = std::time::Instant::now(); let result = generate_partial_signatures( message, presig, @@ -262,6 +266,10 @@ impl SigningManager { derivation_address, ) .map_err(|e| SigningError::CryptoError(e.to_string()))?; + metrics + .mpc_sign_partial_gen_duration_seconds + .with_label_values(&[MPC_LABEL_SIGNING]) + .observe(gen_start.elapsed().as_secs_f64()); // Trigger refill based on the latest batch's consumption. if let Some(latest) = mgr.batches.last() { @@ -318,6 +326,7 @@ impl SigningManager { .collect(); let request = GetPartialSignaturesRequest { sui_request_id }; let deadline = Instant::now() + timeout; + let collection_start = std::time::Instant::now(); loop { if all_partial_sigs.len() >= threshold as usize { break; @@ -336,6 +345,10 @@ impl SigningManager { ) .await; } + metrics + .mpc_sign_collection_duration_seconds + .with_label_values(&[MPC_LABEL_SIGNING]) + .observe(collection_start.elapsed().as_secs_f64()); let params = AggregationParams { message, public_nonce: &public_nonce, @@ -344,6 +357,7 @@ impl SigningManager { verifying_key: &verifying_key, derivation_address, }; + let agg_start = std::time::Instant::now(); let result = match aggregate_signatures( params.message, params.public_nonce, @@ -372,6 +386,10 @@ impl SigningManager { } Err(e) => Err(SigningError::CryptoError(e.to_string())), }; + metrics + .mpc_sign_aggregation_duration_seconds + .with_label_values(&[MPC_LABEL_SIGNING]) + .observe(agg_start.elapsed().as_secs_f64()); match &result { Ok(_) => {} Err(e) => { @@ -533,6 +551,10 @@ mod tests { use rand::SeedableRng; use rand::rngs::StdRng; + fn test_metrics() -> Metrics { + Metrics::new(&prometheus::Registry::new()) + } + fn mock_shares(rng: &mut impl AllowedRng, secret: S, t: u16, n: u16) -> Vec> { let p = Poly::rand_fixed_c0(t - 1, secret, rng); (1..=n) @@ -1080,6 +1102,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await .unwrap(); @@ -1131,6 +1154,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await .unwrap(); @@ -1159,6 +1183,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await .unwrap(); @@ -1187,6 +1212,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await .unwrap(); @@ -1219,6 +1245,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await; @@ -1255,6 +1282,7 @@ mod tests { &beacon, None, Duration::from_millis(1), // very short timeout + &test_metrics(), ) .await; @@ -1335,6 +1363,7 @@ mod tests { &S::zero(), None, Duration::from_secs(30), + &test_metrics(), ) .await .unwrap(); @@ -1361,6 +1390,7 @@ mod tests { &S::zero(), None, Duration::from_secs(30), + &test_metrics(), ) .await; @@ -1417,6 +1447,7 @@ mod tests { &S::zero(), None, Duration::from_secs(30), + &test_metrics(), ) .await; @@ -1446,6 +1477,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await; assert!(result.is_ok(), "sign {i} should succeed"); @@ -1469,6 +1501,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await; @@ -1513,6 +1546,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await; @@ -1540,6 +1574,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await; assert!(result1.is_ok()); @@ -1556,6 +1591,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await; assert!(matches!(result2, Err(SigningError::PoolExhausted))); @@ -1577,6 +1613,7 @@ mod tests { &S::zero(), None, Duration::from_secs(30), + &test_metrics(), ) .await; @@ -1605,6 +1642,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await .unwrap(); @@ -1626,6 +1664,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await .unwrap(); @@ -1669,6 +1708,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await .unwrap(); @@ -1684,6 +1724,7 @@ mod tests { &beacon, None, Duration::from_secs(30), + &test_metrics(), ) .await .unwrap(); diff --git a/crates/hashi/src/withdrawals.rs b/crates/hashi/src/withdrawals.rs index 2a2621cd2..bedea01b3 100644 --- a/crates/hashi/src/withdrawals.rs +++ b/crates/hashi/src/withdrawals.rs @@ -551,7 +551,12 @@ impl Hashi { epoch, ); } - let p2p_channel = RpcP2PChannel::new(onchain_state, epoch); + let p2p_channel = RpcP2PChannel::new( + onchain_state, + epoch, + crate::metrics::MPC_LABEL_SIGNING, + self.metrics.clone(), + ); let signing_manager = self.signing_manager(); let beacon = S::from_bytes_mod_order(&pending.randomness); let signing_messages = self.withdrawal_signing_messages(unsigned_tx, &pending.inputs)?; @@ -573,6 +578,7 @@ impl Hashi { &beacon, derivation_address.as_ref(), WITHDRAWAL_SIGNING_TIMEOUT, + &self.metrics, ) .await; let sign_duration = sign_start.elapsed().as_secs_f64();