Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## v0.13.5 (TBD)

- OpenTelemetry traces are now flushed before program termination on panic ([#1643](https://github.com/0xMiden/miden-node/pull/1643)).
- Added support for the note transport layer in the network monitor ([#1660](https://github.com/0xMiden/miden-node/pull/1660)).

## v0.13.4 (2026-02-04)

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ tokio = { features = ["rt-multi-thread"], version = "1.46" }
tokio-stream = { version = "0.1" }
toml = { version = "0.9" }
tonic = { default-features = false, version = "0.14" }
tonic-health = { version = "0.14" }
tonic-prost = { version = "0.14" }
tonic-prost-build = { version = "0.14" }
tonic-reflection = { version = "0.14" }
Expand Down
2 changes: 2 additions & 0 deletions bin/network-monitor/.env
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ MIDEN_MONITOR_COUNTER_INCREMENT_INTERVAL=30s
MIDEN_MONITOR_COUNTER_LATENCY_TIMEOUT=2m
# explorer checks
MIDEN_MONITOR_EXPLORER_URL=https://scan-backend-devnet-miden.eu-central-8.gateway.fm/graphql
# note transport checks
MIDEN_MONITOR_NOTE_TRANSPORT_URL=https://transport.miden.io
1 change: 1 addition & 0 deletions bin/network-monitor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ serde_json = { version = "1.0" }
sha2 = { version = "0.10" }
tokio = { features = ["full"], workspace = true }
tonic = { features = ["codegen", "tls-native-roots", "transport"], workspace = true }
tonic-health = { workspace = true }
tracing = { workspace = true }
url = { features = ["serde"], workspace = true }
9 changes: 9 additions & 0 deletions bin/network-monitor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ miden-network-monitor start --faucet-url http://localhost:8080 --enable-otel
- `--remote-prover-urls`: Comma-separated list of remote prover URLs. If omitted or empty, prover tasks are disabled.
- `--faucet-url`: Faucet service URL for testing. If omitted, faucet testing is disabled.
- `--explorer-url`: Explorer service GraphQL endpoint. If omitted, explorer checks are disabled.
- `--note-transport-url`: Note transport service URL for health checking. If omitted, note transport checks are disabled.
- `--disable-ntx-service`: Disable the network transaction service checks (enabled by default). The network transaction service consists of two components: counter increment (sending increment transactions) and counter tracking (monitoring counter value changes).
- `--remote-prover-test-interval`: Interval at which to test the remote provers services (default: `2m`)
- `--faucet-test-interval`: Interval at which to test the faucet services (default: `2m`)
Expand All @@ -54,6 +55,7 @@ If command-line arguments are not provided, the application falls back to enviro
- `MIDEN_MONITOR_REMOTE_PROVER_URLS`: Comma-separated list of remote prover URLs. If unset or empty, prover tasks are disabled.
- `MIDEN_MONITOR_FAUCET_URL`: Faucet service URL for testing. If unset, faucet testing is disabled.
- `MIDEN_MONITOR_EXPLORER_URL`: Explorer service GraphQL endpoint. If unset, explorer checks are disabled.
- `MIDEN_MONITOR_NOTE_TRANSPORT_URL`: Note transport service URL for health checking. If unset, note transport checks are disabled.
- `MIDEN_MONITOR_DISABLE_NTX_SERVICE`: Set to `true` to disable the network transaction service checks (enabled by default). This affects both counter increment and tracking components.
- `MIDEN_MONITOR_REMOTE_PROVER_TEST_INTERVAL`: Interval at which to test the remote provers services
- `MIDEN_MONITOR_FAUCET_TEST_INTERVAL`: Interval at which to test the faucet services
Expand All @@ -78,6 +80,7 @@ Starts the network monitoring service with the web dashboard. RPC status is alwa
- Prover checks/tests: enabled when `--remote-prover-urls` (or `MIDEN_MONITOR_REMOTE_PROVER_URLS`) is provided
- Faucet testing: enabled when `--faucet-url` (or `MIDEN_MONITOR_FAUCET_URL`) is provided
- Network transaction service: enabled when `--disable-ntx-service=false` or unset (or `MIDEN_MONITOR_DISABLE_NTX_SERVICE=false` or unset)
- Note transport checks: enabled when `--note-transport-url` (or `MIDEN_MONITOR_NOTE_TRANSPORT_URL`) is provided

```bash
# Start with default configuration (RPC only)
Expand Down Expand Up @@ -205,6 +208,12 @@ The monitor application provides real-time status monitoring for the following M
- Pending notes: How many transactions are queued/unprocessed
- Last updated timestamp

### Note Transport
- **Service Health**: Checks the note transport service via the standard gRPC Health Checking Protocol
- **Metrics**:
- Service URL
- gRPC serving status (Serving, NotServing, Unknown)

## User Interface

The web dashboard provides a clean, responsive interface with the following features:
Expand Down
54 changes: 36 additions & 18 deletions bin/network-monitor/assets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,9 @@ function updateDisplay() {
detailsHtml = `
<div class="service-details">
${details.RpcStatus ? `
${details.RpcStatus.url ? `
<div class="detail-item"><strong>URL:</strong> ${details.RpcStatus.url}${renderCopyButton(details.RpcStatus.url, 'URL')}</div>
` : ''}
<div class="detail-item"><strong>Version:</strong> ${details.RpcStatus.version}</div>
${details.RpcStatus.genesis_commitment ? `
<div class="detail-item">
Expand Down Expand Up @@ -471,31 +474,31 @@ function updateDisplay() {
` : ''}
` : ''}
${details.RemoteProverStatus ? `
<div class="nested-status">
<strong>Prover Status (${details.RemoteProverStatus.url}):</strong>
<div class="detail-item"><strong>Version:</strong> ${details.RemoteProverStatus.version}</div>
<div class="detail-item"><strong>URL:</strong> ${details.RemoteProverStatus.url}${renderCopyButton(details.RemoteProverStatus.url, 'URL')}</div>
<div class="detail-item"><strong>Version:</strong> ${details.RemoteProverStatus.version}</div>
<div class="detail-item"><strong>Proof Type:</strong> ${details.RemoteProverStatus.supported_proof_type}</div>
${renderGrpcWebProbeSection(details.RemoteProverStatus.url)}
${details.RemoteProverStatus.workers && details.RemoteProverStatus.workers.length > 0 ? `
<div class="nested-status">
<strong>Supported Proof Type:</strong> ${details.RemoteProverStatus.supported_proof_type}
<strong>Workers (${details.RemoteProverStatus.workers.length}):</strong>
${details.RemoteProverStatus.workers.map(worker => `
<div class="worker-status">
<span class="worker-name">${worker.name}</span> -
<span class="worker-version">${worker.version}</span> -
<span class="worker-status-badge ${worker.status === 'Healthy' ? 'healthy' : worker.status === 'Unhealthy' ? 'unhealthy' : 'unknown'}">${worker.status}</span>
</div>
`).join('')}
</div>
${details.RemoteProverStatus.workers && details.RemoteProverStatus.workers.length > 0 ? `
<div class="nested-status">
<strong>Workers (${details.RemoteProverStatus.workers.length}):</strong>
${details.RemoteProverStatus.workers.map(worker => `
<div class="worker-status">
<span class="worker-name">${worker.name}</span> -
<span class="worker-version">${worker.version}</span> -
<span class="worker-status-badge ${worker.status === 'Healthy' ? 'healthy' : worker.status === 'Unhealthy' ? 'unhealthy' : 'unknown'}">${worker.status}</span>
</div>
`).join('')}
</div>
` : ''}
${renderGrpcWebProbeSection(details.RemoteProverStatus.url)}
</div>
` : ''}
` : ''}
${details.FaucetTest ? `
<div class="nested-status">
<strong>Faucet:</strong>
<div class="test-metrics ${service.status === 'Healthy' ? 'healthy' : 'unhealthy'}">
<div class="metric-row">
<span class="metric-label">URL:</span>
<span class="metric-value">${details.FaucetTest.url}${renderCopyButton(details.FaucetTest.url, 'URL')}</span>
</div>
<div class="metric-row">
<span class="metric-label">Success Rate:</span>
<span class="metric-value">${formatSuccessRate(details.FaucetTest.success_count, details.FaucetTest.failure_count)}</span>
Expand Down Expand Up @@ -616,6 +619,21 @@ function updateDisplay() {
</div>
</div>
` : ''}
${details.NoteTransportStatus ? `
<div class="nested-status">
<strong>Note Transport:</strong>
<div class="test-metrics ${service.status === 'Healthy' ? 'healthy' : 'unhealthy'}">
<div class="metric-row">
<span class="metric-label">URL:</span>
<span class="metric-value">${details.NoteTransportStatus.url}${renderCopyButton(details.NoteTransportStatus.url, 'URL')}</span>
</div>
<div class="metric-row">
<span class="metric-label">Serving Status:</span>
<span class="metric-value">${details.NoteTransportStatus.serving_status}</span>
</div>
</div>
</div>
` : ''}
${service.testDetails ? `
<div class="nested-status">
<strong>Proof Generation Testing (${service.testDetails.proof_type}):</strong>
Expand Down
8 changes: 8 additions & 0 deletions bin/network-monitor/src/commands/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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);

Expand Down
8 changes: 8 additions & 0 deletions bin/network-monitor/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ pub struct MonitorConfig {
)]
pub explorer_url: Option<Url>,

/// 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<Url>,

/// 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
Expand Down
2 changes: 2 additions & 0 deletions bin/network-monitor/src/faucet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
18 changes: 12 additions & 6 deletions bin/network-monitor/src/frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct ServerState {
pub ntx_increment: Option<watch::Receiver<ServiceStatus>>,
pub ntx_tracking: Option<watch::Receiver<ServiceStatus>>,
pub explorer: Option<watch::Receiver<ServiceStatus>>,
pub note_transport: Option<watch::Receiver<ServiceStatus>>,
}

/// Runs the frontend server.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions bin/network-monitor/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
35 changes: 35 additions & 0 deletions bin/network-monitor/src/monitor/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<Receiver<ServiceStatus>> {
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,
Expand Down Expand Up @@ -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,
Expand Down
Loading