From 4e8df764aa34969e26d4bf7168f49199888eb793 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 7 Jul 2025 18:46:29 +0200 Subject: [PATCH 01/49] refactor(dmq): move 'publisher' in 'client' sub-module --- .../mithril-dmq/src/publisher/{ => client}/interface.rs | 0 internal/mithril-dmq/src/publisher/client/mod.rs | 5 +++++ internal/mithril-dmq/src/publisher/{ => client}/pallas.rs | 0 internal/mithril-dmq/src/publisher/mod.rs | 6 ++---- 4 files changed, 7 insertions(+), 4 deletions(-) rename internal/mithril-dmq/src/publisher/{ => client}/interface.rs (100%) create mode 100644 internal/mithril-dmq/src/publisher/client/mod.rs rename internal/mithril-dmq/src/publisher/{ => client}/pallas.rs (100%) diff --git a/internal/mithril-dmq/src/publisher/interface.rs b/internal/mithril-dmq/src/publisher/client/interface.rs similarity index 100% rename from internal/mithril-dmq/src/publisher/interface.rs rename to internal/mithril-dmq/src/publisher/client/interface.rs diff --git a/internal/mithril-dmq/src/publisher/client/mod.rs b/internal/mithril-dmq/src/publisher/client/mod.rs new file mode 100644 index 00000000000..4035f6c0659 --- /dev/null +++ b/internal/mithril-dmq/src/publisher/client/mod.rs @@ -0,0 +1,5 @@ +mod interface; +mod pallas; + +pub use interface::*; +pub use pallas::*; diff --git a/internal/mithril-dmq/src/publisher/pallas.rs b/internal/mithril-dmq/src/publisher/client/pallas.rs similarity index 100% rename from internal/mithril-dmq/src/publisher/pallas.rs rename to internal/mithril-dmq/src/publisher/client/pallas.rs diff --git a/internal/mithril-dmq/src/publisher/mod.rs b/internal/mithril-dmq/src/publisher/mod.rs index 4035f6c0659..be50984ff39 100644 --- a/internal/mithril-dmq/src/publisher/mod.rs +++ b/internal/mithril-dmq/src/publisher/mod.rs @@ -1,5 +1,3 @@ -mod interface; -mod pallas; +mod client; -pub use interface::*; -pub use pallas::*; +pub use client::*; From 861f97e9ce51e658d8d644270d5d33a958caa1c4 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 7 Jul 2025 18:44:17 +0200 Subject: [PATCH 02/49] refactor(dmq): move 'consumer' in 'client' sub-module --- internal/mithril-dmq/src/consumer/{ => client}/interface.rs | 0 internal/mithril-dmq/src/consumer/client/mod.rs | 5 +++++ internal/mithril-dmq/src/consumer/{ => client}/pallas.rs | 0 internal/mithril-dmq/src/consumer/mod.rs | 6 ++---- 4 files changed, 7 insertions(+), 4 deletions(-) rename internal/mithril-dmq/src/consumer/{ => client}/interface.rs (100%) create mode 100644 internal/mithril-dmq/src/consumer/client/mod.rs rename internal/mithril-dmq/src/consumer/{ => client}/pallas.rs (100%) diff --git a/internal/mithril-dmq/src/consumer/interface.rs b/internal/mithril-dmq/src/consumer/client/interface.rs similarity index 100% rename from internal/mithril-dmq/src/consumer/interface.rs rename to internal/mithril-dmq/src/consumer/client/interface.rs diff --git a/internal/mithril-dmq/src/consumer/client/mod.rs b/internal/mithril-dmq/src/consumer/client/mod.rs new file mode 100644 index 00000000000..4035f6c0659 --- /dev/null +++ b/internal/mithril-dmq/src/consumer/client/mod.rs @@ -0,0 +1,5 @@ +mod interface; +mod pallas; + +pub use interface::*; +pub use pallas::*; diff --git a/internal/mithril-dmq/src/consumer/pallas.rs b/internal/mithril-dmq/src/consumer/client/pallas.rs similarity index 100% rename from internal/mithril-dmq/src/consumer/pallas.rs rename to internal/mithril-dmq/src/consumer/client/pallas.rs diff --git a/internal/mithril-dmq/src/consumer/mod.rs b/internal/mithril-dmq/src/consumer/mod.rs index 4035f6c0659..be50984ff39 100644 --- a/internal/mithril-dmq/src/consumer/mod.rs +++ b/internal/mithril-dmq/src/consumer/mod.rs @@ -1,5 +1,3 @@ -mod interface; -mod pallas; +mod client; -pub use interface::*; -pub use pallas::*; +pub use client::*; From c0868f00948f9f44d9416494ee5ae32145cc03f6 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 7 Jul 2025 18:54:09 +0200 Subject: [PATCH 03/49] refactor(dmq): rename 'DmqConsumerPallas' to 'DmqConsumerClientPallas' --- .../src/consumer/client/interface.rs | 4 ++-- .../mithril-dmq/src/consumer/client/pallas.rs | 20 +++++++++---------- internal/mithril-dmq/src/lib.rs | 2 +- .../mithril-dmq/src/test/double/consumer.rs | 6 +++--- .../builder/enablers/misc.rs | 13 ++++++------ .../src/services/signature_consumer/dmq.rs | 6 +++--- 6 files changed, 26 insertions(+), 25 deletions(-) diff --git a/internal/mithril-dmq/src/consumer/client/interface.rs b/internal/mithril-dmq/src/consumer/client/interface.rs index 4c8bc74d995..e9b4915c13e 100644 --- a/internal/mithril-dmq/src/consumer/client/interface.rs +++ b/internal/mithril-dmq/src/consumer/client/interface.rs @@ -2,10 +2,10 @@ use std::fmt::Debug; use mithril_common::{StdResult, crypto_helper::TryFromBytes, entities::PartyId}; -/// Trait for consuming messages from a DMQ node. +/// Trait for the client side of consuming messages from a DMQ node. #[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] -pub trait DmqConsumer: Send + Sync { +pub trait DmqConsumerClient: Send + Sync { /// Consume messages from the DMQ node. async fn consume_messages(&self) -> StdResult>; } diff --git a/internal/mithril-dmq/src/consumer/client/pallas.rs b/internal/mithril-dmq/src/consumer/client/pallas.rs index dee67e173ef..c2086363aa2 100644 --- a/internal/mithril-dmq/src/consumer/client/pallas.rs +++ b/internal/mithril-dmq/src/consumer/client/pallas.rs @@ -12,12 +12,12 @@ use mithril_common::{ logging::LoggerExtensions, }; -use crate::DmqConsumer; +use crate::DmqConsumerClient; -/// A DMQ consumer implementation. +/// A DMQ client consumer implementation. /// /// This implementation is built upon the n2c mini-protocols DMQ implementation in Pallas. -pub struct DmqConsumerPallas { +pub struct DmqConsumerClientPallas { socket: PathBuf, network: CardanoNetwork, client: Mutex>, @@ -25,8 +25,8 @@ pub struct DmqConsumerPallas { phantom: PhantomData, } -impl DmqConsumerPallas { - /// Creates a new `DmqConsumerPallas` instance. +impl DmqConsumerClientPallas { + /// Creates a new `DmqConsumerClientPallas` instance. pub fn new(socket: PathBuf, network: CardanoNetwork, logger: Logger) -> Self { Self { socket, @@ -47,7 +47,7 @@ impl DmqConsumerPallas { ); DmqClient::connect(&self.socket, self.network.magic_id()) .await - .with_context(|| "DmqConsumerPallas failed to create a new client") + .with_context(|| "DmqConsumerClientPallas failed to create a new client") } /// Gets the cached `DmqClient`, creating a new one if it does not exist. @@ -128,7 +128,7 @@ impl DmqConsumerPallas { } #[async_trait::async_trait] -impl DmqConsumer for DmqConsumerPallas { +impl DmqConsumerClient for DmqConsumerClientPallas { async fn consume_messages(&self) -> StdResult> { let messages = self.consume_messages_internal().await; if messages.is_err() { @@ -247,7 +247,7 @@ mod tests { let reply_messages = fake_msgs(); let server = setup_dmq_server(socket_path.clone(), reply_messages); let client = tokio::spawn(async move { - let consumer = DmqConsumerPallas::new( + let consumer = DmqConsumerClientPallas::new( socket_path, CardanoNetwork::TestNet(0), TestLogger::stdout(), @@ -280,7 +280,7 @@ mod tests { let reply_messages = vec![]; let server = setup_dmq_server(socket_path.clone(), reply_messages); let client = tokio::spawn(async move { - let consumer = DmqConsumerPallas::::new( + let consumer = DmqConsumerClientPallas::::new( socket_path, CardanoNetwork::TestNet(0), TestLogger::stdout(), @@ -304,7 +304,7 @@ mod tests { let reply_messages = fake_msgs(); let server = setup_dmq_server(socket_path.clone(), reply_messages); let client = tokio::spawn(async move { - let consumer = DmqConsumerPallas::::new( + let consumer = DmqConsumerClientPallas::::new( socket_path, CardanoNetwork::TestNet(0), TestLogger::stdout(), diff --git a/internal/mithril-dmq/src/lib.rs b/internal/mithril-dmq/src/lib.rs index 73549c74bac..6ea5ce02dff 100644 --- a/internal/mithril-dmq/src/lib.rs +++ b/internal/mithril-dmq/src/lib.rs @@ -6,6 +6,6 @@ mod message; mod publisher; pub mod test; -pub use consumer::{DmqConsumer, DmqConsumerPallas}; +pub use consumer::{DmqConsumerClient, DmqConsumerClientPallas}; pub use message::DmqMessageBuilder; pub use publisher::{DmqPublisher, DmqPublisherPallas}; diff --git a/internal/mithril-dmq/src/test/double/consumer.rs b/internal/mithril-dmq/src/test/double/consumer.rs index 23ff0cc3e03..45182b93b2b 100644 --- a/internal/mithril-dmq/src/test/double/consumer.rs +++ b/internal/mithril-dmq/src/test/double/consumer.rs @@ -4,11 +4,11 @@ use tokio::sync::Mutex; use mithril_common::{StdResult, crypto_helper::TryFromBytes, entities::PartyId}; -use crate::DmqConsumer; +use crate::DmqConsumerClient; type ConsumerReturn = StdResult>; -/// A fake implementation of the [DmqConsumer] trait for testing purposes. +/// A fake implementation of the [DmqConsumerClient] trait for testing purposes. pub struct DmqConsumerFake { results: Mutex>>, } @@ -23,7 +23,7 @@ impl DmqConsumerFake { } #[async_trait::async_trait] -impl DmqConsumer for DmqConsumerFake { +impl DmqConsumerClient for DmqConsumerFake { async fn consume_messages(&self) -> ConsumerReturn { let mut results = self.results.lock().await; diff --git a/mithril-aggregator/src/dependency_injection/builder/enablers/misc.rs b/mithril-aggregator/src/dependency_injection/builder/enablers/misc.rs index 497f3262ecb..62f4e17526a 100644 --- a/mithril-aggregator/src/dependency_injection/builder/enablers/misc.rs +++ b/mithril-aggregator/src/dependency_injection/builder/enablers/misc.rs @@ -12,7 +12,7 @@ use std::time::Duration; #[cfg(feature = "future_dmq")] use mithril_common::messages::RegisterSignatureMessageDmq; #[cfg(feature = "future_dmq")] -use mithril_dmq::DmqConsumerPallas; +use mithril_dmq::DmqConsumerClientPallas; use mithril_signed_entity_lock::SignedEntityTypeLock; use crate::database::repository::CertificateRepository; @@ -89,11 +89,12 @@ impl DependenciesBuilder { #[cfg(feature = "future_dmq")] let signature_consumer = match self.configuration.dmq_node_socket_path() { Some(dmq_node_socket_path) => { - let dmq_consumer = Arc::new(DmqConsumerPallas::::new( - dmq_node_socket_path, - self.configuration.get_network()?, - self.root_logger(), - )); + let dmq_consumer = + Arc::new(DmqConsumerClientPallas::::new( + dmq_node_socket_path, + self.configuration.get_network()?, + self.root_logger(), + )); Arc::new(SignatureConsumerDmq::new(dmq_consumer)) as Arc } _ => Arc::new(SignatureConsumerNoop) as Arc, diff --git a/mithril-aggregator/src/services/signature_consumer/dmq.rs b/mithril-aggregator/src/services/signature_consumer/dmq.rs index 1bdf0b771cf..a2d9842d432 100644 --- a/mithril-aggregator/src/services/signature_consumer/dmq.rs +++ b/mithril-aggregator/src/services/signature_consumer/dmq.rs @@ -9,18 +9,18 @@ use mithril_common::{ messages::RegisterSignatureMessageDmq, }; -use mithril_dmq::DmqConsumer; +use mithril_dmq::DmqConsumerClient; use super::SignatureConsumer; /// DMQ implementation of the [SignatureConsumer] trait. pub struct SignatureConsumerDmq { - dmq_consumer: Arc>, + dmq_consumer: Arc>, } impl SignatureConsumerDmq { /// Creates a new instance of [SignatureConsumerDmq]. - pub fn new(dmq_consumer: Arc>) -> Self { + pub fn new(dmq_consumer: Arc>) -> Self { Self { dmq_consumer } } } From 764a4b67af24aa9a2a518a95e60f5730e165490a Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 7 Jul 2025 19:19:28 +0200 Subject: [PATCH 04/49] refactor(dmq): rename 'DmqPublisherPallas' to 'DmqPublisherClientPallas' --- internal/mithril-dmq/src/lib.rs | 27 ++++++++++++++++++- .../src/publisher/client/interface.rs | 4 +-- .../src/publisher/client/pallas.rs | 18 ++++++------- .../mithril-dmq/src/test/double/publisher.rs | 6 ++--- .../src/dependency_injection/builder.rs | 18 ++++++------- .../src/services/signature_publisher/dmq.rs | 6 ++--- 6 files changed, 52 insertions(+), 27 deletions(-) diff --git a/internal/mithril-dmq/src/lib.rs b/internal/mithril-dmq/src/lib.rs index 6ea5ce02dff..2f5c6cf7a0e 100644 --- a/internal/mithril-dmq/src/lib.rs +++ b/internal/mithril-dmq/src/lib.rs @@ -8,4 +8,29 @@ pub mod test; pub use consumer::{DmqConsumerClient, DmqConsumerClientPallas}; pub use message::DmqMessageBuilder; -pub use publisher::{DmqPublisher, DmqPublisherPallas}; +pub use publisher::{DmqPublisherClient, DmqPublisherClientPallas}; + +#[cfg(test)] +pub(crate) mod test_tools { + use std::io; + use std::sync::Arc; + + use slog::{Drain, Logger}; + use slog_async::Async; + use slog_term::{CompactFormat, PlainDecorator}; + + pub struct TestLogger; + + impl TestLogger { + fn from_writer(writer: W) -> Logger { + let decorator = PlainDecorator::new(writer); + let drain = CompactFormat::new(decorator).build().fuse(); + let drain = Async::new(drain).build().fuse(); + Logger::root(Arc::new(drain), slog::o!()) + } + + pub fn stdout() -> Logger { + Self::from_writer(slog_term::TestStdoutWriter) + } + } +} diff --git a/internal/mithril-dmq/src/publisher/client/interface.rs b/internal/mithril-dmq/src/publisher/client/interface.rs index d5d5ed25098..f58a6687fcf 100644 --- a/internal/mithril-dmq/src/publisher/client/interface.rs +++ b/internal/mithril-dmq/src/publisher/client/interface.rs @@ -1,9 +1,9 @@ use mithril_common::{StdResult, crypto_helper::TryToBytes}; -/// Trait for publishing messages from a DMQ node. +/// Trait for the client side of publishing messages from a DMQ node. #[cfg_attr(test, mockall::automock)] #[async_trait::async_trait] -pub trait DmqPublisher: Send + Sync { +pub trait DmqPublisherClient: Send + Sync { /// Publishes a message to the DMQ node. async fn publish_message(&self, message: M) -> StdResult<()>; } diff --git a/internal/mithril-dmq/src/publisher/client/pallas.rs b/internal/mithril-dmq/src/publisher/client/pallas.rs index 64fc089f098..1402fc40618 100644 --- a/internal/mithril-dmq/src/publisher/client/pallas.rs +++ b/internal/mithril-dmq/src/publisher/client/pallas.rs @@ -8,12 +8,12 @@ use mithril_common::{ CardanoNetwork, StdResult, crypto_helper::TryToBytes, logging::LoggerExtensions, }; -use crate::{DmqMessageBuilder, DmqPublisher}; +use crate::{DmqMessageBuilder, DmqPublisherClient}; -/// A DMQ publisher implementation. +/// A DMQ client publisher implementation. /// /// This implementation is built upon the n2c mini-protocols DMQ implementation in Pallas. -pub struct DmqPublisherPallas { +pub struct DmqPublisherClientPallas { socket: PathBuf, network: CardanoNetwork, dmq_message_builder: DmqMessageBuilder, @@ -21,8 +21,8 @@ pub struct DmqPublisherPallas { phantom: PhantomData, } -impl DmqPublisherPallas { - /// Creates a new instance of [DmqPublisherPallas]. +impl DmqPublisherClientPallas { + /// Creates a new instance of [DmqPublisherClientPallas]. pub fn new( socket: PathBuf, network: CardanoNetwork, @@ -43,12 +43,12 @@ impl DmqPublisherPallas { let magic = self.network.magic_id(); DmqClient::connect(&self.socket, magic) .await - .with_context(|| "DmqPublisherPallas failed to create a new client") + .with_context(|| "DmqPublisherClientPallas failed to create a new client") } } #[async_trait::async_trait] -impl DmqPublisher for DmqPublisherPallas { +impl DmqPublisherClient for DmqPublisherClientPallas { async fn publish_message(&self, message: M) -> StdResult<()> { debug!( self.logger, @@ -146,7 +146,7 @@ mod tests { let reply_success = true; let server = setup_dmq_server(socket_path.clone(), reply_success); let client = tokio::spawn(async move { - let publisher = DmqPublisherPallas::new( + let publisher = DmqPublisherClientPallas::new( socket_path, CardanoNetwork::TestNet(0), DmqMessageBuilder::new( @@ -180,7 +180,7 @@ mod tests { let reply_success = false; let server = setup_dmq_server(socket_path.clone(), reply_success); let client = tokio::spawn(async move { - let publisher = DmqPublisherPallas::new( + let publisher = DmqPublisherClientPallas::new( socket_path, CardanoNetwork::TestNet(0), DmqMessageBuilder::new( diff --git a/internal/mithril-dmq/src/test/double/publisher.rs b/internal/mithril-dmq/src/test/double/publisher.rs index 0cc8d5e7032..4f6ca39a6d7 100644 --- a/internal/mithril-dmq/src/test/double/publisher.rs +++ b/internal/mithril-dmq/src/test/double/publisher.rs @@ -4,9 +4,9 @@ use tokio::sync::Mutex; use mithril_common::{StdResult, crypto_helper::TryToBytes}; -use crate::DmqPublisher; +use crate::DmqPublisherClient; -/// A fake implementation of the [DmqPublisher] trait for testing purposes. +/// A fake implementation of the [DmqPublisherClient] trait for testing purposes. pub struct DmqPublisherFake { results: Mutex>>, phantom: PhantomData, @@ -23,7 +23,7 @@ impl DmqPublisherFake { } #[async_trait::async_trait] -impl DmqPublisher for DmqPublisherFake { +impl DmqPublisherClient for DmqPublisherFake { async fn publish_message(&self, _message: M) -> StdResult<()> { let mut results = self.results.lock().await; diff --git a/mithril-signer/src/dependency_injection/builder.rs b/mithril-signer/src/dependency_injection/builder.rs index 65980f6cff0..adb904ee931 100644 --- a/mithril-signer/src/dependency_injection/builder.rs +++ b/mithril-signer/src/dependency_injection/builder.rs @@ -42,7 +42,7 @@ use mithril_persistence::database::{ApplicationNodeType, SqlMigration}; use mithril_persistence::sqlite::{ConnectionBuilder, SqliteConnection, SqliteConnectionPool}; #[cfg(feature = "future_dmq")] -use mithril_dmq::{DmqMessageBuilder, DmqPublisherPallas}; +use mithril_dmq::{DmqMessageBuilder, DmqPublisherClientPallas}; use crate::dependency_injection::SignerDependencyContainer; #[cfg(feature = "future_dmq")] @@ -430,14 +430,14 @@ impl<'a> DependenciesBuilder<'a> { ))?, chain_observer.clone(), ); - Arc::new(SignaturePublisherDmq::new(Arc::new(DmqPublisherPallas::< - RegisterSignatureMessageDmq, - >::new( - dmq_node_socket_path.to_owned(), - *cardano_network, - dmq_message_builder, - self.root_logger(), - )))) as Arc + Arc::new(SignaturePublisherDmq::new(Arc::new( + DmqPublisherClientPallas::::new( + dmq_node_socket_path.to_owned(), + *cardano_network, + dmq_message_builder, + self.root_logger(), + ), + ))) as Arc } _ => Arc::new(SignaturePublisherNoop) as Arc, }; diff --git a/mithril-signer/src/services/signature_publisher/dmq.rs b/mithril-signer/src/services/signature_publisher/dmq.rs index 68c8ea5f61b..2b65eb7d7b2 100644 --- a/mithril-signer/src/services/signature_publisher/dmq.rs +++ b/mithril-signer/src/services/signature_publisher/dmq.rs @@ -8,18 +8,18 @@ use mithril_common::{ entities::{ProtocolMessage, SignedEntityType, SingleSignature}, messages::RegisterSignatureMessageDmq, }; -use mithril_dmq::DmqPublisher; +use mithril_dmq::DmqPublisherClient; use super::SignaturePublisher; /// DMQ implementation of the [SignaturePublisher] trait. pub struct SignaturePublisherDmq { - dmq_publisher: Arc>, + dmq_publisher: Arc>, } impl SignaturePublisherDmq { /// Creates a new instance of [SignaturePublisherDmq]. - pub fn new(dmq_publisher: Arc>) -> Self { + pub fn new(dmq_publisher: Arc>) -> Self { Self { dmq_publisher } } } From dbe6497db4e406759cd53b9b3f375dbaca48005c Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 12:04:03 +0200 Subject: [PATCH 05/49] refactor(dmq): promote 'message' module to directory --- internal/mithril-dmq/src/lib.rs | 4 ++-- .../src/{message.rs => model/builder.rs} | 16 +++++++++------- internal/mithril-dmq/src/model/mod.rs | 3 +++ 3 files changed, 14 insertions(+), 9 deletions(-) rename internal/mithril-dmq/src/{message.rs => model/builder.rs} (96%) create mode 100644 internal/mithril-dmq/src/model/mod.rs diff --git a/internal/mithril-dmq/src/lib.rs b/internal/mithril-dmq/src/lib.rs index 2f5c6cf7a0e..db4d1180ebe 100644 --- a/internal/mithril-dmq/src/lib.rs +++ b/internal/mithril-dmq/src/lib.rs @@ -2,12 +2,12 @@ //! This crate provides mechanisms to publish and consume messages of a Decentralized Message Queue network through a DMQ node. mod consumer; -mod message; +mod model; mod publisher; pub mod test; pub use consumer::{DmqConsumerClient, DmqConsumerClientPallas}; -pub use message::DmqMessageBuilder; +pub use model::{DmqMessage, DmqMessageBuilder}; pub use publisher::{DmqPublisherClient, DmqPublisherClientPallas}; #[cfg(test)] diff --git a/internal/mithril-dmq/src/message.rs b/internal/mithril-dmq/src/model/builder.rs similarity index 96% rename from internal/mithril-dmq/src/message.rs rename to internal/mithril-dmq/src/model/builder.rs index 7abb40ab496..3725b70d3f0 100644 --- a/internal/mithril-dmq/src/message.rs +++ b/internal/mithril-dmq/src/model/builder.rs @@ -10,6 +10,8 @@ use mithril_common::{ crypto_helper::{KesSigner, TryToBytes}, }; +use crate::model::DmqMessage; + /// The TTL (Time To Live) for DMQ messages in blocks. const DMQ_MESSAGE_TTL_IN_BLOCKS: u16 = 100; @@ -38,7 +40,7 @@ impl DmqMessageBuilder { } /// Builds a DMQ message from the provided message bytes. - pub async fn build(&self, message_bytes: &[u8]) -> StdResult { + pub async fn build(&self, message_bytes: &[u8]) -> StdResult { fn compute_msg_id(dmq_message: &DmqMsg) -> Vec { let mut hasher = Blake2b::::new(); hasher.update(&dmq_message.msg_body); @@ -63,16 +65,16 @@ impl DmqMessageBuilder { let block_number = (*block_number) .try_into() .with_context(|| "Failed to convert block number to u32")?; - let (kes_signature, operational_certificate) = self - .kes_signer - .sign(message_bytes, block_number) - .with_context(|| "Failed to KES sign message while building DMQ message")?; let kes_period = self .chain_observer .get_current_kes_period() .await .with_context(|| "Failed to get KES period while building DMQ message")? .unwrap_or_default(); + let (kes_signature, operational_certificate) = self + .kes_signer + .sign(message_bytes, kes_period) + .with_context(|| "Failed to KES sign message while building DMQ message")?; let mut dmq_message = DmqMsg { msg_id: vec![], msg_body: message_bytes.to_vec(), @@ -84,7 +86,7 @@ impl DmqMessageBuilder { }; dmq_message.msg_id = compute_msg_id(&dmq_message); - Ok(dmq_message) + Ok(dmq_message.into()) } } @@ -147,7 +149,7 @@ mod tests { }, DmqMsg { msg_id: vec![], - ..dmq_message + ..dmq_message.into() } ); } diff --git a/internal/mithril-dmq/src/model/mod.rs b/internal/mithril-dmq/src/model/mod.rs new file mode 100644 index 00000000000..342062db2fa --- /dev/null +++ b/internal/mithril-dmq/src/model/mod.rs @@ -0,0 +1,3 @@ +mod builder; + +pub use builder::*; From 300d792c44d85027b8d6b6a6380cbe3d96643d7f Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 12:04:39 +0200 Subject: [PATCH 06/49] feat(dmq): add 'DmqMessage' type to wrap a 'DmqMsg' --- Cargo.lock | 4 + internal/mithril-dmq/Cargo.toml | 8 ++ internal/mithril-dmq/src/model/message.rs | 100 ++++++++++++++++++++++ internal/mithril-dmq/src/model/mod.rs | 2 + 4 files changed, 114 insertions(+) create mode 100644 internal/mithril-dmq/src/model/message.rs diff --git a/Cargo.lock b/Cargo.lock index d3781f079a3..bbfbb1546e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4247,11 +4247,15 @@ version = "0.1.9" dependencies = [ "anyhow", "async-trait", + "bincode", "blake2 0.10.6", "mithril-cardano-node-chain", "mithril-common", "mockall", + "pallas-codec 1.0.0-alpha.2", "pallas-network 1.0.0-alpha.2", + "serde", + "serde_bytes", "slog", "slog-async", "slog-term", diff --git a/internal/mithril-dmq/Cargo.toml b/internal/mithril-dmq/Cargo.toml index 341b48dffbc..17f3218226a 100644 --- a/internal/mithril-dmq/Cargo.toml +++ b/internal/mithril-dmq/Cargo.toml @@ -10,16 +10,24 @@ license.workspace = true repository.workspace = true include = ["**/*.rs", "Cargo.toml", "README.md", ".gitignore"] +[package.metadata.cargo-machete] +# `serde_bytes` is used for DmqMessage serialization +ignored = ["serde_bytes"] + [lib] crate-type = ["lib", "cdylib", "staticlib"] [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } +bincode = { version = "2.0.1" } blake2 = "0.10.6" mithril-cardano-node-chain = { path = "../cardano-node/mithril-cardano-node-chain" } mithril-common = { path = "../../mithril-common" } pallas-network = { git = "https://github.com/txpipe/pallas.git", branch = "main" } +pallas-codec = { git = "https://github.com/txpipe/pallas.git", branch = "main" } +serde = { workspace = true } +serde_bytes = "0.11.17" slog = { workspace = true } tokio = { workspace = true, features = ["sync"] } diff --git a/internal/mithril-dmq/src/model/message.rs b/internal/mithril-dmq/src/model/message.rs new file mode 100644 index 00000000000..848704bce99 --- /dev/null +++ b/internal/mithril-dmq/src/model/message.rs @@ -0,0 +1,100 @@ +use std::ops::{Deref, DerefMut}; + +use pallas_codec::minicbor::{Decode, Decoder, Encode, Encoder}; +use pallas_network::miniprotocols::localmsgsubmission::DmqMsg; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// Wrapper for a DMQ message which can be serialized and deserialized. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DmqMessage(DmqMsg); + +#[derive(Serialize, Deserialize)] +struct RawBytes(#[serde(with = "serde_bytes")] Vec); + +impl Deref for DmqMessage { + type Target = DmqMsg; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for DmqMessage { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for DmqMessage { + fn from(msg: DmqMsg) -> Self { + Self(msg) + } +} + +impl From for DmqMsg { + fn from(msg: DmqMessage) -> Self { + msg.0 + } +} + +impl Serialize for DmqMessage { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let raw_bytes = RawBytes({ + let mut e = Encoder::new(Vec::new()); + self.0.encode(&mut e, &mut ()).map_err(|e| { + serde::ser::Error::custom(format!("DMQ message serialization error: {e}")) + })?; + Ok(e.into_writer()) + }?); + + raw_bytes.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for DmqMessage { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let raw_bytes = RawBytes::deserialize(deserializer)?; + let res = DmqMsg::decode(&mut Decoder::new(&raw_bytes.0), &mut ()) + .map_err(|e| { + serde::de::Error::custom(format!("DMQ message deserialization error: {e}")) + })? + .into(); + + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_dmq_message_serialize_deserialize() { + let dmq_msg = DmqMsg { + msg_id: vec![1, 2, 3], + msg_body: vec![4, 5, 6], + block_number: 123, + ttl: 10, + kes_signature: vec![7, 8, 9], + operational_certificate: vec![10, 11, 12], + kes_period: 0, + }; + + let dmq_message = DmqMessage::from(dmq_msg.clone()); + let serialized = bincode::serde::encode_to_vec(&dmq_message, bincode::config::standard()) + .expect("Serialization failed"); + + let (deserialized, _) = + bincode::serde::decode_from_slice(&serialized, bincode::config::standard()) + .expect("Deserialization failed"); + + assert_eq!(dmq_message, deserialized); + assert_eq!(dmq_message.0, dmq_msg); + } +} diff --git a/internal/mithril-dmq/src/model/mod.rs b/internal/mithril-dmq/src/model/mod.rs index 342062db2fa..959c5f7ee05 100644 --- a/internal/mithril-dmq/src/model/mod.rs +++ b/internal/mithril-dmq/src/model/mod.rs @@ -1,3 +1,5 @@ mod builder; +mod message; pub use builder::*; +pub use message::*; From 9f583c09273f9ee3ec85313c79d017f7d574f014 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 11:54:07 +0200 Subject: [PATCH 07/49] feat(dmq): add 'DmqPublisherServer' trait --- internal/mithril-dmq/src/publisher/mod.rs | 2 ++ .../mithril-dmq/src/publisher/server/interface.rs | 12 ++++++++++++ internal/mithril-dmq/src/publisher/server/mod.rs | 3 +++ 3 files changed, 17 insertions(+) create mode 100644 internal/mithril-dmq/src/publisher/server/interface.rs create mode 100644 internal/mithril-dmq/src/publisher/server/mod.rs diff --git a/internal/mithril-dmq/src/publisher/mod.rs b/internal/mithril-dmq/src/publisher/mod.rs index be50984ff39..24bf375cd40 100644 --- a/internal/mithril-dmq/src/publisher/mod.rs +++ b/internal/mithril-dmq/src/publisher/mod.rs @@ -1,3 +1,5 @@ mod client; +mod server; pub use client::*; +pub use server::*; diff --git a/internal/mithril-dmq/src/publisher/server/interface.rs b/internal/mithril-dmq/src/publisher/server/interface.rs new file mode 100644 index 00000000000..dea89974ded --- /dev/null +++ b/internal/mithril-dmq/src/publisher/server/interface.rs @@ -0,0 +1,12 @@ +use mithril_common::StdResult; + +/// Trait for the server side of publishing messages from a DMQ node. +#[cfg_attr(test, mockall::automock)] +#[async_trait::async_trait] +pub trait DmqPublisherServer: Send + Sync { + /// Processes the next message received from the DMQ network. + async fn process_message(&self) -> StdResult<()>; + + /// Runs the DMQ publisher server. + async fn run(&self) -> StdResult<()>; +} diff --git a/internal/mithril-dmq/src/publisher/server/mod.rs b/internal/mithril-dmq/src/publisher/server/mod.rs new file mode 100644 index 00000000000..1a9edaabf6f --- /dev/null +++ b/internal/mithril-dmq/src/publisher/server/mod.rs @@ -0,0 +1,3 @@ +mod interface; + +pub use interface::*; From f60fc6434e0c335f9abaf95b8a23483c96420a3d Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 11:54:39 +0200 Subject: [PATCH 08/49] feat(dmq): add 'DmqPublisherServerPallas' implementation of 'DmqPublisherServer' --- .../src/publisher/client/pallas.rs | 10 +- .../mithril-dmq/src/publisher/server/mod.rs | 2 + .../src/publisher/server/pallas.rs | 296 ++++++++++++++++++ 3 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 internal/mithril-dmq/src/publisher/server/pallas.rs diff --git a/internal/mithril-dmq/src/publisher/client/pallas.rs b/internal/mithril-dmq/src/publisher/client/pallas.rs index 1402fc40618..5f938dff398 100644 --- a/internal/mithril-dmq/src/publisher/client/pallas.rs +++ b/internal/mithril-dmq/src/publisher/client/pallas.rs @@ -64,7 +64,7 @@ impl DmqPublisherClient for DmqPublisher .with_context(|| "Failed to build DMQ message")?; client .msg_submission() - .send_submit_tx(dmq_message) + .send_submit_tx(dmq_message.into()) .await .with_context(|| "Failed to submit DMQ message")?; let response = client.msg_submission().recv_submit_tx_response().await?; @@ -82,7 +82,7 @@ impl DmqPublisherClient for DmqPublisher #[cfg(all(test, unix))] mod tests { - use std::{fs, sync::Arc}; + use std::{fs, sync::Arc, time::Duration}; use pallas_network::miniprotocols::{ localmsgsubmission::DmqMsgValidationError, localtxsubmission, @@ -146,6 +146,9 @@ mod tests { let reply_success = true; let server = setup_dmq_server(socket_path.clone(), reply_success); let client = tokio::spawn(async move { + // sleep to avoid refused connection from the server + tokio::time::sleep(Duration::from_millis(10)).await; + let publisher = DmqPublisherClientPallas::new( socket_path, CardanoNetwork::TestNet(0), @@ -180,6 +183,9 @@ mod tests { let reply_success = false; let server = setup_dmq_server(socket_path.clone(), reply_success); let client = tokio::spawn(async move { + // sleep to avoid refused connection from the server + tokio::time::sleep(Duration::from_millis(10)).await; + let publisher = DmqPublisherClientPallas::new( socket_path, CardanoNetwork::TestNet(0), diff --git a/internal/mithril-dmq/src/publisher/server/mod.rs b/internal/mithril-dmq/src/publisher/server/mod.rs index 1a9edaabf6f..4035f6c0659 100644 --- a/internal/mithril-dmq/src/publisher/server/mod.rs +++ b/internal/mithril-dmq/src/publisher/server/mod.rs @@ -1,3 +1,5 @@ mod interface; +mod pallas; pub use interface::*; +pub use pallas::*; diff --git a/internal/mithril-dmq/src/publisher/server/pallas.rs b/internal/mithril-dmq/src/publisher/server/pallas.rs new file mode 100644 index 00000000000..8f742068faa --- /dev/null +++ b/internal/mithril-dmq/src/publisher/server/pallas.rs @@ -0,0 +1,296 @@ +use std::{fs, path::PathBuf}; + +use anyhow::{Context, anyhow}; +use pallas_network::{ + facades::DmqServer, + miniprotocols::{ + localmsgsubmission::DmqMsgValidationError, + localtxsubmission::{Request, Response}, + }, +}; +use tokio::{ + net::UnixListener, + select, + sync::{Mutex, MutexGuard, mpsc::UnboundedSender, watch::Receiver}, +}; + +use slog::{Logger, debug, error, info, warn}; + +use mithril_common::{CardanoNetwork, StdResult, logging::LoggerExtensions}; + +use crate::{DmqMessage, DmqPublisherServer}; + +/// A DMQ server implementation for messages publication to a DMQ node. +pub struct DmqPublisherServerPallas { + socket: PathBuf, + network: CardanoNetwork, + server: Mutex>, + transmitters: Mutex>>, + stop_rx: Receiver<()>, + logger: Logger, +} + +impl DmqPublisherServerPallas { + /// Creates a new instance of [DmqPublisherServerPallas]. + pub fn new( + socket: PathBuf, + network: CardanoNetwork, + stop_rx: Receiver<()>, + logger: Logger, + ) -> Self { + Self { + socket, + network, + server: Mutex::new(None), + transmitters: Mutex::new(Vec::new()), + stop_rx, + logger: logger.new_with_component_name::(), + } + } + + /// Creates and returns a new `DmqServer` connected to the specified socket. + async fn new_server(&self) -> StdResult { + let magic = self.network.code(); + if self.socket.exists() { + fs::remove_file(self.socket.clone()).unwrap(); + } + let listener = UnixListener::bind(&self.socket) + .map_err(|err| anyhow!(err)) + .with_context(|| { + format!( + "DmqPublisherServerPallas failed to bind Unix socket at {}", + self.socket.display() + ) + })?; + + DmqServer::accept(&listener, magic) + .await + .map_err(|err| anyhow!(err)) + .with_context(|| "DmqPublisherServerPallas failed to create a new server") + } + + /// Gets the cached `DmqServer`, creating a new one if it does not exist. + async fn get_server(&self) -> StdResult>> { + { + // Run this in a separate block to avoid dead lock on the Mutex + let server_lock = self.server.lock().await; + if server_lock.as_ref().is_some() { + return Ok(server_lock); + } + } + + let mut server_lock = self.server.lock().await; + *server_lock = Some(self.new_server().await?); + + Ok(server_lock) + } + + /// Drops the current `DmqServer`, if it exists. + async fn drop_server(&self) -> StdResult<()> { + debug!( + self.logger, + "Drop existing DMQ publisher server"; + "socket" => ?self.socket, + "network" => ?self.network + ); + let mut server_lock = self.server.lock().await; + if let Some(server) = server_lock.take() { + server.abort().await; + } + + Ok(()) + } + + /// Registers a transmitter for DMQ messages. + pub async fn register_transmitter( + &self, + transmitter: UnboundedSender, + ) -> StdResult<()> { + debug!(self.logger, "Register message transmitter for DMQ messages"); + let mut transmitters_guard = self.transmitters.lock().await; + transmitters_guard.push(transmitter); + + Ok(()) + } +} + +#[async_trait::async_trait] +impl DmqPublisherServer for DmqPublisherServerPallas { + async fn process_message(&self) -> StdResult<()> { + debug!( + self.logger, + "Waiting for message to publish to the DMQ network"; + "socket" => ?self.socket, + "network" => ?self.network + ); + let mut server_guard = self.get_server().await?; + let server = server_guard + .as_mut() + .ok_or(anyhow!("DMQ publisher server does not exist"))?; + + let request = server + .msg_submission() + .recv_next_request() + .await + .map_err(|err| anyhow!("Failed to receive next DMQ message: {}", err))?; + let (dmq_message, response) = match request { + Request::Submit(dmq_message) => { + debug!(self.logger, "Received message to publish to DMQ"); + (Some(dmq_message), Response::Accepted) + } + request => { + error!( + self.logger, + "Expected a Submit request, but received: {request:?}" + ); + ( + None, + Response::Rejected(DmqMsgValidationError(format!( + "Expected a Submit request, but received: {request:?}" + ))), + ) + } + }; + server + .msg_submission() + .send_submit_tx_response(response) + .await + .map_err(|err| anyhow!("Failed to send response to DMQ publisher client: {}", err))?; + + if let Some(dmq_message) = dmq_message { + for transmitter in self.transmitters.lock().await.iter() { + if let Err(err) = transmitter.send(dmq_message.to_owned().into()) { + error!( + self.logger, + "Failed to send DMQ message to transmitter"; + "error" => ?err + ); + } + } + } + + Ok(()) + } + + async fn run(&self) -> StdResult<()> { + info!( + self.logger, + "Starting DMQ publisher server"; + "socket" => ?self.socket, + "network" => ?self.network + ); + + let mut stop_rx = self.stop_rx.clone(); + loop { + select! { + _ = stop_rx.changed() => { + warn!(self.logger, "Stopping signature processor..."); + + return Ok(()); + } + res = self.process_message() => { + match res { + Ok(_) => { + debug!(self.logger, "Processed a message successfully"); + } + Err(err) => { + error!(self.logger, "Failed to process message"; "error" => ?err); + if let Err(drop_err) = self.drop_server().await { + error!(self.logger, "Failed to drop DMQ publisher server"; "error" => ?drop_err); + } + } + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use pallas_network::{ + facades::DmqClient, + miniprotocols::{localmsgsubmission::DmqMsg, localtxsubmission}, + }; + use tokio::sync::{mpsc::unbounded_channel, watch}; + + use mithril_common::{current_function, test::TempDir}; + + use crate::test_tools::TestLogger; + + use super::*; + + fn create_temp_dir(folder_name: &str) -> PathBuf { + TempDir::create_with_short_path("dmq_publisher_server", folder_name) + } + + async fn fake_msg() -> DmqMsg { + DmqMsg { + msg_id: vec![0, 1], + msg_body: vec![2, 3, 4, 5], + block_number: 10, + ttl: 100, + kes_signature: vec![0, 1, 2, 3], + operational_certificate: vec![0, 1, 2, 3, 4, 5], + } + } + + #[tokio::test] + async fn pallas_dmq_publisher_server_success() { + let (stop_tx, stop_rx) = watch::channel(()); + let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); + let socket_path = create_temp_dir(current_function!()).join("node.socket"); + let cardano_network = CardanoNetwork::TestNet(0); + let dmq_publisher_server = Arc::new(DmqPublisherServerPallas::new( + socket_path.to_path_buf(), + cardano_network.to_owned(), + stop_rx, + TestLogger::stdout(), + )); + dmq_publisher_server + .register_transmitter(signature_dmq_tx) + .await + .unwrap(); + let message = fake_msg().await; + let message_clone = message.clone(); + let client = tokio::spawn({ + async move { + // client setup + let mut client = DmqClient::connect(socket_path.clone(), 0).await.unwrap(); + + // init local msg submission client + let client_msg = client.msg_submission(); + assert_eq!(*client_msg.state(), localtxsubmission::State::Idle); + + // client sends a request to server and waits for a reply from the server + client_msg.send_submit_tx(message_clone).await.unwrap(); + assert_eq!(*client_msg.state(), localtxsubmission::State::Busy); + + let response = client_msg.recv_submit_tx_response().await.unwrap(); + assert_eq!(*client_msg.state(), localtxsubmission::State::Idle); + assert_eq!(response, localtxsubmission::Response::Accepted); + } + }); + let recorder = tokio::spawn(async move { + let result = { + let mut signature_dmq_rx = signature_dmq_rx; + if let Some(message) = signature_dmq_rx.recv().await { + return Ok(message); + } + + Err(anyhow::anyhow!("No message received in recorder")) + }; + stop_tx + .send(()) + .expect("Failed to send stop signal to DMQ publisher server"); + + result + }); + + let (_, _, message_res) = tokio::join!(dmq_publisher_server.run(), client, recorder); + let message_received = message_res.unwrap().unwrap(); + assert_eq!(message, message_received.into()); + } +} From 8bb5f136de0a0f0563f1e07aa5791832072d69e2 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 13:52:55 +0200 Subject: [PATCH 09/49] feat(dmq): add 'DmqConsumerServer' trait --- internal/mithril-dmq/src/consumer/mod.rs | 2 ++ .../mithril-dmq/src/consumer/server/interface.rs | 12 ++++++++++++ internal/mithril-dmq/src/consumer/server/mod.rs | 3 +++ 3 files changed, 17 insertions(+) create mode 100644 internal/mithril-dmq/src/consumer/server/interface.rs create mode 100644 internal/mithril-dmq/src/consumer/server/mod.rs diff --git a/internal/mithril-dmq/src/consumer/mod.rs b/internal/mithril-dmq/src/consumer/mod.rs index be50984ff39..24bf375cd40 100644 --- a/internal/mithril-dmq/src/consumer/mod.rs +++ b/internal/mithril-dmq/src/consumer/mod.rs @@ -1,3 +1,5 @@ mod client; +mod server; pub use client::*; +pub use server::*; diff --git a/internal/mithril-dmq/src/consumer/server/interface.rs b/internal/mithril-dmq/src/consumer/server/interface.rs new file mode 100644 index 00000000000..e8d9b8e80c5 --- /dev/null +++ b/internal/mithril-dmq/src/consumer/server/interface.rs @@ -0,0 +1,12 @@ +use mithril_common::StdResult; + +/// Trait for the server side of consuming messages from a DMQ node. +#[cfg_attr(test, mockall::automock)] +#[async_trait::async_trait] +pub trait DmqConsumerServer: Send + Sync { + /// Processes the next message received from the DMQ network. + async fn process_message(&self) -> StdResult<()>; + + /// Runs the DMQ consumer server. + async fn run(&self) -> StdResult<()>; +} diff --git a/internal/mithril-dmq/src/consumer/server/mod.rs b/internal/mithril-dmq/src/consumer/server/mod.rs new file mode 100644 index 00000000000..1a9edaabf6f --- /dev/null +++ b/internal/mithril-dmq/src/consumer/server/mod.rs @@ -0,0 +1,3 @@ +mod interface; + +pub use interface::*; From afce4292168f690924f52a7b0af62540395bedb6 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 16:18:58 +0200 Subject: [PATCH 10/49] feat(dmq): add 'MessageQueue' implementation for the consumer server --- .../mithril-dmq/src/consumer/server/mod.rs | 3 + .../mithril-dmq/src/consumer/server/queue.rs | 168 ++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 internal/mithril-dmq/src/consumer/server/queue.rs diff --git a/internal/mithril-dmq/src/consumer/server/mod.rs b/internal/mithril-dmq/src/consumer/server/mod.rs index 1a9edaabf6f..498010ae3c5 100644 --- a/internal/mithril-dmq/src/consumer/server/mod.rs +++ b/internal/mithril-dmq/src/consumer/server/mod.rs @@ -1,3 +1,6 @@ mod interface; +mod pallas; +mod queue; pub use interface::*; +pub use pallas::*; diff --git a/internal/mithril-dmq/src/consumer/server/queue.rs b/internal/mithril-dmq/src/consumer/server/queue.rs new file mode 100644 index 00000000000..cad7e0e278b --- /dev/null +++ b/internal/mithril-dmq/src/consumer/server/queue.rs @@ -0,0 +1,168 @@ +use std::collections::VecDeque; + +use tokio::sync::{Mutex, Notify}; + +use crate::DmqMessage; + +/// A queue for storing DMQ messages. +pub(crate) struct MessageQueue { + messages: Mutex>, + new_message_notify: Notify, +} + +impl MessageQueue { + /// Creates a new instance of [BlockingNonBlockingQueue]. + pub fn new() -> Self { + Self { + messages: Mutex::new(VecDeque::new()), + new_message_notify: Notify::new(), + } + } + + /// Enqueues a new message into the queue. + pub async fn enqueue(&self, message: DmqMessage) { + let mut message_queue_guard = self.messages.lock().await; + (*message_queue_guard).push_back(message); + + self.new_message_notify.notify_waiters(); + } + + /// Returns the messages from the queue in a non blocking way, if available. + pub async fn dequeue_non_blocking(&self, limit: Option) -> Vec { + let mut message_queue_guard = self.messages.lock().await; + let limit = limit.unwrap_or((*message_queue_guard).len()); + let mut messages = Vec::new(); + for _ in 0..limit { + if let Some(message) = (*message_queue_guard).pop_front() { + messages.push(message); + } + } + + messages + } + + /// Returns the messages from the queue in a blocking way, waiting for new messages if necessary. + pub async fn dequeue_blocking(&self, limit: Option) -> Vec { + loop { + let messages = self.dequeue_non_blocking(limit).await; + if !messages.is_empty() { + return messages; + } + + self.new_message_notify.notified().await; + } + } + + /// Checks if the message queue is empty. + pub async fn is_empty(&self) -> bool { + self.len().await == 0 + } + + /// Get the length of the message queue. + pub async fn len(&self) -> usize { + let message_queue_guard = self.messages.lock().await; + (*message_queue_guard).len() + } +} + +#[cfg(test)] +mod tests { + use std::{ops::RangeInclusive, time::Duration}; + + use anyhow::anyhow; + use pallas_network::miniprotocols::localmsgsubmission::DmqMsg; + use tokio::time::sleep; + + use super::*; + + fn fake_msg() -> DmqMsg { + DmqMsg { + msg_id: vec![0, 1], + msg_body: vec![0, 1, 2], + block_number: 10, + ttl: 100, + kes_signature: vec![0, 1, 2, 3], + operational_certificate: vec![0, 1, 2, 3, 4], + } + } + + fn fake_messages(range: RangeInclusive) -> Vec { + range + .map(|i| { + DmqMsg { + msg_id: vec![i], + ..fake_msg() + } + .into() + }) + .collect::>() + } + + #[tokio::test] + async fn enqueue_and_dequeue_non_blocking_no_limit() { + let queue = MessageQueue::new(); + let messages = fake_messages(1..=5); + for message in messages.clone() { + queue.enqueue(message).await; + } + let limit = None; + + let dequeued_messages = queue.dequeue_non_blocking(limit).await; + + assert_eq!(messages, dequeued_messages); + } + + #[tokio::test] + async fn enqueue_and_dequeue_non_blocking_with_limit() { + let queue = MessageQueue::new(); + let messages = fake_messages(1..=5); + for message in messages.clone() { + queue.enqueue(message).await; + } + let limit = Some(2); + + let dequeued_messages = queue.dequeue_non_blocking(limit).await; + + assert_eq!(messages[0..=1].to_vec(), dequeued_messages); + } + + #[tokio::test] + async fn enqueue_and_dequeue_blocking_no_limit() { + let queue = MessageQueue::new(); + let messages = fake_messages(1..=5); + for message in messages.clone() { + queue.enqueue(message).await; + } + let limit = None; + + let dequeued_messages = queue.dequeue_blocking(limit).await; + + assert_eq!(messages, dequeued_messages); + } + + #[tokio::test] + async fn enqueue_and_dequeue_blocking_with_limit() { + let queue = MessageQueue::new(); + let messages = fake_messages(1..=5); + for message in messages.clone() { + queue.enqueue(message).await; + } + let limit = Some(2); + + let dequeued_messages = queue.dequeue_blocking(limit).await; + + assert_eq!(messages[0..=1].to_vec(), dequeued_messages); + } + + #[tokio::test] + async fn dequeue_blocking_blocks_when_no_message_available() { + let queue = MessageQueue::new(); + + let result = tokio::select!( + _res = sleep(Duration::from_millis(100)) => {Err(anyhow!("Timeout"))}, + _res = queue.dequeue_blocking(None) => {Ok(())}, + ); + + result.expect_err("Should have timed out"); + } +} From ac2d6a763b7e724b9e49d91f0de575419a3cc8f0 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 8 Aug 2025 15:29:15 +0200 Subject: [PATCH 11/49] feat(dmq): add 'DmqConsumerServerPallas' implementation of 'DmqConsumerServer' --- .../mithril-dmq/src/consumer/server/pallas.rs | 406 ++++++++++++++++++ 1 file changed, 406 insertions(+) create mode 100644 internal/mithril-dmq/src/consumer/server/pallas.rs diff --git a/internal/mithril-dmq/src/consumer/server/pallas.rs b/internal/mithril-dmq/src/consumer/server/pallas.rs new file mode 100644 index 00000000000..6476b6c04e7 --- /dev/null +++ b/internal/mithril-dmq/src/consumer/server/pallas.rs @@ -0,0 +1,406 @@ +use std::{fs, path::PathBuf}; + +use anyhow::{Context, anyhow}; +use pallas_network::{facades::DmqServer, miniprotocols::localmsgnotification::Request}; +use tokio::{ + net::UnixListener, + select, + sync::{Mutex, MutexGuard, mpsc::UnboundedReceiver, watch::Receiver}, +}; + +use slog::{Logger, debug, error, info, warn}; + +use mithril_common::{CardanoNetwork, StdResult, logging::LoggerExtensions}; + +use crate::{DmqConsumerServer, DmqMessage}; + +use super::queue::MessageQueue; + +/// A DMQ server implementation for messages notification from a DMQ node. +pub struct DmqConsumerServerPallas { + socket: PathBuf, + network: CardanoNetwork, + server: Mutex>, + messages_receiver: Mutex>>, + messages_buffer: MessageQueue, + stop_rx: Receiver<()>, + logger: Logger, +} + +impl DmqConsumerServerPallas { + /// Creates a new instance of [DmqConsumerServerPallas]. + pub fn new( + socket: PathBuf, + network: CardanoNetwork, + stop_rx: Receiver<()>, + logger: Logger, + ) -> Self { + Self { + socket, + network, + server: Mutex::new(None), + messages_receiver: Mutex::new(None), + messages_buffer: MessageQueue::new(), + stop_rx, + logger: logger.new_with_component_name::(), + } + } + + /// Creates and returns a new `DmqServer` connected to the specified socket. + async fn new_server(&self) -> StdResult { + let magic = self.network.code(); + if self.socket.exists() { + fs::remove_file(self.socket.clone()).unwrap(); + } + let listener = UnixListener::bind(&self.socket) + .map_err(|err| anyhow!(err)) + .with_context(|| { + format!( + "DmqConsumerServerPallas failed to bind Unix socket at {}", + self.socket.display() + ) + })?; + + DmqServer::accept(&listener, magic) + .await + .map_err(|err| anyhow!(err)) + .with_context(|| "DmqConsumerServerPallas failed to create a new server") + } + + /// Gets the cached `DmqServer`, creating a new one if it does not exist. + async fn get_server(&self) -> StdResult>> { + { + // Run this in a separate block to avoid dead lock on the Mutex + let server_lock = self.server.lock().await; + if server_lock.as_ref().is_some() { + return Ok(server_lock); + } + } + + let mut server_lock = self.server.lock().await; + *server_lock = Some(self.new_server().await?); + + Ok(server_lock) + } + + /// Drops the current `DmqServer`, if it exists. + async fn drop_server(&self) -> StdResult<()> { + debug!( + self.logger, + "Drop existing DMQ server"; + "socket" => ?self.socket, + "network" => ?self.network + ); + let mut server_lock = self.server.lock().await; + if let Some(server) = server_lock.take() { + server.abort().await; + } + + Ok(()) + } + + /// Registers the receiver for DMQ messages (only one receiver is allowed). + pub async fn register_receiver( + &self, + receiver: UnboundedReceiver, + ) -> StdResult<()> { + debug!(self.logger, "Register message receiver for DMQ messages"); + let mut receiver_guard = self.messages_receiver.lock().await; + *receiver_guard = Some(receiver); + + Ok(()) + } +} + +#[async_trait::async_trait] +impl DmqConsumerServer for DmqConsumerServerPallas { + async fn process_message(&self) -> StdResult<()> { + debug!( + self.logger, + "Waiting for message received from the DMQ network" + ); + let mut server_guard = self.get_server().await?; + let server = server_guard.as_mut().ok_or(anyhow!("DMQ server does not exist"))?; + + let request = server + .msg_notification() + .recv_next_request() + .await + .map_err(|err| anyhow!("Failed to receive next DMQ message: {}", err))?; + + match request { + Request::Blocking => { + debug!( + self.logger, + "Blocking notification of messages received from the DMQ network" + ); + let reply_messages = self.messages_buffer.dequeue_blocking(None).await; + let reply_messages = + reply_messages.into_iter().map(|msg| msg.into()).collect::>(); + server + .msg_notification() + .send_reply_messages_blocking(reply_messages) + .await?; + } + Request::NonBlocking => { + debug!( + self.logger, + "Non blocking notification of messages received from the DMQ network" + ); + let reply_messages = self.messages_buffer.dequeue_non_blocking(None).await; + let reply_messages = + reply_messages.into_iter().map(|msg| msg.into()).collect::>(); + let has_more = !self.messages_buffer.is_empty().await; + server + .msg_notification() + .send_reply_messages_non_blocking(reply_messages, has_more) + .await?; + server.msg_notification().recv_done().await?; + } + }; + + Ok(()) + } + + /// Runs the DMQ publisher server, processing messages in a loop. + async fn run(&self) -> StdResult<()> { + info!( + self.logger, + "Starting DMQ consumer server"; + "socket" => ?self.socket, + "network" => ?self.network + ); + + let mut stop_rx = self.stop_rx.clone(); + let mut receiver = self.messages_receiver.lock().await; + match *receiver { + Some(ref mut receiver) => loop { + select! { + _ = stop_rx.changed() => { + warn!(self.logger, "Stopping signature processor..."); + + return Ok(()); + } + message = receiver.recv() => { + if let Some(message) = message { + debug!(self.logger, "Received a message from the DMQ network"; "message" => ?message); + self.messages_buffer.enqueue(message).await; + } else { + warn!(self.logger, "DMQ message receiver channel closed"); + return Ok(()); + } + + } + res = self.process_message() => { + match res { + Ok(_) => { + debug!(self.logger, "Processed a message successfully"); + } + Err(err) => { + error!(self.logger, "Failed to process message"; "error" => ?err); + if let Err(drop_err) = self.drop_server().await { + error!(self.logger, "Failed to drop DMQ consumer server"; "error" => ?drop_err); + } + } + } + } + } + }, + None => { + return Err(anyhow!("DMQ message receiver is not registered")); + } + } + } +} + +#[cfg(test)] +mod tests { + use std::{sync::Arc, time::Duration}; + + use pallas_network::{ + facades::DmqClient, + miniprotocols::{localmsgnotification, localmsgsubmission::DmqMsg}, + }; + use tokio::sync::{mpsc::unbounded_channel, watch}; + use tokio::time::sleep; + + use mithril_common::{current_function, test::TempDir}; + + use crate::test_tools::TestLogger; + + use super::*; + + fn create_temp_dir(folder_name: &str) -> PathBuf { + TempDir::create_with_short_path("dmq_consumer_server", folder_name) + } + + fn fake_msg() -> DmqMsg { + DmqMsg { + msg_id: vec![0, 1], + msg_body: vec![0, 1, 2], + block_number: 10, + ttl: 100, + kes_signature: vec![0, 1, 2, 3], + operational_certificate: vec![0, 1, 2, 3, 4], + } + } + + #[tokio::test] + async fn pallas_dmq_consumer_server_non_blocking_success() { + let (stop_tx, stop_rx) = watch::channel(()); + let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); + let socket_path = create_temp_dir(current_function!()).join("node.socket"); + let cardano_network = CardanoNetwork::TestNet(0); + let dmq_consumer_server = Arc::new(DmqConsumerServerPallas::new( + socket_path.to_path_buf(), + cardano_network.to_owned(), + stop_rx, + TestLogger::stdout(), + )); + dmq_consumer_server.register_receiver(signature_dmq_rx).await.unwrap(); + let message = fake_msg(); + let client = tokio::spawn({ + async move { + tokio::time::sleep(Duration::from_secs(1)).await; + + // client setup + let mut client = DmqClient::connect(socket_path.clone(), 0).await.unwrap(); + + // init local msg notification client + let client_msg = client.msg_notification(); + assert_eq!(*client_msg.state(), localmsgnotification::State::Idle); + + // client sends a non blocking request to server and waits for a reply from the server + client_msg.send_request_messages_non_blocking().await.unwrap(); + assert_eq!( + *client_msg.state(), + localmsgnotification::State::BusyNonBlocking + ); + + let reply = client_msg.recv_next_reply().await.unwrap(); + assert_eq!(*client_msg.state(), localmsgnotification::State::Idle); + let result = match reply { + localmsgnotification::Reply(messages, false) => Ok(messages), + _ => Err(anyhow::anyhow!( + "Failed to receive blocking reply from DMQ server" + )), + }; + + // stop the consumer server + stop_tx.send(()).unwrap(); + + result + } + }); + let message_clone = message.clone(); + let _signature_dmq_tx_clone = signature_dmq_tx.clone(); + let recorder = tokio::spawn(async move { + _signature_dmq_tx_clone.send(message_clone.into()).unwrap(); + }); + + let (_, messages_res, _) = tokio::join!(dmq_consumer_server.run(), client, recorder); + let messages_received: Vec<_> = messages_res.unwrap().unwrap(); + assert_eq!(vec![message], messages_received); + } + + #[tokio::test] + async fn pallas_dmq_consumer_server_blocking_success() { + let (stop_tx, stop_rx) = watch::channel(()); + let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); + let socket_path = create_temp_dir(current_function!()).join("node.socket"); + let cardano_network = CardanoNetwork::TestNet(0); + let dmq_consumer_server = Arc::new(DmqConsumerServerPallas::new( + socket_path.to_path_buf(), + cardano_network.to_owned(), + stop_rx, + TestLogger::stdout(), + )); + dmq_consumer_server.register_receiver(signature_dmq_rx).await.unwrap(); + let message = fake_msg(); + let client = tokio::spawn({ + async move { + tokio::time::sleep(Duration::from_secs(1)).await; + + // client setup + let mut client = DmqClient::connect(socket_path.clone(), 0).await.unwrap(); + + // init local msg notification client + let client_msg = client.msg_notification(); + assert_eq!(*client_msg.state(), localmsgnotification::State::Idle); + + // client sends a blocking request to server and waits for a reply from the server + client_msg.send_request_messages_blocking().await.unwrap(); + assert_eq!( + *client_msg.state(), + localmsgnotification::State::BusyBlocking + ); + + let reply = client_msg.recv_next_reply().await.unwrap(); + assert_eq!(*client_msg.state(), localmsgnotification::State::Idle); + let result = match reply { + localmsgnotification::Reply(messages, false) => Ok(messages), + _ => Err(anyhow::anyhow!( + "Failed to receive blocking reply from DMQ server" + )), + }; + + // stop the consumer server + stop_tx.send(()).unwrap(); + + result + } + }); + let message_clone = message.clone(); + let _signature_dmq_tx_clone = signature_dmq_tx.clone(); + let recorder = tokio::spawn(async move { + _signature_dmq_tx_clone.send(message_clone.into()).unwrap(); + }); + + let (_, messages_res, _) = tokio::join!(dmq_consumer_server.run(), client, recorder); + let messages_received: Vec<_> = messages_res.unwrap().unwrap(); + assert_eq!(vec![message], messages_received); + } + + #[tokio::test] + async fn pallas_dmq_consumer_server_blocking_blocks_when_no_message_available() { + let (_stop_tx, stop_rx) = watch::channel(()); + let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); + let socket_path = create_temp_dir(current_function!()).join("node.socket"); + let cardano_network = CardanoNetwork::TestNet(0); + let dmq_consumer_server = Arc::new(DmqConsumerServerPallas::new( + socket_path.to_path_buf(), + cardano_network.to_owned(), + stop_rx, + TestLogger::stdout(), + )); + dmq_consumer_server.register_receiver(signature_dmq_rx).await.unwrap(); + let client = tokio::spawn({ + async move { + // client setup + let mut client = DmqClient::connect(socket_path.clone(), 0).await.unwrap(); + + // init local msg notification client + let client_msg = client.msg_notification(); + assert_eq!(*client_msg.state(), localmsgnotification::State::Idle); + + // client sends a blocking request to server and waits for a reply from the server + client_msg.send_request_messages_blocking().await.unwrap(); + assert_eq!( + *client_msg.state(), + localmsgnotification::State::BusyBlocking + ); + + client_msg.recv_next_reply().await.unwrap(); + } + }); + let _signature_dmq_tx_clone = signature_dmq_tx.clone(); + + let result = tokio::select!( + _res = sleep(Duration::from_millis(1000)) => {Err(anyhow!("Timeout"))}, + _res = dmq_consumer_server.run() => {Ok(())}, + _res = client => {Ok(())}, + ); + + result.expect_err("Should have timed out"); + } +} From 790b635ca4e03f580c6e1217e1a7ae3bf6e059e9 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 16:41:38 +0200 Subject: [PATCH 12/49] feat(relay): add support for DMQ messages --- Cargo.lock | 1 + mithril-relay/Cargo.toml | 3 ++- mithril-relay/src/lib.rs | 11 +++++--- mithril-relay/src/p2p/peer.rs | 47 ++++++++++++++++++++++++----------- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbfbb1546e1..ac2f6cde88c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4363,6 +4363,7 @@ dependencies = [ "httpmock", "libp2p", "mithril-common", + "mithril-dmq", "mithril-doc", "mithril-test-http-server", "reqwest", diff --git a/mithril-relay/Cargo.toml b/mithril-relay/Cargo.toml index 12645d7a211..edbef2c976e 100644 --- a/mithril-relay/Cargo.toml +++ b/mithril-relay/Cargo.toml @@ -11,7 +11,7 @@ repository = { workspace = true } [features] bundle_tls = ["reqwest/native-tls-vendored"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +future_dmq = ["dep:mithril-dmq"] [dependencies] anyhow = { workspace = true } @@ -35,6 +35,7 @@ libp2p = { version = "0.56.0", features = [ "yamux", ] } mithril-common = { path = "../mithril-common" } +mithril-dmq = { path = "../internal/mithril-dmq", optional = true } mithril-doc = { path = "../internal/mithril-doc" } mithril-test-http-server = { path = "../internal/tests/mithril-test-http-server" } reqwest = { workspace = true, features = [ diff --git a/mithril-relay/src/lib.rs b/mithril-relay/src/lib.rs index af3929eeec0..2ec4e508324 100644 --- a/mithril-relay/src/lib.rs +++ b/mithril-relay/src/lib.rs @@ -16,11 +16,14 @@ pub use relay::SignerRelayMode; /// The P2P topic names used by Mithril pub mod mithril_p2p_topic { - /// The topic name where signer registrations are published - pub const SIGNERS: &str = "mithril/signers"; + /// The topic name where HTTP signer registrations are published + pub const SIGNERS_HTTP: &str = "mithril/signers/http"; - /// The topic name where signatures are published - pub const SIGNATURES: &str = "mithril/signatures"; + /// The topic name where HTTP signatures are published + pub const SIGNATURES_HTTP: &str = "mithril/signatures/http"; + + /// The topic name where DMQ signatures are published + pub const SIGNATURES_DMQ: &str = "mithril/signatures/dmq"; } #[cfg(test)] diff --git a/mithril-relay/src/p2p/peer.rs b/mithril-relay/src/p2p/peer.rs index 9393d1da52d..7c473afdddc 100644 --- a/mithril-relay/src/p2p/peer.rs +++ b/mithril-relay/src/p2p/peer.rs @@ -14,6 +14,7 @@ use mithril_common::{ logging::LoggerExtensions, messages::{RegisterSignatureMessageHttp, RegisterSignerMessage}, }; +use mithril_dmq::DmqMessage; use serde::{Deserialize, Serialize}; use slog::{Logger, debug, info}; use std::{collections::HashMap, time::Duration}; @@ -63,11 +64,14 @@ pub type TopicName = String; /// The broadcast message received from a Gossip sub event #[derive(Serialize, Deserialize)] pub enum BroadcastMessage { - /// A signer registration message received from the Gossip sub - RegisterSigner(RegisterSignerMessage), + /// A HTTP signer registration message received from the Gossip sub + RegisterSignerHttp(RegisterSignerMessage), - /// A signature registration message received from the Gossip sub - RegisterSignature(RegisterSignatureMessageHttp), + /// A HTTP signature registration message received from the Gossip sub + RegisterSignatureHttp(RegisterSignatureMessageHttp), + + /// A DMQ signature registration message received from the Gossip sub + RegisterSignatureDmq(DmqMessage), } /// A peer in the P2P network @@ -95,12 +99,16 @@ impl Peer { fn build_topics() -> HashMap { HashMap::from([ ( - mithril_p2p_topic::SIGNATURES.into(), - gossipsub::IdentTopic::new(mithril_p2p_topic::SIGNATURES), + mithril_p2p_topic::SIGNATURES_HTTP.into(), + gossipsub::IdentTopic::new(mithril_p2p_topic::SIGNATURES_HTTP), + ), + ( + mithril_p2p_topic::SIGNATURES_DMQ.into(), + gossipsub::IdentTopic::new(mithril_p2p_topic::SIGNATURES_DMQ), ), ( - mithril_p2p_topic::SIGNERS.into(), - gossipsub::IdentTopic::new(mithril_p2p_topic::SIGNERS), + mithril_p2p_topic::SIGNERS_HTTP.into(), + gossipsub::IdentTopic::new(mithril_p2p_topic::SIGNERS_HTTP), ), ]) } @@ -217,14 +225,25 @@ impl Peer { } } - /// Publish a signature on the P2P pubsub - pub fn publish_signature( + /// Publish a HTTP signature on the P2P pubsub + pub fn publish_signature_http( &mut self, message: &RegisterSignatureMessageHttp, ) -> StdResult { self.publish_broadcast_message( - &BroadcastMessage::RegisterSignature(message.to_owned()), - mithril_p2p_topic::SIGNATURES, + &BroadcastMessage::RegisterSignatureHttp(message.to_owned()), + mithril_p2p_topic::SIGNATURES_HTTP, + ) + } + + /// Publish a DMQ signature on the P2P pubsub + pub fn publish_signature_dmq( + &mut self, + message: &DmqMessage, + ) -> StdResult { + self.publish_broadcast_message( + &BroadcastMessage::RegisterSignatureDmq(message.to_owned()), + mithril_p2p_topic::SIGNATURES_DMQ, ) } @@ -270,8 +289,8 @@ impl Peer { message: &RegisterSignerMessage, ) -> StdResult { self.publish_broadcast_message( - &BroadcastMessage::RegisterSigner(message.to_owned()), - mithril_p2p_topic::SIGNERS, + &BroadcastMessage::RegisterSignerHttp(message.to_owned()), + mithril_p2p_topic::SIGNERS_HTTP, ) } From 5e7dc5182fdf37b968163151770b5015cd0ed09b Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 16:42:32 +0200 Subject: [PATCH 13/49] feat(relay): update passive relay for DMQ messages --- mithril-relay/src/relay/passive.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mithril-relay/src/relay/passive.rs b/mithril-relay/src/relay/passive.rs index d931d38dd54..a3ffbd8d8f2 100644 --- a/mithril-relay/src/relay/passive.rs +++ b/mithril-relay/src/relay/passive.rs @@ -32,11 +32,14 @@ impl PassiveRelay { pub async fn tick(&mut self) -> StdResult<()> { if let Some(peer_event) = self.peer.tick_swarm().await? { match self.peer.convert_peer_event_to_message(peer_event) { - Ok(Some(BroadcastMessage::RegisterSigner(signer_message_received))) => { - info!(self.logger, "Received signer registration message from P2P network"; "signer_message" => #?signer_message_received); + Ok(Some(BroadcastMessage::RegisterSignerHttp(signer_message_received))) => { + info!(self.logger, "Received HTTP signer registration message from P2P network"; "signer_message" => #?signer_message_received); } - Ok(Some(BroadcastMessage::RegisterSignature(signature_message_received))) => { - info!(self.logger, "Received signature message from P2P network"; "signature_message" => #?signature_message_received); + Ok(Some(BroadcastMessage::RegisterSignatureHttp(signature_message_received))) => { + info!(self.logger, "Received HTTP signature message from P2P network"; "signature_message" => #?signature_message_received); + } + Ok(Some(BroadcastMessage::RegisterSignatureDmq(signature_message_received))) => { + info!(self.logger, "Received DMQ signature message from P2P network"; "signature_message" => #?signature_message_received); } Ok(None) => {} Err(e) => return Err(e), From b7d85535557f912ce24ab4a899bb8530a0e6fdd1 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 16:43:27 +0200 Subject: [PATCH 14/49] feat(relay): update signer relay for DMQ messages --- mithril-relay/src/relay/signer.rs | 99 ++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 21 deletions(-) diff --git a/mithril-relay/src/relay/signer.rs b/mithril-relay/src/relay/signer.rs index c1bdb20b206..8d98dd567e9 100644 --- a/mithril-relay/src/relay/signer.rs +++ b/mithril-relay/src/relay/signer.rs @@ -1,13 +1,18 @@ +use std::{net::SocketAddr, path::Path, sync::Arc, time::Duration}; + use clap::ValueEnum; use libp2p::Multiaddr; -use slog::{Logger, debug, info}; -use std::{net::SocketAddr, sync::Arc, time::Duration}; +use mithril_dmq::{DmqMessage, DmqPublisherServer, DmqPublisherServerPallas}; +use slog::{Logger, debug, error, info}; use strum::Display; -use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}; +use tokio::sync::{ + mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}, + watch::{self, Receiver}, +}; use warp::Filter; use mithril_common::{ - StdResult, + CardanoNetwork, StdResult, logging::LoggerExtensions, messages::{RegisterSignatureMessageHttp, RegisterSignerMessage}, }; @@ -47,10 +52,11 @@ struct HTTPServerConfiguration<'a> { /// A relay for a Mithril signer pub struct SignerRelay { - server: TestHttpServer, + http_server: TestHttpServer, peer: Peer, - signature_rx: UnboundedReceiver, - signer_rx: UnboundedReceiver, + signature_http_rx: UnboundedReceiver, + signature_dmq_rx: UnboundedReceiver, + signer_http_rx: UnboundedReceiver, signer_repeater: Arc>, logger: Logger, } @@ -60,6 +66,8 @@ impl SignerRelay { pub async fn start( address: &Multiaddr, server_port: &u16, + dmq_node_socket_path: &Path, + cardano_network: &CardanoNetwork, signer_registration_mode: &SignerRelayMode, signature_registration_mode: &SignerRelayMode, aggregator_endpoint: &str, @@ -76,7 +84,7 @@ impl SignerRelay { logger, )); let peer = Peer::new(address).start().await?; - let server = Self::start_http_server(&HTTPServerConfiguration { + let http_server = Self::start_http_server(&HTTPServerConfiguration { server_port, signer_registration_mode: signer_registration_mode.to_owned(), signature_registration_mode: signature_registration_mode.to_owned(), @@ -87,18 +95,54 @@ impl SignerRelay { logger: &relay_logger, }) .await; - info!(relay_logger, "Listening on"; "address" => ?server.address()); + info!(relay_logger, "Listening on"; "address" => ?http_server.address()); + + let (_stop_tx, stop_rx) = watch::channel(()); + let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); + let _dmq_publisher_server = Self::start_dmq_publisher_server( + dmq_node_socket_path, + cardano_network, + signature_dmq_tx, + stop_rx, + relay_logger.clone(), + ) + .await?; Ok(Self { - server, + http_server, peer, - signature_rx, - signer_rx, + signature_http_rx: signature_rx, + signature_dmq_rx, + signer_http_rx: signer_rx, signer_repeater, logger: relay_logger, }) } + async fn start_dmq_publisher_server( + socket: &Path, + cardano_network: &CardanoNetwork, + signature_dmq_tx: UnboundedSender, + stop_rx: Receiver<()>, + logger: Logger, + ) -> StdResult> { + let dmq_publisher_server = Arc::new(DmqPublisherServerPallas::new( + socket.to_path_buf(), + cardano_network.to_owned(), + stop_rx, + logger.clone(), + )); + dmq_publisher_server.register_transmitter(signature_dmq_tx).await?; + let dmq_publisher_server_clone = dmq_publisher_server.clone(); + tokio::spawn(async move { + if let Err(err) = dmq_publisher_server_clone.run().await { + error!(logger.to_owned(), "DMQ Publisher server failed"; "error" => ?err); + } + }); + + Ok(dmq_publisher_server) + } + async fn start_http_server(configuration: &HTTPServerConfiguration<'_>) -> TestHttpServer { let server_logger = configuration.logger.new_with_name("http_server"); test_http_server_with_socket_address( @@ -154,28 +198,41 @@ impl SignerRelay { /// Tick the signer relay pub async fn tick(&mut self) -> StdResult<()> { tokio::select! { - message = self.signature_rx.recv() => { + message = self.signature_http_rx.recv() => { + match message { + Some(signature_message) => { + info!(self.logger, "Publish HTTP signature to p2p network"; "message" => #?signature_message); + self.peer.publish_signature_http(&signature_message)?; + Ok(()) + } + None => { + debug!(self.logger, "No HTTP signature message available"); + Ok(()) + } + } + }, + message = self.signature_dmq_rx.recv() => { match message { Some(signature_message) => { - info!(self.logger, "Publish signature to p2p network"; "message" => #?signature_message); - self.peer.publish_signature(&signature_message)?; + info!(self.logger, "Publish DMQ signature to p2p network"; "message" => #?signature_message); + self.peer.publish_signature_dmq(&signature_message)?; Ok(()) } None => { - debug!(self.logger, "No signature message available"); + //debug!(self.logger, "No DMQ signature message available"); Ok(()) } } }, - message = self.signer_rx.recv() => { + message = self.signer_http_rx.recv() => { match message { Some(signer_message) => { - info!(self.logger, "Publish signer-registration to p2p network"; "message" => #?signer_message); + info!(self.logger, "Publish HTTP signer-registration to p2p network"; "message" => #?signer_message); self.peer.publish_signer_registration(&signer_message)?; Ok(()) } None => { - debug!(self.logger, "No signer message available"); + debug!(self.logger, "No HTTP signer message available"); Ok(()) } } @@ -188,7 +245,7 @@ impl SignerRelay { /// Receive signature from the underlying channel #[allow(dead_code)] pub async fn receive_signature(&mut self) -> Option { - self.signature_rx.recv().await + self.signature_http_rx.recv().await } /// Tick the peer of the signer relay @@ -203,7 +260,7 @@ impl SignerRelay { /// Retrieve address on which the HTTP Server is listening pub fn address(&self) -> SocketAddr { - self.server.address() + self.http_server.address() } /// Retrieve address on which the peer is listening From 723cc51f03c8267357d1fdcaf1167b91c6f86e26 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 16:44:10 +0200 Subject: [PATCH 15/49] feat(relay): update signer command for DMQ messages --- mithril-relay/src/commands/signer.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/mithril-relay/src/commands/signer.rs b/mithril-relay/src/commands/signer.rs index 7045f5c883c..b51ee37c2b5 100644 --- a/mithril-relay/src/commands/signer.rs +++ b/mithril-relay/src/commands/signer.rs @@ -1,8 +1,8 @@ -use std::time::Duration; +use std::{path::PathBuf, time::Duration}; use clap::Parser; use libp2p::Multiaddr; -use mithril_common::StdResult; +use mithril_common::{CardanoNetwork, StdResult}; use slog::error; use super::CommandContext; @@ -22,6 +22,24 @@ pub struct SignerCommand { #[clap(long, env = "DIAL_TO")] dial_to: Option, + /// Path to the DMQ socket file + #[clap( + long, + env = "DMQ_NODE_SOCKET_PATH", + value_name = "PATH", + default_value = "./dmq.socket" + )] + dmq_node_socket_path: PathBuf, + + /// Cardano network + #[clap(long, env = "NETWORK")] + pub network: String, + + /// Cardano Network Magic number + /// useful for TestNet & DevNet + #[clap(long, env = "NETWORK_MAGIC")] + pub network_magic: Option, + /// Aggregator endpoint URL. #[clap(long, env = "AGGREGATOR_ENDPOINT")] aggregator_endpoint: String, @@ -50,10 +68,14 @@ impl SignerCommand { let signature_registration_mode = &self.signature_registration_mode; let aggregator_endpoint = self.aggregator_endpoint.to_owned(); let signer_repeater_delay = Duration::from_millis(self.signer_repeater_delay); + let cardano_network = + CardanoNetwork::from_code(self.network.to_owned(), self.network_magic)?; let mut relay = SignerRelay::start( &addr, &server_port, + &self.dmq_node_socket_path, + &cardano_network, signer_registration_mode, signature_registration_mode, &aggregator_endpoint, From 46b07c7abd6fcbe67b9fe752270ff98aef39fb57 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 16:45:12 +0200 Subject: [PATCH 16/49] feat(relay): update aggregator relay for DMQ messages --- mithril-relay/src/relay/aggregator.rs | 77 +++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/mithril-relay/src/relay/aggregator.rs b/mithril-relay/src/relay/aggregator.rs index bc958f67978..b6eab02f6f8 100644 --- a/mithril-relay/src/relay/aggregator.rs +++ b/mithril-relay/src/relay/aggregator.rs @@ -1,18 +1,28 @@ -use crate::p2p::{BroadcastMessage, Peer, PeerEvent}; +use std::{path::Path, sync::Arc}; + use anyhow::anyhow; use libp2p::Multiaddr; +use mithril_dmq::{DmqConsumerServer, DmqConsumerServerPallas, DmqMessage}; +use reqwest::StatusCode; +use slog::{Logger, error, info}; +use tokio::sync::{ + mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}, + watch::{self, Receiver}, +}; + use mithril_common::{ - StdResult, + CardanoNetwork, StdResult, logging::LoggerExtensions, messages::{RegisterSignatureMessageHttp, RegisterSignerMessage}, }; -use reqwest::StatusCode; -use slog::{Logger, error, info}; + +use crate::p2p::{BroadcastMessage, Peer, PeerEvent}; /// A relay for a Mithril aggregator pub struct AggregatorRelay { aggregator_endpoint: String, peer: Peer, + signature_dmq_tx: UnboundedSender, logger: Logger, } @@ -20,16 +30,54 @@ impl AggregatorRelay { /// Start a relay for a Mithril aggregator pub async fn start( addr: &Multiaddr, + dmq_node_socket_path: &Path, + cardano_network: &CardanoNetwork, aggregator_endpoint: &str, logger: &Logger, ) -> StdResult { + let (_stop_tx, stop_rx) = watch::channel(()); + let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); + let _dmq_consumer_server = Self::start_dmq_consumer_server( + dmq_node_socket_path, + cardano_network, + signature_dmq_rx, + stop_rx, + logger.clone(), + ) + .await?; + Ok(Self { aggregator_endpoint: aggregator_endpoint.to_owned(), peer: Peer::new(addr).with_logger(logger).start().await?, + signature_dmq_tx, logger: logger.new_with_component_name::(), }) } + async fn start_dmq_consumer_server( + socket: &Path, + cardano_network: &CardanoNetwork, + signature_dmq_rx: UnboundedReceiver, + stop_rx: Receiver<()>, + logger: Logger, + ) -> StdResult> { + let dmq_consumer_server = Arc::new(DmqConsumerServerPallas::new( + socket.to_path_buf(), + cardano_network.to_owned(), + stop_rx, + logger.clone(), + )); + dmq_consumer_server.register_receiver(signature_dmq_rx).await?; + let dmq_consumer_server_clone = dmq_consumer_server.clone(); + tokio::spawn(async move { + if let Err(err) = dmq_consumer_server_clone.run().await { + error!(logger.to_owned(), "DMQ Consumer server failed"; "error" => ?err); + } + }); + + Ok(dmq_consumer_server) + } + async fn notify_signature_to_aggregator( &self, signature_message: &RegisterSignatureMessageHttp, @@ -100,7 +148,7 @@ impl AggregatorRelay { pub async fn tick(&mut self) -> StdResult<()> { if let Some(peer_event) = self.peer.tick_swarm().await? { match self.peer.convert_peer_event_to_message(peer_event) { - Ok(Some(BroadcastMessage::RegisterSigner(signer_message_received))) => { + Ok(Some(BroadcastMessage::RegisterSignerHttp(signer_message_received))) => { let retry_max = 3; let mut retry_count = 0; while let Err(e) = @@ -113,7 +161,7 @@ impl AggregatorRelay { } } } - Ok(Some(BroadcastMessage::RegisterSignature(signature_message_received))) => { + Ok(Some(BroadcastMessage::RegisterSignatureHttp(signature_message_received))) => { let retry_max = 3; let mut retry_count = 0; while let Err(e) = @@ -126,6 +174,11 @@ impl AggregatorRelay { } } } + Ok(Some(BroadcastMessage::RegisterSignatureDmq(signature_message_received))) => { + self.signature_dmq_tx.send(signature_message_received).map_err(|e| { + anyhow!("Failed to send signature message to DMQ consumer server: {e}") + })?; + } Ok(None) => {} Err(e) => return Err(e), } @@ -180,9 +233,15 @@ mod tests { then.status(201).body("ok"); }); let addr: Multiaddr = "/ip4/0.0.0.0/tcp/0".parse().unwrap(); - let relay = AggregatorRelay::start(&addr, &server.url(""), &TestLogger::stdout()) - .await - .unwrap(); + let relay = AggregatorRelay::start( + &addr, + &Path::new("test"), + &CardanoNetwork::TestNet(123), + &server.url(""), + &TestLogger::stdout(), + ) + .await + .unwrap(); relay .notify_signature_to_aggregator(&RegisterSignatureMessageHttp::dummy()) From 9cbfcc9a7ea65afd2e2bee0ea27c5efc7eda72e6 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 16:45:51 +0200 Subject: [PATCH 17/49] feat(relay): update aggregator command for DMQ messages --- mithril-relay/src/commands/aggregator.rs | 39 +++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/mithril-relay/src/commands/aggregator.rs b/mithril-relay/src/commands/aggregator.rs index e37c1873dd9..4eacd78abea 100644 --- a/mithril-relay/src/commands/aggregator.rs +++ b/mithril-relay/src/commands/aggregator.rs @@ -1,11 +1,15 @@ +use std::path::PathBuf; + use clap::Parser; use libp2p::Multiaddr; -use mithril_common::StdResult; use slog::error; -use super::CommandContext; +use mithril_common::{CardanoNetwork, StdResult}; + use crate::AggregatorRelay; +use super::CommandContext; + #[derive(Parser, Debug, Clone)] pub struct AggregatorCommand { /// Peer listening port @@ -16,6 +20,24 @@ pub struct AggregatorCommand { #[clap(long, env = "DIAL_TO")] dial_to: Option, + /// Path to the DMQ socket file + #[clap( + long, + env = "DMQ_NODE_SOCKET_PATH", + value_name = "PATH", + default_value = "./dmq.socket" + )] + dmq_node_socket_path: PathBuf, + + /// Cardano network + #[clap(long, env = "NETWORK")] + pub network: String, + + /// Cardano Network Magic number + /// useful for TestNet & DevNet + #[clap(long, env = "NETWORK_MAGIC")] + pub network_magic: Option, + /// Aggregator endpoint URL. #[clap(long, env = "AGGREGATOR_ENDPOINT")] aggregator_endpoint: String, @@ -24,12 +46,21 @@ pub struct AggregatorCommand { impl AggregatorCommand { /// Main command execution pub async fn execute(&self, context: CommandContext) -> StdResult<()> { + let logger = context.logger(); let dial_to = self.dial_to.to_owned(); let addr: Multiaddr = format!("/ip4/0.0.0.0/tcp/{}", self.listen_port).parse()?; let aggregator_endpoint = self.aggregator_endpoint.to_owned(); - let logger = context.logger(); + let cardano_network = + CardanoNetwork::from_code(self.network.to_owned(), self.network_magic)?; - let mut relay = AggregatorRelay::start(&addr, &aggregator_endpoint, logger).await?; + let mut relay = AggregatorRelay::start( + &addr, + &self.dmq_node_socket_path, + &cardano_network, + &aggregator_endpoint, + logger, + ) + .await?; if let Some(dial_to_address) = dial_to { relay.dial_peer(dial_to_address.clone())?; } From 5220f9458a625fd5c2ee8660f18a6be874973fab Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 16:46:18 +0200 Subject: [PATCH 18/49] feat(relay): update integration test for DMQ messages --- .../tests/register_signer_signature.rs | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/mithril-relay/tests/register_signer_signature.rs b/mithril-relay/tests/register_signer_signature.rs index 24e4d4d5801..b10793472a6 100644 --- a/mithril-relay/tests/register_signer_signature.rs +++ b/mithril-relay/tests/register_signer_signature.rs @@ -1,15 +1,19 @@ -use std::{sync::Arc, time::Duration}; +use std::{path::PathBuf, sync::Arc, time::Duration}; use libp2p::{Multiaddr, gossipsub}; -use mithril_common::messages::{RegisterSignatureMessageHttp, RegisterSignerMessage}; -use mithril_common::test::double::Dummy; +use reqwest::StatusCode; +use slog::{Drain, Level, Logger}; +use slog_scope::{error, info}; + +use mithril_common::test_utils::double::Dummy; +use mithril_common::{ + CardanoNetwork, + messages::{RegisterSignatureMessageHttp, RegisterSignerMessage}, +}; use mithril_relay::{ PassiveRelay, SignerRelay, SignerRelayMode, p2p::{BroadcastMessage, PeerBehaviourEvent, PeerEvent}, }; -use reqwest::StatusCode; -use slog::{Drain, Level, Logger}; -use slog_scope::{error, info}; // Launch a relay that connects to P2P network. The relay is a peer in the P2P // network. The relay sends some signer registrations that must be received by other @@ -36,6 +40,8 @@ async fn should_receive_registrations_from_signers_when_subscribed_to_pubsub() { let total_peers = 1 + total_p2p_client; let addr: Multiaddr = "/ip4/0.0.0.0/tcp/0".parse().unwrap(); let server_port = 0; + let dmq_node_socket_path = PathBuf::new(); + let cardano_network = CardanoNetwork::TestNet(123); let signer_registration_mode = SignerRelayMode::P2P; let signature_registration_mode = SignerRelayMode::P2P; let aggregator_endpoint = "http://0.0.0.0:1234".to_string(); @@ -43,6 +49,8 @@ async fn should_receive_registrations_from_signers_when_subscribed_to_pubsub() { let mut signer_relay = SignerRelay::start( &addr, &server_port, + &dmq_node_socket_path, + &cardano_network, &signer_registration_mode, &signature_registration_mode, &aggregator_endpoint, @@ -143,7 +151,7 @@ async fn should_receive_registrations_from_signers_when_subscribed_to_pubsub() { loop { tokio::select! { event = p2p_client1.tick_peer() => { - if let Ok(Some(BroadcastMessage::RegisterSigner(signer_message_received))) = p2p_client1.peer_mut().convert_peer_event_to_message(event.unwrap().unwrap()) + if let Ok(Some(BroadcastMessage::RegisterSignerHttp(signer_message_received))) = p2p_client1.peer_mut().convert_peer_event_to_message(event.unwrap().unwrap()) { info!("Test: client1 consumed signer registration"; "message" => #?signer_message_received); assert_eq!(signer_message_sent, signer_message_received); @@ -151,7 +159,7 @@ async fn should_receive_registrations_from_signers_when_subscribed_to_pubsub() { } } event = p2p_client2.tick_peer() => { - if let Ok(Some(BroadcastMessage::RegisterSigner(signer_message_received))) = p2p_client2.peer_mut().convert_peer_event_to_message(event.unwrap().unwrap()) + if let Ok(Some(BroadcastMessage::RegisterSignerHttp(signer_message_received))) = p2p_client2.peer_mut().convert_peer_event_to_message(event.unwrap().unwrap()) { info!("Test: client2 consumed signer registration"; "message" => #?signer_message_received); assert_eq!(signer_message_sent, signer_message_received); @@ -190,7 +198,7 @@ async fn should_receive_registrations_from_signers_when_subscribed_to_pubsub() { loop { tokio::select! { event = p2p_client1.tick_peer() => { - if let Ok(Some(BroadcastMessage::RegisterSignature(signature_message_received))) = p2p_client1.peer_mut().convert_peer_event_to_message(event.unwrap().unwrap()) + if let Ok(Some(BroadcastMessage::RegisterSignatureHttp(signature_message_received))) = p2p_client1.peer_mut().convert_peer_event_to_message(event.unwrap().unwrap()) { info!("Test: client1 consumed signature"; "message" => #?signature_message_received); assert_eq!(signature_message_sent, signature_message_received); @@ -198,7 +206,7 @@ async fn should_receive_registrations_from_signers_when_subscribed_to_pubsub() { } } event = p2p_client2.tick_peer() => { - if let Ok(Some(BroadcastMessage::RegisterSignature(signature_message_received))) = p2p_client2.peer_mut().convert_peer_event_to_message(event.unwrap().unwrap()) + if let Ok(Some(BroadcastMessage::RegisterSignatureHttp(signature_message_received))) = p2p_client2.peer_mut().convert_peer_event_to_message(event.unwrap().unwrap()) { info!("Test: client2 consumed signature"; "message" => #?signature_message_received); assert_eq!(signature_message_sent, signature_message_received); From 3317a41930ea8fb2b22100f140a61db1d02735c7 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 16:58:22 +0200 Subject: [PATCH 19/49] feat(dmq): export DMQ servers for publisher and consumer --- internal/mithril-dmq/src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/mithril-dmq/src/lib.rs b/internal/mithril-dmq/src/lib.rs index db4d1180ebe..776e0b7180e 100644 --- a/internal/mithril-dmq/src/lib.rs +++ b/internal/mithril-dmq/src/lib.rs @@ -6,9 +6,13 @@ mod model; mod publisher; pub mod test; -pub use consumer::{DmqConsumerClient, DmqConsumerClientPallas}; +pub use consumer::{ + DmqConsumerClient, DmqConsumerClientPallas, DmqConsumerServer, DmqConsumerServerPallas, +}; pub use model::{DmqMessage, DmqMessageBuilder}; -pub use publisher::{DmqPublisherClient, DmqPublisherClientPallas}; +pub use publisher::{ + DmqPublisherClient, DmqPublisherClientPallas, DmqPublisherServer, DmqPublisherServerPallas, +}; #[cfg(test)] pub(crate) mod test_tools { From 69fcc101757589825e2fc89ae40f36ddc0ad03c3 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 21 Jul 2025 16:47:06 +0200 Subject: [PATCH 20/49] feat(relay): use binary encoding for exchanging messages in P2P pubsub topics --- Cargo.lock | 1 + mithril-relay/Cargo.toml | 1 + mithril-relay/src/p2p/peer.rs | 47 +++++++++++++++++++++++++---------- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac2f6cde88c..5cf8b0482c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4358,6 +4358,7 @@ name = "mithril-relay" version = "0.1.50" dependencies = [ "anyhow", + "bincode", "clap", "config", "httpmock", diff --git a/mithril-relay/Cargo.toml b/mithril-relay/Cargo.toml index edbef2c976e..de0d6259e50 100644 --- a/mithril-relay/Cargo.toml +++ b/mithril-relay/Cargo.toml @@ -15,6 +15,7 @@ future_dmq = ["dep:mithril-dmq"] [dependencies] anyhow = { workspace = true } +bincode = { version = "2.0.1" } clap = { workspace = true } config = { workspace = true } libp2p = { version = "0.56.0", features = [ diff --git a/mithril-relay/src/p2p/peer.rs b/mithril-relay/src/p2p/peer.rs index 7c473afdddc..ee0497f3e5d 100644 --- a/mithril-relay/src/p2p/peer.rs +++ b/mithril-relay/src/p2p/peer.rs @@ -11,6 +11,7 @@ use libp2p::{ }; use mithril_common::{ StdResult, + crypto_helper::{TryFromBytes, TryToBytes}, logging::LoggerExtensions, messages::{RegisterSignatureMessageHttp, RegisterSignerMessage}, }; @@ -74,6 +75,22 @@ pub enum BroadcastMessage { RegisterSignatureDmq(DmqMessage), } +impl TryToBytes for BroadcastMessage { + fn to_bytes_vec(&self) -> StdResult> { + bincode::serde::encode_to_vec(self, bincode::config::standard()).map_err(|e| e.into()) + } +} + +impl TryFromBytes for BroadcastMessage { + fn try_from_bytes(bytes: &[u8]) -> StdResult { + let (res, _) = + bincode::serde::decode_from_slice::(bytes, bincode::config::standard()) + .map_err(|e| anyhow!(e))?; + + Ok(res) + } +} + /// A peer in the P2P network pub struct Peer { topics: HashMap, @@ -185,7 +202,11 @@ impl Peer { match event { PeerEvent::Behaviour { event: PeerBehaviourEvent::Gossipsub(gossipsub::Event::Message { message, .. }), - } => Ok(Some(serde_json::from_slice(&message.data)?)), + } => Ok(Some( + BroadcastMessage::try_from_bytes(&message.data).with_context( + || "Failed to deserialize BroadcastMessage from gossipsub event", + )?, + )), _ => Ok(None), } } @@ -247,6 +268,17 @@ impl Peer { ) } + /// Publish a signer registration on the P2P pubsub + pub fn publish_signer_registration( + &mut self, + message: &RegisterSignerMessage, + ) -> StdResult { + self.publish_broadcast_message( + &BroadcastMessage::RegisterSignerHttp(message.to_owned()), + mithril_p2p_topic::SIGNERS_HTTP, + ) + } + /// Publish a broadcast message on the P2P pubsub pub fn publish_broadcast_message( &mut self, @@ -261,7 +293,7 @@ impl Peer { format!("Can not publish broadcast message on invalid topic: {topic_name}") })? .to_owned(); - let data = serde_json::to_vec(message).with_context(|| { + let data = message.to_bytes_vec().with_context(|| { format!("Can not publish broadcast message with invalid format on topic {topic_name}") })?; @@ -283,17 +315,6 @@ impl Peer { Ok(message_id.to_owned()) } - /// Publish a signer registration on the P2P pubsub - pub fn publish_signer_registration( - &mut self, - message: &RegisterSignerMessage, - ) -> StdResult { - self.publish_broadcast_message( - &BroadcastMessage::RegisterSignerHttp(message.to_owned()), - mithril_p2p_topic::SIGNERS_HTTP, - ) - } - /// Connect to a remote peer pub fn dial(&mut self, addr: Multiaddr) -> StdResult<()> { debug!(self.logger, "Dialing to"; "address" => ?addr, "local_peer_id" => ?self.local_peer_id()); From 4cd18ac24b78ebfdb66fa56056af1ac4272bbe3d Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 15 Jul 2025 15:02:34 +0200 Subject: [PATCH 21/49] fix(dmq): activate Pallas server sides for Unix only --- internal/mithril-dmq/src/consumer/server/mod.rs | 4 ++++ internal/mithril-dmq/src/lib.rs | 12 ++++++------ internal/mithril-dmq/src/publisher/server/mod.rs | 2 ++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/internal/mithril-dmq/src/consumer/server/mod.rs b/internal/mithril-dmq/src/consumer/server/mod.rs index 498010ae3c5..491ecf0f0d3 100644 --- a/internal/mithril-dmq/src/consumer/server/mod.rs +++ b/internal/mithril-dmq/src/consumer/server/mod.rs @@ -1,6 +1,10 @@ mod interface; +#[cfg(unix)] mod pallas; +#[cfg(unix)] mod queue; pub use interface::*; + +#[cfg(unix)] pub use pallas::*; diff --git a/internal/mithril-dmq/src/lib.rs b/internal/mithril-dmq/src/lib.rs index 776e0b7180e..21abe080378 100644 --- a/internal/mithril-dmq/src/lib.rs +++ b/internal/mithril-dmq/src/lib.rs @@ -6,13 +6,13 @@ mod model; mod publisher; pub mod test; -pub use consumer::{ - DmqConsumerClient, DmqConsumerClientPallas, DmqConsumerServer, DmqConsumerServerPallas, -}; +#[cfg(unix)] +pub use consumer::DmqConsumerServerPallas; +pub use consumer::{DmqConsumerClient, DmqConsumerClientPallas, DmqConsumerServer}; pub use model::{DmqMessage, DmqMessageBuilder}; -pub use publisher::{ - DmqPublisherClient, DmqPublisherClientPallas, DmqPublisherServer, DmqPublisherServerPallas, -}; +#[cfg(unix)] +pub use publisher::DmqPublisherServerPallas; +pub use publisher::{DmqPublisherClient, DmqPublisherClientPallas, DmqPublisherServer}; #[cfg(test)] pub(crate) mod test_tools { diff --git a/internal/mithril-dmq/src/publisher/server/mod.rs b/internal/mithril-dmq/src/publisher/server/mod.rs index 4035f6c0659..80dea9d418d 100644 --- a/internal/mithril-dmq/src/publisher/server/mod.rs +++ b/internal/mithril-dmq/src/publisher/server/mod.rs @@ -1,5 +1,7 @@ mod interface; +#[cfg(unix)] mod pallas; pub use interface::*; +#[cfg(unix)] pub use pallas::*; From 985ff94cedd7919a4cd288bcf933c082b704a15b Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 15 Jul 2025 15:12:37 +0200 Subject: [PATCH 22/49] fix(dmq): add missing 'kes_period' in 'DmqMsg' --- internal/mithril-dmq/src/consumer/server/pallas.rs | 1 + internal/mithril-dmq/src/consumer/server/queue.rs | 1 + internal/mithril-dmq/src/publisher/server/pallas.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/internal/mithril-dmq/src/consumer/server/pallas.rs b/internal/mithril-dmq/src/consumer/server/pallas.rs index 6476b6c04e7..234ec4d09d1 100644 --- a/internal/mithril-dmq/src/consumer/server/pallas.rs +++ b/internal/mithril-dmq/src/consumer/server/pallas.rs @@ -242,6 +242,7 @@ mod tests { ttl: 100, kes_signature: vec![0, 1, 2, 3], operational_certificate: vec![0, 1, 2, 3, 4], + kes_period: 10, } } diff --git a/internal/mithril-dmq/src/consumer/server/queue.rs b/internal/mithril-dmq/src/consumer/server/queue.rs index cad7e0e278b..b3681b4c092 100644 --- a/internal/mithril-dmq/src/consumer/server/queue.rs +++ b/internal/mithril-dmq/src/consumer/server/queue.rs @@ -83,6 +83,7 @@ mod tests { ttl: 100, kes_signature: vec![0, 1, 2, 3], operational_certificate: vec![0, 1, 2, 3, 4], + kes_period: 10, } } diff --git a/internal/mithril-dmq/src/publisher/server/pallas.rs b/internal/mithril-dmq/src/publisher/server/pallas.rs index 8f742068faa..e319a54b383 100644 --- a/internal/mithril-dmq/src/publisher/server/pallas.rs +++ b/internal/mithril-dmq/src/publisher/server/pallas.rs @@ -234,6 +234,7 @@ mod tests { ttl: 100, kes_signature: vec![0, 1, 2, 3], operational_certificate: vec![0, 1, 2, 3, 4, 5], + kes_period: 10, } } From a3c77f51d6e477452603042123c4fd6da89ab3cb Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 15 Jul 2025 16:23:21 +0200 Subject: [PATCH 23/49] fix(dmq): gate code behind 'future_dmq' feature --- .../mithril-dmq/src/consumer/server/pallas.rs | 12 +- .../src/publisher/server/pallas.rs | 12 +- mithril-relay/src/commands/aggregator.rs | 11 +- mithril-relay/src/commands/signer.rs | 18 +- mithril-relay/src/p2p/peer.rs | 3 + mithril-relay/src/relay/aggregator.rs | 57 ++++-- mithril-relay/src/relay/passive.rs | 1 + mithril-relay/src/relay/signer.rs | 171 ++++++++++++------ .../tests/register_signer_signature.rs | 15 +- 9 files changed, 213 insertions(+), 87 deletions(-) diff --git a/internal/mithril-dmq/src/consumer/server/pallas.rs b/internal/mithril-dmq/src/consumer/server/pallas.rs index 234ec4d09d1..3dcb7142c38 100644 --- a/internal/mithril-dmq/src/consumer/server/pallas.rs +++ b/internal/mithril-dmq/src/consumer/server/pallas.rs @@ -48,9 +48,15 @@ impl DmqConsumerServerPallas { /// Creates and returns a new `DmqServer` connected to the specified socket. async fn new_server(&self) -> StdResult { - let magic = self.network.code(); + info!( + self.logger, + "Creating a new DMQ consumer server"; + "socket" => ?self.socket, + "network" => ?self.network + ); + let magic = self.network.magic_id(); if self.socket.exists() { - fs::remove_file(self.socket.clone()).unwrap(); + fs::remove_file(self.socket.clone())?; } let listener = UnixListener::bind(&self.socket) .map_err(|err| anyhow!(err)) @@ -177,7 +183,7 @@ impl DmqConsumerServer for DmqConsumerServerPallas { Some(ref mut receiver) => loop { select! { _ = stop_rx.changed() => { - warn!(self.logger, "Stopping signature processor..."); + warn!(self.logger, "Stopping DMQ consumer server..."); return Ok(()); } diff --git a/internal/mithril-dmq/src/publisher/server/pallas.rs b/internal/mithril-dmq/src/publisher/server/pallas.rs index e319a54b383..2e8232225b1 100644 --- a/internal/mithril-dmq/src/publisher/server/pallas.rs +++ b/internal/mithril-dmq/src/publisher/server/pallas.rs @@ -50,9 +50,15 @@ impl DmqPublisherServerPallas { /// Creates and returns a new `DmqServer` connected to the specified socket. async fn new_server(&self) -> StdResult { - let magic = self.network.code(); + info!( + self.logger, + "Creating a new DMQ publisher server"; + "socket" => ?self.socket, + "network" => ?self.network + ); + let magic = self.network.magic_id(); if self.socket.exists() { - fs::remove_file(self.socket.clone()).unwrap(); + fs::remove_file(self.socket.clone())?; } let listener = UnixListener::bind(&self.socket) .map_err(|err| anyhow!(err)) @@ -184,7 +190,7 @@ impl DmqPublisherServer for DmqPublisherServerPallas { loop { select! { _ = stop_rx.changed() => { - warn!(self.logger, "Stopping signature processor..."); + warn!(self.logger, "Stopping DMQ publisher server..."); return Ok(()); } diff --git a/mithril-relay/src/commands/aggregator.rs b/mithril-relay/src/commands/aggregator.rs index 4eacd78abea..ba75b5fdf39 100644 --- a/mithril-relay/src/commands/aggregator.rs +++ b/mithril-relay/src/commands/aggregator.rs @@ -1,10 +1,13 @@ +#[cfg(feature = "future_dmq")] use std::path::PathBuf; use clap::Parser; use libp2p::Multiaddr; use slog::error; -use mithril_common::{CardanoNetwork, StdResult}; +#[cfg(feature = "future_dmq")] +use mithril_common::CardanoNetwork; +use mithril_common::StdResult; use crate::AggregatorRelay; @@ -21,6 +24,7 @@ pub struct AggregatorCommand { dial_to: Option, /// Path to the DMQ socket file + #[cfg(feature = "future_dmq")] #[clap( long, env = "DMQ_NODE_SOCKET_PATH", @@ -30,11 +34,13 @@ pub struct AggregatorCommand { dmq_node_socket_path: PathBuf, /// Cardano network + #[cfg(feature = "future_dmq")] #[clap(long, env = "NETWORK")] pub network: String, /// Cardano Network Magic number /// useful for TestNet & DevNet + #[cfg(feature = "future_dmq")] #[clap(long, env = "NETWORK_MAGIC")] pub network_magic: Option, @@ -50,12 +56,15 @@ impl AggregatorCommand { let dial_to = self.dial_to.to_owned(); let addr: Multiaddr = format!("/ip4/0.0.0.0/tcp/{}", self.listen_port).parse()?; let aggregator_endpoint = self.aggregator_endpoint.to_owned(); + #[cfg(feature = "future_dmq")] let cardano_network = CardanoNetwork::from_code(self.network.to_owned(), self.network_magic)?; let mut relay = AggregatorRelay::start( &addr, + #[cfg(feature = "future_dmq")] &self.dmq_node_socket_path, + #[cfg(feature = "future_dmq")] &cardano_network, &aggregator_endpoint, logger, diff --git a/mithril-relay/src/commands/signer.rs b/mithril-relay/src/commands/signer.rs index b51ee37c2b5..dd01c4fe8de 100644 --- a/mithril-relay/src/commands/signer.rs +++ b/mithril-relay/src/commands/signer.rs @@ -1,13 +1,19 @@ -use std::{path::PathBuf, time::Duration}; +#[cfg(feature = "future_dmq")] +use std::path::PathBuf; +use std::time::Duration; use clap::Parser; use libp2p::Multiaddr; -use mithril_common::{CardanoNetwork, StdResult}; use slog::error; -use super::CommandContext; +#[cfg(feature = "future_dmq")] +use mithril_common::CardanoNetwork; +use mithril_common::StdResult; + use crate::{SignerRelay, SignerRelayMode}; +use super::CommandContext; + #[derive(Parser, Debug, Clone)] pub struct SignerCommand { /// HTTP Server listening port @@ -23,6 +29,7 @@ pub struct SignerCommand { dial_to: Option, /// Path to the DMQ socket file + #[cfg(feature = "future_dmq")] #[clap( long, env = "DMQ_NODE_SOCKET_PATH", @@ -32,11 +39,13 @@ pub struct SignerCommand { dmq_node_socket_path: PathBuf, /// Cardano network + #[cfg(feature = "future_dmq")] #[clap(long, env = "NETWORK")] pub network: String, /// Cardano Network Magic number /// useful for TestNet & DevNet + #[cfg(feature = "future_dmq")] #[clap(long, env = "NETWORK_MAGIC")] pub network_magic: Option, @@ -68,13 +77,16 @@ impl SignerCommand { let signature_registration_mode = &self.signature_registration_mode; let aggregator_endpoint = self.aggregator_endpoint.to_owned(); let signer_repeater_delay = Duration::from_millis(self.signer_repeater_delay); + #[cfg(feature = "future_dmq")] let cardano_network = CardanoNetwork::from_code(self.network.to_owned(), self.network_magic)?; let mut relay = SignerRelay::start( &addr, &server_port, + #[cfg(feature = "future_dmq")] &self.dmq_node_socket_path, + #[cfg(feature = "future_dmq")] &cardano_network, signer_registration_mode, signature_registration_mode, diff --git a/mithril-relay/src/p2p/peer.rs b/mithril-relay/src/p2p/peer.rs index ee0497f3e5d..742fceb50e4 100644 --- a/mithril-relay/src/p2p/peer.rs +++ b/mithril-relay/src/p2p/peer.rs @@ -15,6 +15,7 @@ use mithril_common::{ logging::LoggerExtensions, messages::{RegisterSignatureMessageHttp, RegisterSignerMessage}, }; +#[cfg(feature = "future_dmq")] use mithril_dmq::DmqMessage; use serde::{Deserialize, Serialize}; use slog::{Logger, debug, info}; @@ -72,6 +73,7 @@ pub enum BroadcastMessage { RegisterSignatureHttp(RegisterSignatureMessageHttp), /// A DMQ signature registration message received from the Gossip sub + #[cfg(feature = "future_dmq")] RegisterSignatureDmq(DmqMessage), } @@ -258,6 +260,7 @@ impl Peer { } /// Publish a DMQ signature on the P2P pubsub + #[cfg(feature = "future_dmq")] pub fn publish_signature_dmq( &mut self, message: &DmqMessage, diff --git a/mithril-relay/src/relay/aggregator.rs b/mithril-relay/src/relay/aggregator.rs index b6eab02f6f8..6d834dc6971 100644 --- a/mithril-relay/src/relay/aggregator.rs +++ b/mithril-relay/src/relay/aggregator.rs @@ -1,17 +1,22 @@ +#[cfg(feature = "future_dmq")] use std::{path::Path, sync::Arc}; use anyhow::anyhow; use libp2p::Multiaddr; +#[cfg(feature = "future_dmq")] use mithril_dmq::{DmqConsumerServer, DmqConsumerServerPallas, DmqMessage}; use reqwest::StatusCode; use slog::{Logger, error, info}; +#[cfg(feature = "future_dmq")] use tokio::sync::{ mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}, watch::{self, Receiver}, }; +#[cfg(feature = "future_dmq")] +use mithril_common::CardanoNetwork; use mithril_common::{ - CardanoNetwork, StdResult, + StdResult, logging::LoggerExtensions, messages::{RegisterSignatureMessageHttp, RegisterSignerMessage}, }; @@ -22,7 +27,9 @@ use crate::p2p::{BroadcastMessage, Peer, PeerEvent}; pub struct AggregatorRelay { aggregator_endpoint: String, peer: Peer, + #[cfg(feature = "future_dmq")] signature_dmq_tx: UnboundedSender, + #[cfg(feature = "future_dmq")] logger: Logger, } @@ -30,30 +37,43 @@ impl AggregatorRelay { /// Start a relay for a Mithril aggregator pub async fn start( addr: &Multiaddr, - dmq_node_socket_path: &Path, - cardano_network: &CardanoNetwork, + #[cfg(feature = "future_dmq")] dmq_node_socket_path: &Path, + #[cfg(feature = "future_dmq")] cardano_network: &CardanoNetwork, aggregator_endpoint: &str, logger: &Logger, ) -> StdResult { - let (_stop_tx, stop_rx) = watch::channel(()); - let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); - let _dmq_consumer_server = Self::start_dmq_consumer_server( - dmq_node_socket_path, - cardano_network, - signature_dmq_rx, - stop_rx, - logger.clone(), - ) - .await?; + let peer = Peer::new(addr).with_logger(logger).start().await?; + let logger = logger.new_with_component_name::(); + #[cfg(feature = "future_dmq")] + { + let (_stop_tx, stop_rx) = watch::channel(()); + let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); + #[cfg(unix)] + let _dmq_consumer_server = Self::start_dmq_consumer_server( + dmq_node_socket_path, + cardano_network, + signature_dmq_rx, + stop_rx, + logger.clone(), + ) + .await?; + Ok(Self { + aggregator_endpoint: aggregator_endpoint.to_owned(), + peer, + signature_dmq_tx, + logger, + }) + } + #[cfg(not(feature = "future_dmq"))] Ok(Self { aggregator_endpoint: aggregator_endpoint.to_owned(), - peer: Peer::new(addr).with_logger(logger).start().await?, - signature_dmq_tx, - logger: logger.new_with_component_name::(), + peer, + logger, }) } + #[cfg(feature = "future_dmq")] async fn start_dmq_consumer_server( socket: &Path, cardano_network: &CardanoNetwork, @@ -174,6 +194,7 @@ impl AggregatorRelay { } } } + #[cfg(feature = "future_dmq")] Ok(Some(BroadcastMessage::RegisterSignatureDmq(signature_message_received))) => { self.signature_dmq_tx.send(signature_message_received).map_err(|e| { anyhow!("Failed to send signature message to DMQ consumer server: {e}") @@ -235,7 +256,9 @@ mod tests { let addr: Multiaddr = "/ip4/0.0.0.0/tcp/0".parse().unwrap(); let relay = AggregatorRelay::start( &addr, - &Path::new("test"), + #[cfg(feature = "future_dmq")] + Path::new("test"), + #[cfg(feature = "future_dmq")] &CardanoNetwork::TestNet(123), &server.url(""), &TestLogger::stdout(), diff --git a/mithril-relay/src/relay/passive.rs b/mithril-relay/src/relay/passive.rs index a3ffbd8d8f2..e58c5d1acfc 100644 --- a/mithril-relay/src/relay/passive.rs +++ b/mithril-relay/src/relay/passive.rs @@ -38,6 +38,7 @@ impl PassiveRelay { Ok(Some(BroadcastMessage::RegisterSignatureHttp(signature_message_received))) => { info!(self.logger, "Received HTTP signature message from P2P network"; "signature_message" => #?signature_message_received); } + #[cfg(feature = "future_dmq")] Ok(Some(BroadcastMessage::RegisterSignatureDmq(signature_message_received))) => { info!(self.logger, "Received DMQ signature message from P2P network"; "signature_message" => #?signature_message_received); } diff --git a/mithril-relay/src/relay/signer.rs b/mithril-relay/src/relay/signer.rs index 8d98dd567e9..532de81fece 100644 --- a/mithril-relay/src/relay/signer.rs +++ b/mithril-relay/src/relay/signer.rs @@ -1,21 +1,27 @@ -use std::{net::SocketAddr, path::Path, sync::Arc, time::Duration}; +#[cfg(feature = "future_dmq")] +use std::path::Path; +use std::{net::SocketAddr, sync::Arc, time::Duration}; use clap::ValueEnum; use libp2p::Multiaddr; -use mithril_dmq::{DmqMessage, DmqPublisherServer, DmqPublisherServerPallas}; -use slog::{Logger, debug, error, info}; +#[cfg(feature = "future_dmq")] +use slog::error; +use slog::{Logger, debug, info}; use strum::Display; -use tokio::sync::{ - mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}, - watch::{self, Receiver}, -}; +use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}; +#[cfg(feature = "future_dmq")] +use tokio::sync::watch::{self, Receiver}; use warp::Filter; +#[cfg(feature = "future_dmq")] +use mithril_common::CardanoNetwork; use mithril_common::{ - CardanoNetwork, StdResult, + StdResult, logging::LoggerExtensions, messages::{RegisterSignatureMessageHttp, RegisterSignerMessage}, }; +#[cfg(feature = "future_dmq")] +use mithril_dmq::{DmqMessage, DmqPublisherServer, DmqPublisherServerPallas}; use mithril_test_http_server::{TestHttpServer, test_http_server_with_socket_address}; use crate::{ @@ -55,9 +61,11 @@ pub struct SignerRelay { http_server: TestHttpServer, peer: Peer, signature_http_rx: UnboundedReceiver, + #[cfg(feature = "future_dmq")] signature_dmq_rx: UnboundedReceiver, signer_http_rx: UnboundedReceiver, signer_repeater: Arc>, + #[cfg(feature = "future_dmq")] logger: Logger, } @@ -66,8 +74,8 @@ impl SignerRelay { pub async fn start( address: &Multiaddr, server_port: &u16, - dmq_node_socket_path: &Path, - cardano_network: &CardanoNetwork, + #[cfg(feature = "future_dmq")] dmq_node_socket_path: &Path, + #[cfg(feature = "future_dmq")] cardano_network: &CardanoNetwork, signer_registration_mode: &SignerRelayMode, signature_registration_mode: &SignerRelayMode, aggregator_endpoint: &str, @@ -97,28 +105,42 @@ impl SignerRelay { .await; info!(relay_logger, "Listening on"; "address" => ?http_server.address()); - let (_stop_tx, stop_rx) = watch::channel(()); - let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); - let _dmq_publisher_server = Self::start_dmq_publisher_server( - dmq_node_socket_path, - cardano_network, - signature_dmq_tx, - stop_rx, - relay_logger.clone(), - ) - .await?; - + #[cfg(feature = "future_dmq")] + { + let (_stop_tx, stop_rx) = watch::channel(()); + let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); + #[cfg(unix)] + let _dmq_publisher_server = Self::start_dmq_publisher_server( + dmq_node_socket_path, + cardano_network, + signature_dmq_tx, + stop_rx, + relay_logger.clone(), + ) + .await?; + + Ok(Self { + http_server, + peer, + signature_http_rx: signature_rx, + signature_dmq_rx, + signer_http_rx: signer_rx, + signer_repeater, + logger: relay_logger, + }) + } + #[cfg(not(feature = "future_dmq"))] Ok(Self { http_server, peer, signature_http_rx: signature_rx, - signature_dmq_rx, signer_http_rx: signer_rx, signer_repeater, logger: relay_logger, }) } + #[cfg(feature = "future_dmq")] async fn start_dmq_publisher_server( socket: &Path, cardano_network: &CardanoNetwork, @@ -195,47 +217,86 @@ impl SignerRelay { ) } + fn process_register_signature_http_message( + &mut self, + message: Option, + ) -> StdResult<()> { + match message { + Some(signature_message) => { + info!(self.logger, "Publish HTTP signature to p2p network"; "message" => #?signature_message); + self.peer.publish_signature_http(&signature_message)?; + + Ok(()) + } + None => { + debug!(self.logger, "No HTTP signature message available"); + + Ok(()) + } + } + } + + fn process_register_signer_http_message( + &mut self, + message: Option, + ) -> StdResult<()> { + match message { + Some(signer_message) => { + info!(self.logger, "Publish HTTP signer-registration to p2p network"; "message" => #?signer_message); + self.peer.publish_signer_registration(&signer_message)?; + + Ok(()) + } + None => { + debug!(self.logger, "No HTTP signer message available"); + + Ok(()) + } + } + } + + #[cfg(feature = "future_dmq")] + fn process_register_signature_dmq_message( + &mut self, + message: Option, + ) -> StdResult<()> { + match message { + Some(signature_message) => { + info!(self.logger, "Publish DMQ signature to p2p network"; "message" => #?signature_message); + self.peer.publish_signature_dmq(&signature_message)?; + + Ok(()) + } + None => { + //debug!(self.logger, "No DMQ signature message available"); + Ok(()) + } + } + } + /// Tick the signer relay pub async fn tick(&mut self) -> StdResult<()> { + #[cfg(feature = "future_dmq")] tokio::select! { message = self.signature_http_rx.recv() => { - match message { - Some(signature_message) => { - info!(self.logger, "Publish HTTP signature to p2p network"; "message" => #?signature_message); - self.peer.publish_signature_http(&signature_message)?; - Ok(()) - } - None => { - debug!(self.logger, "No HTTP signature message available"); - Ok(()) - } - } + self.process_register_signature_http_message(message) + }, + message = self.signer_http_rx.recv() => { + self.process_register_signer_http_message(message) }, message = self.signature_dmq_rx.recv() => { - match message { - Some(signature_message) => { - info!(self.logger, "Publish DMQ signature to p2p network"; "message" => #?signature_message); - self.peer.publish_signature_dmq(&signature_message)?; - Ok(()) - } - None => { - //debug!(self.logger, "No DMQ signature message available"); - Ok(()) - } - } + self.process_register_signature_dmq_message(message) + }, + _ = self.signer_repeater.repeat_message() => {Ok(())}, + _event = self.peer.tick_swarm() => {Ok(())} + } + #[cfg(not(feature = "future_dmq"))] + tokio::select! { + message = self.signature_http_rx.recv() => { + self.process_register_signature_http_message(message) }, message = self.signer_http_rx.recv() => { - match message { - Some(signer_message) => { - info!(self.logger, "Publish HTTP signer-registration to p2p network"; "message" => #?signer_message); - self.peer.publish_signer_registration(&signer_message)?; - Ok(()) - } - None => { - debug!(self.logger, "No HTTP signer message available"); - Ok(()) - } - } + self.process_register_signer_http_message(message) }, _ = self.signer_repeater.repeat_message() => {Ok(())}, _event = self.peer.tick_swarm() => {Ok(())} diff --git a/mithril-relay/tests/register_signer_signature.rs b/mithril-relay/tests/register_signer_signature.rs index b10793472a6..bf16d32a9a6 100644 --- a/mithril-relay/tests/register_signer_signature.rs +++ b/mithril-relay/tests/register_signer_signature.rs @@ -1,15 +1,16 @@ -use std::{path::PathBuf, sync::Arc, time::Duration}; +#[cfg(feature = "future_dmq")] +use std::path::PathBuf; +use std::{sync::Arc, time::Duration}; use libp2p::{Multiaddr, gossipsub}; use reqwest::StatusCode; use slog::{Drain, Level, Logger}; use slog_scope::{error, info}; +#[cfg(feature = "future_dmq")] +use mithril_common::CardanoNetwork; +use mithril_common::messages::{RegisterSignatureMessageHttp, RegisterSignerMessage}; use mithril_common::test_utils::double::Dummy; -use mithril_common::{ - CardanoNetwork, - messages::{RegisterSignatureMessageHttp, RegisterSignerMessage}, -}; use mithril_relay::{ PassiveRelay, SignerRelay, SignerRelayMode, p2p::{BroadcastMessage, PeerBehaviourEvent, PeerEvent}, @@ -40,7 +41,9 @@ async fn should_receive_registrations_from_signers_when_subscribed_to_pubsub() { let total_peers = 1 + total_p2p_client; let addr: Multiaddr = "/ip4/0.0.0.0/tcp/0".parse().unwrap(); let server_port = 0; + #[cfg(feature = "future_dmq")] let dmq_node_socket_path = PathBuf::new(); + #[cfg(feature = "future_dmq")] let cardano_network = CardanoNetwork::TestNet(123); let signer_registration_mode = SignerRelayMode::P2P; let signature_registration_mode = SignerRelayMode::P2P; @@ -49,7 +52,9 @@ async fn should_receive_registrations_from_signers_when_subscribed_to_pubsub() { let mut signer_relay = SignerRelay::start( &addr, &server_port, + #[cfg(feature = "future_dmq")] &dmq_node_socket_path, + #[cfg(feature = "future_dmq")] &cardano_network, &signer_registration_mode, &signature_registration_mode, From c5ae78142e1288a8bda3af419e545c2fc33d218e Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Wed, 27 Aug 2025 17:59:28 +0200 Subject: [PATCH 24/49] fix(dmq): better support for stopping consumer server --- mithril-relay/src/relay/aggregator.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/mithril-relay/src/relay/aggregator.rs b/mithril-relay/src/relay/aggregator.rs index 6d834dc6971..ccc5c0edcae 100644 --- a/mithril-relay/src/relay/aggregator.rs +++ b/mithril-relay/src/relay/aggregator.rs @@ -10,7 +10,7 @@ use slog::{Logger, error, info}; #[cfg(feature = "future_dmq")] use tokio::sync::{ mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}, - watch::{self, Receiver}, + watch::{self, Receiver, Sender}, }; #[cfg(feature = "future_dmq")] @@ -30,6 +30,7 @@ pub struct AggregatorRelay { #[cfg(feature = "future_dmq")] signature_dmq_tx: UnboundedSender, #[cfg(feature = "future_dmq")] + stop_tx: Sender<()>, logger: Logger, } @@ -46,7 +47,7 @@ impl AggregatorRelay { let logger = logger.new_with_component_name::(); #[cfg(feature = "future_dmq")] { - let (_stop_tx, stop_rx) = watch::channel(()); + let (stop_tx, stop_rx) = watch::channel(()); let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); #[cfg(unix)] let _dmq_consumer_server = Self::start_dmq_consumer_server( @@ -62,6 +63,7 @@ impl AggregatorRelay { aggregator_endpoint: aggregator_endpoint.to_owned(), peer, signature_dmq_tx, + stop_tx, logger, }) } @@ -73,6 +75,16 @@ impl AggregatorRelay { }) } + /// Stop the aggregator relay + pub async fn stop(&self) -> StdResult<()> { + #[cfg(feature = "future_dmq")] + self.stop_tx + .send(()) + .map_err(|e| anyhow!("Failed to send stop signal to DMQ consumer server: {e}"))?; + + Ok(()) + } + #[cfg(feature = "future_dmq")] async fn start_dmq_consumer_server( socket: &Path, From 79bc2bd9ffd15dc17967d116c8f85d371a4bef75 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Wed, 27 Aug 2025 17:59:39 +0200 Subject: [PATCH 25/49] fix(dmq): better support for stopping publisher server --- mithril-relay/src/relay/signer.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/mithril-relay/src/relay/signer.rs b/mithril-relay/src/relay/signer.rs index 532de81fece..32e80150744 100644 --- a/mithril-relay/src/relay/signer.rs +++ b/mithril-relay/src/relay/signer.rs @@ -2,6 +2,7 @@ use std::path::Path; use std::{net::SocketAddr, sync::Arc, time::Duration}; +use anyhow::anyhow; use clap::ValueEnum; use libp2p::Multiaddr; #[cfg(feature = "future_dmq")] @@ -10,7 +11,7 @@ use slog::{Logger, debug, info}; use strum::Display; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}; #[cfg(feature = "future_dmq")] -use tokio::sync::watch::{self, Receiver}; +use tokio::sync::watch::{self, Receiver, Sender}; use warp::Filter; #[cfg(feature = "future_dmq")] @@ -66,6 +67,7 @@ pub struct SignerRelay { signer_http_rx: UnboundedReceiver, signer_repeater: Arc>, #[cfg(feature = "future_dmq")] + stop_tx: Sender<()>, logger: Logger, } @@ -107,7 +109,7 @@ impl SignerRelay { #[cfg(feature = "future_dmq")] { - let (_stop_tx, stop_rx) = watch::channel(()); + let (stop_tx, stop_rx) = watch::channel(()); let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); #[cfg(unix)] let _dmq_publisher_server = Self::start_dmq_publisher_server( @@ -126,6 +128,7 @@ impl SignerRelay { signature_dmq_rx, signer_http_rx: signer_rx, signer_repeater, + stop_tx, logger: relay_logger, }) } @@ -140,6 +143,16 @@ impl SignerRelay { }) } + /// Stop the signer relay + pub async fn stop(&self) -> StdResult<()> { + #[cfg(feature = "future_dmq")] + self.stop_tx + .send(()) + .map_err(|e| anyhow!("Failed to send stop signal to DMQ publisher server: {e}"))?; + + Ok(()) + } + #[cfg(feature = "future_dmq")] async fn start_dmq_publisher_server( socket: &Path, From f50beae09b4bc063ccf4bc4f058e40fd9362be9d Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 22 Jul 2025 11:32:08 +0200 Subject: [PATCH 26/49] fix(dmq): missing wait for Done message in publisher server state machine --- .../src/publisher/server/pallas.rs | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/internal/mithril-dmq/src/publisher/server/pallas.rs b/internal/mithril-dmq/src/publisher/server/pallas.rs index 2e8232225b1..6b1984a5b03 100644 --- a/internal/mithril-dmq/src/publisher/server/pallas.rs +++ b/internal/mithril-dmq/src/publisher/server/pallas.rs @@ -175,6 +175,30 @@ impl DmqPublisherServer for DmqPublisherServerPallas { } } + let request = server.msg_submission().recv_next_request().await.map_err(|err| { + anyhow!( + "Failed to receive next request from DMQ publisher client: {}", + err + ) + })?; + match request { + Request::Done => { + debug!( + self.logger, + "Received Done request from DMQ publisher client" + ); + } + _ => { + error!( + self.logger, + "Expected a Done request, but received: {request:?}" + ); + return Err(anyhow!( + "Expected a Done request, but received: {request:?}" + )); + } + } + Ok(()) } @@ -201,11 +225,11 @@ impl DmqPublisherServer for DmqPublisherServerPallas { } Err(err) => { error!(self.logger, "Failed to process message"; "error" => ?err); - if let Err(drop_err) = self.drop_server().await { - error!(self.logger, "Failed to drop DMQ publisher server"; "error" => ?drop_err); - } } } + if let Err(drop_err) = self.drop_server().await { + error!(self.logger, "Failed to drop DMQ publisher server"; "error" => ?drop_err); + } } } } From a6d547bc69406eb4adbcf02d775916ba110486d7 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Wed, 16 Jul 2025 12:35:56 +0200 Subject: [PATCH 27/49] fix(relay): fix clippy warning --- mithril-relay/src/commands/signer.rs | 18 +++--- mithril-relay/src/lib.rs | 10 ++- mithril-relay/src/relay/mod.rs | 3 +- mithril-relay/src/relay/signer.rs | 63 +++++++++++-------- .../tests/register_signer_signature.rs | 24 +++---- 5 files changed, 63 insertions(+), 55 deletions(-) diff --git a/mithril-relay/src/commands/signer.rs b/mithril-relay/src/commands/signer.rs index dd01c4fe8de..cafb6857684 100644 --- a/mithril-relay/src/commands/signer.rs +++ b/mithril-relay/src/commands/signer.rs @@ -10,7 +10,7 @@ use slog::error; use mithril_common::CardanoNetwork; use mithril_common::StdResult; -use crate::{SignerRelay, SignerRelayMode}; +use crate::{SignerRelay, SignerRelayConfiguration, SignerRelayMode}; use super::CommandContext; @@ -81,19 +81,19 @@ impl SignerCommand { let cardano_network = CardanoNetwork::from_code(self.network.to_owned(), self.network_magic)?; - let mut relay = SignerRelay::start( - &addr, - &server_port, + let mut relay = SignerRelay::start(SignerRelayConfiguration { + address: &addr, + server_port: &server_port, #[cfg(feature = "future_dmq")] - &self.dmq_node_socket_path, + dmq_node_socket_path: &self.dmq_node_socket_path, #[cfg(feature = "future_dmq")] - &cardano_network, + cardano_network: &cardano_network, signer_registration_mode, signature_registration_mode, - &aggregator_endpoint, - &signer_repeater_delay, + aggregator_endpoint: &aggregator_endpoint, + signer_repeater_delay: &signer_repeater_delay, logger, - ) + }) .await?; if let Some(dial_to_address) = dial_to { relay.dial_peer(dial_to_address.clone())?; diff --git a/mithril-relay/src/lib.rs b/mithril-relay/src/lib.rs index 2ec4e508324..74ae44e80fd 100644 --- a/mithril-relay/src/lib.rs +++ b/mithril-relay/src/lib.rs @@ -7,12 +7,10 @@ pub mod p2p; mod relay; mod repeater; -pub use commands::Args; -pub use commands::RelayCommands; -pub use relay::AggregatorRelay; -pub use relay::PassiveRelay; -pub use relay::SignerRelay; -pub use relay::SignerRelayMode; +pub use commands::{Args, RelayCommands}; +pub use relay::{ + AggregatorRelay, PassiveRelay, SignerRelay, SignerRelayConfiguration, SignerRelayMode, +}; /// The P2P topic names used by Mithril pub mod mithril_p2p_topic { diff --git a/mithril-relay/src/relay/mod.rs b/mithril-relay/src/relay/mod.rs index 3dffee0edb5..fffa415285d 100644 --- a/mithril-relay/src/relay/mod.rs +++ b/mithril-relay/src/relay/mod.rs @@ -4,5 +4,4 @@ mod signer; pub use aggregator::AggregatorRelay; pub use passive::PassiveRelay; -pub use signer::SignerRelay; -pub use signer::SignerRelayMode; +pub use signer::{SignerRelay, SignerRelayConfiguration, SignerRelayMode}; diff --git a/mithril-relay/src/relay/signer.rs b/mithril-relay/src/relay/signer.rs index 32e80150744..f3679a39155 100644 --- a/mithril-relay/src/relay/signer.rs +++ b/mithril-relay/src/relay/signer.rs @@ -57,6 +57,30 @@ struct HTTPServerConfiguration<'a> { logger: &'a Logger, } +/// Configuration for a Mithril Signer Relay +pub struct SignerRelayConfiguration<'a> { + /// Address on which the HTTP server will listen + pub address: &'a Multiaddr, + /// Port on which the HTTP server will listen + pub server_port: &'a u16, + /// Path to the DMQ node socket file + #[cfg(feature = "future_dmq")] + pub dmq_node_socket_path: &'a Path, + /// Cardano network + #[cfg(feature = "future_dmq")] + pub cardano_network: &'a CardanoNetwork, + /// Signer registration mode + pub signer_registration_mode: &'a SignerRelayMode, + /// Signature registration mode + pub signature_registration_mode: &'a SignerRelayMode, + /// Aggregator endpoint URL + pub aggregator_endpoint: &'a str, + /// Delay for repeating a signer registration message + pub signer_repeater_delay: &'a Duration, + /// Logger for the signer relay + pub logger: &'a Logger, +} + /// A relay for a Mithril signer pub struct SignerRelay { http_server: TestHttpServer, @@ -73,32 +97,22 @@ pub struct SignerRelay { impl SignerRelay { /// Start a relay for a Mithril signer - pub async fn start( - address: &Multiaddr, - server_port: &u16, - #[cfg(feature = "future_dmq")] dmq_node_socket_path: &Path, - #[cfg(feature = "future_dmq")] cardano_network: &CardanoNetwork, - signer_registration_mode: &SignerRelayMode, - signature_registration_mode: &SignerRelayMode, - aggregator_endpoint: &str, - signer_repeater_delay: &Duration, - logger: &Logger, - ) -> StdResult { - let relay_logger = logger.new_with_component_name::(); - debug!(relay_logger, "Starting..."; "signer_registration_mode" => ?signer_registration_mode, "signature_registration_mode" => ?signature_registration_mode); + pub async fn start(config: SignerRelayConfiguration<'_>) -> StdResult { + let relay_logger = config.logger.new_with_component_name::(); + debug!(relay_logger, "Starting..."; "signer_registration_mode" => ?config.signer_registration_mode, "signature_registration_mode" => ?config.signature_registration_mode); let (signature_tx, signature_rx) = unbounded_channel::(); let (signer_tx, signer_rx) = unbounded_channel::(); let signer_repeater = Arc::new(MessageRepeater::new( signer_tx.clone(), - signer_repeater_delay.to_owned(), - logger, + config.signer_repeater_delay.to_owned(), + config.logger, )); - let peer = Peer::new(address).start().await?; + let peer = Peer::new(config.address).start().await?; let http_server = Self::start_http_server(&HTTPServerConfiguration { - server_port, - signer_registration_mode: signer_registration_mode.to_owned(), - signature_registration_mode: signature_registration_mode.to_owned(), - aggregator_endpoint, + server_port: config.server_port, + signer_registration_mode: config.signer_registration_mode.to_owned(), + signature_registration_mode: config.signature_registration_mode.to_owned(), + aggregator_endpoint: config.aggregator_endpoint, signer_tx: signer_tx.clone(), signature_tx: signature_tx.clone(), signer_repeater: signer_repeater.clone(), @@ -113,8 +127,8 @@ impl SignerRelay { let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); #[cfg(unix)] let _dmq_publisher_server = Self::start_dmq_publisher_server( - dmq_node_socket_path, - cardano_network, + config.dmq_node_socket_path, + config.cardano_network, signature_dmq_tx, stop_rx, relay_logger.clone(), @@ -280,10 +294,7 @@ impl SignerRelay { Ok(()) } - None => { - //debug!(self.logger, "No DMQ signature message available"); - Ok(()) - } + None => Ok(()), } } diff --git a/mithril-relay/tests/register_signer_signature.rs b/mithril-relay/tests/register_signer_signature.rs index bf16d32a9a6..3d9c52bdb69 100644 --- a/mithril-relay/tests/register_signer_signature.rs +++ b/mithril-relay/tests/register_signer_signature.rs @@ -12,7 +12,7 @@ use mithril_common::CardanoNetwork; use mithril_common::messages::{RegisterSignatureMessageHttp, RegisterSignerMessage}; use mithril_common::test_utils::double::Dummy; use mithril_relay::{ - PassiveRelay, SignerRelay, SignerRelayMode, + PassiveRelay, SignerRelay, SignerRelayConfiguration, SignerRelayMode, p2p::{BroadcastMessage, PeerBehaviourEvent, PeerEvent}, }; @@ -49,19 +49,19 @@ async fn should_receive_registrations_from_signers_when_subscribed_to_pubsub() { let signature_registration_mode = SignerRelayMode::P2P; let aggregator_endpoint = "http://0.0.0.0:1234".to_string(); let signer_repeater_delay = Duration::from_secs(100); - let mut signer_relay = SignerRelay::start( - &addr, - &server_port, + let mut signer_relay = SignerRelay::start(SignerRelayConfiguration { + address: &addr, + server_port: &server_port, #[cfg(feature = "future_dmq")] - &dmq_node_socket_path, + dmq_node_socket_path: &dmq_node_socket_path, #[cfg(feature = "future_dmq")] - &cardano_network, - &signer_registration_mode, - &signature_registration_mode, - &aggregator_endpoint, - &signer_repeater_delay, - &logger, - ) + cardano_network: &cardano_network, + signer_registration_mode: &signer_registration_mode, + signature_registration_mode: &signature_registration_mode, + aggregator_endpoint: &aggregator_endpoint, + signer_repeater_delay: &signer_repeater_delay, + logger: &logger, + }) .await .expect("Relay start failed"); let relay_address = signer_relay.address(); From 87954af3b469dcfbcfa957cc2280b498f013e061 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Wed, 16 Jul 2025 12:07:26 +0200 Subject: [PATCH 28/49] feat(infra): support for DMQ protocol in aggregator infrastructure --- ...ker-compose-aggregator-p2p-dmq-override.yaml | 13 +++++++++++++ mithril-infra/mithril.aggregator.tf | 5 +++++ mithril-infra/variables.tf | 17 +++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 mithril-infra/assets/docker/docker-compose-aggregator-p2p-dmq-override.yaml diff --git a/mithril-infra/assets/docker/docker-compose-aggregator-p2p-dmq-override.yaml b/mithril-infra/assets/docker/docker-compose-aggregator-p2p-dmq-override.yaml new file mode 100644 index 00000000000..36510a687b0 --- /dev/null +++ b/mithril-infra/assets/docker/docker-compose-aggregator-p2p-dmq-override.yaml @@ -0,0 +1,13 @@ +services: + mithril-aggregator: + environment: + - DMQ_NODE_SOCKET_PATH=/ipc/dmq.socket + - NETWORK=${NETWORK} + - NETWORK_MAGIC=${NETWORK_MAGIC} + mithril-aggregator-relay: + volumes: + - ../data/${NETWORK}/mithril-aggregator/cardano/ipc:/ipc + environment: + - DMQ_NODE_SOCKET_PATH=/ipc/dmq.socket + - NETWORK=${NETWORK} + - NETWORK_MAGIC=${NETWORK_MAGIC} diff --git a/mithril-infra/mithril.aggregator.tf b/mithril-infra/mithril.aggregator.tf index 0f3885ec2a5..48d24262975 100644 --- a/mithril-infra/mithril.aggregator.tf +++ b/mithril-infra/mithril.aggregator.tf @@ -76,6 +76,7 @@ EOT inline = [ "set -e", "export NETWORK=${var.cardano_network}", + "export NETWORK_MAGIC=${var.cardano_network_magic_map[var.cardano_network]}", "export CARDANO_IMAGE_ID=${var.cardano_image_id}", "export CARDANO_IMAGE_REGISTRY=${var.cardano_image_registry}", "export MITHRIL_IMAGE_ID=${var.mithril_image_id}", @@ -157,6 +158,10 @@ fi if [ "${local.mithril_aggregator_is_follower}" = "true" ]; then DOCKER_COMPOSE_FILES="$DOCKER_COMPOSE_FILES -f $DOCKER_DIRECTORY/docker-compose-aggregator-follower-override.yaml" fi +# Support for DMQ protocol +if [ "${var.mithril_p2p_use_dmq_protocol}" = "true" ]; then + DOCKER_COMPOSE_FILES="$DOCKER_COMPOSE_FILES -f $DOCKER_DIRECTORY/docker-compose-aggregator-p2p-dmq-override.yaml" +fi EOT , "docker compose $DOCKER_COMPOSE_FILES --profile all up -d", diff --git a/mithril-infra/variables.tf b/mithril-infra/variables.tf index 45058f2f565..36980c2a6e7 100644 --- a/mithril-infra/variables.tf +++ b/mithril-infra/variables.tf @@ -14,6 +14,16 @@ variable "cardano_network" { description = "The Cardano network name to attach: preview, preprod or mainnet" } +variable "cardano_network_magic_map" { + type = map(number) + description = "The Cardano network magic number mapping from Cardano network name" + default = { + "mainnet" = 764824073, + "preprod" = 1, + "preview" = 2, + } +} + locals { environment_name_short = format("%s%s", "${var.environment_prefix}-${var.cardano_network}", var.environment_suffix != "" ? "-${var.environment_suffix}" : "") environment_name = "mithril-${local.environment_name_short}" @@ -184,6 +194,12 @@ variable "mithril_use_p2p_network" { default = false } +variable "mithril_p2p_use_dmq_protocol" { + type = bool + description = "Use the Decentralized Message Queue protocol (DMQ) (experimental, for test only)" + default = false +} + variable "mithril_p2p_network_bootstrap_peer" { type = string description = "The dial to address of a bootstrap peer of the P2P network layer. Useful when setting-up a follower aggregator and signers in a different VM. (experimental, for test only)" @@ -457,6 +473,7 @@ variable "mithril_signers" { type = string pool_id = string })) + description = "The Mithril signers configuration to deploy" default = { "1" = { type = "unverified-cardano-passive", From c7f69e385707ace4ef4802972b59c96e89847d93 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Wed, 16 Jul 2025 12:07:36 +0200 Subject: [PATCH 29/49] feat(infra): support for DMQ protocol in signer infrastructure --- .../docker-compose-signer-p2p-dmq-override.yaml | 12 ++++++++++++ mithril-infra/mithril.signer.tf | 5 +++++ 2 files changed, 17 insertions(+) create mode 100644 mithril-infra/assets/docker/docker-compose-signer-p2p-dmq-override.yaml diff --git a/mithril-infra/assets/docker/docker-compose-signer-p2p-dmq-override.yaml b/mithril-infra/assets/docker/docker-compose-signer-p2p-dmq-override.yaml new file mode 100644 index 00000000000..b3cf77d22d2 --- /dev/null +++ b/mithril-infra/assets/docker/docker-compose-signer-p2p-dmq-override.yaml @@ -0,0 +1,12 @@ +services: + mithril-signer: + environment: + - DMQ_NODE_SOCKET_PATH=/ipc/dmq.socket + - NETWORK_MAGIC=${NETWORK_MAGIC} + mithril-signer-relay: + volumes: + - ../data/${NETWORK}/mithril-signer-${SIGNER_ID}/cardano/ipc:/ipc + environment: + - DMQ_NODE_SOCKET_PATH=/ipc/dmq.socket + - NETWORK=${NETWORK} + - NETWORK_MAGIC=${NETWORK_MAGIC} diff --git a/mithril-infra/mithril.signer.tf b/mithril-infra/mithril.signer.tf index 8a23cb8dbde..0905ff59840 100644 --- a/mithril-infra/mithril.signer.tf +++ b/mithril-infra/mithril.signer.tf @@ -103,6 +103,7 @@ EOT "export SIGNER_ID=${each.key}", "export PARTY_ID=${each.value.pool_id}", "export NETWORK=${var.cardano_network}", + "export NETWORK_MAGIC=${var.cardano_network_magic_map[var.cardano_network]}", "export CARDANO_IMAGE_ID=${var.cardano_image_id}", "export CARDANO_IMAGE_REGISTRY=${var.cardano_image_registry}", "export MITHRIL_IMAGE_ID=${var.mithril_image_id}", @@ -181,6 +182,10 @@ if [ "${var.mithril_use_p2p_network}" = "true" ]; then DOCKER_COMPOSE_FILES="$DOCKER_COMPOSE_FILES -f $DOCKER_DIRECTORY/docker-compose-signer-p2p-bootstrap-override.yaml" fi fi +# Support for DMQ protocol +if [ "${var.mithril_p2p_use_dmq_protocol}" = "true" ]; then + DOCKER_COMPOSE_FILES="$DOCKER_COMPOSE_FILES -f $DOCKER_DIRECTORY/docker-compose-signer-p2p-dmq-override.yaml" +fi EOT , "docker compose -p $SIGNER_ID $DOCKER_COMPOSE_FILES --profile all up -d", From 58b2df6ea6b0d39d898581042a62c4ce1de265b6 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Wed, 16 Jul 2025 12:11:37 +0200 Subject: [PATCH 30/49] feat(ci): support for using DMQ in infrastucture deployment --- .../actions/deploy-terraform-infrastructure/action.yml | 5 +++++ .github/workflows/test-deploy-network.yml | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/.github/workflows/actions/deploy-terraform-infrastructure/action.yml b/.github/workflows/actions/deploy-terraform-infrastructure/action.yml index d6f15666d85..2be5d887891 100644 --- a/.github/workflows/actions/deploy-terraform-infrastructure/action.yml +++ b/.github/workflows/actions/deploy-terraform-infrastructure/action.yml @@ -63,6 +63,10 @@ inputs: description: Mithril use P2P network (experimental, for test only). required: false default: "false" + mithril_p2p_use_dmq_protocol: + description: Mithril P2P network use DMQ protocol (experimental, for test only). + required: false + default: "false" mithril_p2p_network_bootstrap_peer: description: Mithril P2P network bootstrap peer (experimental, for test only). required: false @@ -247,6 +251,7 @@ runs: google_compute_instance_ssh_keys_environment = "${{ inputs.google_compute_instance_ssh_keys_environment }}" google_service_credentials_json_file = "./google-application-credentials.json" mithril_use_p2p_network = "${{ inputs.mithril_use_p2p_network }}" + mithril_p2p_use_dmq_protocol = "${{ inputs.mithril_p2p_use_dmq_protocol }}" mithril_p2p_network_bootstrap_peer = "${{ inputs.mithril_p2p_network_bootstrap_peer }}" mithril_p2p_signer_relay_signer_registration_mode = "${{ inputs.mithril_p2p_signer_relay_signer_registration_mode }}" mithril_p2p_signer_relay_signature_registration_mode = "${{ inputs.mithril_p2p_signer_relay_signature_registration_mode }}" diff --git a/.github/workflows/test-deploy-network.yml b/.github/workflows/test-deploy-network.yml index d028116b4dd..3ad2c130833 100644 --- a/.github/workflows/test-deploy-network.yml +++ b/.github/workflows/test-deploy-network.yml @@ -34,6 +34,7 @@ jobs: environment_prefix: dev cardano_network: preview mithril_use_p2p_network: true + mithril_p2p_use_dmq_protocol: false mithril_p2p_signer_relay_signer_registration_mode: passthrough mithril_p2p_signer_relay_signature_registration_mode: p2p mithril_api_domain: api.mithril.network @@ -72,6 +73,7 @@ jobs: environment_prefix: dev-follower cardano_network: preview mithril_use_p2p_network: true + mithril_p2p_use_dmq_protocol: false mithril_p2p_network_bootstrap_peer: "/dns4/aggregator.dev-preview.api.mithril.network/tcp/6060" mithril_p2p_signer_relay_signer_registration_mode: passthrough mithril_p2p_signer_relay_signature_registration_mode: p2p @@ -103,6 +105,7 @@ jobs: environment_prefix: dev cardano_network: mainnet mithril_use_p2p_network: false + mithril_p2p_use_dmq_protocol: false mithril_api_domain: api.mithril.network mithril_era_reader_adapter_type: bootstrap mithril_protocol_parameters: | @@ -160,6 +163,7 @@ jobs: google_compute_instance_ssh_keys_environment: testing google_application_credentials: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} mithril_use_p2p_network: ${{ matrix.mithril_use_p2p_network }} + mithril_p2p_use_dmq_protocol: ${{ matrix.mithril_p2p_use_dmq_protocol }} mithril_p2p_network_bootstrap_peer: ${{ matrix.mithril_p2p_network_bootstrap_peer }} mithril_api_domain: ${{ matrix.mithril_api_domain }} mithril_image_id: ${{ inputs.mithril_image_id }} From e199ad17f65162411eac4d884384afec2ee723d0 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 8 Aug 2025 15:14:36 +0200 Subject: [PATCH 31/49] fix: rebase from main branch 'mithril_common::test_utils' has been renamed to 'mithril_common::test'. --- mithril-relay/tests/register_signer_signature.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mithril-relay/tests/register_signer_signature.rs b/mithril-relay/tests/register_signer_signature.rs index 3d9c52bdb69..3ed905491cf 100644 --- a/mithril-relay/tests/register_signer_signature.rs +++ b/mithril-relay/tests/register_signer_signature.rs @@ -10,7 +10,7 @@ use slog_scope::{error, info}; #[cfg(feature = "future_dmq")] use mithril_common::CardanoNetwork; use mithril_common::messages::{RegisterSignatureMessageHttp, RegisterSignerMessage}; -use mithril_common::test_utils::double::Dummy; +use mithril_common::test::double::Dummy; use mithril_relay::{ PassiveRelay, SignerRelay, SignerRelayConfiguration, SignerRelayMode, p2p::{BroadcastMessage, PeerBehaviourEvent, PeerEvent}, From 71760f6594fa6542a9fa90bcbeac2bc194ea971d Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Thu, 21 Aug 2025 11:20:51 +0200 Subject: [PATCH 32/49] feat(dmq): add 'MessageQueue' size limit --- .../mithril-dmq/src/consumer/server/pallas.rs | 4 +- .../mithril-dmq/src/consumer/server/queue.rs | 42 ++++++++++++++++--- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/internal/mithril-dmq/src/consumer/server/pallas.rs b/internal/mithril-dmq/src/consumer/server/pallas.rs index 3dcb7142c38..ee8d9e8fb45 100644 --- a/internal/mithril-dmq/src/consumer/server/pallas.rs +++ b/internal/mithril-dmq/src/consumer/server/pallas.rs @@ -40,7 +40,7 @@ impl DmqConsumerServerPallas { network, server: Mutex::new(None), messages_receiver: Mutex::new(None), - messages_buffer: MessageQueue::new(), + messages_buffer: MessageQueue::default(), stop_rx, logger: logger.new_with_component_name::(), } @@ -90,6 +90,8 @@ impl DmqConsumerServerPallas { } /// Drops the current `DmqServer`, if it exists. + // TODO: remove allow dead code + #[allow(dead_code)] async fn drop_server(&self) -> StdResult<()> { debug!( self.logger, diff --git a/internal/mithril-dmq/src/consumer/server/queue.rs b/internal/mithril-dmq/src/consumer/server/queue.rs index b3681b4c092..5ee5c8190f0 100644 --- a/internal/mithril-dmq/src/consumer/server/queue.rs +++ b/internal/mithril-dmq/src/consumer/server/queue.rs @@ -8,14 +8,19 @@ use crate::DmqMessage; pub(crate) struct MessageQueue { messages: Mutex>, new_message_notify: Notify, + max_size: usize, } impl MessageQueue { + /// The default maximum size of the message queue. + const MAX_SIZE_DEFAULT: usize = 10000; + /// Creates a new instance of [BlockingNonBlockingQueue]. - pub fn new() -> Self { + pub fn new(max_size: usize) -> Self { Self { messages: Mutex::new(VecDeque::new()), new_message_notify: Notify::new(), + max_size, } } @@ -24,6 +29,10 @@ impl MessageQueue { let mut message_queue_guard = self.messages.lock().await; (*message_queue_guard).push_back(message); + while message_queue_guard.len() > self.max_size { + message_queue_guard.pop_front(); + } + self.new_message_notify.notify_waiters(); } @@ -65,6 +74,12 @@ impl MessageQueue { } } +impl Default for MessageQueue { + fn default() -> Self { + Self::new(Self::MAX_SIZE_DEFAULT) + } +} + #[cfg(test)] mod tests { use std::{ops::RangeInclusive, time::Duration}; @@ -101,7 +116,7 @@ mod tests { #[tokio::test] async fn enqueue_and_dequeue_non_blocking_no_limit() { - let queue = MessageQueue::new(); + let queue = MessageQueue::default(); let messages = fake_messages(1..=5); for message in messages.clone() { queue.enqueue(message).await; @@ -115,7 +130,7 @@ mod tests { #[tokio::test] async fn enqueue_and_dequeue_non_blocking_with_limit() { - let queue = MessageQueue::new(); + let queue = MessageQueue::default(); let messages = fake_messages(1..=5); for message in messages.clone() { queue.enqueue(message).await; @@ -129,7 +144,7 @@ mod tests { #[tokio::test] async fn enqueue_and_dequeue_blocking_no_limit() { - let queue = MessageQueue::new(); + let queue = MessageQueue::default(); let messages = fake_messages(1..=5); for message in messages.clone() { queue.enqueue(message).await; @@ -143,7 +158,7 @@ mod tests { #[tokio::test] async fn enqueue_and_dequeue_blocking_with_limit() { - let queue = MessageQueue::new(); + let queue = MessageQueue::default(); let messages = fake_messages(1..=5); for message in messages.clone() { queue.enqueue(message).await; @@ -157,7 +172,7 @@ mod tests { #[tokio::test] async fn dequeue_blocking_blocks_when_no_message_available() { - let queue = MessageQueue::new(); + let queue = MessageQueue::default(); let result = tokio::select!( _res = sleep(Duration::from_millis(100)) => {Err(anyhow!("Timeout"))}, @@ -166,4 +181,19 @@ mod tests { result.expect_err("Should have timed out"); } + + #[tokio::test] + async fn enqueue_blocks_over_max_size_drains_oldest_messages() { + let max_queue_size = 3; + let queue = MessageQueue::new(max_queue_size); + let messages = fake_messages(1..=5); + for message in messages.clone() { + queue.enqueue(message).await; + } + let limit = None; + + let dequeued_messages = queue.dequeue_blocking(limit).await; + + assert_eq!(messages[2..=4].to_vec(), dequeued_messages); + } } From 07e1bfa8213f34ff53d406588b0f989ea345d384 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 18 Aug 2025 18:42:00 +0200 Subject: [PATCH 33/49] fix(dmq): run receive/serve messages on separate threads --- .../mithril-dmq/src/consumer/server/pallas.rs | 135 ++++++++++++------ 1 file changed, 95 insertions(+), 40 deletions(-) diff --git a/internal/mithril-dmq/src/consumer/server/pallas.rs b/internal/mithril-dmq/src/consumer/server/pallas.rs index ee8d9e8fb45..76ffef08c47 100644 --- a/internal/mithril-dmq/src/consumer/server/pallas.rs +++ b/internal/mithril-dmq/src/consumer/server/pallas.rs @@ -1,8 +1,12 @@ use std::{fs, path::PathBuf}; use anyhow::{Context, anyhow}; -use pallas_network::{facades::DmqServer, miniprotocols::localmsgnotification::Request}; +use pallas_network::{ + facades::DmqServer, + miniprotocols::localmsgnotification::{Request, State}, +}; use tokio::{ + join, net::UnixListener, select, sync::{Mutex, MutexGuard, mpsc::UnboundedReceiver, watch::Receiver}, @@ -118,6 +122,77 @@ impl DmqConsumerServerPallas { Ok(()) } + + /// Receives incoming messages into the DMQ consumer server. + async fn receive_incoming_messages(&self) -> StdResult<()> { + info!( + self.logger, + "Receive incoming messages into DMQ consumer server..."; + "socket" => ?self.socket, + "network" => ?self.network + ); + + let mut stop_rx = self.stop_rx.clone(); + let mut receiver = self.messages_receiver.lock().await; + match *receiver { + Some(ref mut receiver) => loop { + select! { + _ = stop_rx.changed() => { + warn!(self.logger, "Stopping DMQ consumer server..."); + + return Ok(()); + } + message = receiver.recv() => { + if let Some(message) = message { + debug!(self.logger, "Received a message from the DMQ network"; "message" => ?message); + self.messages_buffer.enqueue(message).await; + } else { + warn!(self.logger, "DMQ message receiver channel closed"); + return Ok(()); + } + + } + } + }, + None => { + return Err(anyhow!("DMQ message receiver is not registered")); + } + } + } + + /// Serves incoming messages from the DMQ consumer server. + async fn serve_incoming_messages(&self) -> StdResult<()> { + info!( + self.logger, + "Serve incoming messages from DMQ consumer server..."; + "socket" => ?self.socket, + "network" => ?self.network + ); + + let mut stop_rx = self.stop_rx.clone(); + loop { + select! { + _ = stop_rx.changed() => { + warn!(self.logger, "Stopping DMQ consumer server..."); + + return Ok(()); + } + res = self.process_message() => { + match res { + Ok(_) => { + debug!(self.logger, "Processed a message successfully"); + } + Err(err) => { + error!(self.logger, "Failed to process message"; "error" => ?err); + /* if let Err(drop_err) = self.drop_server().await { + error!(self.logger, "Failed to drop DMQ consumer server"; "error" => ?drop_err); + } */ + } + } + } + } + } + } } #[async_trait::async_trait] @@ -130,6 +205,12 @@ impl DmqConsumerServer for DmqConsumerServerPallas { let mut server_guard = self.get_server().await?; let server = server_guard.as_mut().ok_or(anyhow!("DMQ server does not exist"))?; + debug!( + self.logger, + "DMQ Server state: {:?}", + server.msg_notification().state() + ); + let request = server .msg_notification() .recv_next_request() @@ -147,8 +228,13 @@ impl DmqConsumerServer for DmqConsumerServerPallas { reply_messages.into_iter().map(|msg| msg.into()).collect::>(); server .msg_notification() - .send_reply_messages_blocking(reply_messages) + .send_reply_messages_blocking(reply_messages.clone()) .await?; + debug!( + self.logger, + "Blocking notification replied to the DMQ notification client: {:?}", + reply_messages + ); } Request::NonBlocking => { debug!( @@ -179,45 +265,14 @@ impl DmqConsumerServer for DmqConsumerServerPallas { "network" => ?self.network ); - let mut stop_rx = self.stop_rx.clone(); - let mut receiver = self.messages_receiver.lock().await; - match *receiver { - Some(ref mut receiver) => loop { - select! { - _ = stop_rx.changed() => { - warn!(self.logger, "Stopping DMQ consumer server..."); - - return Ok(()); - } - message = receiver.recv() => { - if let Some(message) = message { - debug!(self.logger, "Received a message from the DMQ network"; "message" => ?message); - self.messages_buffer.enqueue(message).await; - } else { - warn!(self.logger, "DMQ message receiver channel closed"); - return Ok(()); - } + let (receive_result, serve_result) = join!( + self.receive_incoming_messages(), + self.serve_incoming_messages() + ); + receive_result?; + serve_result?; - } - res = self.process_message() => { - match res { - Ok(_) => { - debug!(self.logger, "Processed a message successfully"); - } - Err(err) => { - error!(self.logger, "Failed to process message"; "error" => ?err); - if let Err(drop_err) = self.drop_server().await { - error!(self.logger, "Failed to drop DMQ consumer server"; "error" => ?drop_err); - } - } - } - } - } - }, - None => { - return Err(anyhow!("DMQ message receiver is not registered")); - } - } + Ok(()) } } From 0e1f91101ac521e581c5c12866861dc0e3fb1057 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Wed, 20 Aug 2025 12:04:59 +0200 Subject: [PATCH 34/49] test(dmq): add integration test for publisher client/server --- .../tests/publisher_client_server.rs | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 internal/mithril-dmq/tests/publisher_client_server.rs diff --git a/internal/mithril-dmq/tests/publisher_client_server.rs b/internal/mithril-dmq/tests/publisher_client_server.rs new file mode 100644 index 00000000000..ce223ae052b --- /dev/null +++ b/internal/mithril-dmq/tests/publisher_client_server.rs @@ -0,0 +1,123 @@ +#![cfg(unix)] +use std::sync::Arc; + +use tokio::sync::{mpsc::unbounded_channel, watch}; + +use mithril_cardano_node_chain::test::double::FakeChainObserver; +use mithril_common::{ + CardanoNetwork, + crypto_helper::TryToBytes, + current_function, + test::{TempDir, crypto_helper::KesSignerFake}, +}; +use mithril_dmq::{ + DmqMessage, DmqMessageBuilder, DmqPublisherClient, DmqPublisherClientPallas, + DmqPublisherServer, DmqPublisherServerPallas, test::payload::DmqMessageTestPayload, +}; + +async fn create_fake_msg(bytes: &[u8]) -> DmqMessage { + let dmq_builder = DmqMessageBuilder::new( + { + let (kes_signature, operational_certificate) = KesSignerFake::dummy_signature(); + let kes_signer = KesSignerFake::new(vec![ + Ok((kes_signature, operational_certificate.clone())), + Ok((kes_signature, operational_certificate.clone())), // TODO: remove this line once the hack of KES signature is removed in DMQ message builder + ]); + + Arc::new(kes_signer) + }, + Arc::new(FakeChainObserver::default()), + ) + .set_ttl(100); + let message = DmqMessageTestPayload::new(bytes); + dmq_builder.build(&message.to_bytes_vec().unwrap()).await.unwrap() +} + +#[tokio::test] +async fn dmq_publisher_client_server() { + let cardano_network = CardanoNetwork::TestNet(0); + let socket_path = + TempDir::create_with_short_path("dmq_publisher_client_server", current_function!()) + .join("node.socket"); + let (stop_tx, stop_rx) = watch::channel(()); + + let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); + let server = tokio::spawn({ + let socket_path = socket_path.clone(); + async move { + let dmq_publisher_server = Arc::new(DmqPublisherServerPallas::new( + socket_path.to_path_buf(), + cardano_network, + stop_rx, + slog_scope::logger(), + )); + dmq_publisher_server + .register_transmitter(signature_dmq_tx) + .await + .unwrap(); + dmq_publisher_server.run().await.unwrap(); + } + }); + + let client = tokio::spawn({ + let socket_path = socket_path.clone(); + async move { + let dmq_builder = DmqMessageBuilder::new( + { + let (kes_signature, operational_certificate) = KesSignerFake::dummy_signature(); + let kes_signer = KesSignerFake::new(vec![ + Ok((kes_signature, operational_certificate.clone())), + Ok((kes_signature, operational_certificate.clone())), + Ok((kes_signature, operational_certificate.clone())), // TODO: remove this line once the hack of KES signature is removed in DMQ message builder + Ok((kes_signature, operational_certificate.clone())), // TODO: remove this line once the hack of KES signature is removed in DMQ message builder + ]); + + Arc::new(kes_signer) + }, + Arc::new(FakeChainObserver::default()), + ) + .set_ttl(100); + let publisher_client = DmqPublisherClientPallas::::new( + socket_path, + cardano_network, + dmq_builder, + slog_scope::logger(), + ); + + publisher_client + .publish_message(DmqMessageTestPayload::new(b"msg_1")) + .await + .unwrap(); + // Sleep to avoid refused connection from the server + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + publisher_client + .publish_message(DmqMessageTestPayload::new(b"msg_2")) + .await + .unwrap(); + + stop_tx + .send(()) + .expect("Failed to send stop signal to DMQ publisher server"); + } + }); + + let recorder = tokio::spawn(async move { + let messages: Vec = { + let mut messages = vec![]; + let mut signature_dmq_rx = signature_dmq_rx; + while let Some(message) = signature_dmq_rx.recv().await { + messages.push(message); + } + + messages + }; + + messages + }); + + let (_, _, messages) = tokio::try_join!(server, client, recorder).unwrap(); + assert_eq!( + vec![create_fake_msg(b"msg_1").await, create_fake_msg(b"msg_2").await], + messages + ); +} From 036ab54b29dc0411bb102c893e8120a436c9d41c Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Wed, 20 Aug 2025 11:18:20 +0200 Subject: [PATCH 35/49] test(dmq): add integration test for consumer client/server --- Cargo.lock | 1 + internal/mithril-dmq/Cargo.toml | 3 +- .../mithril-dmq/src/consumer/server/pallas.rs | 5 +- internal/mithril-dmq/src/test/mod.rs | 3 +- internal/mithril-dmq/src/test/payload.rs | 4 +- .../tests/consumer_client_server.rs | 86 +++++++++++++++++++ 6 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 internal/mithril-dmq/tests/consumer_client_server.rs diff --git a/Cargo.lock b/Cargo.lock index 5cf8b0482c3..17b45fc0154 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4258,6 +4258,7 @@ dependencies = [ "serde_bytes", "slog", "slog-async", + "slog-scope", "slog-term", "tokio", ] diff --git a/internal/mithril-dmq/Cargo.toml b/internal/mithril-dmq/Cargo.toml index 17f3218226a..3b390644db4 100644 --- a/internal/mithril-dmq/Cargo.toml +++ b/internal/mithril-dmq/Cargo.toml @@ -24,11 +24,12 @@ bincode = { version = "2.0.1" } blake2 = "0.10.6" mithril-cardano-node-chain = { path = "../cardano-node/mithril-cardano-node-chain" } mithril-common = { path = "../../mithril-common" } -pallas-network = { git = "https://github.com/txpipe/pallas.git", branch = "main" } pallas-codec = { git = "https://github.com/txpipe/pallas.git", branch = "main" } +pallas-network = { git = "https://github.com/txpipe/pallas.git", branch = "main" } serde = { workspace = true } serde_bytes = "0.11.17" slog = { workspace = true } +slog-scope = "4.4.0" tokio = { workspace = true, features = ["sync"] } [dev-dependencies] diff --git a/internal/mithril-dmq/src/consumer/server/pallas.rs b/internal/mithril-dmq/src/consumer/server/pallas.rs index 76ffef08c47..9cb015814de 100644 --- a/internal/mithril-dmq/src/consumer/server/pallas.rs +++ b/internal/mithril-dmq/src/consumer/server/pallas.rs @@ -1,10 +1,7 @@ use std::{fs, path::PathBuf}; use anyhow::{Context, anyhow}; -use pallas_network::{ - facades::DmqServer, - miniprotocols::localmsgnotification::{Request, State}, -}; +use pallas_network::{facades::DmqServer, miniprotocols::localmsgnotification::Request}; use tokio::{ join, net::UnixListener, diff --git a/internal/mithril-dmq/src/test/mod.rs b/internal/mithril-dmq/src/test/mod.rs index 77858983f2e..97121e33c28 100644 --- a/internal/mithril-dmq/src/test/mod.rs +++ b/internal/mithril-dmq/src/test/mod.rs @@ -6,8 +6,7 @@ pub mod double; -#[cfg(test)] -pub(crate) mod payload; +pub mod payload; #[cfg(test)] mithril_common::define_test_logger!(); diff --git a/internal/mithril-dmq/src/test/payload.rs b/internal/mithril-dmq/src/test/payload.rs index d43b5b9d52f..8d34689697b 100644 --- a/internal/mithril-dmq/src/test/payload.rs +++ b/internal/mithril-dmq/src/test/payload.rs @@ -1,3 +1,5 @@ +//! DmqMessageTestPayload module for tests only + use std::fmt::Debug; use mithril_common::{ @@ -7,7 +9,7 @@ use mithril_common::{ }; /// A test message payload for the DMQ. -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone)] pub struct DmqMessageTestPayload { message: Vec, } diff --git a/internal/mithril-dmq/tests/consumer_client_server.rs b/internal/mithril-dmq/tests/consumer_client_server.rs new file mode 100644 index 00000000000..059c2a17144 --- /dev/null +++ b/internal/mithril-dmq/tests/consumer_client_server.rs @@ -0,0 +1,86 @@ +#![cfg(unix)] +use std::sync::Arc; + +use tokio::sync::{mpsc::unbounded_channel, watch}; + +use mithril_cardano_node_chain::test::double::FakeChainObserver; +use mithril_common::{ + CardanoNetwork, + crypto_helper::TryToBytes, + current_function, + test::{TempDir, crypto_helper::KesSignerFake}, +}; +use mithril_dmq::{ + DmqConsumerClient, DmqConsumerClientPallas, DmqConsumerServer, DmqConsumerServerPallas, + DmqMessage, DmqMessageBuilder, test::payload::DmqMessageTestPayload, +}; + +async fn create_fake_msg(bytes: &[u8]) -> DmqMessage { + let dmq_builder = DmqMessageBuilder::new( + { + let (kes_signature, operational_certificate) = KesSignerFake::dummy_signature(); + let kes_signer = KesSignerFake::new(vec![ + Ok((kes_signature, operational_certificate.clone())), + Ok((kes_signature, operational_certificate.clone())), // TODO: remove this line once the hack of KES signature is removed in DMQ message builder + ]); + + Arc::new(kes_signer) + }, + Arc::new(FakeChainObserver::default()), + ) + .set_ttl(100); + let message = DmqMessageTestPayload::new(bytes); + dmq_builder.build(&message.to_bytes_vec().unwrap()).await.unwrap() +} + +#[tokio::test] +async fn dmq_consumer_client_server() { + let cardano_network = CardanoNetwork::TestNet(0); + let socket_path = + TempDir::create_with_short_path("dmq_consumer_client_server", current_function!()) + .join("node.socket"); + let (stop_tx, stop_rx) = watch::channel(()); + + let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); + let server = tokio::spawn({ + let socket_path = socket_path.clone(); + async move { + let dmq_consumer_server = Arc::new(DmqConsumerServerPallas::new( + socket_path.to_path_buf(), + cardano_network.to_owned(), + stop_rx, + slog_scope::logger(), + )); + dmq_consumer_server.register_receiver(signature_dmq_rx).await.unwrap(); + dmq_consumer_server.run().await.unwrap(); + } + }); + + let client = tokio::spawn({ + let socket_path = socket_path.clone(); + async move { + let consumer_client = DmqConsumerClientPallas::::new( + socket_path, + cardano_network.clone(), + slog_scope::logger(), + ); + let mut messages = vec![]; + signature_dmq_tx.send(create_fake_msg(b"msg_1").await.into()).unwrap(); + messages.extend_from_slice(&consumer_client.consume_messages().await.unwrap()); + signature_dmq_tx.send(create_fake_msg(b"msg_2").await.into()).unwrap(); + messages.extend_from_slice(&consumer_client.consume_messages().await.unwrap()); + stop_tx.send(()).unwrap(); + + messages.into_iter().map(|(msg, _)| msg).collect::>() + } + }); + + let (_, messages) = tokio::try_join!(server, client).unwrap(); + assert_eq!( + vec![ + DmqMessageTestPayload::new(b"msg_1"), + DmqMessageTestPayload::new(b"msg_2") + ], + messages + ); +} From 18da2a067b3c8bf55f0659048967c6ab10cd6c12 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Wed, 20 Aug 2025 17:08:24 +0200 Subject: [PATCH 36/49] fix(dmq): consumer can not serve messages multiple times --- .../mithril-dmq/src/consumer/client/pallas.rs | 23 ++++++++----------- .../mithril-dmq/src/consumer/server/pallas.rs | 14 +++++------ .../tests/consumer_client_server.rs | 12 ++++++---- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/internal/mithril-dmq/src/consumer/client/pallas.rs b/internal/mithril-dmq/src/consumer/client/pallas.rs index c2086363aa2..a819c86b4ca 100644 --- a/internal/mithril-dmq/src/consumer/client/pallas.rs +++ b/internal/mithril-dmq/src/consumer/client/pallas.rs @@ -1,8 +1,8 @@ use std::{fmt::Debug, marker::PhantomData, path::PathBuf}; use anyhow::{Context, anyhow}; -use pallas_network::facades::DmqClient; -use slog::{Logger, debug, error}; +use pallas_network::{facades::DmqClient, miniprotocols::localmsgnotification::State}; +use slog::{Logger, debug}; use tokio::sync::{Mutex, MutexGuard}; use mithril_common::{ @@ -94,11 +94,13 @@ impl DmqConsumerClientPallas { debug!(self.logger, "Waiting for messages from DMQ..."); let mut client_guard = self.get_client().await?; let client = client_guard.as_mut().ok_or(anyhow!("DMQ client does not exist"))?; - client - .msg_notification() - .send_request_messages_blocking() - .await - .with_context(|| "Failed to request notifications from DMQ server: {}")?; + if *client.msg_notification().state() == State::Idle { + client + .msg_notification() + .send_request_messages_blocking() + .await + .with_context(|| "Failed to request notifications from DMQ server: {}")?; + } let reply = client .msg_notification() @@ -106,9 +108,6 @@ impl DmqConsumerClientPallas { .await .with_context(|| "Failed to receive notifications from DMQ server")?; debug!(self.logger, "Received single signatures from DMQ"; "messages" => ?reply); - if let Err(e) = client.msg_notification().send_done().await { - error!(self.logger, "Failed to send Done"; "error" => ?e); - } reply .0 @@ -227,10 +226,6 @@ mod tests { // server replies with messages if any server_msg.send_reply_messages_blocking(reply_messages).await.unwrap(); assert_eq!(*server_msg.state(), localmsgnotification::State::Idle); - - // server receives done from client - server_msg.recv_done().await.unwrap(); - assert_eq!(*server_msg.state(), localmsgnotification::State::Done); } else { // server waits if no message available future::pending().await diff --git a/internal/mithril-dmq/src/consumer/server/pallas.rs b/internal/mithril-dmq/src/consumer/server/pallas.rs index 9cb015814de..9ddf35be4d2 100644 --- a/internal/mithril-dmq/src/consumer/server/pallas.rs +++ b/internal/mithril-dmq/src/consumer/server/pallas.rs @@ -151,9 +151,7 @@ impl DmqConsumerServerPallas { } } }, - None => { - return Err(anyhow!("DMQ message receiver is not registered")); - } + None => Err(anyhow!("DMQ message receiver is not registered")), } } @@ -229,8 +227,7 @@ impl DmqConsumerServer for DmqConsumerServerPallas { .await?; debug!( self.logger, - "Blocking notification replied to the DMQ notification client: {:?}", - reply_messages + "Messages replied to the DMQ notification client: {:?}", reply_messages ); } Request::NonBlocking => { @@ -244,9 +241,12 @@ impl DmqConsumerServer for DmqConsumerServerPallas { let has_more = !self.messages_buffer.is_empty().await; server .msg_notification() - .send_reply_messages_non_blocking(reply_messages, has_more) + .send_reply_messages_non_blocking(reply_messages.clone(), has_more) .await?; - server.msg_notification().recv_done().await?; + debug!( + self.logger, + "Messages replied to the DMQ notification client: {:?}", reply_messages + ); } }; diff --git a/internal/mithril-dmq/tests/consumer_client_server.rs b/internal/mithril-dmq/tests/consumer_client_server.rs index 059c2a17144..9f1545f8a06 100644 --- a/internal/mithril-dmq/tests/consumer_client_server.rs +++ b/internal/mithril-dmq/tests/consumer_client_server.rs @@ -47,7 +47,7 @@ async fn dmq_consumer_client_server() { async move { let dmq_consumer_server = Arc::new(DmqConsumerServerPallas::new( socket_path.to_path_buf(), - cardano_network.to_owned(), + cardano_network, stop_rx, slog_scope::logger(), )); @@ -61,13 +61,14 @@ async fn dmq_consumer_client_server() { async move { let consumer_client = DmqConsumerClientPallas::::new( socket_path, - cardano_network.clone(), + cardano_network, slog_scope::logger(), ); let mut messages = vec![]; - signature_dmq_tx.send(create_fake_msg(b"msg_1").await.into()).unwrap(); + signature_dmq_tx.send(create_fake_msg(b"msg_1").await).unwrap(); + signature_dmq_tx.send(create_fake_msg(b"msg_2").await).unwrap(); messages.extend_from_slice(&consumer_client.consume_messages().await.unwrap()); - signature_dmq_tx.send(create_fake_msg(b"msg_2").await.into()).unwrap(); + signature_dmq_tx.send(create_fake_msg(b"msg_3").await).unwrap(); messages.extend_from_slice(&consumer_client.consume_messages().await.unwrap()); stop_tx.send(()).unwrap(); @@ -79,7 +80,8 @@ async fn dmq_consumer_client_server() { assert_eq!( vec![ DmqMessageTestPayload::new(b"msg_1"), - DmqMessageTestPayload::new(b"msg_2") + DmqMessageTestPayload::new(b"msg_2"), + DmqMessageTestPayload::new(b"msg_3") ], messages ); From 80a6571d8d44b7dd86c0c226ca14f6eebecaa86b Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Thu, 21 Aug 2025 17:47:14 +0200 Subject: [PATCH 37/49] test(dmq): make publisher integration test check client/server deconnection --- .../tests/publisher_client_server.rs | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/internal/mithril-dmq/tests/publisher_client_server.rs b/internal/mithril-dmq/tests/publisher_client_server.rs index ce223ae052b..9ec930cf5e8 100644 --- a/internal/mithril-dmq/tests/publisher_client_server.rs +++ b/internal/mithril-dmq/tests/publisher_client_server.rs @@ -19,10 +19,8 @@ async fn create_fake_msg(bytes: &[u8]) -> DmqMessage { let dmq_builder = DmqMessageBuilder::new( { let (kes_signature, operational_certificate) = KesSignerFake::dummy_signature(); - let kes_signer = KesSignerFake::new(vec![ - Ok((kes_signature, operational_certificate.clone())), - Ok((kes_signature, operational_certificate.clone())), // TODO: remove this line once the hack of KES signature is removed in DMQ message builder - ]); + let kes_signer = + KesSignerFake::new(vec![Ok((kes_signature, operational_certificate.clone()))]); Arc::new(kes_signer) }, @@ -41,6 +39,7 @@ async fn dmq_publisher_client_server() { .join("node.socket"); let (stop_tx, stop_rx) = watch::channel(()); + // Start the server let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); let server = tokio::spawn({ let socket_path = socket_path.clone(); @@ -59,6 +58,7 @@ async fn dmq_publisher_client_server() { } }); + // Start a first client, publish messages and wait for its deconnection let client = tokio::spawn({ let socket_path = socket_path.clone(); async move { @@ -68,8 +68,6 @@ async fn dmq_publisher_client_server() { let kes_signer = KesSignerFake::new(vec![ Ok((kes_signature, operational_certificate.clone())), Ok((kes_signature, operational_certificate.clone())), - Ok((kes_signature, operational_certificate.clone())), // TODO: remove this line once the hack of KES signature is removed in DMQ message builder - Ok((kes_signature, operational_certificate.clone())), // TODO: remove this line once the hack of KES signature is removed in DMQ message builder ]); Arc::new(kes_signer) @@ -94,6 +92,41 @@ async fn dmq_publisher_client_server() { .publish_message(DmqMessageTestPayload::new(b"msg_2")) .await .unwrap(); + } + }); + client.await.unwrap(); + + // Sleep to avoid refused connection from the server + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + + // Start a second client and publish messages + let client = tokio::spawn({ + let socket_path = socket_path.clone(); + async move { + let dmq_builder = DmqMessageBuilder::new( + { + let (kes_signature, operational_certificate) = KesSignerFake::dummy_signature(); + let kes_signer = KesSignerFake::new(vec![ + Ok((kes_signature, operational_certificate.clone())), + Ok((kes_signature, operational_certificate.clone())), + ]); + + Arc::new(kes_signer) + }, + Arc::new(FakeChainObserver::default()), + ) + .set_ttl(100); + let publisher_client = DmqPublisherClientPallas::::new( + socket_path, + cardano_network, + dmq_builder, + slog_scope::logger(), + ); + + publisher_client + .publish_message(DmqMessageTestPayload::new(b"msg_3")) + .await + .unwrap(); stop_tx .send(()) @@ -101,6 +134,7 @@ async fn dmq_publisher_client_server() { } }); + // Record messages received by the server let recorder = tokio::spawn(async move { let messages: Vec = { let mut messages = vec![]; @@ -115,9 +149,14 @@ async fn dmq_publisher_client_server() { messages }); + // Check that all messages have been correctly received let (_, _, messages) = tokio::try_join!(server, client, recorder).unwrap(); assert_eq!( - vec![create_fake_msg(b"msg_1").await, create_fake_msg(b"msg_2").await], + vec![ + create_fake_msg(b"msg_1").await, + create_fake_msg(b"msg_2").await, + create_fake_msg(b"msg_3").await, + ], messages ); } From 1d34617d068b301e1cda99f0667d4a5416fcf326 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Thu, 21 Aug 2025 18:34:34 +0200 Subject: [PATCH 38/49] test(dmq): make consumer integration test check client/server deconnection --- .../tests/consumer_client_server.rs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/internal/mithril-dmq/tests/consumer_client_server.rs b/internal/mithril-dmq/tests/consumer_client_server.rs index 9f1545f8a06..748801201b7 100644 --- a/internal/mithril-dmq/tests/consumer_client_server.rs +++ b/internal/mithril-dmq/tests/consumer_client_server.rs @@ -41,6 +41,7 @@ async fn dmq_consumer_client_server() { .join("node.socket"); let (stop_tx, stop_rx) = watch::channel(()); + // Start the server let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); let server = tokio::spawn({ let socket_path = socket_path.clone(); @@ -56,8 +57,10 @@ async fn dmq_consumer_client_server() { } }); + // Start a first client, receive messages and wait for its deconnection let client = tokio::spawn({ let socket_path = socket_path.clone(); + let signature_dmq_tx = signature_dmq_tx.clone(); async move { let consumer_client = DmqConsumerClientPallas::::new( socket_path, @@ -70,13 +73,12 @@ async fn dmq_consumer_client_server() { messages.extend_from_slice(&consumer_client.consume_messages().await.unwrap()); signature_dmq_tx.send(create_fake_msg(b"msg_3").await).unwrap(); messages.extend_from_slice(&consumer_client.consume_messages().await.unwrap()); - stop_tx.send(()).unwrap(); messages.into_iter().map(|(msg, _)| msg).collect::>() } }); - let (_, messages) = tokio::try_join!(server, client).unwrap(); + let messages = client.await.unwrap(); assert_eq!( vec![ DmqMessageTestPayload::new(b"msg_1"), @@ -85,4 +87,30 @@ async fn dmq_consumer_client_server() { ], messages ); + + // Sleep to avoid refused connection from the server + tokio::time::sleep(std::time::Duration::from_millis(3000)).await; + + // Start a second client, receive messages + let client = tokio::spawn({ + let socket_path = socket_path.clone(); + let signature_dmq_tx = signature_dmq_tx.clone(); + async move { + let consumer_client = DmqConsumerClientPallas::::new( + socket_path, + cardano_network, + slog_scope::logger(), + ); + let mut messages = vec![]; + signature_dmq_tx.send(create_fake_msg(b"msg_4").await).unwrap(); + messages.extend_from_slice(&consumer_client.consume_messages().await.unwrap()); + stop_tx.send(()).unwrap(); + + messages.into_iter().map(|(msg, _)| msg).collect::>() + } + }); + + // Check that all messages have been correctly received + let (_, messages) = tokio::try_join!(server, client).unwrap(); + assert_eq!(vec![DmqMessageTestPayload::new(b"msg_4")], messages); } From c7c2c6384419d75f923e0c65833677cbca565621 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 22 Aug 2025 12:50:48 +0200 Subject: [PATCH 39/49] fix(dmq): consumer client can't connect more that once to server --- internal/mithril-dmq/Cargo.toml | 2 +- .../mithril-dmq/src/consumer/client/pallas.rs | 18 +++++-- .../mithril-dmq/src/consumer/server/pallas.rs | 47 +++++++++++-------- .../tests/consumer_client_server.rs | 10 ++-- 4 files changed, 47 insertions(+), 30 deletions(-) diff --git a/internal/mithril-dmq/Cargo.toml b/internal/mithril-dmq/Cargo.toml index 3b390644db4..bb4667ca847 100644 --- a/internal/mithril-dmq/Cargo.toml +++ b/internal/mithril-dmq/Cargo.toml @@ -30,7 +30,7 @@ serde = { workspace = true } serde_bytes = "0.11.17" slog = { workspace = true } slog-scope = "4.4.0" -tokio = { workspace = true, features = ["sync"] } +tokio = { workspace = true, features = ["sync","rt-multi-thread"] } [dev-dependencies] mockall = { workspace = true } diff --git a/internal/mithril-dmq/src/consumer/client/pallas.rs b/internal/mithril-dmq/src/consumer/client/pallas.rs index a819c86b4ca..06eb04dbc7d 100644 --- a/internal/mithril-dmq/src/consumer/client/pallas.rs +++ b/internal/mithril-dmq/src/consumer/client/pallas.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, marker::PhantomData, path::PathBuf}; use anyhow::{Context, anyhow}; use pallas_network::{facades::DmqClient, miniprotocols::localmsgnotification::State}; -use slog::{Logger, debug}; +use slog::{Logger, debug, error}; use tokio::sync::{Mutex, MutexGuard}; use mithril_common::{ @@ -138,6 +138,18 @@ impl DmqConsumerClient for DmqConsumer } } +impl Drop for DmqConsumerClientPallas { + fn drop(&mut self) { + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + if let Err(e) = self.drop_client().await { + error!(self.logger, "Failed to drop DMQ consumer client: {}", e); + } + }); + }); + } +} + #[cfg(all(test, unix))] mod tests { @@ -236,7 +248,7 @@ mod tests { }) } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn pallas_dmq_consumer_publisher_succeeds_when_messages_are_available() { let socket_path = create_temp_dir(current_function!()).join("node.socket"); let reply_messages = fake_msgs(); @@ -293,7 +305,7 @@ mod tests { result.expect_err("Should have timed out"); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn pallas_dmq_consumer_client_is_dropped_when_returning_error() { let socket_path = create_temp_dir(current_function!()).join("node.socket"); let reply_messages = fake_msgs(); diff --git a/internal/mithril-dmq/src/consumer/server/pallas.rs b/internal/mithril-dmq/src/consumer/server/pallas.rs index 9ddf35be4d2..4877f070256 100644 --- a/internal/mithril-dmq/src/consumer/server/pallas.rs +++ b/internal/mithril-dmq/src/consumer/server/pallas.rs @@ -91,8 +91,6 @@ impl DmqConsumerServerPallas { } /// Drops the current `DmqServer`, if it exists. - // TODO: remove allow dead code - #[allow(dead_code)] async fn drop_server(&self) -> StdResult<()> { debug!( self.logger, @@ -179,9 +177,9 @@ impl DmqConsumerServerPallas { } Err(err) => { error!(self.logger, "Failed to process message"; "error" => ?err); - /* if let Err(drop_err) = self.drop_server().await { + if let Err(drop_err) = self.drop_server().await { error!(self.logger, "Failed to drop DMQ consumer server"; "error" => ?drop_err); - } */ + } } } } @@ -197,15 +195,9 @@ impl DmqConsumerServer for DmqConsumerServerPallas { self.logger, "Waiting for message received from the DMQ network" ); + let mut server_guard = self.get_server().await?; let server = server_guard.as_mut().ok_or(anyhow!("DMQ server does not exist"))?; - - debug!( - self.logger, - "DMQ Server state: {:?}", - server.msg_notification().state() - ); - let request = server .msg_notification() .recv_next_request() @@ -253,7 +245,6 @@ impl DmqConsumerServer for DmqConsumerServerPallas { Ok(()) } - /// Runs the DMQ publisher server, processing messages in a loop. async fn run(&self) -> StdResult<()> { info!( self.logger, @@ -273,6 +264,18 @@ impl DmqConsumerServer for DmqConsumerServerPallas { } } +impl Drop for DmqConsumerServerPallas { + fn drop(&mut self) { + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + if let Err(e) = self.drop_server().await { + error!(self.logger, "Failed to drop DMQ consumer server: {}", e); + } + }); + }); + } +} + #[cfg(test)] mod tests { use std::{sync::Arc, time::Duration}; @@ -306,7 +309,7 @@ mod tests { } } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn pallas_dmq_consumer_server_non_blocking_success() { let (stop_tx, stop_rx) = watch::channel(()); let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); @@ -322,7 +325,8 @@ mod tests { let message = fake_msg(); let client = tokio::spawn({ async move { - tokio::time::sleep(Duration::from_secs(1)).await; + // sleep to avoid refused connection from the server + tokio::time::sleep(Duration::from_millis(10)).await; // client setup let mut client = DmqClient::connect(socket_path.clone(), 0).await.unwrap(); @@ -364,7 +368,7 @@ mod tests { assert_eq!(vec![message], messages_received); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn pallas_dmq_consumer_server_blocking_success() { let (stop_tx, stop_rx) = watch::channel(()); let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); @@ -380,7 +384,8 @@ mod tests { let message = fake_msg(); let client = tokio::spawn({ async move { - tokio::time::sleep(Duration::from_secs(1)).await; + // sleep to avoid refused connection from the server + tokio::time::sleep(Duration::from_millis(10)).await; // client setup let mut client = DmqClient::connect(socket_path.clone(), 0).await.unwrap(); @@ -422,10 +427,10 @@ mod tests { assert_eq!(vec![message], messages_received); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn pallas_dmq_consumer_server_blocking_blocks_when_no_message_available() { let (_stop_tx, stop_rx) = watch::channel(()); - let (signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); + let (_signature_dmq_tx, signature_dmq_rx) = unbounded_channel::(); let socket_path = create_temp_dir(current_function!()).join("node.socket"); let cardano_network = CardanoNetwork::TestNet(0); let dmq_consumer_server = Arc::new(DmqConsumerServerPallas::new( @@ -437,6 +442,9 @@ mod tests { dmq_consumer_server.register_receiver(signature_dmq_rx).await.unwrap(); let client = tokio::spawn({ async move { + // sleep to avoid refused connection from the server + tokio::time::sleep(Duration::from_millis(10)).await; + // client setup let mut client = DmqClient::connect(socket_path.clone(), 0).await.unwrap(); @@ -451,10 +459,9 @@ mod tests { localmsgnotification::State::BusyBlocking ); - client_msg.recv_next_reply().await.unwrap(); + let _ = client_msg.recv_next_reply().await; } }); - let _signature_dmq_tx_clone = signature_dmq_tx.clone(); let result = tokio::select!( _res = sleep(Duration::from_millis(1000)) => {Err(anyhow!("Timeout"))}, diff --git a/internal/mithril-dmq/tests/consumer_client_server.rs b/internal/mithril-dmq/tests/consumer_client_server.rs index 748801201b7..91f94190c0b 100644 --- a/internal/mithril-dmq/tests/consumer_client_server.rs +++ b/internal/mithril-dmq/tests/consumer_client_server.rs @@ -19,10 +19,8 @@ async fn create_fake_msg(bytes: &[u8]) -> DmqMessage { let dmq_builder = DmqMessageBuilder::new( { let (kes_signature, operational_certificate) = KesSignerFake::dummy_signature(); - let kes_signer = KesSignerFake::new(vec![ - Ok((kes_signature, operational_certificate.clone())), - Ok((kes_signature, operational_certificate.clone())), // TODO: remove this line once the hack of KES signature is removed in DMQ message builder - ]); + let kes_signer = + KesSignerFake::new(vec![Ok((kes_signature, operational_certificate.clone()))]); Arc::new(kes_signer) }, @@ -33,7 +31,7 @@ async fn create_fake_msg(bytes: &[u8]) -> DmqMessage { dmq_builder.build(&message.to_bytes_vec().unwrap()).await.unwrap() } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn dmq_consumer_client_server() { let cardano_network = CardanoNetwork::TestNet(0); let socket_path = @@ -89,7 +87,7 @@ async fn dmq_consumer_client_server() { ); // Sleep to avoid refused connection from the server - tokio::time::sleep(std::time::Duration::from_millis(3000)).await; + tokio::time::sleep(std::time::Duration::from_millis(10)).await; // Start a second client, receive messages let client = tokio::spawn({ From ed95e69d7aaac18fb6a952f8ed116ee42d7dd515 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 25 Aug 2025 11:04:19 +0200 Subject: [PATCH 40/49] fix(dmq): flakiness in macOS unit tests Likely due to some deadlock on the mutex in the drop server/client (which is a blocking operation). --- .../mithril-dmq/src/consumer/client/pallas.rs | 15 ++++++++++++--- .../mithril-dmq/src/consumer/server/pallas.rs | 4 ++-- .../mithril-dmq/src/publisher/server/pallas.rs | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/internal/mithril-dmq/src/consumer/client/pallas.rs b/internal/mithril-dmq/src/consumer/client/pallas.rs index 06eb04dbc7d..6030e7b9bcb 100644 --- a/internal/mithril-dmq/src/consumer/client/pallas.rs +++ b/internal/mithril-dmq/src/consumer/client/pallas.rs @@ -74,7 +74,7 @@ impl DmqConsumerClientPallas { "socket" => ?self.socket, "network" => ?self.network ); - let mut client_lock = self.client.lock().await; + let mut client_lock = self.client.try_lock()?; if let Some(client) = client_lock.take() { client.abort().await; } @@ -249,11 +249,14 @@ mod tests { } #[tokio::test(flavor = "multi_thread")] - async fn pallas_dmq_consumer_publisher_succeeds_when_messages_are_available() { + async fn pallas_dmq_consumer_client_succeeds_when_messages_are_available() { let socket_path = create_temp_dir(current_function!()).join("node.socket"); let reply_messages = fake_msgs(); let server = setup_dmq_server(socket_path.clone(), reply_messages); let client = tokio::spawn(async move { + // sleep to avoid refused connection from the server + tokio::time::sleep(Duration::from_millis(10)).await; + let consumer = DmqConsumerClientPallas::new( socket_path, CardanoNetwork::TestNet(0), @@ -282,11 +285,14 @@ mod tests { } #[tokio::test] - async fn pallas_dmq_consumer_publisher_blocks_when_no_message_available() { + async fn pallas_dmq_consumer_client_blocks_when_no_message_available() { let socket_path = create_temp_dir(current_function!()).join("node.socket"); let reply_messages = vec![]; let server = setup_dmq_server(socket_path.clone(), reply_messages); let client = tokio::spawn(async move { + // sleep to avoid refused connection from the server + tokio::time::sleep(Duration::from_millis(10)).await; + let consumer = DmqConsumerClientPallas::::new( socket_path, CardanoNetwork::TestNet(0), @@ -311,6 +317,9 @@ mod tests { let reply_messages = fake_msgs(); let server = setup_dmq_server(socket_path.clone(), reply_messages); let client = tokio::spawn(async move { + // sleep to avoid refused connection from the server + tokio::time::sleep(Duration::from_millis(10)).await; + let consumer = DmqConsumerClientPallas::::new( socket_path, CardanoNetwork::TestNet(0), diff --git a/internal/mithril-dmq/src/consumer/server/pallas.rs b/internal/mithril-dmq/src/consumer/server/pallas.rs index 4877f070256..0a7ac3d0c3c 100644 --- a/internal/mithril-dmq/src/consumer/server/pallas.rs +++ b/internal/mithril-dmq/src/consumer/server/pallas.rs @@ -98,7 +98,7 @@ impl DmqConsumerServerPallas { "socket" => ?self.socket, "network" => ?self.network ); - let mut server_lock = self.server.lock().await; + let mut server_lock = self.server.try_lock()?; if let Some(server) = server_lock.take() { server.abort().await; } @@ -464,7 +464,7 @@ mod tests { }); let result = tokio::select!( - _res = sleep(Duration::from_millis(1000)) => {Err(anyhow!("Timeout"))}, + _res = sleep(Duration::from_millis(100)) => {Err(anyhow!("Timeout"))}, _res = dmq_consumer_server.run() => {Ok(())}, _res = client => {Ok(())}, ); diff --git a/internal/mithril-dmq/src/publisher/server/pallas.rs b/internal/mithril-dmq/src/publisher/server/pallas.rs index 6b1984a5b03..deb0ea250f1 100644 --- a/internal/mithril-dmq/src/publisher/server/pallas.rs +++ b/internal/mithril-dmq/src/publisher/server/pallas.rs @@ -99,7 +99,7 @@ impl DmqPublisherServerPallas { "socket" => ?self.socket, "network" => ?self.network ); - let mut server_lock = self.server.lock().await; + let mut server_lock = self.server.try_lock()?; if let Some(server) = server_lock.take() { server.abort().await; } From 4a74c9f2d27974a43f6dc90ee1fe2b50dc48b606 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 22 Aug 2025 17:05:06 +0200 Subject: [PATCH 41/49] fix(aggregator): add single signature authentication in signature processor --- .../src/services/signature_processor.rs | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/mithril-aggregator/src/services/signature_processor.rs b/mithril-aggregator/src/services/signature_processor.rs index 65240936e90..9340c4e2e8f 100644 --- a/mithril-aggregator/src/services/signature_processor.rs +++ b/mithril-aggregator/src/services/signature_processor.rs @@ -2,7 +2,11 @@ use std::sync::Arc; use slog::{Logger, error, warn}; -use mithril_common::{StdResult, logging::LoggerExtensions}; +use mithril_common::{ + StdResult, + entities::{SingleSignature, SingleSignatureAuthenticationStatus}, + logging::LoggerExtensions, +}; use tokio::{select, sync::watch::Receiver}; use crate::MetricsService; @@ -46,6 +50,13 @@ impl SequentialSignatureProcessor { metrics_service, } } + + /// Authenticates a single signature + /// + /// This is always the case with single signatures received from the DMQ network. + fn authenticate_signature(&self, signature: &mut SingleSignature) { + signature.authentication_status = SingleSignatureAuthenticationStatus::Authenticated; + } } #[async_trait::async_trait] @@ -53,7 +64,8 @@ impl SignatureProcessor for SequentialSignatureProcessor { async fn process_signatures(&self) -> StdResult<()> { match self.consumer.get_signatures().await { Ok(signatures) => { - for (signature, signed_entity_type) in signatures { + for (mut signature, signed_entity_type) in signatures { + self.authenticate_signature(&mut signature); match self .certifier .register_single_signature(&signed_entity_type, &signature) @@ -150,15 +162,27 @@ mod tests { .expect_register_single_signature() .with( eq(SignedEntityType::MithrilStakeDistribution(Epoch(1))), - eq(fake_data::single_signature(vec![1, 2, 3])), + eq(SingleSignature { + authentication_status: SingleSignatureAuthenticationStatus::Authenticated, + ..fake_data::single_signature(vec![1, 2, 3]) + }), ) - .returning(|_, _| Ok(SignatureRegistrationStatus::Registered)) + .returning(|_, single_signature| { + assert_eq!( + single_signature.authentication_status, + SingleSignatureAuthenticationStatus::Authenticated + ); + Ok(SignatureRegistrationStatus::Registered) + }) .times(1); mock_certifier .expect_register_single_signature() .with( eq(SignedEntityType::MithrilStakeDistribution(Epoch(2))), - eq(fake_data::single_signature(vec![4, 5, 6])), + eq(SingleSignature { + authentication_status: SingleSignatureAuthenticationStatus::Authenticated, + ..fake_data::single_signature(vec![4, 5, 6]) + }), ) .returning(|_, _| Ok(SignatureRegistrationStatus::Registered)) .times(1); @@ -207,7 +231,10 @@ mod tests { .expect_register_single_signature() .with( eq(SignedEntityType::MithrilStakeDistribution(Epoch(1))), - eq(fake_data::single_signature(vec![1, 2, 3])), + eq(SingleSignature { + authentication_status: SingleSignatureAuthenticationStatus::Authenticated, + ..fake_data::single_signature(vec![1, 2, 3]) + }), ) .returning(|_, _| Ok(SignatureRegistrationStatus::Registered)) .times(1); From fb5cda7bdbb3da7aea7ac7345ba65699404fbf8b Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 8 Jul 2025 17:47:34 +0200 Subject: [PATCH 42/49] feat(e2e): support for fake DMQ node --- .../mithril-end-to-end/src/main.rs | 8 ++++++++ .../src/mithril/aggregator.rs | 12 +++++++++++- .../src/mithril/infrastructure.rs | 9 ++++++++- .../src/mithril/relay_aggregator.rs | 15 ++++++++++++++- .../src/mithril/relay_signer.rs | 19 ++++++++++++++++++- .../mithril-end-to-end/src/mithril/signer.rs | 12 +++++++++++- .../src/stress_test/aggregator_helpers.rs | 1 + 7 files changed, 71 insertions(+), 5 deletions(-) diff --git a/mithril-test-lab/mithril-end-to-end/src/main.rs b/mithril-test-lab/mithril-end-to-end/src/main.rs index 9a8375ab92b..e4942cabfe3 100644 --- a/mithril-test-lab/mithril-end-to-end/src/main.rs +++ b/mithril-test-lab/mithril-end-to-end/src/main.rs @@ -123,6 +123,12 @@ pub struct Args { #[clap(long, default_value = "false")] use_p2p_passive_relays: bool, + /// Use DMQ protocol (used to broadcast signatures) + /// + /// Requires the Mithril nodes to be compiled with the 'future_dmq' feature + #[clap(long)] + use_dmq: bool, + /// Skip cardano binaries download #[clap(long)] skip_cardano_bin_download: bool, @@ -327,6 +333,7 @@ impl App { let use_relays = args.use_relays; let relay_signer_registration_mode = args.relay_signer_registration_mode; let relay_signature_registration_mode = args.relay_signature_registration_mode; + let use_dmq = args.use_dmq; let use_p2p_passive_relays = args.use_p2p_passive_relays; @@ -360,6 +367,7 @@ impl App { mithril_era_reader_adapter: args.mithril_era_reader_adapter, signed_entity_types: args.signed_entity_types.clone(), run_only_mode, + use_dmq, use_relays, relay_signer_registration_mode, relay_signature_registration_mode, diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/aggregator.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/aggregator.rs index 4b078d6b4b1..cca71603ff3 100644 --- a/mithril-test-lab/mithril-end-to-end/src/mithril/aggregator.rs +++ b/mithril-test-lab/mithril-end-to-end/src/mithril/aggregator.rs @@ -39,6 +39,7 @@ pub struct AggregatorConfig<'a> { pub signed_entity_types: &'a [String], pub chain_observer_type: &'a str, pub leader_aggregator_endpoint: &'a Option, + pub use_dmq: bool, } pub struct Aggregator { @@ -75,6 +76,7 @@ impl Aggregator { let public_server_url = format!("http://localhost:{server_port_parameter}/aggregator"); let mut env = HashMap::from([ ("NETWORK", "devnet"), + ("NETWORK_MAGIC", &magic_id), ("RUN_INTERVAL", &mithril_run_interval), ("SERVER_IP", "0.0.0.0"), ("SERVER_PORT", &server_port_parameter), @@ -85,7 +87,6 @@ impl Aggregator { "SNAPSHOT_DIRECTORY", aggregator_config.artifacts_dir.to_str().unwrap(), ), - ("NETWORK_MAGIC", &magic_id), ( "DATA_STORES_DIRECTORY", aggregator_config.store_dir.to_str().unwrap(), @@ -129,6 +130,15 @@ impl Aggregator { if let Some(leader_aggregator_endpoint) = aggregator_config.leader_aggregator_endpoint { env.insert("LEADER_AGGREGATOR_ENDPOINT", leader_aggregator_endpoint); } + let dmq_node_socket_path = aggregator_config + .work_dir + .join(format!("dmq-aggregator-{}.socket", aggregator_config.index)); + if aggregator_config.use_dmq { + env.insert( + "DMQ_NODE_SOCKET_PATH", + dmq_node_socket_path.to_str().unwrap(), + ); + } let args = vec![ "--db-directory", aggregator_config.full_node.db_path.to_str().unwrap(), diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs index 2649d305e66..2454c052235 100644 --- a/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs +++ b/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs @@ -35,6 +35,7 @@ pub struct MithrilInfrastructureConfig { pub relay_signer_registration_mode: String, pub relay_signature_registration_mode: String, pub use_p2p_passive_relays: bool, + pub use_dmq: bool, pub use_era_specific_work_dir: bool, } @@ -68,6 +69,7 @@ impl MithrilInfrastructureConfig { relay_signer_registration_mode: "passthrough".to_string(), relay_signature_registration_mode: "passthrough".to_string(), use_p2p_passive_relays: false, + use_dmq: false, use_era_specific_work_dir: false, } } @@ -255,6 +257,7 @@ impl MithrilInfrastructure { signed_entity_types: &config.signed_entity_types, chain_observer_type, leader_aggregator_endpoint: &leader_aggregator_endpoint, + use_dmq: config.use_dmq, })?; aggregator @@ -294,12 +297,13 @@ impl MithrilInfrastructure { let mut bootstrap_peer_addr = None; for (index, aggregator_endpoint) in aggregator_endpoints.iter().enumerate() { let mut relay_aggregator = RelayAggregator::new( - Aggregator::name_suffix(index), + index, config.server_port + index as u64 + 100, bootstrap_peer_addr.clone(), aggregator_endpoint, &config.work_dir, &config.bin_dir, + config.use_dmq, )?; if bootstrap_peer_addr.is_none() { bootstrap_peer_addr = Some(relay_aggregator.peer_addr().to_owned()); @@ -310,6 +314,7 @@ impl MithrilInfrastructure { for (index, party_id) in signers_party_ids.iter().enumerate() { let mut relay_signer = RelaySigner::new(&RelaySignerConfiguration { + signer_number: index + 1, listen_port: config.server_port + index as u64 + 200, server_port: config.server_port + index as u64 + 300, dial_to: bootstrap_peer_addr.clone(), @@ -319,6 +324,7 @@ impl MithrilInfrastructure { party_id: party_id.clone(), work_dir: &config.work_dir, bin_dir: &config.bin_dir, + use_dmq: config.use_dmq, })?; relay_signer.start()?; @@ -389,6 +395,7 @@ impl MithrilInfrastructure { mithril_era_reader_adapter: &config.mithril_era_reader_adapter, mithril_era_marker_address: &config.devnet.mithril_era_marker_address()?, enable_certification, + use_dmq: config.use_dmq, })?; signer.start().await?; diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/relay_aggregator.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/relay_aggregator.rs index adb22f03491..f5a39e3e342 100644 --- a/mithril-test-lab/mithril-end-to-end/src/mithril/relay_aggregator.rs +++ b/mithril-test-lab/mithril-end-to-end/src/mithril/relay_aggregator.rs @@ -1,4 +1,5 @@ use crate::utils::MithrilCommand; +use crate::{Aggregator, DEVNET_MAGIC_ID}; use mithril_common::StdResult; use std::collections::HashMap; use std::path::Path; @@ -14,21 +15,33 @@ pub struct RelayAggregator { impl RelayAggregator { pub fn new( - name: String, + index: usize, listen_port: u64, dial_to: Option, aggregator_endpoint: &str, work_dir: &Path, bin_dir: &Path, + use_dmq: bool, ) -> StdResult { + let name = Aggregator::name_suffix(index); let listen_port_str = format!("{listen_port}"); + let magic_id = DEVNET_MAGIC_ID.to_string(); let mut env = HashMap::from([ ("LISTEN_PORT", listen_port_str.as_str()), + ("NETWORK", "devnet"), + ("NETWORK_MAGIC", &magic_id), ("AGGREGATOR_ENDPOINT", aggregator_endpoint), ]); if let Some(dial_to) = &dial_to { env.insert("DIAL_TO", dial_to); } + let dmq_node_socket_path = work_dir.join(format!("dmq-aggregator-{index}.socket")); + if use_dmq { + env.insert( + "DMQ_NODE_SOCKET_PATH", + dmq_node_socket_path.to_str().unwrap(), + ); + } let args = vec!["-vvv", "aggregator"]; let mut command = MithrilCommand::new("mithril-relay", work_dir, bin_dir, env, &args)?; diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/relay_signer.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/relay_signer.rs index 55c87781924..26e4c9bca32 100644 --- a/mithril-test-lab/mithril-end-to-end/src/mithril/relay_signer.rs +++ b/mithril-test-lab/mithril-end-to-end/src/mithril/relay_signer.rs @@ -1,3 +1,4 @@ +use crate::DEVNET_MAGIC_ID; use crate::utils::MithrilCommand; use mithril_common::StdResult; use mithril_common::entities::PartyId; @@ -6,6 +7,7 @@ use std::path::Path; use tokio::process::Child; pub struct RelaySignerConfiguration<'a> { + pub signer_number: usize, pub listen_port: u64, pub server_port: u64, pub dial_to: Option, @@ -15,6 +17,7 @@ pub struct RelaySignerConfiguration<'a> { pub party_id: PartyId, pub work_dir: &'a Path, pub bin_dir: &'a Path, + pub use_dmq: bool, } #[derive(Debug)] @@ -28,8 +31,11 @@ pub struct RelaySigner { impl RelaySigner { pub fn new(configuration: &RelaySignerConfiguration) -> StdResult { + let party_id = configuration.party_id.to_owned(); let listen_port_str = format!("{}", configuration.listen_port); let server_port_str = format!("{}", configuration.server_port); + + let magic_id = DEVNET_MAGIC_ID.to_string(); let relay_signer_registration_mode = configuration.relay_signer_registration_mode.to_string(); let relay_signature_registration_mode = @@ -37,6 +43,8 @@ impl RelaySigner { let mut env = HashMap::from([ ("LISTEN_PORT", listen_port_str.as_str()), ("SERVER_PORT", server_port_str.as_str()), + ("NETWORK", "devnet"), + ("NETWORK_MAGIC", &magic_id), ("AGGREGATOR_ENDPOINT", configuration.aggregator_endpoint), ("SIGNER_REPEATER_DELAY", "100"), ( @@ -51,6 +59,15 @@ impl RelaySigner { if let Some(dial_to) = &configuration.dial_to { env.insert("DIAL_TO", dial_to); } + let dmq_node_socket_path = configuration + .work_dir + .join(format!("dmq-signer-{}.socket", configuration.signer_number)); + if configuration.use_dmq { + env.insert( + "DMQ_NODE_SOCKET_PATH", + dmq_node_socket_path.to_str().unwrap(), + ); + } let args = vec!["-vvv", "signer"]; let mut command = MithrilCommand::new( @@ -65,7 +82,7 @@ impl RelaySigner { Ok(Self { listen_port: configuration.listen_port, server_port: configuration.server_port, - party_id: configuration.party_id.to_owned(), + party_id, command, process: None, }) diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/signer.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/signer.rs index 496a5b4ede0..216a98c85a7 100644 --- a/mithril-test-lab/mithril-end-to-end/src/mithril/signer.rs +++ b/mithril-test-lab/mithril-end-to-end/src/mithril/signer.rs @@ -25,6 +25,7 @@ pub struct SignerConfig<'a> { pub mithril_era_reader_adapter: &'a str, pub mithril_era_marker_address: &'a str, pub enable_certification: bool, + pub use_dmq: bool, } #[derive(Debug)] @@ -54,6 +55,7 @@ impl Signer { let mithril_run_interval = format!("{}", signer_config.mithril_run_interval); let mut env = HashMap::from([ ("NETWORK", "devnet"), + ("NETWORK_MAGIC", &magic_id), ("RUN_INTERVAL", &mithril_run_interval), ("AGGREGATOR_ENDPOINT", &signer_config.aggregator_endpoint), ( @@ -65,7 +67,6 @@ impl Signer { signer_config.store_dir.to_str().unwrap(), ), ("STORE_RETENTION_LIMIT", "10"), - ("NETWORK_MAGIC", &magic_id), ( "CARDANO_NODE_SOCKET_PATH", signer_config.pool_node.socket_path.to_str().unwrap(), @@ -96,6 +97,15 @@ impl Signer { } else { env.insert("PARTY_ID", &party_id); } + let dmq_node_socket_path = signer_config + .work_dir + .join(format!("dmq-signer-{}.socket", signer_config.signer_number)); + if signer_config.use_dmq { + env.insert( + "DMQ_NODE_SOCKET_PATH", + dmq_node_socket_path.to_str().unwrap(), + ); + } let args = vec!["-vvv"]; let mut command = MithrilCommand::new( diff --git a/mithril-test-lab/mithril-end-to-end/src/stress_test/aggregator_helpers.rs b/mithril-test-lab/mithril-end-to-end/src/stress_test/aggregator_helpers.rs index 25a99ae522f..cc7e9091286 100644 --- a/mithril-test-lab/mithril-end-to-end/src/stress_test/aggregator_helpers.rs +++ b/mithril-test-lab/mithril-end-to-end/src/stress_test/aggregator_helpers.rs @@ -36,6 +36,7 @@ pub async fn bootstrap_aggregator( signed_entity_types: &signed_entity_types, chain_observer_type, leader_aggregator_endpoint: &None, + use_dmq: false, }) .unwrap(); From 0800bdd2ac0747b2e998a27804dcb27d73f8fdfb Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 25 Aug 2025 14:45:09 +0200 Subject: [PATCH 43/49] feat(signer): add support for skipping the signature delayer --- mithril-signer/src/configuration.rs | 10 +++++++++- .../src/dependency_injection/builder.rs | 20 +++++++++++++------ mithril-signer/src/main.rs | 17 ++++++++++++++++ 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/mithril-signer/src/configuration.rs b/mithril-signer/src/configuration.rs index 6ea58008e01..069d0bc2a00 100644 --- a/mithril-signer/src/configuration.rs +++ b/mithril-signer/src/configuration.rs @@ -25,6 +25,13 @@ pub struct SignaturePublisherConfig { /// Delay (in milliseconds) between two separate publications done by the delayer signature publisher pub delayer_delay_ms: u64, + + /// Whether to skip the delayer when publishing the signature + /// + /// If set to true, the signatures will be published only once: + /// - if the 'future_dmq` feature is used to compile, the signatures will be published only with the DMQ protocol + /// - if the `future_dmq` feature is not used, the signatures will be published with the regular HTTP protocol + pub skip_delayer: bool, } /// Client configuration @@ -137,7 +144,7 @@ pub struct Configuration { pub preloading_refresh_interval_in_seconds: u64, /// Signature publisher configuration - #[example = "`{ retry_attempts: 3, retry_delay_ms: 2000, delayer_delay_ms: 10000 }`"] + #[example = "`{ retry_attempts: 3, retry_delay_ms: 2000, delayer_delay_ms: 10000, skip_delayer: false }`"] pub signature_publisher_config: SignaturePublisherConfig, } @@ -183,6 +190,7 @@ impl Configuration { retry_attempts: 1, retry_delay_ms: 1, delayer_delay_ms: 1, + skip_delayer: false, }, } } diff --git a/mithril-signer/src/dependency_injection/builder.rs b/mithril-signer/src/dependency_injection/builder.rs index adb904ee931..51d979dc5da 100644 --- a/mithril-signer/src/dependency_injection/builder.rs +++ b/mithril-signer/src/dependency_injection/builder.rs @@ -459,12 +459,20 @@ impl<'a> DependenciesBuilder<'a> { }, ); - Arc::new(SignaturePublisherDelayer::new( - Arc::new(first_publisher), - Arc::new(second_publisher), - Duration::from_millis(self.config.signature_publisher_config.delayer_delay_ms), - self.root_logger(), - )) + if self.config.signature_publisher_config.skip_delayer { + if cfg!(feature = "future_dmq") { + Arc::new(first_publisher) as Arc + } else { + Arc::new(second_publisher) as Arc + } + } else { + Arc::new(SignaturePublisherDelayer::new( + Arc::new(first_publisher), + Arc::new(second_publisher), + Duration::from_millis(self.config.signature_publisher_config.delayer_delay_ms), + self.root_logger(), + )) as Arc + } }; let certifier = Arc::new(SignerCertifierService::new( diff --git a/mithril-signer/src/main.rs b/mithril-signer/src/main.rs index 7ddcffaa243..96cf203f158 100644 --- a/mithril-signer/src/main.rs +++ b/mithril-signer/src/main.rs @@ -108,6 +108,19 @@ pub struct Args { default_value_t = 10_000 )] signature_publisher_delayer_delay_ms: u64, + + /// Whether to skip the delayer when publishing the signature + /// + /// If set to true, the signatures will be published only once: + /// - if the 'future_dmq` feature is used to compile, the signatures will be published only with the DMQ protocol + /// - if the `future_dmq` feature is not used, the signatures will be published with the regular HTTP protocol + + #[clap( + long, + env = "SIGNATURE_PUBLISHER_SKIP_DELAYER", + default_value_t = false + )] + signature_publisher_skip_delayer: bool, } impl Args { @@ -207,6 +220,10 @@ async fn main() -> StdResult<()> { "signature_publisher_config.delayer_delay_ms", args.signature_publisher_delayer_delay_ms, )? + .set_default( + "signature_publisher_config.skip_delayer", + args.signature_publisher_skip_delayer, + )? .add_source(DefaultConfiguration::default()) .add_source( config::File::with_name(&format!( From af9fa40f037ea8e8a3fb49184f1ec3165895ce4a Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Thu, 28 Aug 2025 15:06:01 +0200 Subject: [PATCH 44/49] refactor(signer): make signature publisher build more readable in CI --- .../src/dependency_injection/builder.rs | 81 ++++++++++++------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/mithril-signer/src/dependency_injection/builder.rs b/mithril-signer/src/dependency_injection/builder.rs index 51d979dc5da..2a737d04244 100644 --- a/mithril-signer/src/dependency_injection/builder.rs +++ b/mithril-signer/src/dependency_injection/builder.rs @@ -417,34 +417,28 @@ impl<'a> DependenciesBuilder<'a> { _ => None, }; + #[cfg(feature = "future_dmq")] let signature_publisher = { let first_publisher = SignaturePublisherRetrier::new( - { - #[cfg(feature = "future_dmq")] - let publisher = match &self.config.dmq_node_socket_path { - Some(dmq_node_socket_path) => { - let cardano_network = &self.config.get_network()?; - let dmq_message_builder = DmqMessageBuilder::new( - kes_signer.clone().ok_or(anyhow!( - "A KES signer is mandatory to sign DMQ messages" - ))?, - chain_observer.clone(), - ); - Arc::new(SignaturePublisherDmq::new(Arc::new( - DmqPublisherClientPallas::::new( - dmq_node_socket_path.to_owned(), - *cardano_network, - dmq_message_builder, - self.root_logger(), - ), - ))) as Arc - } - _ => Arc::new(SignaturePublisherNoop) as Arc, - }; - #[cfg(not(feature = "future_dmq"))] - let publisher = Arc::new(SignaturePublisherNoop) as Arc; - - publisher + match &self.config.dmq_node_socket_path { + Some(dmq_node_socket_path) => { + let cardano_network = &self.config.get_network()?; + let dmq_message_builder = DmqMessageBuilder::new( + kes_signer + .clone() + .ok_or(anyhow!("A KES signer is mandatory to sign DMQ messages"))?, + chain_observer.clone(), + ); + Arc::new(SignaturePublisherDmq::new(Arc::new( + DmqPublisherClientPallas::::new( + dmq_node_socket_path.to_owned(), + *cardano_network, + dmq_message_builder, + self.root_logger(), + ), + ))) as Arc + } + _ => Arc::new(SignaturePublisherNoop) as Arc, }, SignaturePublishRetryPolicy::never(), ); @@ -460,11 +454,36 @@ impl<'a> DependenciesBuilder<'a> { ); if self.config.signature_publisher_config.skip_delayer { - if cfg!(feature = "future_dmq") { - Arc::new(first_publisher) as Arc - } else { - Arc::new(second_publisher) as Arc - } + Arc::new(first_publisher) as Arc + } else { + Arc::new(SignaturePublisherDelayer::new( + Arc::new(first_publisher), + Arc::new(second_publisher), + Duration::from_millis(self.config.signature_publisher_config.delayer_delay_ms), + self.root_logger(), + )) as Arc + } + }; + + #[cfg(not(feature = "future_dmq"))] + let signature_publisher = { + let first_publisher = SignaturePublisherRetrier::new( + Arc::new(SignaturePublisherNoop) as Arc, + SignaturePublishRetryPolicy::never(), + ); + + let second_publisher = SignaturePublisherRetrier::new( + aggregator_client.clone(), + SignaturePublishRetryPolicy { + attempts: self.config.signature_publisher_config.retry_attempts, + delay_between_attempts: Duration::from_millis( + self.config.signature_publisher_config.retry_delay_ms, + ), + }, + ); + + if self.config.signature_publisher_config.skip_delayer { + Arc::new(second_publisher) as Arc } else { Arc::new(SignaturePublisherDelayer::new( Arc::new(first_publisher), From e73bd0af0ef345608333b6a423cd9e09235dc3da Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 25 Aug 2025 14:45:42 +0200 Subject: [PATCH 45/49] feat(e2e): support skipping signature delayer in signer --- mithril-test-lab/mithril-end-to-end/src/main.rs | 5 +++++ .../mithril-end-to-end/src/mithril/infrastructure.rs | 3 +++ mithril-test-lab/mithril-end-to-end/src/mithril/signer.rs | 8 ++++++++ 3 files changed, 16 insertions(+) diff --git a/mithril-test-lab/mithril-end-to-end/src/main.rs b/mithril-test-lab/mithril-end-to-end/src/main.rs index e4942cabfe3..d041d4211d7 100644 --- a/mithril-test-lab/mithril-end-to-end/src/main.rs +++ b/mithril-test-lab/mithril-end-to-end/src/main.rs @@ -123,6 +123,10 @@ pub struct Args { #[clap(long, default_value = "false")] use_p2p_passive_relays: bool, + /// Skip the signature delayer + #[clap(long)] + skip_signature_delayer: bool, + /// Use DMQ protocol (used to broadcast signatures) /// /// Requires the Mithril nodes to be compiled with the 'future_dmq' feature @@ -371,6 +375,7 @@ impl App { use_relays, relay_signer_registration_mode, relay_signature_registration_mode, + skip_signature_delayer: args.skip_signature_delayer, use_p2p_passive_relays, use_era_specific_work_dir: args.mithril_next_era.is_some(), }) diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs index 2454c052235..8212015db5c 100644 --- a/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs +++ b/mithril-test-lab/mithril-end-to-end/src/mithril/infrastructure.rs @@ -35,6 +35,7 @@ pub struct MithrilInfrastructureConfig { pub relay_signer_registration_mode: String, pub relay_signature_registration_mode: String, pub use_p2p_passive_relays: bool, + pub skip_signature_delayer: bool, pub use_dmq: bool, pub use_era_specific_work_dir: bool, } @@ -69,6 +70,7 @@ impl MithrilInfrastructureConfig { relay_signer_registration_mode: "passthrough".to_string(), relay_signature_registration_mode: "passthrough".to_string(), use_p2p_passive_relays: false, + skip_signature_delayer: false, use_dmq: false, use_era_specific_work_dir: false, } @@ -395,6 +397,7 @@ impl MithrilInfrastructure { mithril_era_reader_adapter: &config.mithril_era_reader_adapter, mithril_era_marker_address: &config.devnet.mithril_era_marker_address()?, enable_certification, + skip_signature_delayer: config.skip_signature_delayer, use_dmq: config.use_dmq, })?; signer.start().await?; diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/signer.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/signer.rs index 216a98c85a7..2d6fbe958a8 100644 --- a/mithril-test-lab/mithril-end-to-end/src/mithril/signer.rs +++ b/mithril-test-lab/mithril-end-to-end/src/mithril/signer.rs @@ -25,6 +25,7 @@ pub struct SignerConfig<'a> { pub mithril_era_reader_adapter: &'a str, pub mithril_era_marker_address: &'a str, pub enable_certification: bool, + pub skip_signature_delayer: bool, pub use_dmq: bool, } @@ -53,6 +54,11 @@ impl Signer { ) }; let mithril_run_interval = format!("{}", signer_config.mithril_run_interval); + let skip_signature_delayer = if signer_config.skip_signature_delayer { + "true" + } else { + "false" + }; let mut env = HashMap::from([ ("NETWORK", "devnet"), ("NETWORK_MAGIC", &magic_id), @@ -84,6 +90,8 @@ impl Signer { ("PRELOADING_REFRESH_INTERVAL_IN_SECONDS", "10"), ("SIGNATURE_PUBLISHER_RETRY_DELAY_MS", "1"), ("SIGNATURE_PUBLISHER_DELAYER_DELAY_MS", "1"), + ("SIGNATURE_PUBLISHER_SKIP_DELAYER", skip_signature_delayer), + ("PARTY_ID", &party_id), ]); if signer_config.enable_certification { env.insert( From ee076df5d716504f79dd72bd6f68a31101f492c2 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Thu, 28 Aug 2025 17:57:44 +0200 Subject: [PATCH 46/49] fix(kes): fake KES signer dummy signature creates flakiness in tests --- internal/mithril-dmq/src/model/builder.rs | 4 +++- .../src/publisher/client/pallas.rs | 15 +++++++----- .../tests/consumer_client_server.rs | 24 +++++++++++++------ .../tests/publisher_client_server.rs | 20 +++++++++------- .../crypto_helper/cardano/kes/signer_fake.rs | 7 +++--- 5 files changed, 45 insertions(+), 25 deletions(-) diff --git a/internal/mithril-dmq/src/model/builder.rs b/internal/mithril-dmq/src/model/builder.rs index 3725b70d3f0..2d49ae5afcc 100644 --- a/internal/mithril-dmq/src/model/builder.rs +++ b/internal/mithril-dmq/src/model/builder.rs @@ -95,6 +95,7 @@ mod tests { use mithril_cardano_node_chain::test::double::FakeChainObserver; use mithril_common::{ crypto_helper::TryToBytes, + current_function, entities::{BlockNumber, ChainPoint, TimePoint}, test::{crypto_helper::KesSignerFake, double::Dummy}, }; @@ -117,7 +118,8 @@ mod tests { #[tokio::test] async fn test_build_dmq_message() { - let (kes_signature, operational_certificate) = KesSignerFake::dummy_signature(); + let (kes_signature, operational_certificate) = + KesSignerFake::dummy_signature(current_function!()); let kes_signer = Arc::new(KesSignerFake::new(vec![Ok(( kes_signature, operational_certificate.clone(), diff --git a/internal/mithril-dmq/src/publisher/client/pallas.rs b/internal/mithril-dmq/src/publisher/client/pallas.rs index 5f938dff398..d2b268585d3 100644 --- a/internal/mithril-dmq/src/publisher/client/pallas.rs +++ b/internal/mithril-dmq/src/publisher/client/pallas.rs @@ -140,9 +140,11 @@ mod tests { }) } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn pallas_dmq_signature_publisher_success() { - let socket_path = create_temp_dir(current_function!()).join("node.socket"); + let current_function_name = current_function!(); + + let socket_path = create_temp_dir(current_function_name).join("node.socket"); let reply_success = true; let server = setup_dmq_server(socket_path.clone(), reply_success); let client = tokio::spawn(async move { @@ -155,7 +157,7 @@ mod tests { DmqMessageBuilder::new( { let (kes_signature, operational_certificate) = - KesSignerFake::dummy_signature(); + KesSignerFake::dummy_signature(current_function_name); let kes_signer = KesSignerFake::new(vec![Ok(( kes_signature, operational_certificate.clone(), @@ -177,9 +179,10 @@ mod tests { res.unwrap().unwrap(); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn pallas_dmq_signature_publisher_fails() { - let socket_path = create_temp_dir(current_function!()).join("node.socket"); + let current_function_name = current_function!(); + let socket_path = create_temp_dir(current_function_name).join("node.socket"); let reply_success = false; let server = setup_dmq_server(socket_path.clone(), reply_success); let client = tokio::spawn(async move { @@ -192,7 +195,7 @@ mod tests { DmqMessageBuilder::new( { let (kes_signature, operational_certificate) = - KesSignerFake::dummy_signature(); + KesSignerFake::dummy_signature(current_function_name); let kes_signer = KesSignerFake::new(vec![Ok(( kes_signature, operational_certificate.clone(), diff --git a/internal/mithril-dmq/tests/consumer_client_server.rs b/internal/mithril-dmq/tests/consumer_client_server.rs index 91f94190c0b..d3b35317f55 100644 --- a/internal/mithril-dmq/tests/consumer_client_server.rs +++ b/internal/mithril-dmq/tests/consumer_client_server.rs @@ -15,10 +15,11 @@ use mithril_dmq::{ DmqMessage, DmqMessageBuilder, test::payload::DmqMessageTestPayload, }; -async fn create_fake_msg(bytes: &[u8]) -> DmqMessage { +async fn create_fake_msg(bytes: &[u8], test_directory: &str) -> DmqMessage { let dmq_builder = DmqMessageBuilder::new( { - let (kes_signature, operational_certificate) = KesSignerFake::dummy_signature(); + let (kes_signature, operational_certificate) = + KesSignerFake::dummy_signature(test_directory); let kes_signer = KesSignerFake::new(vec![Ok((kes_signature, operational_certificate.clone()))]); @@ -33,9 +34,10 @@ async fn create_fake_msg(bytes: &[u8]) -> DmqMessage { #[tokio::test(flavor = "multi_thread")] async fn dmq_consumer_client_server() { + let current_function_name = current_function!(); let cardano_network = CardanoNetwork::TestNet(0); let socket_path = - TempDir::create_with_short_path("dmq_consumer_client_server", current_function!()) + TempDir::create_with_short_path("dmq_consumer_client_server", current_function_name) .join("node.socket"); let (stop_tx, stop_rx) = watch::channel(()); @@ -66,10 +68,16 @@ async fn dmq_consumer_client_server() { slog_scope::logger(), ); let mut messages = vec![]; - signature_dmq_tx.send(create_fake_msg(b"msg_1").await).unwrap(); - signature_dmq_tx.send(create_fake_msg(b"msg_2").await).unwrap(); + signature_dmq_tx + .send(create_fake_msg(b"msg_1", current_function_name).await) + .unwrap(); + signature_dmq_tx + .send(create_fake_msg(b"msg_2", current_function_name).await) + .unwrap(); messages.extend_from_slice(&consumer_client.consume_messages().await.unwrap()); - signature_dmq_tx.send(create_fake_msg(b"msg_3").await).unwrap(); + signature_dmq_tx + .send(create_fake_msg(b"msg_3", current_function_name).await) + .unwrap(); messages.extend_from_slice(&consumer_client.consume_messages().await.unwrap()); messages.into_iter().map(|(msg, _)| msg).collect::>() @@ -100,7 +108,9 @@ async fn dmq_consumer_client_server() { slog_scope::logger(), ); let mut messages = vec![]; - signature_dmq_tx.send(create_fake_msg(b"msg_4").await).unwrap(); + signature_dmq_tx + .send(create_fake_msg(b"msg_4", current_function_name).await) + .unwrap(); messages.extend_from_slice(&consumer_client.consume_messages().await.unwrap()); stop_tx.send(()).unwrap(); diff --git a/internal/mithril-dmq/tests/publisher_client_server.rs b/internal/mithril-dmq/tests/publisher_client_server.rs index 9ec930cf5e8..8aebd9c6f6d 100644 --- a/internal/mithril-dmq/tests/publisher_client_server.rs +++ b/internal/mithril-dmq/tests/publisher_client_server.rs @@ -15,10 +15,11 @@ use mithril_dmq::{ DmqPublisherServer, DmqPublisherServerPallas, test::payload::DmqMessageTestPayload, }; -async fn create_fake_msg(bytes: &[u8]) -> DmqMessage { +async fn create_fake_msg(bytes: &[u8], test_directory: &str) -> DmqMessage { let dmq_builder = DmqMessageBuilder::new( { - let (kes_signature, operational_certificate) = KesSignerFake::dummy_signature(); + let (kes_signature, operational_certificate) = + KesSignerFake::dummy_signature(test_directory); let kes_signer = KesSignerFake::new(vec![Ok((kes_signature, operational_certificate.clone()))]); @@ -33,9 +34,10 @@ async fn create_fake_msg(bytes: &[u8]) -> DmqMessage { #[tokio::test] async fn dmq_publisher_client_server() { + let current_function_name = current_function!(); let cardano_network = CardanoNetwork::TestNet(0); let socket_path = - TempDir::create_with_short_path("dmq_publisher_client_server", current_function!()) + TempDir::create_with_short_path("dmq_publisher_client_server", current_function_name) .join("node.socket"); let (stop_tx, stop_rx) = watch::channel(()); @@ -64,7 +66,8 @@ async fn dmq_publisher_client_server() { async move { let dmq_builder = DmqMessageBuilder::new( { - let (kes_signature, operational_certificate) = KesSignerFake::dummy_signature(); + let (kes_signature, operational_certificate) = + KesSignerFake::dummy_signature(current_function_name); let kes_signer = KesSignerFake::new(vec![ Ok((kes_signature, operational_certificate.clone())), Ok((kes_signature, operational_certificate.clone())), @@ -105,7 +108,8 @@ async fn dmq_publisher_client_server() { async move { let dmq_builder = DmqMessageBuilder::new( { - let (kes_signature, operational_certificate) = KesSignerFake::dummy_signature(); + let (kes_signature, operational_certificate) = + KesSignerFake::dummy_signature(current_function_name); let kes_signer = KesSignerFake::new(vec![ Ok((kes_signature, operational_certificate.clone())), Ok((kes_signature, operational_certificate.clone())), @@ -153,9 +157,9 @@ async fn dmq_publisher_client_server() { let (_, _, messages) = tokio::try_join!(server, client, recorder).unwrap(); assert_eq!( vec![ - create_fake_msg(b"msg_1").await, - create_fake_msg(b"msg_2").await, - create_fake_msg(b"msg_3").await, + create_fake_msg(b"msg_1", current_function_name).await, + create_fake_msg(b"msg_2", current_function_name).await, + create_fake_msg(b"msg_3", current_function_name).await, ], messages ); diff --git a/mithril-common/src/test/crypto_helper/cardano/kes/signer_fake.rs b/mithril-common/src/test/crypto_helper/cardano/kes/signer_fake.rs index 5a76f7c1ad3..7eb9073ee3d 100644 --- a/mithril-common/src/test/crypto_helper/cardano/kes/signer_fake.rs +++ b/mithril-common/src/test/crypto_helper/cardano/kes/signer_fake.rs @@ -27,7 +27,7 @@ impl KesSignerFake { } /// Returns a dummy signature result that is always successful. - pub fn dummy_signature() -> (Sum6KesSig, OpCert) { + pub fn dummy_signature(test_directory: &str) -> (Sum6KesSig, OpCert) { let KesCryptographicMaterialForTest { party_id: _, operational_certificate_file, @@ -35,10 +35,11 @@ impl KesSignerFake { } = create_kes_cryptographic_material( 1 as KesPartyIndexForTest, 0 as KesPeriod, - "fake_kes_signer_returns_signature_batches_in_expected_order", + &format!("{}-kes", test_directory), ); let message = b"Test message for KES signing"; - let kes_signer = KesSignerStandard::new(kes_secret_key_file, operational_certificate_file); + let kes_signer = + KesSignerStandard::new(kes_secret_key_file.clone(), operational_certificate_file); let kes_signing_period = 1; let (kes_signature, op_cert) = kes_signer .sign(message, kes_signing_period) From 218636d8972b1fa511b0e7f48e29afcaa40ad3b2 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 25 Aug 2025 14:47:33 +0200 Subject: [PATCH 47/49] fix(ci): replace remaining Cardano '10.3.1' with '10.4.1' --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bcd43c70af0..ebf7b24d31a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -352,7 +352,7 @@ jobs: - mode: "leader-follower" era: ${{ fromJSON(needs.build-ubuntu.outputs.eras)[0] }} next_era: [""] - cardano_node_version: "10.3.1" + cardano_node_version: "10.4.1" hard_fork_latest_era_at_epoch: 0 run_id: "#1" extra_args: "--number-of-aggregators=2 --use-relays --relay-signer-registration-mode=passthrough --relay-signature-registration-mode=p2p" @@ -360,7 +360,7 @@ jobs: - mode: "decentralized" era: ${{ fromJSON(needs.build-ubuntu.outputs.eras)[0] }} next_era: "" - cardano_node_version: "10.3.1" + cardano_node_version: "10.4.1" hard_fork_latest_era_at_epoch: 0 run_id: "#1" extra_args: "--number-of-aggregators=2 --use-relays --relay-signer-registration-mode=p2p --relay-signature-registration-mode=p2p" @@ -368,7 +368,7 @@ jobs: - mode: "std" era: ${{ fromJSON(needs.build-ubuntu.outputs.eras)[0] }} next_era: ${{ fromJSON(needs.build-ubuntu.outputs.eras)[1] }} - cardano_node_version: "10.3.1" + cardano_node_version: "10.4.1" hard_fork_latest_era_at_epoch: 0 run_id: "#1" extra_args: "" @@ -376,7 +376,7 @@ jobs: - mode: "std" era: ${{ fromJSON(needs.build-ubuntu.outputs.eras)[0] }} next_era: ${{ fromJSON(needs.build-ubuntu.outputs.eras)[1] }} - cardano_node_version: "10.3.1" + cardano_node_version: "10.4.1" hard_fork_latest_era_at_epoch: 0 run_id: "#1" extra_args: "--mithril-era-regenesis-on-switch" From e75c1f6413704e691fa74caa296344c0575a1e35 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Thu, 28 Aug 2025 12:26:54 +0200 Subject: [PATCH 48/49] docs: update CHANGELOG --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0d6e1d495d..4cc58b8a529 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,9 +32,11 @@ As a minor extension, we have adopted a slightly different versioning convention - Support for Mithril era transition in the client library, CLI and WASM. - **UNSTABLE** : + - Support for certificates chain synchronization between leader/follower aggregators. + - Support for DMQ signature publisher in the signer and signature consumer in the aggregator. - - Implement automatic certificates chain synchronization between leader/follower aggregators. + - Support for fake DMQ node implementation in the relay for testing purposes. - Crates versions: From 1eb864b1dec70eb1f95e3fa3f31949f3b1811dbb Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Thu, 28 Aug 2025 12:27:05 +0200 Subject: [PATCH 49/49] chore: upgrade crate versions * mithril-dmq from `0.1.9` to `0.1.10` * mithril-aggregator from `0.7.82` to `0.7.83` * mithril-relay from `0.1.50` to `0.1.51` * mithril-signer from `0.2.266` to `0.2.267` * mithril-end-to-end from `0.4.101` to `0.4.102` * mithril-infra/assets/infra.version from `0.4.9` to `0.4.10` --- Cargo.lock | 10 +++++----- internal/mithril-dmq/Cargo.toml | 2 +- mithril-aggregator/Cargo.toml | 2 +- mithril-infra/assets/infra.version | 2 +- mithril-relay/Cargo.toml | 2 +- mithril-signer/Cargo.toml | 2 +- mithril-test-lab/mithril-end-to-end/Cargo.toml | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17b45fc0154..a0e321f8b70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3928,7 +3928,7 @@ dependencies = [ [[package]] name = "mithril-aggregator" -version = "0.7.82" +version = "0.7.83" dependencies = [ "anyhow", "async-trait", @@ -4243,7 +4243,7 @@ dependencies = [ [[package]] name = "mithril-dmq" -version = "0.1.9" +version = "0.1.10" dependencies = [ "anyhow", "async-trait", @@ -4284,7 +4284,7 @@ dependencies = [ [[package]] name = "mithril-end-to-end" -version = "0.4.101" +version = "0.4.102" dependencies = [ "anyhow", "async-recursion", @@ -4356,7 +4356,7 @@ dependencies = [ [[package]] name = "mithril-relay" -version = "0.1.50" +version = "0.1.51" dependencies = [ "anyhow", "bincode", @@ -4418,7 +4418,7 @@ dependencies = [ [[package]] name = "mithril-signer" -version = "0.2.266" +version = "0.2.267" dependencies = [ "anyhow", "async-trait", diff --git a/internal/mithril-dmq/Cargo.toml b/internal/mithril-dmq/Cargo.toml index bb4667ca847..ca6af881dfd 100644 --- a/internal/mithril-dmq/Cargo.toml +++ b/internal/mithril-dmq/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mithril-dmq" description = "Mechanisms to publish and consume messages of a 'Decentralized Message Queue network' through a DMQ node" -version = "0.1.9" +version = "0.1.10" authors.workspace = true documentation.workspace = true edition.workspace = true diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml index 8781b9ba5e0..60647219261 100644 --- a/mithril-aggregator/Cargo.toml +++ b/mithril-aggregator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-aggregator" -version = "0.7.82" +version = "0.7.83" description = "A Mithril Aggregator server" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-infra/assets/infra.version b/mithril-infra/assets/infra.version index 3afe591e5bd..aeadfff0788 100644 --- a/mithril-infra/assets/infra.version +++ b/mithril-infra/assets/infra.version @@ -1,2 +1,2 @@ -0.4.9 +0.4.10 diff --git a/mithril-relay/Cargo.toml b/mithril-relay/Cargo.toml index de0d6259e50..e0b2e336245 100644 --- a/mithril-relay/Cargo.toml +++ b/mithril-relay/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-relay" -version = "0.1.50" +version = "0.1.51" description = "A Mithril relay" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-signer/Cargo.toml b/mithril-signer/Cargo.toml index 12c70e25697..ce952f96443 100644 --- a/mithril-signer/Cargo.toml +++ b/mithril-signer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-signer" -version = "0.2.266" +version = "0.2.267" description = "A Mithril Signer" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-test-lab/mithril-end-to-end/Cargo.toml b/mithril-test-lab/mithril-end-to-end/Cargo.toml index 017746e4ce8..07d9fd8904b 100644 --- a/mithril-test-lab/mithril-end-to-end/Cargo.toml +++ b/mithril-test-lab/mithril-end-to-end/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-end-to-end" -version = "0.4.101" +version = "0.4.102" authors = { workspace = true } edition = { workspace = true } documentation = { workspace = true }