mina_node_native/http_server/routes/
snarker.rs1use std::str::FromStr;
9
10use axum::{
11 body::Body,
12 extract::{Query, State},
13 http::{header, HeaderMap, Response, StatusCode},
14 Json,
15};
16use mina_p2p_messages::binprot::BinProtWrite;
17use serde::Deserialize;
18use utoipa_axum::{router::OpenApiRouter, routes};
19
20use mina_node::{
21 core::snark::SnarkJobId,
22 rpc::{
23 RpcRequest, RpcSnarkerConfigGetResponse, RpcSnarkerJobCommitResponse,
24 RpcSnarkerJobSpecResponse, RpcSnarkerWorkersResponse,
25 },
26};
27
28use crate::http_server::{AppError, AppResult, AppState};
29
30pub fn routes() -> OpenApiRouter<AppState> {
32 OpenApiRouter::new()
33 .routes(routes!(job_commit))
34 .routes(routes!(job_spec))
35 .routes(routes!(workers))
36 .routes(routes!(config))
37}
38
39#[utoipa::path(
41 post,
42 path = "/snarker/job/commit",
43 tag = "snarker",
44 responses(
45 (status = 201, description = "Job committed"),
46 (status = 400, description = "Invalid input or job error")
47 )
48)]
49async fn job_commit(
50 State(state): State<AppState>,
51 body: String,
52) -> AppResult<(StatusCode, Json<RpcSnarkerJobCommitResponse>)> {
53 let job_id = SnarkJobId::from_str(&body)
57 .map_err(|_| AppError::Json(StatusCode::BAD_REQUEST, serde_json::json!("invalid_input")))?;
58
59 let resp: RpcSnarkerJobCommitResponse =
60 rpc_request!(state, RpcRequest::SnarkerJobCommit { job_id })?;
61
62 let status = match &resp {
63 RpcSnarkerJobCommitResponse::Ok => StatusCode::CREATED,
64 _ => StatusCode::BAD_REQUEST,
65 };
66
67 Ok((status, Json(resp)))
68}
69
70#[derive(Deserialize)]
71struct JobSpecQuery {
72 id: SnarkJobId,
73}
74
75#[utoipa::path(
79 get,
80 path = "/snarker/job/spec",
81 tag = "snarker",
82 params(
83 ("id" = String, Query, description = "Snark job ID")
84 ),
85 responses(
86 (status = 200, description = "JSON job spec", content_type = "application/json"),
87 (status = 200, description = "Binprot job spec", content_type = "application/octet-stream"),
88 (status = 400, description = "Job not found")
89 )
90)]
91async fn job_spec(
92 State(state): State<AppState>,
93 headers: HeaderMap,
94 Query(JobSpecQuery { id: job_id }): Query<JobSpecQuery>,
95) -> AppResult<Response<Body>> {
96 let resp: RpcSnarkerJobSpecResponse =
97 rpc_request!(state, RpcRequest::SnarkerJobSpec { job_id })?;
98
99 let accept = headers
100 .get(header::ACCEPT)
101 .and_then(|v| v.to_str().ok())
102 .unwrap_or("");
103
104 match resp {
105 RpcSnarkerJobSpecResponse::Ok(spec) if accept == "application/octet-stream" => {
106 let mut vec = Vec::new();
108 spec.binprot_write(&mut vec)
109 .map_err(|e| AppError::Internal(format!("binprot serialization failed: {e}")))?;
110
111 let mut result = Vec::with_capacity(vec.len() + std::mem::size_of::<u64>());
112 result.extend((vec.len() as u64).to_le_bytes());
113 result.extend(vec);
114
115 Response::builder()
116 .status(StatusCode::OK)
117 .header(header::CONTENT_TYPE, "application/octet-stream")
118 .body(Body::from(result))
119 .map_err(|e| AppError::Internal(e.to_string()))
120 }
121 RpcSnarkerJobSpecResponse::Ok(spec) => {
122 let body = serde_json::to_vec(&spec)
124 .map_err(|e| AppError::Internal(format!("JSON serialization failed: {e}")))?;
125
126 Response::builder()
127 .status(StatusCode::OK)
128 .header(header::CONTENT_TYPE, "application/json")
129 .body(Body::from(body))
130 .map_err(|e| AppError::Internal(e.to_string()))
131 }
132 _ => {
133 let body = serde_json::to_vec(&"error")
135 .map_err(|e| AppError::Internal(format!("JSON serialization failed: {e}")))?;
136
137 Response::builder()
138 .status(StatusCode::BAD_REQUEST)
139 .header(header::CONTENT_TYPE, "application/json")
140 .body(Body::from(body))
141 .map_err(|e| AppError::Internal(e.to_string()))
142 }
143 }
144}
145
146#[utoipa::path(
148 get,
149 path = "/snarker/workers",
150 tag = "snarker",
151 responses(
152 (status = 200, description = "Snarker workers")
153 )
154)]
155async fn workers(State(state): State<AppState>) -> AppResult<Json<RpcSnarkerWorkersResponse>> {
156 jsonify_rpc!(state, RpcRequest::SnarkerWorkers)
157}
158
159#[utoipa::path(
161 get,
162 path = "/snarker/config",
163 tag = "snarker",
164 responses(
165 (status = 200, description = "Snarker configuration")
166 )
167)]
168async fn config(State(state): State<AppState>) -> AppResult<Json<RpcSnarkerConfigGetResponse>> {
169 jsonify_rpc!(state, RpcRequest::SnarkerConfig)
170}