Skip to main content

mina_node_native/http_server/routes/
status.rs

1//! Status and health check endpoints.
2//!
3//! - `GET /build_env` - Build environment information
4//! - `GET /status` - Node status
5//! - `GET /healthz` - Kubernetes health check
6//! - `GET /readyz` - Kubernetes readiness check
7//! - `POST /make_heartbeat` - Trigger a heartbeat
8
9use axum::{extract::State, Json};
10use utoipa_axum::{router::OpenApiRouter, routes};
11
12use mina_node::{
13    rpc::{
14        RpcHealthCheckResponse, RpcHeartbeatGetResponse, RpcNodeStatus, RpcReadinessCheckResponse,
15        RpcRequest, RpcStatusGetResponse,
16    },
17    BuildEnv,
18};
19
20use crate::http_server::{AppError, AppResult, AppState, JsonErrorResponse};
21
22/// Returns status routes as an OpenApiRouter.
23pub fn routes() -> OpenApiRouter<AppState> {
24    OpenApiRouter::new()
25        .routes(routes!(build_env))
26        .routes(routes!(status))
27        .routes(routes!(healthz))
28        .routes(routes!(readyz))
29        .routes(routes!(make_heartbeat))
30}
31
32/// Build environment information
33#[utoipa::path(
34    get,
35    path = "/build_env",
36    tag = "status",
37    responses(
38        (status = 200, body = BuildEnv)
39    )
40)]
41async fn build_env() -> Json<BuildEnv> {
42    Json(mina_node::BuildEnv::get())
43}
44
45/// Current node status
46#[utoipa::path(
47    get,
48    path = "/status",
49    tag = "status",
50    responses(
51        (status = 200, description = "Current node status", body = RpcNodeStatus)
52    )
53)]
54async fn status(State(state): State<AppState>) -> AppResult<Json<RpcNodeStatus>> {
55    let reply: RpcStatusGetResponse = rpc_request!(state, RpcRequest::StatusGet)?;
56    reply.map(Json).ok_or(AppError::Internal(
57        "StatusGet should always return Some(...)".into(),
58    ))
59}
60
61/// Liveness probe
62#[utoipa::path(
63    get,
64    path = "/healthz",
65    tags = ["status", "kubernetes"],
66    responses(
67        (status = 200, description = "Node is healthy"),
68        (status = 503, description = "Node is unhealthy", body = JsonErrorResponse,
69            examples(
70                ("NoReadyPeers" = (value = json!({"error": "no ready peers"}))),
71            ))
72    )
73)]
74async fn healthz(State(state): State<AppState>) -> AppResult<()> {
75    let reply: RpcHealthCheckResponse = rpc_request!(state, RpcRequest::HealthCheck)?;
76    reply.map_err(AppError::ServiceUnavailable)
77}
78
79/// Readiness probe
80#[utoipa::path(
81    get,
82    path = "/readyz",
83    tags = ["status", "kubernetes"],
84    responses(
85        (status = 200, description = "Node is ready to accept traffic"),
86        (status = 503, description = "Node is not ready", body = JsonErrorResponse,
87            examples(
88                ("NotSynced" = (value = json!({"error": "not synced"}))),
89                ("Desynced" = (value = json!({"error": "Synced 2000s ago, which is more than the threshold 1800s"}))),
90            )),
91    )
92)]
93async fn readyz(State(state): State<AppState>) -> AppResult<()> {
94    let reply: RpcReadinessCheckResponse = rpc_request!(state, RpcRequest::ReadinessCheck)?;
95    reply.map_err(AppError::ServiceUnavailable)
96}
97
98/// Trigger heartbeat
99#[utoipa::path(
100    post,
101    path = "/make_heartbeat",
102    tag = "status",
103    responses(
104        // inline: type alias for Option<T> would register as "Option"
105        (status = 200, description = "Heartbeat triggered successfully", body = inline(RpcHeartbeatGetResponse))
106    )
107)]
108async fn make_heartbeat(State(state): State<AppState>) -> AppResult<Json<RpcHeartbeatGetResponse>> {
109    jsonify_rpc!(state, RpcRequest::HeartbeatGet)
110}