diff --git a/mithril-aggregator/src/multi_signer.rs b/mithril-aggregator/src/multi_signer.rs index dcffe59de8a..2e40937ca43 100644 --- a/mithril-aggregator/src/multi_signer.rs +++ b/mithril-aggregator/src/multi_signer.rs @@ -324,11 +324,11 @@ impl MultiSigner for MultiSignerImpl { /// Creates a multi signature from single signatures fn create_multi_signature(&mut self) -> Result, ProtocolError> { - debug!("Create multi signature"); - let message = &self .get_current_message() .ok_or_else(ProtocolError::UnavailableMessage)?; + + debug!("Create multi signature"; "message" => ?message); let signatures: Vec = self .single_signatures .iter() diff --git a/mithril-aggregator/src/runtime.rs b/mithril-aggregator/src/runtime.rs index 01ff8912470..7bc1285d57b 100644 --- a/mithril-aggregator/src/runtime.rs +++ b/mithril-aggregator/src/runtime.rs @@ -122,9 +122,9 @@ impl AggregatorRuntime { Ok(digest_result) => { let mut beacon = fake_data::beacon(); beacon.immutable_file_number = digest_result.last_immutable_file_number; - let message = fake_data::digest(&beacon); + let message = &digest_result.digest.clone().into_bytes(); - match self.manage_trigger_snapshot(&message, &beacon).await { + match self.manage_trigger_snapshot(message, &beacon).await { Ok(true) => { let snapshot_name = format!("{}.{}.tar.gz", self.network, &digest_result.digest); diff --git a/mithril-common/src/fake_data.rs b/mithril-common/src/fake_data.rs index 38607d767e5..8a97c76c45c 100644 --- a/mithril-common/src/fake_data.rs +++ b/mithril-common/src/fake_data.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] +use crate::digesters::DigesterResult; use std::time::{SystemTime, UNIX_EPOCH}; use crate::entities; @@ -26,6 +27,14 @@ pub fn digest(beacon: &entities::Beacon) -> Vec { .to_vec() } +/// Fake DigesterResult +pub fn digester_result(digest: &str) -> DigesterResult { + DigesterResult { + digest: digest.to_string(), + last_immutable_file_number: 0, + } +} + /// Fake ProtocolParameters pub fn protocol_parameters() -> entities::ProtocolParameters { let k = 5; diff --git a/mithril-signer/config/dev.json b/mithril-signer/config/dev.json index 387f2176894..29800992f1e 100644 --- a/mithril-signer/config/dev.json +++ b/mithril-signer/config/dev.json @@ -2,5 +2,6 @@ "network": "testnet", "aggregator_endpoint": "http://localhost:8080/aggregator", "party_id": 0, - "run_interval": 20000 + "run_interval": 20000, + "db_directory": "/db" } \ No newline at end of file diff --git a/mithril-signer/config/testnet.json b/mithril-signer/config/testnet.json index 8c758a5ec11..8b1c3be0f24 100644 --- a/mithril-signer/config/testnet.json +++ b/mithril-signer/config/testnet.json @@ -2,5 +2,6 @@ "network": "testnet", "aggregator_endpoint": "http://aggregator.api.mithril.network/aggregator", "party_id": 0, - "run_interval": 20000 + "run_interval": 20000, + "db_directory": "/db" } \ No newline at end of file diff --git a/mithril-signer/src/certificate_handler.rs b/mithril-signer/src/certificate_handler.rs index ffaf22d55cd..de7d86e6bc9 100644 --- a/mithril-signer/src/certificate_handler.rs +++ b/mithril-signer/src/certificate_handler.rs @@ -138,6 +138,7 @@ mod tests { use super::*; use httpmock::prelude::*; use serde_json::json; + use std::path::Path; use mithril_common::fake_data; @@ -150,6 +151,7 @@ mod tests { aggregator_endpoint: server.url(""), party_id: 0, run_interval: 100, + db_directory: Path::new("./").to_path_buf(), }; (server, config) } diff --git a/mithril-signer/src/entities.rs b/mithril-signer/src/entities.rs index 33a13efd10e..67b9b0cc218 100644 --- a/mithril-signer/src/entities.rs +++ b/mithril-signer/src/entities.rs @@ -1,5 +1,6 @@ use cli_table::Table; use serde::{Deserialize, Serialize}; +use std::path::{Display, Path, PathBuf}; /// Client configuration #[derive(Table, Debug, Clone, Serialize, Deserialize)] @@ -19,4 +20,15 @@ pub struct Config { /// Run Interval #[table(title = "Interval between two signatures attempts")] pub run_interval: u64, + + /// Directory to snapshot + #[table( + title = "Path to the Cardano Node db directory", + display_fn = "display_path" + )] + pub db_directory: PathBuf, +} + +fn display_path(path: &Path) -> Display<'_> { + path.display() } diff --git a/mithril-signer/src/lib.rs b/mithril-signer/src/lib.rs index 3b9eb24c52d..3355ecb8251 100644 --- a/mithril-signer/src/lib.rs +++ b/mithril-signer/src/lib.rs @@ -1,9 +1,9 @@ mod certificate_handler; mod entities; -mod signer; +mod runtime; mod single_signer; pub use certificate_handler::CertificateHandlerHTTPClient; pub use entities::Config; -pub use signer::Signer; +pub use runtime::Runtime; pub use single_signer::MithrilSingleSigner; diff --git a/mithril-signer/src/main.rs b/mithril-signer/src/main.rs index 40fc6af9581..b7f07633b76 100644 --- a/mithril-signer/src/main.rs +++ b/mithril-signer/src/main.rs @@ -1,12 +1,11 @@ use clap::Parser; +use mithril_common::digesters::ImmutableDigester; use slog::{o, Drain, Level, Logger}; -use slog_scope::{debug, error, info}; +use slog_scope::debug; use std::env; use std::sync::Arc; -use std::time::Duration; -use tokio::time::sleep; -use mithril_signer::{CertificateHandlerHTTPClient, Config, MithrilSingleSigner, Signer}; +use mithril_signer::{CertificateHandlerHTTPClient, Config, MithrilSingleSigner, Runtime}; /// CLI args #[derive(Parser)] @@ -65,13 +64,15 @@ async fn main() -> Result<(), String> { let protocol_initializer_encoded = ""; let single_signer = MithrilSingleSigner::new(config.party_id, protocol_initializer_encoded); let certificate_handler = CertificateHandlerHTTPClient::new(config.aggregator_endpoint.clone()); + let digester = ImmutableDigester::new(config.db_directory, slog_scope::logger()); - let mut signer = Signer::new(Box::new(certificate_handler), Box::new(single_signer)); - loop { - if let Err(e) = signer.run().await { - error!("{:?}", e) - } - info!("Sleeping for {}", config.run_interval); - sleep(Duration::from_millis(config.run_interval)).await; - } + // Should the runtime loop returns an error ? If yes should we abort the loop at the first error or is their some tolerance ? + let mut runtime = Runtime::new( + Box::new(certificate_handler), + Box::new(single_signer), + Box::new(digester), + ); + runtime.infinite_loop(config.run_interval).await; + + Ok(()) } diff --git a/mithril-signer/src/signer.rs b/mithril-signer/src/runtime.rs similarity index 57% rename from mithril-signer/src/signer.rs rename to mithril-signer/src/runtime.rs index 667e6da5866..008b4b9f801 100644 --- a/mithril-signer/src/signer.rs +++ b/mithril-signer/src/runtime.rs @@ -1,114 +1,162 @@ +use slog_scope::{error, info}; use std::collections::HashMap; +use std::time::Duration; use thiserror::Error; +use tokio::time::sleep; -use mithril_common::crypto_helper::key_encode_hex; -use mithril_common::entities::{self, Beacon, SignerWithStake}; +use crate::certificate_handler::CertificateHandlerError; +use crate::single_signer::SingleSignerError; +use mithril_common::crypto_helper::{key_encode_hex, Bytes}; +use mithril_common::digesters::{Digester, DigesterError}; +use mithril_common::entities::{self, Beacon, CertificatePending, SignerWithStake}; use mithril_common::fake_data; use super::certificate_handler::CertificateHandler; use super::single_signer::SingleSigner; -pub struct Signer { +pub struct Runtime { certificate_handler: Box, single_signer: Box, + digester: Box, current_beacon: Option, } -#[derive(Error, Debug, PartialEq)] -pub enum SignerError { +#[derive(Error, Debug)] +pub enum RuntimeError { #[error("single signatures computation failed: `{0}`")] - SingleSignaturesComputeFailed(String), + SingleSignaturesComputeFailed(#[from] SingleSignerError), #[error("could not retrieve pending certificate: `{0}`")] - RetrievePendingCertificateFailed(String), + RetrievePendingCertificateFailed(#[from] CertificateHandlerError), #[error("could not retrieve protocol initializer")] RetrieveProtocolInitializerFailed(), #[error("register signer failed: `{0}`")] RegisterSignerFailed(String), #[error("codec error:`{0}`")] Codec(String), + #[error("digest computation failed: `{0}`")] + Digester(#[from] DigesterError), } -impl Signer { +impl Runtime { pub fn new( certificate_handler: Box, single_signer: Box, + digester: Box, ) -> Self { Self { certificate_handler, single_signer, + digester, current_beacon: None, } } - pub async fn run(&mut self) -> Result<(), SignerError> { + pub async fn infinite_loop(&mut self, loop_interval: u64) { + loop { + if let Err(e) = self.run().await { + error!("{:?}", e) + } + + info!("Sleeping for {}", loop_interval); + sleep(Duration::from_millis(loop_interval)).await; + } + } + + pub async fn run(&mut self) -> Result<(), RuntimeError> { if let Some(pending_certificate) = self .certificate_handler .retrieve_pending_certificate() - .await - .map_err(|e| SignerError::RetrievePendingCertificateFailed(e.to_string()))? + .await? { - let message = fake_data::digest(&pending_certificate.beacon); - let must_register_signature = match &self.current_beacon { - None => true, - Some(beacon) => beacon != &pending_certificate.beacon, - }; - - let must_register_signer = !self.single_signer.get_is_registered(); - if must_register_signer { - if let Some(protocol_initializer) = self.single_signer.get_protocol_initializer() { - let verification_key = protocol_initializer.verification_key(); - let verification_key = - key_encode_hex(verification_key).map_err(SignerError::Codec)?; - let signer = - entities::Signer::new(self.single_signer.get_party_id(), verification_key); - self.certificate_handler - .register_signer(&signer) - .await - .map_err(|e| SignerError::RegisterSignerFailed(e.to_string()))?; - self.single_signer - .update_is_registered(true) - .map_err(|e| SignerError::RegisterSignerFailed(e.to_string()))?; - } + self.register_to_aggregator_if_needed().await?; + + if self.should_register_signature(&pending_certificate.beacon) { + let message = self.digester.compute_digest()?; + info!("Signing digest"; "digester_result" => #?message); + self.register_signature(message.digest.into_bytes(), pending_certificate) + .await?; } + } + + Ok(()) + } - if must_register_signature { - let stake_distribution = fake_data::signers_with_stakes(5); - let verification_keys = pending_certificate - .signers - .iter() - .map(|signer| (signer.party_id, signer.verification_key.as_str())) - .collect::>(); - let stake_distribution_extended = stake_distribution - .into_iter() - .map(|signer| { - let verification_key = match verification_keys.get(&signer.party_id) { - Some(verification_key_found) => *verification_key_found, - None => "", - }; - SignerWithStake::new( - signer.party_id, - verification_key.to_string(), - signer.stake, - ) - }) - .collect::>(); - let signatures = self - .single_signer - .compute_single_signatures( - message, - stake_distribution_extended, - &pending_certificate.protocol_parameters, - ) - .map_err(|e| SignerError::SingleSignaturesComputeFailed(e.to_string()))?; - if !signatures.is_empty() { - let _ = self - .certificate_handler - .register_signatures(&signatures) - .await; + fn should_register_signature(&self, new_beacon: &Beacon) -> bool { + match &self.current_beacon { + None => { + info!("Unknown beacon, signatures will be registered ..."); + true + } + Some(beacon) => { + if beacon != new_beacon { + info!("The beacon changed, signatures will be registered ..."); + true + } else { + info!("Signatures already registered for this beacon"); + false } - self.current_beacon = Some(pending_certificate.beacon); } } + } + + async fn register_to_aggregator_if_needed(&mut self) -> Result<(), RuntimeError> { + let must_register_to_aggregator = !self.single_signer.get_is_registered(); + if !must_register_to_aggregator { + return Ok(()); + } + + if let Some(protocol_initializer) = self.single_signer.get_protocol_initializer() { + let verification_key = protocol_initializer.verification_key(); + let verification_key = key_encode_hex(verification_key).map_err(RuntimeError::Codec)?; + let signer = entities::Signer::new(self.single_signer.get_party_id(), verification_key); + self.certificate_handler + .register_signer(&signer) + .await + .map_err(|e| RuntimeError::RegisterSignerFailed(e.to_string()))?; + self.single_signer + .update_is_registered(true) + .map_err(|e| RuntimeError::RegisterSignerFailed(e.to_string()))?; + } + + Ok(()) + } + + async fn register_signature( + &mut self, + message: Bytes, + pending_certificate: CertificatePending, + ) -> Result<(), RuntimeError> { + let verification_keys = pending_certificate + .signers + .iter() + .map(|signer| (signer.party_id, signer.verification_key.as_str())) + .collect::>(); + + let stake_distribution = fake_data::signers_with_stakes(5); + let stake_distribution_extended = stake_distribution + .into_iter() + .map(|signer| { + let verification_key = match verification_keys.get(&signer.party_id) { + Some(verification_key_found) => *verification_key_found, + None => "", + }; + SignerWithStake::new(signer.party_id, verification_key.to_string(), signer.stake) + }) + .collect::>(); + + let signatures = self.single_signer.compute_single_signatures( + message, + stake_distribution_extended, + &pending_certificate.protocol_parameters, + )?; + + if !signatures.is_empty() { + let _ = self + .certificate_handler + .register_signatures(&signatures) + .await; + } + self.current_beacon = Some(pending_certificate.beacon); Ok(()) } @@ -119,8 +167,18 @@ mod tests { use super::super::certificate_handler::{CertificateHandlerError, MockCertificateHandler}; use super::super::single_signer::{MockSingleSigner, SingleSignerError}; use super::*; + use mithril_common::crypto_helper::tests_setup::*; + use mithril_common::digesters::{Digester, DigesterError, DigesterResult}; use mithril_common::fake_data; + use mockall::mock; + + mock! { + pub DigesterImpl { } + impl Digester for DigesterImpl { + fn compute_digest(&self) -> Result; + } + } #[tokio::test] async fn signer_doesnt_sign_when_there_is_no_pending_certificate() { @@ -129,6 +187,7 @@ mod tests { let protocol_initializer = current_signer.4.clone(); let mut mock_certificate_handler = MockCertificateHandler::new(); let mut mock_single_signer = MockSingleSigner::new(); + let mock_digester = MockDigesterImpl::new(); mock_certificate_handler .expect_retrieve_pending_certificate() .return_once(|| Ok(None)); @@ -148,9 +207,10 @@ mod tests { .expect_get_is_registered() .return_once(|| false); - let mut signer = Signer::new( + let mut signer = Runtime::new( Box::new(mock_certificate_handler), Box::new(mock_single_signer), + Box::new(mock_digester), ); assert!(signer.run().await.is_ok()); } @@ -159,6 +219,7 @@ mod tests { async fn signer_fails_when_pending_certificate_fails() { let mut mock_certificate_handler = MockCertificateHandler::new(); let mut mock_single_signer = MockSingleSigner::new(); + let mock_digester = MockDigesterImpl::new(); mock_certificate_handler .expect_retrieve_pending_certificate() .return_once(|| { @@ -170,15 +231,17 @@ mod tests { .expect_get_protocol_initializer() .return_once(move || None); - let mut signer = Signer::new( + let mut signer = Runtime::new( Box::new(mock_certificate_handler), Box::new(mock_single_signer), + Box::new(mock_digester), ); assert_eq!( - SignerError::RetrievePendingCertificateFailed( - CertificateHandlerError::RemoteServerTechnical("An Error".to_string()).to_string() - ), - signer.run().await.unwrap_err() + RuntimeError::RetrievePendingCertificateFailed( + CertificateHandlerError::RemoteServerTechnical("An Error".to_string()) + ) + .to_string(), + signer.run().await.unwrap_err().to_string() ); } @@ -189,6 +252,7 @@ mod tests { let protocol_initializer = current_signer.4.clone(); let mut mock_certificate_handler = MockCertificateHandler::new(); let mut mock_single_signer = MockSingleSigner::new(); + let mut mock_digester = MockDigesterImpl::new(); let pending_certificate = fake_data::certificate_pending(); mock_certificate_handler .expect_retrieve_pending_certificate() @@ -212,17 +276,21 @@ mod tests { .return_once(move || party_id); mock_single_signer .expect_get_protocol_initializer() - .return_once(move || Some(protocol_initializer.clone())); + .return_once(move || Some(protocol_initializer)); mock_single_signer .expect_get_is_registered() .return_once(|| false); mock_single_signer .expect_update_is_registered() .return_once(move |_| Ok(())); + mock_digester + .expect_compute_digest() + .return_once(|| Ok(fake_data::digester_result("digest"))); - let mut signer = Signer::new( + let mut signer = Runtime::new( Box::new(mock_certificate_handler), Box::new(mock_single_signer), + Box::new(mock_digester), ); assert!(signer.run().await.is_ok()); assert!(signer.run().await.is_ok()); @@ -235,6 +303,7 @@ mod tests { let protocol_initializer = current_signer.4.clone(); let mut mock_certificate_handler = MockCertificateHandler::new(); let mut mock_single_signer = MockSingleSigner::new(); + let mut mock_digester = MockDigesterImpl::new(); let pending_certificate = fake_data::certificate_pending(); mock_certificate_handler .expect_retrieve_pending_certificate() @@ -268,10 +337,14 @@ mod tests { mock_single_signer .expect_get_protocol_initializer() .return_once(move || Some(protocol_initializer.clone())); + mock_digester + .expect_compute_digest() + .return_once(|| Ok(fake_data::digester_result("digest"))); - let mut signer = Signer::new( + let mut signer = Runtime::new( Box::new(mock_certificate_handler), Box::new(mock_single_signer), + Box::new(mock_digester), ); assert!(signer.run().await.is_ok()); assert!(signer.run().await.is_ok()); @@ -284,6 +357,7 @@ mod tests { let protocol_initializer = current_signer.4.clone(); let mut mock_certificate_handler = MockCertificateHandler::new(); let mut mock_single_signer = MockSingleSigner::new(); + let mut mock_digester = MockDigesterImpl::new(); let pending_certificate = fake_data::certificate_pending(); mock_certificate_handler .expect_retrieve_pending_certificate() @@ -309,10 +383,14 @@ mod tests { mock_single_signer .expect_update_is_registered() .return_once(move |_| Ok(())); + mock_digester + .expect_compute_digest() + .return_once(|| Ok(fake_data::digester_result("digest"))); - let mut signer = Signer::new( + let mut signer = Runtime::new( Box::new(mock_certificate_handler), Box::new(mock_single_signer), + Box::new(mock_digester), ); assert!(signer.run().await.is_ok()); } @@ -321,6 +399,7 @@ mod tests { async fn signer_fails_if_signature_computation_fails() { let mut mock_certificate_handler = MockCertificateHandler::new(); let mut mock_single_signer = MockSingleSigner::new(); + let mut mock_digester = MockDigesterImpl::new(); let pending_certificate = fake_data::certificate_pending(); mock_certificate_handler .expect_retrieve_pending_certificate() @@ -334,16 +413,21 @@ mod tests { mock_single_signer .expect_get_protocol_initializer() .return_once(move || None); + mock_digester + .expect_compute_digest() + .return_once(|| Ok(fake_data::digester_result("digest"))); - let mut signer = Signer::new( + let mut signer = Runtime::new( Box::new(mock_certificate_handler), Box::new(mock_single_signer), + Box::new(mock_digester), ); assert_eq!( - SignerError::SingleSignaturesComputeFailed( - SingleSignerError::UnregisteredVerificationKey().to_string() - ), - signer.run().await.unwrap_err() + RuntimeError::SingleSignaturesComputeFailed( + SingleSignerError::UnregisteredVerificationKey() + ) + .to_string(), + signer.run().await.unwrap_err().to_string() ); } @@ -355,6 +439,7 @@ mod tests { let pending_certificate = fake_data::certificate_pending(); let mut mock_certificate_handler = MockCertificateHandler::new(); let mut mock_single_signer = MockSingleSigner::new(); + let mock_digester = MockDigesterImpl::new(); mock_certificate_handler .expect_retrieve_pending_certificate() .return_once(|| Ok(Some(pending_certificate))); @@ -378,16 +463,51 @@ mod tests { .expect_get_is_registered() .return_once(|| false); - let mut signer = Signer::new( + let mut signer = Runtime::new( Box::new(mock_certificate_handler), Box::new(mock_single_signer), + Box::new(mock_digester), ); assert_eq!( - SignerError::RegisterSignerFailed( - CertificateHandlerError::RemoteServerLogical("an error occurred".to_string(),) + RuntimeError::RegisterSignerFailed( + CertificateHandlerError::RemoteServerLogical("an error occurred".to_string()) .to_string() - ), - signer.run().await.unwrap_err() + ) + .to_string(), + signer.run().await.unwrap_err().to_string() + ); + } + + #[tokio::test] + async fn signer_fails_if_digest_computation_fails() { + let mut mock_certificate_handler = MockCertificateHandler::new(); + let mut mock_single_signer = MockSingleSigner::new(); + let mut mock_digester = MockDigesterImpl::new(); + let pending_certificate = fake_data::certificate_pending(); + mock_certificate_handler + .expect_retrieve_pending_certificate() + .return_once(|| Ok(Some(pending_certificate))); + mock_single_signer + .expect_compute_single_signatures() + .never(); + mock_single_signer + .expect_get_is_registered() + .return_once(|| false); + mock_single_signer + .expect_get_protocol_initializer() + .return_once(move || None); + mock_digester + .expect_compute_digest() + .return_once(|| Err(DigesterError::NotEnoughImmutable())); + + let mut signer = Runtime::new( + Box::new(mock_certificate_handler), + Box::new(mock_single_signer), + Box::new(mock_digester), + ); + assert_eq!( + RuntimeError::Digester(DigesterError::NotEnoughImmutable()).to_string(), + signer.run().await.unwrap_err().to_string() ); } } diff --git a/mithril-test-lab/mithril-end-to-end/src/Mithril/Signer.hs b/mithril-test-lab/mithril-end-to-end/src/Mithril/Signer.hs index add2b70bec2..608d2b253e9 100644 --- a/mithril-test-lab/mithril-end-to-end/src/Mithril/Signer.hs +++ b/mithril-test-lab/mithril-end-to-end/src/Mithril/Signer.hs @@ -56,7 +56,8 @@ signerProcess cwd aggregatorEndpoint = do [ ("AGGREGATOR_ENDPOINT", toString aggregatorEndpoint), ("NETWORK", "testnet"), ("PARTY_ID", "0"), - ("RUN_INTERVAL", "2") + ("RUN_INTERVAL", "2"), + ("DB_DIRECTORY", "db") ] <> baseEnv diff --git a/mithril-test-lab/mithril-end-to-end/test/Test/EndToEndSpec.hs b/mithril-test-lab/mithril-end-to-end/test/Test/EndToEndSpec.hs index 0ec6ee4d422..c1df8577fde 100644 --- a/mithril-test-lab/mithril-end-to-end/test/Test/EndToEndSpec.hs +++ b/mithril-test-lab/mithril-end-to-end/test/Test/EndToEndSpec.hs @@ -62,7 +62,7 @@ spec = -- Start aggregator service on some random port withAggregator (takeDirectory nodeSocket) (contramap AggregatorLog tr) $ \aggregator@Aggregator {aggregatorPort} -> do waitForAggregator aggregatorPort - withSigner tmp (contramap SignerLog tr) aggregatorPort node $ \signer -> do + withSigner (takeDirectory nodeSocket) (contramap SignerLog tr) aggregatorPort node $ \signer -> do digest <- assertNodeIsProducingSnapshot tr node aggregatorPort assertSignerIsSigningSnapshot signer aggregatorPort digest assertClientCanVerifySnapshot tmp aggregator digest