diff --git a/Cargo.lock b/Cargo.lock index 7bf64a222..261cc5977 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2090,6 +2090,7 @@ dependencies = [ "hashi", "hashi-screener", "hashi-types", + "mpc", "nix", "prometheus", "rand 0.8.5", @@ -2835,6 +2836,7 @@ dependencies = [ "http 1.4.0", "jiff", "jsonrpc", + "mpc", "prometheus", "prometheus-closure-metric", "proptest", @@ -3490,6 +3492,7 @@ dependencies = [ "hashi", "hashi-types", "hex", + "mpc", "rand 0.8.5", "reqwest", "serde", @@ -3964,6 +3967,33 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "mpc" +version = "0.0.0" +dependencies = [ + "anyhow", + "async-trait", + "backon", + "bcs", + "fastcrypto", + "fastcrypto-tbls", + "futures", + "hashi-types", + "hex", + "prometheus", + "rand 0.8.5", + "serde", + "serde_derive", + "sui-crypto", + "sui-rpc", + "sui-sdk-types", + "thiserror 1.0.69", + "tokio", + "tonic", + "tracing", + "tracing-test", +] + [[package]] name = "multimap" version = "0.10.1" diff --git a/crates/e2e-tests/Cargo.toml b/crates/e2e-tests/Cargo.toml index 7d744d360..37a47d897 100644 --- a/crates/e2e-tests/Cargo.toml +++ b/crates/e2e-tests/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] hashi = { path = "../hashi" } +mpc = { path = "../mpc" } hashi-screener = { path = "../hashi-screener", features = ["test-utils"] } hashi-types = { path = "../hashi-types" } diff --git a/crates/e2e-tests/src/lib.rs b/crates/e2e-tests/src/lib.rs index b882b70f8..f708b73d3 100644 --- a/crates/e2e-tests/src/lib.rs +++ b/crates/e2e-tests/src/lib.rs @@ -655,7 +655,7 @@ mod tests { mgr.committee.clone() }; let (refill_tx, _) = tokio::sync::watch::channel(0u32); - let signing_manager = hashi::mpc::SigningManager::new( + let signing_manager = mpc::SigningManager::new( info.address, committee, t, @@ -679,9 +679,8 @@ mod tests { epoch: u64, sui_request_id: sui_sdk_types::Address, global_presig_index: u64, - ) -> Vec< - hashi::mpc::types::SigningResult, - > { + ) -> Vec> + { let beacon_value = S::rand(&mut rand::thread_rng()); let sign_futures: Vec<_> = nodes .iter() @@ -691,7 +690,7 @@ mod tests { .signing_manager_for(epoch) .unwrap_or_else(|| panic!("SigningManager not initialized for epoch {epoch}")); let onchain_state = node.hashi().onchain_state().clone(); - let p2p_channel = hashi::mpc::rpc::RpcP2PChannel::new( + let p2p_channel = hashi::mpc_p2p_channel::RpcP2PChannel::new( onchain_state, epoch, hashi::metrics::MPC_LABEL_SIGNING, @@ -709,7 +708,7 @@ mod tests { &beacon, None, SIGNING_TIMEOUT, - &metrics, + &metrics.mpc, ) .await } @@ -720,9 +719,7 @@ mod tests { fn assert_all_signatures_match( results: Vec< - hashi::mpc::types::SigningResult< - fastcrypto::groups::secp256k1::schnorr::SchnorrSignature, - >, + mpc::types::SigningResult, >, ) { let mut signatures = Vec::new(); diff --git a/crates/hashi/Cargo.toml b/crates/hashi/Cargo.toml index 027ea79d6..851d9a26f 100644 --- a/crates/hashi/Cargo.toml +++ b/crates/hashi/Cargo.toml @@ -12,6 +12,7 @@ sui-transaction-builder.workspace = true sui-futures = { workspace = true } hashi-types = { path = "../hashi-types" } +mpc = { path = "../mpc" } # Bitcoin dependencies bitcoin.workspace = true diff --git a/crates/hashi/src/constants.rs b/crates/hashi/src/constants.rs index 79add3703..65090ef86 100644 --- a/crates/hashi/src/constants.rs +++ b/crates/hashi/src/constants.rs @@ -19,9 +19,7 @@ pub const BITCOIN_SIGNET_CHAIN_ID: &str = pub const BITCOIN_REGTEST_CHAIN_ID: &str = "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"; -/// Trigger presignature refill when remaining presignatures drop to -/// `initial_pool_size / PRESIG_REFILL_DIVISOR`. -pub const PRESIG_REFILL_DIVISOR: usize = 2; +pub use mpc::constants::PRESIG_REFILL_DIVISOR; pub fn is_production_sui_chain(chain_id: &str) -> bool { chain_id == SUI_MAINNET_CHAIN_ID || chain_id == SUI_TESTNET_CHAIN_ID diff --git a/crates/hashi/src/db.rs b/crates/hashi/src/db.rs index fd3fd84fc..fec2b2aa8 100644 --- a/crates/hashi/src/db.rs +++ b/crates/hashi/src/db.rs @@ -18,7 +18,7 @@ use hashi_types::committee::EncryptionPrivateKey; use serde::de::DeserializeOwned; -use crate::mpc::types::RotationMessages; +use mpc::types::RotationMessages; pub struct Database { db: fjall::Database, @@ -442,13 +442,13 @@ fn prune_keyspace(keyspace: &Keyspace, cutoff_epoch: u64) -> Result<()> { #[cfg(test)] mod tests { - use crate::mpc::EncryptionGroupElement; use fastcrypto_tbls::nodes::Node; use fastcrypto_tbls::nodes::Nodes; use fastcrypto_tbls::threshold_schnorr::avss; use fastcrypto_tbls::threshold_schnorr::batch_avss; use hashi_types::committee::EncryptionPrivateKey; use hashi_types::committee::EncryptionPublicKey; + use mpc::EncryptionGroupElement; use sui_sdk_types::Address; use super::Database; @@ -1069,7 +1069,7 @@ mod tests { #[test] fn test_epoch_store_writes_at_explicit_epoch_not_self_epoch() { use crate::storage::EpochPublicMessagesStore; - use crate::storage::PublicMessagesStore; + use mpc::PublicMessagesStore; use std::collections::BTreeMap; use std::num::NonZeroU16; diff --git a/crates/hashi/src/grpc/mod.rs b/crates/hashi/src/grpc/mod.rs index cd9d9e2de..c852aa400 100644 --- a/crates/hashi/src/grpc/mod.rs +++ b/crates/hashi/src/grpc/mod.rs @@ -42,19 +42,19 @@ 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(); let bridge_service = hashi_types::proto::bridge_service_server::BridgeServiceServer::new(self.clone()) .max_decoding_message_size(max_decoding_message_size); - let mpc_service = - hashi_types::proto::mpc_service_server::MpcServiceServer::new(self.clone()) - .max_decoding_message_size(max_decoding_message_size); + let mpc_service = hashi_types::proto::mpc_service_server::MpcServiceServer::new( + mpc::MpcServiceImpl::new( + self.inner.mpc_state.clone(), + Arc::new(self.inner.metrics.mpc.clone()), + ), + ) + .max_decoding_message_size(max_decoding_message_size); let (health_reporter, health_service) = tonic_health::server::health_reporter(); @@ -136,32 +136,9 @@ impl HttpService { (local_addr, service) } - pub fn mpc_manager( - &self, - ) -> Result>, tonic::Status> { - self.inner - .mpc_manager() - .ok_or_else(|| tonic::Status::unavailable("DKG manager not yet initialized")) - } - - pub fn signing_manager_for( - &self, - epoch: u64, - ) -> Result, tonic::Status> { - self.inner.signing_manager_for(epoch).ok_or_else(|| { - tonic::Status::unavailable(format!( - "SigningManager not available for epoch {epoch}; retry" - )) - }) - } - pub fn btc_monitor(&self) -> &crate::btc_monitor::monitor::MonitorClient { self.inner.btc_monitor() } - - pub fn get_reconfig_signature(&self, epoch: u64) -> Option> { - self.inner.get_reconfig_signature(epoch) - } } async fn health() -> impl axum::response::IntoResponse { diff --git a/crates/hashi/src/lib.rs b/crates/hashi/src/lib.rs index d0e1c2b62..29623b0e6 100644 --- a/crates/hashi/src/lib.rs +++ b/crates/hashi/src/lib.rs @@ -1,10 +1,8 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::collections::HashMap; use std::sync::Arc; use std::sync::OnceLock; -use std::sync::RwLock; use anyhow::anyhow; use sui_futures::service::Service; @@ -12,7 +10,6 @@ use sui_futures::service::Service; pub mod backup; pub mod btc_monitor; pub mod cli; -pub mod communication; pub mod config; pub mod constants; pub mod db; @@ -20,7 +17,9 @@ pub mod deposits; pub mod grpc; pub mod leader; pub mod metrics; -pub mod mpc; +pub mod mpc_p2p_channel; +pub mod mpc_service; +pub mod mpc_sui_tob; pub mod onchain; pub mod publish; pub mod storage; @@ -43,14 +42,11 @@ pub struct Hashi { pub config: config::Config, pub metrics: Arc, pub db: Arc, + pub(crate) mpc_state: Arc, onchain_state: OnceLock, - mpc_manager: OnceLock>>, - signing_manager: RwLock>>, - mpc_handle: OnceLock, - btc_monitor: OnceLock, + mpc_handle: OnceLock, + btc_monitor: OnceLock, screener_client: OnceLock>, - /// Reconfig completion signatures by epoch. - reconfig_signatures: RwLock>>, } impl Hashi { @@ -64,13 +60,11 @@ impl Hashi { config, metrics, db: Arc::new(db), + mpc_state: Arc::new(mpc::MpcServiceState::new()), onchain_state: OnceLock::new(), - mpc_manager: OnceLock::new(), - signing_manager: RwLock::new(None), mpc_handle: OnceLock::new(), btc_monitor: OnceLock::new(), screener_client: OnceLock::new(), - reconfig_signatures: RwLock::new(HashMap::new()), })) } @@ -88,13 +82,11 @@ impl Hashi { config, metrics, db: Arc::new(db), + mpc_state: Arc::new(mpc::MpcServiceState::new()), onchain_state: OnceLock::new(), - mpc_manager: OnceLock::new(), - signing_manager: RwLock::new(None), mpc_handle: OnceLock::new(), btc_monitor: OnceLock::new(), screener_client: OnceLock::new(), - reconfig_signatures: RwLock::new(HashMap::new()), })) } @@ -110,30 +102,16 @@ impl Hashi { self.onchain_state.get() } - pub fn mpc_manager(&self) -> Option>> { - self.mpc_manager.get().cloned() + pub fn mpc_manager(&self) -> Option>> { + self.mpc_state.mpc_manager() } pub fn set_mpc_manager(&self, manager: mpc::MpcManager) { - match self.mpc_manager.get() { - Some(lock) => { - // RwLock::write only fails if poisoned (a thread panicked while holding the lock). - // Poisoning indicates a bug, so we propagate the panic rather than recover. - *lock.write().unwrap() = manager; - } - None => { - // First-time initialization (e.g. new committee member joining mid-rotation). - let _ = self.mpc_manager.set(Arc::new(RwLock::new(manager))); - } - } + self.mpc_state.set_mpc_manager(manager); } pub fn signing_manager_for(&self, epoch: u64) -> Option> { - let stored = self.signing_manager.read().unwrap(); - stored - .as_ref() - .filter(|manager| manager.epoch() == epoch) - .cloned() + self.mpc_state.signing_manager_for(epoch) } pub fn current_signing_manager(&self) -> Option> { @@ -142,20 +120,16 @@ impl Hashi { } pub fn signing_verifying_key(&self) -> Option { - self.signing_manager - .read() - .unwrap() - .as_ref() - .map(|manager| manager.verifying_key()) + self.mpc_state.signing_verifying_key() } pub fn store_signing_manager(&self, manager: mpc::SigningManager) { - *self.signing_manager.write().unwrap() = Some(Arc::new(manager)); + self.mpc_state.store_signing_manager(manager); } /// Test-only pub fn clear_signing_manager_for_test(&self) { - *self.signing_manager.write().unwrap() = None; + self.mpc_state.clear_signing_manager_for_test(); } pub fn btc_monitor(&self) -> &crate::btc_monitor::monitor::MonitorClient { @@ -163,21 +137,14 @@ impl Hashi { } pub fn store_reconfig_signature(&self, epoch: u64, signature: Vec) { - self.reconfig_signatures - .write() - .unwrap() - .insert(epoch, signature); + self.mpc_state.store_reconfig_signature(epoch, signature); } pub fn get_reconfig_signature(&self, epoch: u64) -> Option> { - self.reconfig_signatures - .read() - .unwrap() - .get(&epoch) - .cloned() + self.mpc_state.get_reconfig_signature(epoch) } - pub fn mpc_handle(&self) -> Option<&mpc::MpcHandle> { + pub fn mpc_handle(&self) -> Option<&mpc_service::MpcHandle> { self.mpc_handle.get() } @@ -208,7 +175,8 @@ impl Hashi { let state = self.onchain_state().state(); let hashi = state.hashi(); let committee_set = &hashi.committees; - let session_id = mpc::SessionId::new(self.config.sui_chain_id(), epoch, &protocol_type); + let session_id = + mpc::types::SessionId::new(self.config.sui_chain_id(), epoch, &protocol_type); let encryption_key = self.config.encryption_private_key()?; self.db .store_encryption_key(epoch, &encryption_key) @@ -422,7 +390,7 @@ impl Hashi { ); } - let (mpc_service, mpc_handle) = mpc::MpcService::new(self.clone()); + let (mpc_service, mpc_handle) = mpc_service::MpcService::new(self.clone()); self.mpc_handle .set(mpc_handle) .expect("MpcHandle already set"); diff --git a/crates/hashi/src/metrics.rs b/crates/hashi/src/metrics.rs index eeee4b537..85f09cc94 100644 --- a/crates/hashi/src/metrics.rs +++ b/crates/hashi/src/metrics.rs @@ -15,6 +15,8 @@ use prometheus::register_int_gauge_with_registry; #[derive(Clone)] pub struct Metrics { + pub mpc: mpc::MpcMetrics, + // RPC metrics. Visible to `crate::grpc::metrics_layer`, which owns // the tower CallbackLayer handlers that write into them. pub(crate) inflight_requests: IntGaugeVec, @@ -25,7 +27,6 @@ pub struct Metrics { pub(crate) bytes_sent_total: IntCounterVec, pub(crate) bytes_received_total: IntCounterVec, - // Per-MPC-protocol body-size metrics. pub(crate) mpc_request_size_bytes: HistogramVec, pub(crate) mpc_response_size_bytes: HistogramVec, pub(crate) mpc_bytes_sent_total: IntCounterVec, @@ -80,26 +81,11 @@ 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, } const LATENCY_SEC_BUCKETS: &[f64] = &[ @@ -108,10 +94,10 @@ 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_GENERATION: &str = "nonce_generation"; -pub const MPC_LABEL_SIGNING: &str = "signing"; +pub use mpc::MPC_LABEL_DKG; +pub use mpc::MPC_LABEL_KEY_ROTATION; +pub use mpc::MPC_LABEL_NONCE_GENERATION; +pub use mpc::MPC_LABEL_SIGNING; pub const CONFIRMATION_STATUS_LABELS: &[&str] = &[ "not_found", @@ -148,6 +134,7 @@ impl Metrics { pub fn new(registry: &Registry) -> Self { Self { + mpc: mpc::MpcMetrics::new(registry), inflight_requests: register_int_gauge_vec_with_registry!( "hashi_inflight_requests", "Total in-flight RPC requests per route", @@ -477,8 +464,6 @@ 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", @@ -503,8 +488,6 @@ impl Metrics { 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", @@ -513,78 +496,6 @@ impl Metrics { registry, ) .unwrap(), - mpc_dealer_crypto_duration_seconds: register_histogram_vec_with_registry!( - "hashi_mpc_dealer_crypto_duration_seconds", - "Duration of dealer crypto", - &["protocol"], - LATENCY_SEC_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"], - LATENCY_SEC_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"], - LATENCY_SEC_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"], - LATENCY_SEC_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"], - LATENCY_SEC_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"], - LATENCY_SEC_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"], - LATENCY_SEC_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"], - LATENCY_SEC_BUCKETS.to_vec(), - registry, - ) - .unwrap(), - mpc_completion_duration_seconds: register_histogram_vec_with_registry!( - "hashi_mpc_completion_duration_seconds", - "Duration of final aggregation", - &["protocol"], - LATENCY_SEC_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", @@ -593,50 +504,6 @@ impl Metrics { 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"], - LATENCY_SEC_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"], - LATENCY_SEC_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"], - LATENCY_SEC_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"], - LATENCY_SEC_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"], - LATENCY_SEC_BUCKETS.to_vec(), - registry, - ) - .unwrap(), } } diff --git a/crates/hashi/src/mpc/mod.rs b/crates/hashi/src/mpc/mod.rs deleted file mode 100644 index afa705d6b..000000000 --- a/crates/hashi/src/mpc/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -mod mpc_except_signing; -pub mod rpc; -pub mod service; -pub mod signing; -pub mod types; - -pub use mpc_except_signing::*; -pub use service::MpcHandle; -pub use service::MpcService; -pub use signing::SigningManager; diff --git a/crates/hashi/src/mpc/rpc/p2p_channel.rs b/crates/hashi/src/mpc_p2p_channel.rs similarity index 88% rename from crates/hashi/src/mpc/rpc/p2p_channel.rs rename to crates/hashi/src/mpc_p2p_channel.rs index 2557e8c11..86684dd2c 100644 --- a/crates/hashi/src/mpc/rpc/p2p_channel.rs +++ b/crates/hashi/src/mpc_p2p_channel.rs @@ -1,26 +1,27 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::communication::ChannelError; -use crate::communication::ChannelResult; -use crate::communication::P2PChannel; -use crate::grpc::Client; -use crate::grpc::MPC_PROTOCOL_METADATA_KEY; -use crate::mpc::types::ComplainRequest; -use crate::mpc::types::ComplaintResponses; -use crate::mpc::types::GetPartialSignaturesRequest; -use crate::mpc::types::GetPartialSignaturesResponse; -use crate::mpc::types::GetPublicMpcOutputRequest; -use crate::mpc::types::GetPublicMpcOutputResponse; -use crate::mpc::types::RetrieveMessagesRequest; -use crate::mpc::types::RetrieveMessagesResponse; -use crate::mpc::types::SendMessagesRequest; -use crate::mpc::types::SendMessagesResponse; -use crate::onchain::OnchainState; use async_trait::async_trait; +use mpc::communication::ChannelError; +use mpc::communication::ChannelResult; +use mpc::communication::P2PChannel; +use mpc::types::ComplainRequest; +use mpc::types::ComplaintResponses; +use mpc::types::GetPartialSignaturesRequest; +use mpc::types::GetPartialSignaturesResponse; +use mpc::types::GetPublicMpcOutputRequest; +use mpc::types::GetPublicMpcOutputResponse; +use mpc::types::RetrieveMessagesRequest; +use mpc::types::RetrieveMessagesResponse; +use mpc::types::SendMessagesRequest; +use mpc::types::SendMessagesResponse; use sui_sdk_types::Address; use tonic::metadata::MetadataValue; +use crate::grpc::Client; +use crate::grpc::MPC_PROTOCOL_METADATA_KEY; +use crate::onchain::OnchainState; + pub struct RpcP2PChannel { onchain_state: OnchainState, epoch: u64, diff --git a/crates/hashi/src/mpc/service.rs b/crates/hashi/src/mpc_service.rs similarity index 98% rename from crates/hashi/src/mpc/service.rs rename to crates/hashi/src/mpc_service.rs index c00e7e2a7..bbe1bb96f 100644 --- a/crates/hashi/src/mpc/service.rs +++ b/crates/hashi/src/mpc_service.rs @@ -16,19 +16,20 @@ use tracing::error; use tracing::info; use tracing::warn; +use mpc::MpcManager; +use mpc::MpcOutput; +use mpc::SigningManager; +use mpc::metrics::MPC_LABEL_DKG; +use mpc::metrics::MPC_LABEL_KEY_ROTATION; +use mpc::metrics::MPC_LABEL_NONCE_GENERATION; +use mpc::types::CertificateV1; +use mpc::types::ProtocolType; + 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_GENERATION; -use crate::mpc::MpcManager; -use crate::mpc::MpcOutput; -use crate::mpc::SigningManager; -use crate::mpc::rpc::RpcP2PChannel; -use crate::mpc::types::CertificateV1; -use crate::mpc::types::ProtocolType; +use crate::mpc_p2p_channel::RpcP2PChannel; +use crate::mpc_sui_tob::SuiTobChannel; +use crate::mpc_sui_tob::fetch_certificates; use crate::onchain::Notification; use fastcrypto_tbls::threshold_schnorr::G; use fastcrypto_tbls::threshold_schnorr::presigning::Presignatures; @@ -300,7 +301,7 @@ impl MpcService { &mpc_manager, &p2p_channel, &mut tob_channel, - &self.inner.metrics, + &self.inner.metrics.mpc, ) .await .map_err(|e| anyhow::anyhow!("DKG failed: {e}"))?; @@ -335,8 +336,9 @@ impl MpcService { Some(batch_index), signer, ); - let metrics = &self.inner.metrics; - let _timer = metrics + let _timer = self + .inner + .metrics .mpc_total_duration_seconds .with_label_values(&[MPC_LABEL_NONCE_GENERATION]) .start_timer(); @@ -345,7 +347,7 @@ impl MpcService { batch_index, &p2p_channel, &mut tob_channel, - metrics, + &self.inner.metrics.mpc, ) .await; drop(_timer); @@ -358,7 +360,9 @@ impl MpcService { mgr.mpc_config.max_faulty as usize, ) }; - let _timer = metrics + let _timer = self + .inner + .metrics .mpc_presig_conversion_duration_seconds .with_label_values(&[MPC_LABEL_NONCE_GENERATION]) .start_timer(); @@ -775,7 +779,7 @@ impl MpcService { &previous_certs, &p2p_channel, &mut tob_channel, - &self.inner.metrics, + &self.inner.metrics.mpc, ) .await .map_err(|e| anyhow::anyhow!("Key rotation failed: {e}"))?; diff --git a/crates/hashi/src/communication/sui_tob.rs b/crates/hashi/src/mpc_sui_tob.rs similarity index 96% rename from crates/hashi/src/communication/sui_tob.rs rename to crates/hashi/src/mpc_sui_tob.rs index 86ad598d4..5d79079de 100644 --- a/crates/hashi/src/communication/sui_tob.rs +++ b/crates/hashi/src/mpc_sui_tob.rs @@ -8,16 +8,16 @@ use std::collections::VecDeque; use std::time::Duration; use async_trait::async_trait; +use mpc::communication::ChannelError; +use mpc::communication::ChannelResult; +use mpc::communication::OrderedBroadcastChannel; +use mpc::types::CertificateV1; +use mpc::types::DealerMessagesHash; use sui_crypto::ed25519::Ed25519PrivateKey; use sui_sdk_types::Address; use thiserror::Error; -use super::ChannelError; -use super::ChannelResult; -use super::OrderedBroadcastChannel; use crate::config::HashiIds; -use crate::mpc::types::CertificateV1; -use crate::mpc::types::DealerMessagesHash; use crate::onchain::OnchainState; use crate::sui_tx_executor::SuiTxExecutor; diff --git a/crates/hashi/src/onchain/mod.rs b/crates/hashi/src/onchain/mod.rs index 0504d5c4e..b9e1bdde8 100644 --- a/crates/hashi/src/onchain/mod.rs +++ b/crates/hashi/src/onchain/mod.rs @@ -32,19 +32,19 @@ use tokio::sync::broadcast; use tokio::sync::watch; use crate::config::HashiIds; -use crate::mpc::fallback_encryption_public_key; use hashi_types::committee::Committee; use hashi_types::committee::CommitteeMember; use hashi_types::move_types; +use mpc::fallback_encryption_public_key; const BROADCAST_CHANNEL_CAPACITY: usize = 100; pub mod types; mod watcher; -fn parse_encryption_public_key(bytes: &[u8]) -> Option { +fn parse_encryption_public_key(bytes: &[u8]) -> Option { let array: [u8; 32] = bytes.try_into().ok()?; - crate::mpc::EncryptionGroupElement::from_byte_array(&array).ok() + mpc::EncryptionGroupElement::from_byte_array(&array).ok() } #[derive(Clone)] @@ -1371,7 +1371,7 @@ mod tests { use fastcrypto::traits::KeyPair; use fastcrypto::traits::ToFromBytes; - use crate::mpc::EncryptionGroupElement; + use mpc::EncryptionGroupElement; use super::*; diff --git a/crates/hashi/src/onchain/types.rs b/crates/hashi/src/onchain/types.rs index f31e12573..5dbe42595 100644 --- a/crates/hashi/src/onchain/types.rs +++ b/crates/hashi/src/onchain/types.rs @@ -322,6 +322,20 @@ impl CommitteeSet { } } +impl mpc::CommitteeSetView for CommitteeSet { + fn epoch(&self) -> u64 { + self.epoch + } + + fn pending_epoch_change(&self) -> Option { + self.pending_epoch_change + } + + fn committees(&self) -> &BTreeMap { + &self.committees + } +} + #[derive(Clone)] pub struct MemberInfo { /// Sui Validator Address of this node diff --git a/crates/hashi/src/storage/epoch_public_messages_store.rs b/crates/hashi/src/storage/epoch_public_messages_store.rs index 41666231e..5b9fcd432 100644 --- a/crates/hashi/src/storage/epoch_public_messages_store.rs +++ b/crates/hashi/src/storage/epoch_public_messages_store.rs @@ -7,10 +7,11 @@ use fastcrypto_tbls::threshold_schnorr::avss; use fastcrypto_tbls::threshold_schnorr::batch_avss; use sui_sdk_types::Address; +use mpc::PublicMessagesStore; +use mpc::RotationMessages; +use mpc::types::Messages; + use crate::db::Database; -use crate::mpc::types::Messages; -use crate::mpc::types::RotationMessages; -use crate::storage::PublicMessagesStore; pub struct EpochPublicMessagesStore { db: Arc, diff --git a/crates/hashi/src/storage/mod.rs b/crates/hashi/src/storage/mod.rs index 0208c4a0b..cc0136094 100644 --- a/crates/hashi/src/storage/mod.rs +++ b/crates/hashi/src/storage/mod.rs @@ -2,8 +2,5 @@ // SPDX-License-Identifier: Apache-2.0 mod epoch_public_messages_store; -mod interfaces; pub use epoch_public_messages_store::EpochPublicMessagesStore; -pub use interfaces::PublicMessagesStore; -pub use interfaces::RotationMessages; diff --git a/crates/hashi/src/sui_tx_executor.rs b/crates/hashi/src/sui_tx_executor.rs index 4e9775760..d6a62a04c 100644 --- a/crates/hashi/src/sui_tx_executor.rs +++ b/crates/hashi/src/sui_tx_executor.rs @@ -175,12 +175,12 @@ use sui_transaction_builder::intent::CoinWithBalance; use crate::Hashi; use crate::config::Config; use crate::config::HashiIds; -use crate::mpc::types::CertificateV1; use crate::onchain; use crate::onchain::OnchainState; use crate::onchain::types::DepositConfirmationMessage; use crate::onchain::types::DepositRequest; use crate::withdrawals::WithdrawalTxCommitment; +use mpc::types::CertificateV1; const DEFAULT_TIMEOUT_SECS: u64 = 10; diff --git a/crates/hashi/src/withdrawals.rs b/crates/hashi/src/withdrawals.rs index d0376a29e..a962aad49 100644 --- a/crates/hashi/src/withdrawals.rs +++ b/crates/hashi/src/withdrawals.rs @@ -31,7 +31,7 @@ use sui_sdk_types::Address; use crate::Hashi; use crate::btc_monitor::monitor::TxStatus; use crate::leader::RetryPolicy; -use crate::mpc::rpc::RpcP2PChannel; +use crate::mpc_p2p_channel::RpcP2PChannel; use crate::onchain::types::OutputUtxo; use crate::onchain::types::Utxo; use crate::onchain::types::UtxoId; @@ -586,7 +586,7 @@ impl Hashi { &beacon, derivation_address.as_ref(), WITHDRAWAL_SIGNING_TIMEOUT, - &self.metrics, + &self.metrics.mpc, ) .await; let sign_duration = sign_start.elapsed().as_secs_f64(); @@ -607,12 +607,12 @@ impl Hashi { .with_label_values(&["failure"]) .observe(sign_duration); let reason = match e { - crate::mpc::types::SigningError::Timeout { .. } => "timeout", - crate::mpc::types::SigningError::PoolExhausted => "pool_exhausted", - crate::mpc::types::SigningError::TooManyInvalidSignatures { .. } => { + mpc::types::SigningError::Timeout { .. } => "timeout", + mpc::types::SigningError::PoolExhausted => "pool_exhausted", + mpc::types::SigningError::TooManyInvalidSignatures { .. } => { "too_many_invalid" } - crate::mpc::types::SigningError::CryptoError(_) => "crypto_error", + mpc::types::SigningError::CryptoError(_) => "crypto_error", _ => "other", }; self.metrics diff --git a/crates/internal-tools/Cargo.toml b/crates/internal-tools/Cargo.toml index 9ff3a30dc..e8fc9c129 100644 --- a/crates/internal-tools/Cargo.toml +++ b/crates/internal-tools/Cargo.toml @@ -6,6 +6,7 @@ publish = false [dependencies] hashi = { path = "../hashi" } +mpc = { path = "../mpc" } hashi-types = { path = "../hashi-types" } fastcrypto.workspace = true diff --git a/crates/internal-tools/src/key_recovery.rs b/crates/internal-tools/src/key_recovery.rs index 59f6c5e17..1dfcd6e16 100644 --- a/crates/internal-tools/src/key_recovery.rs +++ b/crates/internal-tools/src/key_recovery.rs @@ -14,18 +14,18 @@ use fastcrypto::serde_helpers::ToFromByteArray; use fastcrypto_tbls::polynomial::Eval; use fastcrypto_tbls::threshold_schnorr::G; use fastcrypto_tbls::threshold_schnorr::S; -use hashi::communication::fetch_certificates; use hashi::db::Database; -use hashi::mpc::MpcManager; -use hashi::mpc::types::CertificateV1; -use hashi::mpc::types::ProtocolType; -use hashi::mpc::types::ReconstructionOutcome; -use hashi::mpc::types::SessionId; +use hashi::mpc_sui_tob::fetch_certificates; use hashi::onchain::OnchainState; use hashi::storage::EpochPublicMessagesStore; use hashi_types::committee::Bls12381PrivateKey; use hashi_types::committee::Committee; use hashi_types::committee::EncryptionPublicKey; +use mpc::MpcManager; +use mpc::types::CertificateV1; +use mpc::types::ProtocolType; +use mpc::types::ReconstructionOutcome; +use mpc::types::SessionId; use sui_sdk_types::Address; #[derive(Parser)] diff --git a/crates/mpc/Cargo.toml b/crates/mpc/Cargo.toml new file mode 100644 index 000000000..4fec83ad4 --- /dev/null +++ b/crates/mpc/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "mpc" +version = "0.0.0" +edition = "2024" + +[dependencies] +hashi-types = { path = "../hashi-types" } + +sui-sdk-types.workspace = true +sui-crypto.workspace = true +sui-rpc.workspace = true + +# RPC +tonic.workspace = true + +# Async + traits +tokio.workspace = true +async-trait.workspace = true +futures.workspace = true + +# Serialization +serde.workspace = true +serde_derive.workspace = true +bcs.workspace = true + +# Crypto +fastcrypto.workspace = true +fastcrypto-tbls.workspace = true +rand.workspace = true +hex.workspace = true + +# Metrics +prometheus.workspace = true + +# Errors / observability +anyhow.workspace = true +thiserror.workspace = true +tracing.workspace = true + +# Retry helper used by communication::timeout_and_retry +backon.workspace = true + +[dev-dependencies] +tracing-test = "0.2" diff --git a/crates/mpc/src/committee_view.rs b/crates/mpc/src/committee_view.rs new file mode 100644 index 000000000..5de9b7417 --- /dev/null +++ b/crates/mpc/src/committee_view.rs @@ -0,0 +1,12 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::BTreeMap; + +use hashi_types::committee::Committee; + +pub trait CommitteeSetView { + fn epoch(&self) -> u64; + fn pending_epoch_change(&self) -> Option; + fn committees(&self) -> &BTreeMap; +} diff --git a/crates/hashi/src/communication/in_memory.rs b/crates/mpc/src/communication/in_memory.rs similarity index 100% rename from crates/hashi/src/communication/in_memory.rs rename to crates/mpc/src/communication/in_memory.rs diff --git a/crates/hashi/src/communication/interfaces.rs b/crates/mpc/src/communication/interfaces.rs similarity index 84% rename from crates/hashi/src/communication/interfaces.rs rename to crates/mpc/src/communication/interfaces.rs index 82613eeba..6262b6b37 100644 --- a/crates/hashi/src/communication/interfaces.rs +++ b/crates/mpc/src/communication/interfaces.rs @@ -3,16 +3,16 @@ //! Communication channel interfaces -use crate::mpc::ComplainRequest; -use crate::mpc::ComplaintResponses; -use crate::mpc::GetPublicMpcOutputRequest; -use crate::mpc::GetPublicMpcOutputResponse; -use crate::mpc::RetrieveMessagesRequest; -use crate::mpc::RetrieveMessagesResponse; -use crate::mpc::SendMessagesRequest; -use crate::mpc::SendMessagesResponse; -use crate::mpc::types::GetPartialSignaturesRequest; -use crate::mpc::types::GetPartialSignaturesResponse; +use crate::ComplainRequest; +use crate::ComplaintResponses; +use crate::GetPublicMpcOutputRequest; +use crate::GetPublicMpcOutputResponse; +use crate::RetrieveMessagesRequest; +use crate::RetrieveMessagesResponse; +use crate::SendMessagesRequest; +use crate::SendMessagesResponse; +use crate::types::GetPartialSignaturesRequest; +use crate::types::GetPartialSignaturesResponse; use async_trait::async_trait; use sui_sdk_types::Address; use thiserror::Error; diff --git a/crates/hashi/src/communication/mod.rs b/crates/mpc/src/communication/mod.rs similarity index 89% rename from crates/hashi/src/communication/mod.rs rename to crates/mpc/src/communication/mod.rs index 47133407f..c751cbf79 100644 --- a/crates/hashi/src/communication/mod.rs +++ b/crates/mpc/src/communication/mod.rs @@ -10,7 +10,6 @@ #[cfg(test)] pub mod in_memory; pub mod interfaces; -pub mod sui_tob; pub mod timeout_and_retry; #[cfg(test)] @@ -19,7 +18,5 @@ pub use interfaces::ChannelError; pub use interfaces::ChannelResult; pub use interfaces::OrderedBroadcastChannel; pub use interfaces::P2PChannel; -pub use sui_tob::SuiTobChannel; -pub use sui_tob::fetch_certificates; pub use timeout_and_retry::send_to_many; pub use timeout_and_retry::with_timeout_and_retry; diff --git a/crates/hashi/src/communication/timeout_and_retry.rs b/crates/mpc/src/communication/timeout_and_retry.rs similarity index 100% rename from crates/hashi/src/communication/timeout_and_retry.rs rename to crates/mpc/src/communication/timeout_and_retry.rs diff --git a/crates/mpc/src/constants.rs b/crates/mpc/src/constants.rs new file mode 100644 index 000000000..428c994a8 --- /dev/null +++ b/crates/mpc/src/constants.rs @@ -0,0 +1,13 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +pub const SUI_MAINNET_CHAIN_ID: &str = "4btiuiMPvEENsttpZC7CZ53DruC3MAgfznDbASZ7DR6S"; +pub const SUI_TESTNET_CHAIN_ID: &str = "69WiPg3DAQiwdxfncX6wYQ2siKwAe6L9BZthQea3JNMD"; + +pub fn is_production_sui_chain(chain_id: &str) -> bool { + chain_id == SUI_MAINNET_CHAIN_ID || chain_id == SUI_TESTNET_CHAIN_ID +} + +/// Trigger presignature refill when remaining presignatures drop to +/// `initial_pool_size / PRESIG_REFILL_DIVISOR`. +pub const PRESIG_REFILL_DIVISOR: usize = 2; diff --git a/crates/mpc/src/lib.rs b/crates/mpc/src/lib.rs new file mode 100644 index 000000000..7f7a33749 --- /dev/null +++ b/crates/mpc/src/lib.rs @@ -0,0 +1,30 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +pub mod committee_view; +pub mod communication; +pub mod constants; +pub mod metrics; +pub mod rpc; +pub mod service_state; +pub mod signing; +pub mod storage; +pub mod types; + +mod mpc_except_signing; + +#[cfg(test)] +mod test_committee_set; + +pub use committee_view::CommitteeSetView; +pub use metrics::MPC_LABEL_DKG; +pub use metrics::MPC_LABEL_KEY_ROTATION; +pub use metrics::MPC_LABEL_NONCE_GENERATION; +pub use metrics::MPC_LABEL_SIGNING; +pub use metrics::MpcMetrics; +pub use mpc_except_signing::*; +pub use rpc::MpcServiceImpl; +pub use service_state::MpcServiceState; +pub use signing::SigningManager; +pub use storage::PublicMessagesStore; +pub use storage::RotationMessages; diff --git a/crates/mpc/src/metrics.rs b/crates/mpc/src/metrics.rs new file mode 100644 index 000000000..f348a5b9e --- /dev/null +++ b/crates/mpc/src/metrics.rs @@ -0,0 +1,158 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use prometheus::HistogramVec; +use prometheus::Registry; +use prometheus::register_histogram_vec_with_registry; + +pub const MPC_LABEL_DKG: &str = "dkg"; +pub const MPC_LABEL_KEY_ROTATION: &str = "key_rotation"; +pub const MPC_LABEL_NONCE_GENERATION: &str = "nonce_generation"; +pub const MPC_LABEL_SIGNING: &str = "signing"; + +const LATENCY_SEC_BUCKETS: &[f64] = &[ + 0.001, 0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 1., 2.5, 5., 10., 20., 30., 60., 90., +]; + +#[derive(Clone)] +pub struct MpcMetrics { + 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_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, +} + +impl MpcMetrics { + pub fn new_default() -> Self { + Self::new(prometheus::default_registry()) + } + + pub fn new(registry: &Registry) -> Self { + Self { + mpc_dealer_crypto_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_dealer_crypto_duration_seconds", + "Duration of dealer crypto", + &["protocol"], + LATENCY_SEC_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"], + LATENCY_SEC_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"], + LATENCY_SEC_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"], + LATENCY_SEC_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"], + LATENCY_SEC_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"], + LATENCY_SEC_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"], + LATENCY_SEC_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"], + LATENCY_SEC_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_completion_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_completion_duration_seconds", + "Duration of final aggregation", + &["protocol"], + LATENCY_SEC_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"], + LATENCY_SEC_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + mpc_sign_partial_gen_duration_seconds: register_histogram_vec_with_registry!( + "hashi_mpc_sign_partial_gen_duration_seconds", + "Duration of generate_partial_signatures", + &["protocol"], + LATENCY_SEC_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"], + LATENCY_SEC_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"], + LATENCY_SEC_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + 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"], + LATENCY_SEC_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + } + } +} diff --git a/crates/hashi/src/mpc/mpc_except_signing.rs b/crates/mpc/src/mpc_except_signing.rs similarity index 98% rename from crates/hashi/src/mpc/mpc_except_signing.rs rename to crates/mpc/src/mpc_except_signing.rs index 902e7846c..7f6fc3406 100644 --- a/crates/hashi/src/mpc/mpc_except_signing.rs +++ b/crates/mpc/src/mpc_except_signing.rs @@ -1,6 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use crate::committee_view::CommitteeSetView; use crate::communication::OrderedBroadcastChannel; use crate::communication::P2PChannel; use crate::communication::send_to_many; @@ -9,41 +10,40 @@ use crate::constants::is_production_sui_chain; use crate::metrics::MPC_LABEL_DKG; use crate::metrics::MPC_LABEL_KEY_ROTATION; use crate::metrics::MPC_LABEL_NONCE_GENERATION; -use crate::metrics::Metrics; -use crate::mpc::types::CertificateV1; -pub use crate::mpc::types::ComplainRequest; -pub use crate::mpc::types::ComplaintResponses; -pub use crate::mpc::types::ComplaintResponsesKey; -pub use crate::mpc::types::ComplaintsToProcessKey; -use crate::mpc::types::DealerCertificate; -pub use crate::mpc::types::DealerFlowData; -use crate::mpc::types::DealerMessagesHash; -pub use crate::mpc::types::DealerOutputsKey; -pub use crate::mpc::types::EncryptionGroupElement; -pub use crate::mpc::types::GetPublicMpcOutputRequest; -pub use crate::mpc::types::GetPublicMpcOutputResponse; -pub use crate::mpc::types::MessageHash; -pub use crate::mpc::types::MessageResponsesKey; -pub use crate::mpc::types::Messages; -use crate::mpc::types::MpcConfig; -pub use crate::mpc::types::MpcError; -pub use crate::mpc::types::MpcOutput; -pub use crate::mpc::types::MpcResult; -pub use crate::mpc::types::NonceMessage; -pub use crate::mpc::types::NonceReconstructionOutcome; -pub use crate::mpc::types::ProtocolType; -pub use crate::mpc::types::ProtocolTypeIndicator; -pub use crate::mpc::types::PublicMpcOutput; -use crate::mpc::types::ReconstructionOutcome; -pub use crate::mpc::types::RetrieveMessagesRequest; -pub use crate::mpc::types::RetrieveMessagesResponse; -use crate::mpc::types::RotationComplainContext; -use crate::mpc::types::RotationMessages; -pub use crate::mpc::types::SendMessagesRequest; -pub use crate::mpc::types::SendMessagesResponse; -pub use crate::mpc::types::SessionId; -use crate::onchain::types::CommitteeSet; +use crate::metrics::MpcMetrics; use crate::storage::PublicMessagesStore; +use crate::types::CertificateV1; +pub use crate::types::ComplainRequest; +pub use crate::types::ComplaintResponses; +pub use crate::types::ComplaintResponsesKey; +pub use crate::types::ComplaintsToProcessKey; +use crate::types::DealerCertificate; +pub use crate::types::DealerFlowData; +use crate::types::DealerMessagesHash; +pub use crate::types::DealerOutputsKey; +pub use crate::types::EncryptionGroupElement; +pub use crate::types::GetPublicMpcOutputRequest; +pub use crate::types::GetPublicMpcOutputResponse; +pub use crate::types::MessageHash; +pub use crate::types::MessageResponsesKey; +pub use crate::types::Messages; +use crate::types::MpcConfig; +pub use crate::types::MpcError; +pub use crate::types::MpcOutput; +pub use crate::types::MpcResult; +pub use crate::types::NonceMessage; +pub use crate::types::NonceReconstructionOutcome; +pub use crate::types::ProtocolType; +pub use crate::types::ProtocolTypeIndicator; +pub use crate::types::PublicMpcOutput; +use crate::types::ReconstructionOutcome; +pub use crate::types::RetrieveMessagesRequest; +pub use crate::types::RetrieveMessagesResponse; +use crate::types::RotationComplainContext; +use crate::types::RotationMessages; +pub use crate::types::SendMessagesRequest; +pub use crate::types::SendMessagesResponse; +pub use crate::types::SessionId; use fastcrypto::bls12381::min_pk::BLS12381Signature; use fastcrypto::error::FastCryptoError; use fastcrypto::groups::HashToGroupElement; @@ -126,7 +126,7 @@ impl MpcManager { #[allow(clippy::too_many_arguments)] pub fn new( address: Address, - committee_set: &CommitteeSet, + committee_set: &dyn CommitteeSetView, session_id: SessionId, encryption_key: PrivateKey, signing_key: Bls12381PrivateKey, @@ -578,7 +578,7 @@ impl MpcManager { mpc_manager: &Arc>, p2p_channel: &impl P2PChannel, tob_channel: &mut impl OrderedBroadcastChannel, - metrics: &Metrics, + metrics: &MpcMetrics, ) -> MpcResult { let certified = tob_channel.certified_dealers().await; let (certified_reduced_weight, threshold) = { @@ -612,7 +612,7 @@ impl MpcManager { previous_certificates: &[CertificateV1], p2p_channel: &impl P2PChannel, ordered_broadcast_channel: &mut impl OrderedBroadcastChannel, - metrics: &Metrics, + metrics: &MpcMetrics, ) -> MpcResult { tracing::info!("run_key_rotation: starting prepare_previous_output"); let _timer = metrics @@ -707,7 +707,7 @@ impl MpcManager { batch_index: u32, p2p_channel: &impl P2PChannel, tob_channel: &mut impl OrderedBroadcastChannel, - metrics: &Metrics, + metrics: &MpcMetrics, ) -> MpcResult> { Self::prune_nonce_state(mpc_manager, batch_index); let certified = tob_channel.certified_dealers().await; @@ -844,7 +844,7 @@ impl MpcManager { mpc_manager: &Arc>, p2p_channel: &impl P2PChannel, tob_channel: &mut impl OrderedBroadcastChannel, - metrics: &Metrics, + metrics: &MpcMetrics, ) -> MpcResult<()> { // TODO(Optimization): Skip dealer phase if certificate is already on TOB let _timer = metrics @@ -913,7 +913,7 @@ impl MpcManager { mpc_manager: &Arc>, p2p_channel: &impl P2PChannel, tob_channel: &mut impl OrderedBroadcastChannel, - metrics: &Metrics, + metrics: &MpcMetrics, ) -> MpcResult { let threshold = { let mgr = mpc_manager.read().unwrap(); @@ -1110,7 +1110,7 @@ impl MpcManager { previous: &MpcOutput, p2p_channel: &impl P2PChannel, ordered_broadcast_channel: &mut impl OrderedBroadcastChannel, - metrics: &Metrics, + metrics: &MpcMetrics, ) -> MpcResult<()> { // TODO(Optimization): Skip dealer phase if certificate is already on TOB let _timer = metrics @@ -1184,7 +1184,7 @@ impl MpcManager { previous: &MpcOutput, p2p_channel: &impl P2PChannel, ordered_broadcast_channel: &mut impl OrderedBroadcastChannel, - metrics: &Metrics, + metrics: &MpcMetrics, ) -> MpcResult { let mut certified_share_indices: Vec = Vec::new(); let mut certified_dealers = HashSet::new(); @@ -1405,7 +1405,7 @@ impl MpcManager { batch_index: u32, p2p_channel: &impl P2PChannel, tob_channel: &mut impl OrderedBroadcastChannel, - metrics: &Metrics, + metrics: &MpcMetrics, ) -> MpcResult<()> { let _timer = metrics .mpc_dealer_crypto_duration_seconds @@ -1477,7 +1477,7 @@ impl MpcManager { batch_index: u32, p2p_channel: &impl P2PChannel, tob_channel: &mut impl OrderedBroadcastChannel, - metrics: &Metrics, + metrics: &MpcMetrics, ) -> MpcResult> { let required_weight = { let mgr = mpc_manager.read().unwrap(); @@ -3549,7 +3549,7 @@ impl MpcManager { /// Reconstruct presignatures from DB, recovering via Complain RPCs if /// cheating dealers' corrupted nonce messages are encountered. - pub(crate) async fn reconstruct_presignatures_with_complaint_recovery( + pub async fn reconstruct_presignatures_with_complaint_recovery( mpc_manager: &Arc>, epoch: u64, batch_index: u32, diff --git a/crates/hashi/src/mpc/mpc_except_signing_tests.rs b/crates/mpc/src/mpc_except_signing_tests.rs similarity index 98% rename from crates/hashi/src/mpc/mpc_except_signing_tests.rs rename to crates/mpc/src/mpc_except_signing_tests.rs index 09895f3d6..31f863d0a 100644 --- a/crates/hashi/src/mpc/mpc_except_signing_tests.rs +++ b/crates/mpc/src/mpc_except_signing_tests.rs @@ -3,16 +3,12 @@ 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; -use crate::mpc::types::RotationMessages; -use crate::onchain::types::MemberInfo; +use crate::metrics::MpcMetrics; +use crate::test_committee_set::TestCommitteeSet; +use crate::types::GetPartialSignaturesRequest; +use crate::types::GetPartialSignaturesResponse; +use crate::types::ProtocolType; +use crate::types::RotationMessages; use fastcrypto::encoding::Encoding; use fastcrypto::encoding::Hex; use fastcrypto::groups::Scalar; @@ -38,6 +34,10 @@ const TEST_WEIGHT_DIVISOR: u16 = 1; const TEST_CHAIN_ID: &str = "testchain"; const TEST_BATCH_SIZE_PER_WEIGHT: u16 = 50; +fn test_metrics() -> MpcMetrics { + MpcMetrics::new(&prometheus::Registry::new()) +} + fn unwrap_reconstruction_success(outcome: ReconstructionOutcome) -> MpcOutput { match outcome { ReconstructionOutcome::Success(output) => output, @@ -73,7 +73,7 @@ fn receive_dealer_messages( )) } struct TestSetup { - pub committee_set: CommitteeSet, + pub committee_set: TestCommitteeSet, pub encryption_keys: Vec>, pub signing_keys: Vec, } @@ -92,24 +92,6 @@ impl TestSetup { let epoch = 100u64; - // Build MemberInfo for each validator - let member_infos: BTreeMap = (0..num_validators) - .map(|i| { - let addr = Address::new([i as u8; 32]); - let next_epoch_encryption_public_key = - Some(PublicKey::from_private_key(&encryption_keys[i])); - let member_info = MemberInfo { - validator_address: addr, - operator_address: addr, - next_epoch_public_key: signing_keys[i].public_key(), - endpoint_url: None, - tls_public_key: None, - next_epoch_encryption_public_key, - }; - (addr, member_info) - }) - .collect(); - // Build Committee let members: Vec<_> = (0..num_validators) .map(|i| { @@ -142,11 +124,8 @@ impl TestSetup { committees.insert(epoch - 1, previous_committee); committees.insert(epoch, committee); - let mut committee_set = CommitteeSet::new(Address::ZERO, Address::ZERO); - committee_set - .set_epoch(epoch) - .set_members(member_infos) - .set_committees(committees); + let mut committee_set = TestCommitteeSet::new(); + committee_set.set_epoch(epoch).set_committees(committees); Self { committee_set, @@ -169,24 +148,6 @@ impl TestSetup { let epoch = 100u64; - // Build MemberInfo for each validator - let member_infos: BTreeMap = (0..num_validators) - .map(|i| { - let addr = Address::new([i as u8; 32]); - let next_epoch_encryption_public_key = - Some(PublicKey::from_private_key(&encryption_keys[i])); - let member_info = MemberInfo { - validator_address: addr, - operator_address: addr, - next_epoch_public_key: signing_keys[i].public_key(), - endpoint_url: None, - tls_public_key: None, - next_epoch_encryption_public_key, - }; - (addr, member_info) - }) - .collect(); - // Build Committee with custom weights let members: Vec<_> = (0..num_validators) .map(|i| { @@ -220,11 +181,8 @@ impl TestSetup { committees.insert(epoch - 1, previous_committee); committees.insert(epoch, committee); - let mut committee_set = CommitteeSet::new(Address::ZERO, Address::ZERO); - committee_set - .set_epoch(epoch) - .set_members(member_infos) - .set_committees(committees); + let mut committee_set = TestCommitteeSet::new(); + committee_set.set_epoch(epoch).set_committees(committees); Self { committee_set, @@ -900,29 +858,14 @@ fn test_mpc_manager_new_fails_if_no_committee_for_epoch() { let epoch = 100u64; - let members: BTreeMap = (0..5) - .map(|i| { - let addr = Address::new([i as u8; 32]); - let member_info = MemberInfo { - validator_address: addr, - operator_address: addr, - next_epoch_public_key: signing_keys[i].public_key(), - endpoint_url: None, - tls_public_key: None, - next_epoch_encryption_public_key: Some(PublicKey::from_private_key( - &encryption_keys[i], - )), - }; - (addr, member_info) - }) - .collect(); + // Silence unused-var warnings for keys we build above but don't need here. + let _ = (&encryption_keys, &signing_keys); // Empty committees map - no committee for the epoch - let mut committee_set = CommitteeSet::new(Address::ZERO, Address::ZERO); + let mut committee_set = TestCommitteeSet::new(); committee_set .set_epoch(epoch) - .set_members(members) - .set_committees(BTreeMap::new()); // Empty! + .set_committees(BTreeMap::new()); let session_id = SessionId::new("test", epoch, &ProtocolType::Dkg); let result = MpcManager::new( @@ -6287,7 +6230,7 @@ async fn test_prepare_previous_output_for_new_member() { committees.insert(epoch - 1, previous_committee); committees.insert(epoch, new_current_committee); - let mut new_committee_set = CommitteeSet::new(Address::ZERO, Address::ZERO); + let mut new_committee_set = TestCommitteeSet::new(); new_committee_set .set_epoch(epoch - 1) .set_pending_epoch_change(Some(epoch)) @@ -7417,7 +7360,7 @@ fn test_reconstruct_from_dkg_certificates_with_shifted_party_ids() { // Build CommitteeSet simulating a live reconfig: // epoch = 100 (current), pending_epoch_change = 101 (target) - let mut committee_set = CommitteeSet::new(Address::ZERO, Address::ZERO); + let mut committee_set = TestCommitteeSet::new(); let mut committees = BTreeMap::new(); committees.insert(epoch, previous_committee); committees.insert(target_epoch, target_committee); @@ -7604,7 +7547,7 @@ fn test_reconstruct_from_dkg_certificates_stops_at_threshold() { TEST_MAX_FAULTY_IN_BASIS_POINTS, ); - let mut committee_set = CommitteeSet::new(Address::ZERO, Address::ZERO); + let mut committee_set = TestCommitteeSet::new(); let mut committees = BTreeMap::new(); committees.insert(epoch, previous_committee); committees.insert(target_epoch, target_committee); @@ -7697,7 +7640,7 @@ fn test_reconstruct_from_rotation_certificates_with_shifted_party_ids() { TEST_MAX_FAULTY_IN_BASIS_POINTS, ); - let mut rotation_committee_set = CommitteeSet::new(Address::ZERO, Address::ZERO); + let mut rotation_committee_set = TestCommitteeSet::new(); let mut rotation_committees = BTreeMap::new(); rotation_committees.insert(dkg_epoch, committee_at_100); rotation_committees.insert(rotation_epoch, committee_at_101); @@ -7817,7 +7760,7 @@ fn test_reconstruct_from_rotation_certificates_with_shifted_party_ids() { TEST_MAX_FAULTY_IN_BASIS_POINTS, ); - let mut committee_set = CommitteeSet::new(Address::ZERO, Address::ZERO); + let mut committee_set = TestCommitteeSet::new(); let mut committees = BTreeMap::new(); committees.insert(rotation_epoch, previous_committee); committees.insert(target_epoch, target_committee); diff --git a/crates/hashi/src/mpc/rpc/mod.rs b/crates/mpc/src/rpc/mod.rs similarity index 67% rename from crates/hashi/src/mpc/rpc/mod.rs rename to crates/mpc/src/rpc/mod.rs index 107a83116..802b4ed0d 100644 --- a/crates/hashi/src/mpc/rpc/mod.rs +++ b/crates/mpc/src/rpc/mod.rs @@ -1,8 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -mod p2p_channel; mod proto_conversions; mod service; -pub use p2p_channel::RpcP2PChannel; +pub use service::MpcServiceImpl; diff --git a/crates/hashi/src/mpc/rpc/proto_conversions.rs b/crates/mpc/src/rpc/proto_conversions.rs similarity index 99% rename from crates/hashi/src/mpc/rpc/proto_conversions.rs rename to crates/mpc/src/rpc/proto_conversions.rs index 951c64e69..aa1957da6 100644 --- a/crates/hashi/src/mpc/rpc/proto_conversions.rs +++ b/crates/mpc/src/rpc/proto_conversions.rs @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::mpc::types; +use crate::types; use fastcrypto::traits::ToFromBytes; use fastcrypto_tbls::threshold_schnorr::avss; use fastcrypto_tbls::threshold_schnorr::batch_avss; diff --git a/crates/hashi/src/mpc/rpc/service.rs b/crates/mpc/src/rpc/service.rs similarity index 87% rename from crates/hashi/src/mpc/rpc/service.rs rename to crates/mpc/src/rpc/service.rs index ed24d954e..8ee489618 100644 --- a/crates/hashi/src/mpc/rpc/service.rs +++ b/crates/mpc/src/rpc/service.rs @@ -1,11 +1,8 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::grpc::HttpService; -use crate::mpc::spawn_blocking; -use crate::mpc::types; -use crate::mpc::types::MpcError; -use crate::mpc::types::SigningError; +use std::sync::Arc; + use hashi_types::proto::ComplainRequest; use hashi_types::proto::ComplainResponse; use hashi_types::proto::GetPartialSignaturesRequest; @@ -22,8 +19,44 @@ use hashi_types::proto::mpc_service_server::MpcService; use sui_sdk_types::Address; use tonic::Status; +use crate::MpcMetrics; +use crate::MpcServiceState; +use crate::metrics::MPC_LABEL_DKG; +use crate::metrics::MPC_LABEL_KEY_ROTATION; +use crate::metrics::MPC_LABEL_NONCE_GENERATION; +use crate::spawn_blocking; +use crate::types; +use crate::types::MpcError; +use crate::types::SigningError; + +#[derive(Clone)] +pub struct MpcServiceImpl { + state: Arc, + metrics: Arc, +} + +impl MpcServiceImpl { + pub fn new(state: Arc, metrics: Arc) -> Self { + Self { state, metrics } + } + + fn mpc_manager(&self) -> Result>, Status> { + self.state + .mpc_manager() + .ok_or_else(|| Status::unavailable("DKG manager not yet initialized")) + } + + fn signing_manager_for(&self, epoch: u64) -> Result, Status> { + self.state.signing_manager_for(epoch).ok_or_else(|| { + Status::unavailable(format!( + "SigningManager not available for epoch {epoch}; retry" + )) + }) + } +} + #[tonic::async_trait] -impl MpcService for HttpService { +impl MpcService for MpcServiceImpl { #[tracing::instrument(skip(self, request))] async fn send_messages( &self, @@ -34,13 +67,13 @@ impl MpcService for HttpService { 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_GENERATION, + types::Messages::Dkg(_) => MPC_LABEL_DKG, + types::Messages::Rotation(_) => MPC_LABEL_KEY_ROTATION, + types::Messages::NonceGeneration(_) => MPC_LABEL_NONCE_GENERATION, }; let mpc_manager = self.mpc_manager()?; let _timer = self - .metrics() + .metrics .mpc_rpc_handler_process_duration_seconds .with_label_values(&[label]) .start_timer(); @@ -156,7 +189,7 @@ impl MpcService for HttpService { let epoch = external_request .epoch .ok_or_else(|| Status::invalid_argument("epoch: missing required field"))?; - let signature = self.get_reconfig_signature(epoch).map(Into::into); + let signature = self.state.get_reconfig_signature(epoch).map(Into::into); Ok(tonic::Response::new( GetReconfigCompletionSignatureResponse { signature }, )) diff --git a/crates/mpc/src/service_state.rs b/crates/mpc/src/service_state.rs new file mode 100644 index 000000000..8b20c6c64 --- /dev/null +++ b/crates/mpc/src/service_state.rs @@ -0,0 +1,78 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; +use std::sync::Arc; +use std::sync::OnceLock; +use std::sync::RwLock; + +use crate::MpcManager; +use crate::SigningManager; + +#[derive(Default)] +pub struct MpcServiceState { + mpc_manager: OnceLock>>, + signing_manager: RwLock>>, + reconfig_signatures: RwLock>>, +} + +impl MpcServiceState { + pub fn new() -> Self { + Self::default() + } + + pub fn mpc_manager(&self) -> Option>> { + self.mpc_manager.get().cloned() + } + + pub fn set_mpc_manager(&self, manager: MpcManager) { + match self.mpc_manager.get() { + Some(lock) => { + *lock.write().unwrap() = manager; + } + None => { + let _ = self.mpc_manager.set(Arc::new(RwLock::new(manager))); + } + } + } + + pub fn signing_manager_for(&self, epoch: u64) -> Option> { + let stored = self.signing_manager.read().unwrap(); + stored + .as_ref() + .filter(|manager| manager.epoch() == epoch) + .cloned() + } + + pub fn signing_verifying_key(&self) -> Option { + self.signing_manager + .read() + .unwrap() + .as_ref() + .map(|manager| manager.verifying_key()) + } + + pub fn store_signing_manager(&self, manager: SigningManager) { + *self.signing_manager.write().unwrap() = Some(Arc::new(manager)); + } + + /// Test-only. + pub fn clear_signing_manager_for_test(&self) { + *self.signing_manager.write().unwrap() = None; + } + + pub fn store_reconfig_signature(&self, epoch: u64, signature: Vec) { + self.reconfig_signatures + .write() + .unwrap() + .insert(epoch, signature); + } + + pub fn get_reconfig_signature(&self, epoch: u64) -> Option> { + self.reconfig_signatures + .read() + .unwrap() + .get(&epoch) + .cloned() + } +} diff --git a/crates/hashi/src/mpc/signing.rs b/crates/mpc/src/signing.rs similarity index 98% rename from crates/hashi/src/mpc/signing.rs rename to crates/mpc/src/signing.rs index f1cba6a05..cb95f7bb1 100644 --- a/crates/hashi/src/mpc/signing.rs +++ b/crates/mpc/src/signing.rs @@ -27,12 +27,12 @@ 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; -use crate::mpc::types::SigningError; -use crate::mpc::types::SigningResult; +use crate::metrics::MpcMetrics; +use crate::types::GetPartialSignaturesRequest; +use crate::types::GetPartialSignaturesResponse; +use crate::types::PartialSigningOutput; +use crate::types::SigningError; +use crate::types::SigningResult; /// A single contiguous batch of presignatures. struct PresigBatch { @@ -229,7 +229,7 @@ impl SigningManager { beacon_value: &S, derivation_address: Option<&DerivationAddress>, timeout: Duration, - metrics: &Metrics, + metrics: &MpcMetrics, ) -> SigningResult { let config = &self.config; let threshold = config.threshold; @@ -570,14 +570,14 @@ mod tests { use super::*; use crate::communication::ChannelError; use crate::communication::ChannelResult; - use crate::mpc::types::ComplainRequest; - use crate::mpc::types::ComplaintResponses; - use crate::mpc::types::GetPublicMpcOutputRequest; - use crate::mpc::types::GetPublicMpcOutputResponse; - use crate::mpc::types::RetrieveMessagesRequest; - use crate::mpc::types::RetrieveMessagesResponse; - use crate::mpc::types::SendMessagesRequest; - use crate::mpc::types::SendMessagesResponse; + use crate::types::ComplainRequest; + use crate::types::ComplaintResponses; + use crate::types::GetPublicMpcOutputRequest; + use crate::types::GetPublicMpcOutputResponse; + use crate::types::RetrieveMessagesRequest; + use crate::types::RetrieveMessagesResponse; + use crate::types::SendMessagesRequest; + use crate::types::SendMessagesResponse; use fastcrypto::groups::GroupElement; use fastcrypto::groups::Scalar; use fastcrypto::groups::secp256k1::schnorr::SchnorrPublicKey; @@ -591,8 +591,8 @@ mod tests { use rand::SeedableRng; use rand::rngs::StdRng; - fn test_metrics() -> Metrics { - Metrics::new(&prometheus::Registry::new()) + fn test_metrics() -> MpcMetrics { + MpcMetrics::new(&prometheus::Registry::new()) } fn mock_shares(rng: &mut impl AllowedRng, secret: S, t: u16, n: u16) -> Vec> { diff --git a/crates/hashi/src/storage/interfaces.rs b/crates/mpc/src/storage/interfaces.rs similarity index 96% rename from crates/hashi/src/storage/interfaces.rs rename to crates/mpc/src/storage/interfaces.rs index 3acad45c8..9dfa060fe 100644 --- a/crates/hashi/src/storage/interfaces.rs +++ b/crates/mpc/src/storage/interfaces.rs @@ -6,8 +6,8 @@ use fastcrypto_tbls::threshold_schnorr::avss; use fastcrypto_tbls::threshold_schnorr::batch_avss; use sui_sdk_types::Address; -pub use crate::mpc::types::Messages; -pub use crate::mpc::types::RotationMessages; +pub use crate::types::Messages; +pub use crate::types::RotationMessages; pub trait PublicMessagesStore: Send + Sync { /// Store a dealer's DKG message at the given epoch. diff --git a/crates/mpc/src/storage/mod.rs b/crates/mpc/src/storage/mod.rs new file mode 100644 index 000000000..2635af2ac --- /dev/null +++ b/crates/mpc/src/storage/mod.rs @@ -0,0 +1,7 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +mod interfaces; + +pub use interfaces::PublicMessagesStore; +pub use interfaces::RotationMessages; diff --git a/crates/mpc/src/test_committee_set.rs b/crates/mpc/src/test_committee_set.rs new file mode 100644 index 000000000..c4c83fe07 --- /dev/null +++ b/crates/mpc/src/test_committee_set.rs @@ -0,0 +1,60 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::BTreeMap; + +use hashi_types::committee::Committee; + +use crate::committee_view::CommitteeSetView; + +#[derive(Default)] +pub struct TestCommitteeSet { + pub epoch: u64, + pub pending_epoch_change: Option, + pub committees: BTreeMap, +} + +impl TestCommitteeSet { + pub fn new() -> Self { + Self::default() + } + + pub fn set_epoch(&mut self, epoch: u64) -> &mut Self { + self.epoch = epoch; + self + } + + pub fn set_pending_epoch_change(&mut self, pending: Option) -> &mut Self { + self.pending_epoch_change = pending; + self + } + + pub fn set_committees(&mut self, committees: BTreeMap) -> &mut Self { + self.committees = committees; + self + } + + pub fn current_committee(&self) -> Option<&Committee> { + self.committees.get(&self.epoch) + } + + pub fn previous_committee(&self) -> Option<&Committee> { + self.epoch + .checked_sub(1) + .and_then(|prev| self.committees.get(&prev)) + } +} + +impl CommitteeSetView for TestCommitteeSet { + fn epoch(&self) -> u64 { + self.epoch + } + + fn pending_epoch_change(&self) -> Option { + self.pending_epoch_change + } + + fn committees(&self) -> &BTreeMap { + &self.committees + } +} diff --git a/crates/hashi/src/mpc/types.rs b/crates/mpc/src/types.rs similarity index 100% rename from crates/hashi/src/mpc/types.rs rename to crates/mpc/src/types.rs diff --git a/docker/hashi/Containerfile b/docker/hashi/Containerfile index 9a8813fbb..7e6eb9c3f 100644 --- a/docker/hashi/Containerfile +++ b/docker/hashi/Containerfile @@ -14,6 +14,7 @@ COPY --from=busybox . / COPY Cargo.toml Cargo.lock /src/ COPY crates/hashi/Cargo.toml /src/crates/hashi/Cargo.toml COPY crates/hashi-types/Cargo.toml /src/crates/hashi-types/Cargo.toml +COPY crates/mpc/Cargo.toml /src/crates/mpc/Cargo.toml WORKDIR /src @@ -22,7 +23,9 @@ RUN mkdir -p crates/hashi/src \ && echo 'fn main(){}' > crates/hashi/src/main.rs \ && touch crates/hashi/src/lib.rs \ && mkdir -p crates/hashi-types/src \ - && touch crates/hashi-types/src/lib.rs + && touch crates/hashi-types/src/lib.rs \ + && mkdir -p crates/mpc/src \ + && touch crates/mpc/src/lib.rs ENV TARGET=x86_64-unknown-linux-musl ENV OPENSSL_STATIC=true @@ -43,6 +46,7 @@ ENV GIT_REVISION=${GIT_REVISION} COPY crates/hashi /src/crates/hashi COPY crates/hashi-types /src/crates/hashi-types +COPY crates/mpc /src/crates/mpc # Touch source files to invalidate cargo's fingerprint cache for local crates RUN find crates/ -name "*.rs" -exec touch {} +