@@ -471,31 +474,31 @@ function updateDisplay() {
` : ''}
` : ''}
${details.RemoteProverStatus ? `
-
+ ` : ''}
` : ''}
${details.FaucetTest ? `
Faucet:
+
+ URL:
+ ${details.FaucetTest.url}${renderCopyButton(details.FaucetTest.url, 'URL')}
+
Success Rate:
${formatSuccessRate(details.FaucetTest.success_count, details.FaucetTest.failure_count)}
@@ -616,6 +619,21 @@ function updateDisplay() {
` : ''}
+ ${details.NoteTransportStatus ? `
+
+
Note Transport:
+
+
+ URL:
+ ${details.NoteTransportStatus.url}${renderCopyButton(details.NoteTransportStatus.url, 'URL')}
+
+
+ Serving Status:
+ ${details.NoteTransportStatus.serving_status}
+
+
+
+ ` : ''}
${service.testDetails ? `
Proof Generation Testing (${service.testDetails.proof_type}):
diff --git a/bin/network-monitor/src/commands/start.rs b/bin/network-monitor/src/commands/start.rs
index 4262db445..75cd8f6e8 100644
--- a/bin/network-monitor/src/commands/start.rs
+++ b/bin/network-monitor/src/commands/start.rs
@@ -48,6 +48,13 @@ pub async fn start_monitor(config: MonitorConfig) -> Result<()> {
None
};
+ // Initialize the note transport status checker task.
+ let note_transport_rx = if config.note_transport_url.is_some() {
+ Some(tasks.spawn_note_transport_checker(&config).await?)
+ } else {
+ None
+ };
+
// Initialize the prover checkers & tests tasks, only if URLs were provided.
let prover_rxs = if config.remote_prover_urls.is_empty() {
debug!(target: COMPONENT, "No remote prover URLs configured, skipping prover tasks");
@@ -85,6 +92,7 @@ pub async fn start_monitor(config: MonitorConfig) -> Result<()> {
ntx_increment: ntx_increment_rx,
ntx_tracking: ntx_tracking_rx,
explorer: explorer_rx,
+ note_transport: note_transport_rx,
};
tasks.spawn_http_server(server_state, &config);
diff --git a/bin/network-monitor/src/config.rs b/bin/network-monitor/src/config.rs
index 7443b759f..8ae07fa27 100644
--- a/bin/network-monitor/src/config.rs
+++ b/bin/network-monitor/src/config.rs
@@ -166,6 +166,14 @@ pub struct MonitorConfig {
)]
pub explorer_url: Option,
+ /// The URL of the note transport service.
+ #[arg(
+ long = "note-transport-url",
+ env = "MIDEN_MONITOR_NOTE_TRANSPORT_URL",
+ help = "The URL of the note transport service"
+ )]
+ pub note_transport_url: Option,
+
/// Maximum time without a chain tip update before marking RPC as unhealthy.
///
/// If the chain tip does not increment within this duration, the RPC service will be
diff --git a/bin/network-monitor/src/faucet.rs b/bin/network-monitor/src/faucet.rs
index bfef177a0..370d7bb10 100644
--- a/bin/network-monitor/src/faucet.rs
+++ b/bin/network-monitor/src/faucet.rs
@@ -34,6 +34,7 @@ const MINT_AMOUNT: u64 = 1_000_000; // 1 token with 6 decimals
/// Details of a faucet test.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FaucetTestDetails {
+ pub url: String,
pub test_duration_ms: u64,
pub success_count: u64,
pub failure_count: u64,
@@ -129,6 +130,7 @@ pub async fn run_faucet_test_task(
let test_duration_ms = start_time.elapsed().as_millis() as u64;
let test_details = FaucetTestDetails {
+ url: faucet_url.to_string(),
test_duration_ms,
success_count,
failure_count,
diff --git a/bin/network-monitor/src/frontend.rs b/bin/network-monitor/src/frontend.rs
index 035db669c..ecc08d26f 100644
--- a/bin/network-monitor/src/frontend.rs
+++ b/bin/network-monitor/src/frontend.rs
@@ -26,6 +26,7 @@ pub struct ServerState {
pub ntx_increment: Option>,
pub ntx_tracking: Option>,
pub explorer: Option>,
+ pub note_transport: Option>,
}
/// Runs the frontend server.
@@ -77,9 +78,9 @@ async fn get_status(
// Collect RPC status
services.push(server_state.rpc.borrow().clone());
- // Collect explorer status if available
- if let Some(explorer_rx) = &server_state.explorer {
- services.push(explorer_rx.borrow().clone());
+ // Collect faucet status if available
+ if let Some(faucet_rx) = &server_state.faucet {
+ services.push(faucet_rx.borrow().clone());
}
// Collect all remote prover statuses
@@ -88,9 +89,9 @@ async fn get_status(
services.push(prover_test_rx.borrow().clone());
}
- // Collect faucet status if available
- if let Some(faucet_rx) = &server_state.faucet {
- services.push(faucet_rx.borrow().clone());
+ // Collect explorer status if available
+ if let Some(explorer_rx) = &server_state.explorer {
+ services.push(explorer_rx.borrow().clone());
}
// Collect counter increment status if enabled
@@ -103,6 +104,11 @@ async fn get_status(
services.push(ntx_tracking_rx.borrow().clone());
}
+ // Collect note transport status if available
+ if let Some(note_transport_rx) = &server_state.note_transport {
+ services.push(note_transport_rx.borrow().clone());
+ }
+
let network_status = NetworkStatus { services, last_updated: current_time };
axum::response::Json(network_status)
diff --git a/bin/network-monitor/src/main.rs b/bin/network-monitor/src/main.rs
index ed0f08cba..80244a47a 100644
--- a/bin/network-monitor/src/main.rs
+++ b/bin/network-monitor/src/main.rs
@@ -16,6 +16,7 @@ pub mod explorer;
pub mod faucet;
pub mod frontend;
mod monitor;
+pub mod note_transport;
pub mod remote_prover;
pub mod status;
diff --git a/bin/network-monitor/src/monitor/tasks.rs b/bin/network-monitor/src/monitor/tasks.rs
index c5b773dc3..57f5ce395 100644
--- a/bin/network-monitor/src/monitor/tasks.rs
+++ b/bin/network-monitor/src/monitor/tasks.rs
@@ -23,6 +23,7 @@ use crate::deploy::ensure_accounts_exist;
use crate::explorer::{initial_explorer_status, run_explorer_status_task};
use crate::faucet::run_faucet_test_task;
use crate::frontend::{ServerState, serve};
+use crate::note_transport::{initial_note_transport_status, run_note_transport_status_task};
use crate::remote_prover::{ProofType, generate_prover_test_payload, run_remote_prover_test_task};
use crate::status::{
ServiceStatus,
@@ -142,6 +143,39 @@ impl Tasks {
Ok(explorer_status_rx)
}
+ /// Spawn the note transport status checker task.
+ #[instrument(target = COMPONENT, name = "tasks.spawn-note-transport-checker", skip_all)]
+ pub async fn spawn_note_transport_checker(
+ &mut self,
+ config: &MonitorConfig,
+ ) -> Result> {
+ let note_transport_url =
+ config.note_transport_url.clone().expect("Note transport URL exists");
+ let name = "Note Transport".to_string();
+ let status_check_interval = config.status_check_interval;
+ let request_timeout = config.request_timeout;
+ let (tx, rx) = watch::channel(initial_note_transport_status());
+
+ let id = self
+ .handles
+ .spawn(async move {
+ run_note_transport_status_task(
+ note_transport_url,
+ name,
+ tx,
+ status_check_interval,
+ request_timeout,
+ )
+ .await;
+ })
+ .id();
+ self.names.insert(id, "note-transport-checker".to_string());
+
+ println!("Spawned note transport status checker task");
+
+ Ok(rx)
+ }
+
/// Spawn prover status and test tasks for all configured provers.
#[instrument(
parent = None,
@@ -287,6 +321,7 @@ impl Tasks {
last_checked: current_time,
error: None,
details: crate::status::ServiceDetails::FaucetTest(crate::faucet::FaucetTestDetails {
+ url: config.faucet_url.as_ref().expect("faucet URL exists").to_string(),
test_duration_ms: 0,
success_count: 0,
failure_count: 0,
diff --git a/bin/network-monitor/src/note_transport.rs b/bin/network-monitor/src/note_transport.rs
new file mode 100644
index 000000000..4556f8bf1
--- /dev/null
+++ b/bin/network-monitor/src/note_transport.rs
@@ -0,0 +1,119 @@
+// NOTE TRANSPORT STATUS CHECKER
+// ================================================================================================
+
+use std::time::Duration;
+
+use tokio::sync::watch;
+use tokio::time::MissedTickBehavior;
+use tonic::transport::{Channel, ClientTlsConfig};
+use tonic_health::pb::health_client::HealthClient;
+use tonic_health::pb::{HealthCheckRequest, health_check_response};
+use tracing::{info, instrument};
+use url::Url;
+
+use crate::status::{NoteTransportStatusDetails, ServiceDetails, ServiceStatus, Status};
+use crate::{COMPONENT, current_unix_timestamp_secs};
+
+/// Creates a `tonic` channel for the given URL, enabling TLS for `https` schemes.
+fn create_channel(url: &Url, timeout: Duration) -> Result {
+ let mut endpoint = Channel::from_shared(url.to_string()).expect("valid URL").timeout(timeout);
+
+ if url.scheme() == "https" {
+ endpoint = endpoint.tls_config(ClientTlsConfig::new().with_native_roots())?;
+ }
+
+ Ok(endpoint.connect_lazy())
+}
+
+/// Runs a task that continuously checks note transport health and updates a watch channel.
+pub async fn run_note_transport_status_task(
+ url: Url,
+ name: String,
+ status_sender: watch::Sender,
+ status_check_interval: Duration,
+ request_timeout: Duration,
+) {
+ let channel = create_channel(&url, request_timeout).expect("failed to create channel");
+ let mut health_client = HealthClient::new(channel);
+
+ let mut interval = tokio::time::interval(status_check_interval);
+ interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
+
+ loop {
+ interval.tick().await;
+
+ let current_time = current_unix_timestamp_secs();
+
+ let status = check_note_transport_status(
+ &mut health_client,
+ url.to_string(),
+ name.clone(),
+ current_time,
+ )
+ .await;
+
+ if status_sender.send(status).is_err() {
+ info!("No receivers for note transport status updates, shutting down");
+ return;
+ }
+ }
+}
+
+/// Checks the health of the note transport service via the standard gRPC Health Checking Protocol.
+#[instrument(
+ target = COMPONENT,
+ name = "check-status.note-transport",
+ skip_all,
+ ret(level = "info")
+)]
+pub(crate) async fn check_note_transport_status(
+ health_client: &mut HealthClient,
+ url: String,
+ name: String,
+ current_time: u64,
+) -> ServiceStatus {
+ let request = HealthCheckRequest { service: String::new() };
+
+ match health_client.check(request).await {
+ Ok(response) => {
+ let serving_status = response.into_inner().status();
+ let is_serving = serving_status == health_check_response::ServingStatus::Serving;
+
+ let status = if is_serving { Status::Healthy } else { Status::Unhealthy };
+ let serving_status_str = format!("{serving_status:?}");
+
+ ServiceStatus {
+ name,
+ status,
+ last_checked: current_time,
+ error: None,
+ details: ServiceDetails::NoteTransportStatus(NoteTransportStatusDetails {
+ url,
+ serving_status: serving_status_str,
+ }),
+ }
+ },
+ Err(e) => unhealthy(&name, current_time, &e),
+ }
+}
+
+/// Returns an unhealthy service status.
+fn unhealthy(name: &str, current_time: u64, err: &impl ToString) -> ServiceStatus {
+ ServiceStatus {
+ name: name.to_owned(),
+ status: Status::Unhealthy,
+ last_checked: current_time,
+ error: Some(err.to_string()),
+ details: ServiceDetails::Error,
+ }
+}
+
+pub(crate) fn initial_note_transport_status() -> ServiceStatus {
+ ServiceStatus {
+ name: "Note Transport".to_string(),
+ status: Status::Unknown,
+ last_checked: current_unix_timestamp_secs(),
+ error: None,
+ details: ServiceDetails::NoteTransportStatus(NoteTransportStatusDetails::default()),
+ }
+}
diff --git a/bin/network-monitor/src/status.rs b/bin/network-monitor/src/status.rs
index 759fb0ed9..419b837b0 100644
--- a/bin/network-monitor/src/status.rs
+++ b/bin/network-monitor/src/status.rs
@@ -170,6 +170,13 @@ pub struct ExplorerStatusDetails {
pub proof_commitment: String,
}
+/// Details of the note transport service.
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct NoteTransportStatusDetails {
+ pub url: String,
+ pub serving_status: String,
+}
+
/// Details of a service.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServiceDetails {
@@ -180,6 +187,7 @@ pub enum ServiceDetails {
NtxIncrement(IncrementDetails),
NtxTracking(CounterTrackingDetails),
ExplorerStatus(ExplorerStatusDetails),
+ NoteTransportStatus(NoteTransportStatusDetails),
Error,
}