Skip to content

Commit 95f02e8

Browse files
committed
Merge branch 'next' of github.com:0xMiden/miden-node into sergerad-store-lock-free
2 parents b8530a9 + 0670698 commit 95f02e8

24 files changed

Lines changed: 734 additions & 568 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,5 @@ node_modules/
4040
*DS_Store
4141
*.iml
4242
book/
43+
44+
.claude/

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
- [BREAKING] Renamed `GetNoteError` endpoint to `GetNetworkNoteStatus` and extended it to return the full lifecycle status of a network note (`Pending`, `Processed`, `Discarded`, `Committed`) instead of only error information. Consumed notes are now retained in the database after block commit instead of being deleted ([#1892](https://github.com/0xMiden/node/pull/1892)).
88
- Extended `ValidatorStatus` proto response with `chain_tip`, `validated_transactions_count`, and `signed_blocks_count`; added Validator card to the network monitor dashboard ([#1900](https://github.com/0xMiden/node/pull/1900)).
99
- Updated the RocksDB SMT backend to use budgeted deserialization for bytes read from disk, ported from `0xMiden/crypto` PR [#846](https://github.com/0xMiden/crypto/pull/846) ([#1923](https://github.com/0xMiden/node/pull/1923)).
10+
- [BREAKING] Network monitor `/status` endpoint now emits a single `RemoteProverStatus` entry per remote prover that bundles status, workers, and test results, instead of separate entries ([#1980](https://github.com/0xMiden/node/pull/1980)).
11+
- Refactored the validator gRPC API implementation to use the new per-method trait implementations ([#1959](https://github.com/0xMiden/node/pull/1959)).
12+
- Aligned `SyncNullifiers` list-limit validation in RPC and store with `nullifier_prefix` parameter semantics, extended `GetLimits` test coverage, and documented query parameter limits ([#1986](https://github.com/0xMiden/node/pull/1986)).
1013

1114
## v0.14.9 (2026-04-21)
1215

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/network-monitor/assets/index.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,10 @@ body {
343343
background: rgba(0, 0, 0, 0.05);
344344
border-radius: 4px;
345345
font-size: 12px;
346+
display: flex;
347+
align-items: center;
348+
gap: 8px;
349+
flex-wrap: wrap;
346350
}
347351

348352
.worker-name {

bin/network-monitor/assets/index.js

Lines changed: 64 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,11 @@ function collectGrpcWebEndpoints() {
172172
});
173173
}
174174
// Remote Prover service
175-
if (service.details.RemoteProverStatus && service.details.RemoteProverStatus.url) {
175+
const proverUrl = service.details.RemoteProverStatus?.status?.url;
176+
if (proverUrl) {
176177
endpoints.push({
177-
serviceKey: service.details.RemoteProverStatus.url,
178-
baseUrl: service.details.RemoteProverStatus.url,
178+
serviceKey: proverUrl,
179+
baseUrl: proverUrl,
179180
grpcPath: '/remote_prover.ProxyStatusApi/Status',
180181
});
181182
}
@@ -303,55 +304,6 @@ async function fetchStatus() {
303304
}
304305
}
305306

306-
// Merge Remote Prover status and test entries into a single card per prover.
307-
function mergeProverStatusAndTests(services) {
308-
const testsByName = new Map();
309-
const merged = [];
310-
const usedTests = new Set();
311-
312-
services.forEach(service => {
313-
if (service.details && service.details.RemoteProverTest) {
314-
testsByName.set(service.name, service);
315-
}
316-
});
317-
318-
services.forEach(service => {
319-
if (service.details && service.details.RemoteProverStatus) {
320-
const test = testsByName.get(service.name);
321-
if (test) {
322-
usedTests.add(service.name);
323-
}
324-
merged.push({
325-
...service,
326-
testDetails: test?.details?.RemoteProverTest ?? null,
327-
testStatus: test?.status ?? null,
328-
testError: test?.error ?? null
329-
});
330-
} else if (!(service.details && service.details.RemoteProverTest)) {
331-
// Non-prover entries pass through unchanged
332-
merged.push(service);
333-
}
334-
});
335-
336-
// Add orphaned tests (in case a test arrives before a status)
337-
testsByName.forEach((test, name) => {
338-
if (!usedTests.has(name)) {
339-
merged.push({
340-
name,
341-
status: test.status,
342-
last_checked: test.last_checked,
343-
error: test.error,
344-
details: null,
345-
testDetails: test.details.RemoteProverTest,
346-
testStatus: test.status,
347-
testError: test.error
348-
});
349-
}
350-
});
351-
352-
return merged;
353-
}
354-
355307
function updateDisplay() {
356308
if (!statusData) return;
357309

@@ -364,29 +316,28 @@ function updateDisplay() {
364316
const lastUpdateTime = new Date(statusData.last_updated * 1000);
365317
lastUpdated.textContent = lastUpdateTime.toLocaleString();
366318

367-
// Group remote prover status + test into single cards
368-
const processedServices = mergeProverStatusAndTests(statusData.services);
369-
const rpcService = processedServices.find(s => s.details && s.details.RpcStatus);
319+
const services = statusData.services;
320+
const rpcService = services.find(s => s.details && s.details.RpcStatus);
370321
const rpcChainTip =
371322
rpcService?.details?.RpcStatus?.store_status?.chain_tip ??
372323
rpcService?.details?.RpcStatus?.block_producer_status?.chain_tip ??
373324
null;
374325

375-
// Compute effective health for a service, considering all signals for remote provers.
326+
// Compute effective health
376327
const isServiceHealthy = (s) => {
377-
if (s.details && s.details.RemoteProverStatus) {
378-
const statusOk = s.status === 'Healthy';
379-
const testOk = s.testStatus == null || s.testStatus === 'Healthy';
380-
const probeResult = grpcWebProbeResults.get(s.details.RemoteProverStatus.url);
381-
const probeOk = !probeResult || probeResult.ok;
382-
return statusOk && testOk && probeOk;
328+
if (s.status !== 'Healthy') return false;
329+
const probeUrl = s.details?.RemoteProverStatus?.status?.url
330+
?? s.details?.RpcStatus?.url;
331+
if (probeUrl) {
332+
const probe = grpcWebProbeResults.get(probeUrl);
333+
if (probe && !probe.ok) return false;
383334
}
384-
return s.status === 'Healthy';
335+
return true;
385336
};
386337

387338
// Count healthy vs unhealthy services
388-
const healthyServices = processedServices.filter(isServiceHealthy).length;
389-
const totalServices = processedServices.length;
339+
const healthyServices = services.filter(isServiceHealthy).length;
340+
const totalServices = services.length;
390341
const allHealthy = healthyServices === totalServices;
391342

392343
// Update footer
@@ -404,7 +355,7 @@ function updateDisplay() {
404355
}
405356

406357
// Generate status cards
407-
const serviceCardsHtml = processedServices.map(service => {
358+
const serviceCardsHtml = services.map(service => {
408359
const isHealthy = isServiceHealthy(service);
409360
const statusColor = isHealthy ? COLOR_HEALTHY : COLOR_UNHEALTHY;
410361
const statusIcon = isHealthy ? '✓' : '✗';
@@ -499,24 +450,32 @@ function updateDisplay() {
499450
</div>
500451
` : ''}
501452
` : ''}
502-
${details.RemoteProverStatus ? `
503-
<div class="detail-item"><strong>URL:</strong> ${details.RemoteProverStatus.url}${renderCopyButton(details.RemoteProverStatus.url, 'URL')}</div>
504-
<div class="detail-item"><strong>Version:</strong> ${details.RemoteProverStatus.version}</div>
505-
<div class="detail-item"><strong>Proof Type:</strong> ${details.RemoteProverStatus.supported_proof_type}</div>
506-
${renderGrpcWebProbeSection(details.RemoteProverStatus.url)}
507-
${details.RemoteProverStatus.workers && details.RemoteProverStatus.workers.length > 0 ? `
508-
<div class="nested-status">
509-
<strong>Workers (${details.RemoteProverStatus.workers.length}):</strong>
510-
${details.RemoteProverStatus.workers.map(worker => `
511-
<div class="worker-status">
512-
<span class="worker-name">${worker.name}</span> -
513-
<span class="worker-version">${worker.version}</span> -
514-
<span class="worker-status-badge ${worker.status === 'Healthy' ? 'healthy' : worker.status === 'Unhealthy' ? 'unhealthy' : 'unknown'}">${worker.status}</span>
515-
</div>
516-
`).join('')}
517-
</div>
518-
` : ''}
519-
` : ''}
453+
${details.RemoteProverStatus ? (() => {
454+
const p = details.RemoteProverStatus.status;
455+
return `
456+
<div class="detail-item"><strong>URL:</strong> ${p.url}${renderCopyButton(p.url, 'URL')}</div>
457+
<div class="detail-item"><strong>Version:</strong> ${p.version}</div>
458+
<div class="detail-item"><strong>Proof Type:</strong> ${p.supported_proof_type}</div>
459+
${renderGrpcWebProbeSection(p.url)}
460+
${p.workers && p.workers.length > 0 ? `
461+
<div class="nested-status">
462+
<strong>Workers (${p.workers.length}):</strong>
463+
${p.workers.map(worker => {
464+
const nameDisplay = worker.name.length > 20
465+
? `${worker.name.substring(0, 20)}...${renderCopyButton(worker.name, 'worker name')}`
466+
: worker.name;
467+
return `
468+
<div class="worker-status">
469+
<span class="worker-name">${nameDisplay}</span>
470+
<span class="worker-version">${worker.version}</span>
471+
<span class="worker-status-badge ${worker.status === 'Healthy' ? 'healthy' : worker.status === 'Unhealthy' ? 'unhealthy' : 'unknown'}">${worker.status}</span>
472+
</div>
473+
`;
474+
}).join('')}
475+
</div>
476+
` : ''}
477+
`;
478+
})() : ''}
520479
${details.FaucetTest ? `
521480
<div class="nested-status">
522481
<strong>Faucet:</strong>
@@ -683,25 +642,29 @@ function updateDisplay() {
683642
</div>
684643
</div>
685644
` : ''}
686-
${service.testDetails ? `
687-
<div class="nested-status">
688-
<strong>Proof Generation Testing (${service.testDetails.proof_type}):</strong>
689-
<div class="test-metrics ${service.testStatus === 'Healthy' ? 'healthy' : 'unhealthy'}">
690-
<div class="metric-row">
691-
<span class="metric-label">Success Rate:</span>
692-
<span class="metric-value">${formatSuccessRate(service.testDetails.success_count, service.testDetails.failure_count)}</span>
693-
</div>
694-
<div class="metric-row">
695-
<span class="metric-label">Last Response Time:</span>
696-
<span class="metric-value">${service.testDetails.test_duration_ms}ms</span>
697-
</div>
698-
<div class="metric-row">
699-
<span class="metric-label">Last Proof Size:</span>
700-
<span class="metric-value">${(service.testDetails.proof_size_bytes / 1024).toFixed(2)} KB</span>
645+
${details.RemoteProverStatus?.test ? (() => {
646+
const t = details.RemoteProverStatus.test.details;
647+
const ts = details.RemoteProverStatus.test.status;
648+
return `
649+
<div class="nested-status">
650+
<strong>Proof Generation Testing (${t.proof_type}):</strong>
651+
<div class="test-metrics ${ts === 'Healthy' ? 'healthy' : 'unhealthy'}">
652+
<div class="metric-row">
653+
<span class="metric-label">Success Rate:</span>
654+
<span class="metric-value">${formatSuccessRate(t.success_count, t.failure_count)}</span>
655+
</div>
656+
<div class="metric-row">
657+
<span class="metric-label">Last Response Time:</span>
658+
<span class="metric-value">${t.test_duration_ms}ms</span>
659+
</div>
660+
<div class="metric-row">
661+
<span class="metric-label">Last Proof Size:</span>
662+
<span class="metric-value">${(t.proof_size_bytes / 1024).toFixed(2)} KB</span>
663+
</div>
701664
</div>
702665
</div>
703-
</div>
704-
` : ''}
666+
`;
667+
})() : ''}
705668
</div>
706669
`;
707670
}
@@ -864,4 +827,3 @@ window.addEventListener('beforeunload', () => {
864827
clearInterval(grpcWebProbeInterval);
865828
}
866829
});
867-

bin/network-monitor/src/commands/start.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,20 @@ pub async fn start_monitor(config: MonitorConfig) -> Result<()> {
9292

9393
// Initialize HTTP server.
9494
debug!(target: COMPONENT, "Initializing HTTP server");
95+
96+
// Build the flat services Vec in the order the dashboard expects to render cards.
97+
let services = std::iter::once(rpc_rx)
98+
.chain(prover_rxs)
99+
.chain(faucet_rx)
100+
.chain(explorer_rx)
101+
.chain(ntx_increment_rx)
102+
.chain(ntx_tracking_rx)
103+
.chain(note_transport_rx)
104+
.chain(validator_rx)
105+
.collect();
106+
95107
let server_state = ServerState {
96-
rpc: rpc_rx,
97-
provers: prover_rxs,
98-
faucet: faucet_rx,
99-
ntx_increment: ntx_increment_rx,
100-
ntx_tracking: ntx_tracking_rx,
101-
explorer: explorer_rx,
102-
note_transport: note_transport_rx,
103-
validator: validator_rx,
108+
services,
104109
monitor_version: env!("CARGO_PKG_VERSION").to_string(),
105110
network_name: config.network_name.clone(),
106111
};

bin/network-monitor/src/frontend.rs

Lines changed: 12 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,13 @@ use crate::status::{NetworkStatus, ServiceStatus};
1818
// ================================================================================================
1919

2020
/// State for the web server containing watch receivers for all services.
21+
///
22+
/// Each entry in `services` is a `ServiceStatus` channel. The frontend simply snapshots every
23+
/// entry on each `/status` request. Adding a new service is just pushing another receiver into
24+
/// this Vec at startup; no changes to this struct or `get_status` are required.
2125
#[derive(Clone)]
2226
pub struct ServerState {
23-
pub rpc: watch::Receiver<ServiceStatus>,
24-
pub provers: Vec<(watch::Receiver<ServiceStatus>, watch::Receiver<ServiceStatus>)>,
25-
pub faucet: Option<watch::Receiver<ServiceStatus>>,
26-
pub ntx_increment: Option<watch::Receiver<ServiceStatus>>,
27-
pub ntx_tracking: Option<watch::Receiver<ServiceStatus>>,
28-
pub explorer: Option<watch::Receiver<ServiceStatus>>,
29-
pub note_transport: Option<watch::Receiver<ServiceStatus>>,
30-
pub validator: Option<watch::Receiver<ServiceStatus>>,
27+
pub services: Vec<watch::Receiver<ServiceStatus>>,
3128
pub monitor_version: String,
3229
pub network_name: String,
3330
}
@@ -71,60 +68,20 @@ async fn get_dashboard() -> Html<&'static str> {
7168
async fn get_status(
7269
axum::extract::State(server_state): axum::extract::State<ServerState>,
7370
) -> axum::response::Json<NetworkStatus> {
74-
let current_time = SystemTime::now()
71+
let services: Vec<ServiceStatus> =
72+
server_state.services.iter().map(|rx| rx.borrow().clone()).collect();
73+
74+
let last_updated = SystemTime::now()
7575
.duration_since(UNIX_EPOCH)
7676
.unwrap_or_else(|_| Duration::from_secs(0))
7777
.as_secs();
7878

79-
let mut services = Vec::new();
80-
81-
// Collect RPC status
82-
services.push(server_state.rpc.borrow().clone());
83-
84-
// Collect faucet status if available
85-
if let Some(faucet_rx) = &server_state.faucet {
86-
services.push(faucet_rx.borrow().clone());
87-
}
88-
89-
// Collect all remote prover statuses
90-
for (prover_status_rx, prover_test_rx) in &server_state.provers {
91-
services.push(prover_status_rx.borrow().clone());
92-
services.push(prover_test_rx.borrow().clone());
93-
}
94-
95-
// Collect explorer status if available
96-
if let Some(explorer_rx) = &server_state.explorer {
97-
services.push(explorer_rx.borrow().clone());
98-
}
99-
100-
// Collect counter increment status if enabled
101-
if let Some(ntx_increment_rx) = &server_state.ntx_increment {
102-
services.push(ntx_increment_rx.borrow().clone());
103-
}
104-
105-
// Collect counter tracking status if enabled
106-
if let Some(ntx_tracking_rx) = &server_state.ntx_tracking {
107-
services.push(ntx_tracking_rx.borrow().clone());
108-
}
109-
110-
// Collect note transport status if available
111-
if let Some(note_transport_rx) = &server_state.note_transport {
112-
services.push(note_transport_rx.borrow().clone());
113-
}
114-
115-
// Collect validator status if available
116-
if let Some(validator_rx) = &server_state.validator {
117-
services.push(validator_rx.borrow().clone());
118-
}
119-
120-
let network_status = NetworkStatus {
79+
axum::response::Json(NetworkStatus {
12180
services,
122-
last_updated: current_time,
81+
last_updated,
12382
monitor_version: server_state.monitor_version.clone(),
12483
network_name: server_state.network_name.clone(),
125-
};
126-
127-
axum::response::Json(network_status)
84+
})
12885
}
12986

13087
async fn serve_css() -> Response {

0 commit comments

Comments
 (0)