1mod axum;
5pub mod error;
6mod render;
7pub mod v1;
8
9pub mod proto {
13 #![allow(clippy::useless_borrows_in_formatting)]
15 #![expect(
16 clippy::large_enum_variant,
17 reason = "prost lays every oneof arm out inline, so the ADVZ transaction proof dwarfs its \
18 siblings, and boxing it (`Builder::boxed`) breaks pbjson-build's serde"
19 )]
20
21 include!(concat!(env!("OUT_DIR"), "/espresso.api.v2.rs"));
22 include!(concat!(env!("OUT_DIR"), "/espresso.api.v2.serde.rs"));
23}
24
25pub mod rest {
28 #![allow(unfulfilled_lint_expectations)]
30 #![expect(
31 clippy::too_many_arguments,
32 reason = "`all_rest_routes` takes one service per proto file, so its arity grows with the \
33 API"
34 )]
35
36 include!(concat!(env!("OUT_DIR"), "/espresso.api.v2.rest.rs"));
37}
38
39pub const FILE_DESCRIPTOR_SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/descriptor.bin"));
41
42use tower::Layer;
43
44pub use self::axum::{create_router_v1, routes};
46use self::proto::{
47 availability_service_server::{AvailabilityService, AvailabilityServiceServer},
48 catchup_service_server::{CatchupService, CatchupServiceServer},
49 config_service_server::{ConfigService, ConfigServiceServer},
50 database_service_server::{DatabaseService, DatabaseServiceServer},
51 explorer_service_server::{ExplorerService, ExplorerServiceServer},
52 light_client_service_server::{LightClientService, LightClientServiceServer},
53 merklized_state_service_server::{MerklizedStateService, MerklizedStateServiceServer},
54 node_service_server::{NodeService, NodeServiceServer},
55 reward_state_service_server::{RewardStateService, RewardStateServiceServer},
56 state_signature_service_server::{StateSignatureService, StateSignatureServiceServer},
57 status_service_server::{StatusService, StatusServiceServer},
58 submit_service_server::{SubmitService, SubmitServiceServer},
59 token_service_server::{TokenService, TokenServiceServer},
60};
61
62pub fn url(base: &::url::Url, path: impl AsRef<str>) -> ::url::Url {
69 base.join(path.as_ref())
70 .expect("path produced by routes::*::path_fn is always a valid relative URL")
71}
72
73pub async fn serve_axum<S>(
84 port: u16,
85 state: S,
86 modules: OptionalModules,
87 max_connections: Option<usize>,
88) -> anyhow::Result<()>
89where
90 S: v1::RewardApi
91 + v1::AvailabilityApi
92 + v1::HotShotAvailabilityApi
93 + v1::BlockStateApi
94 + v1::FeeStateApi
95 + v1::StatusApi
96 + v1::ConfigApi
97 + v1::NodeApi
98 + v1::CatchupApi
99 + v1::SubmitApi
100 + v1::StateSignatureApi
101 + v1::LightClientApi
102 + v1::ExplorerApi
103 + v1::TokenApi
104 + v1::DatabaseApi
105 + StatusService
106 + TokenService
107 + NodeService
108 + ConfigService
109 + DatabaseService
110 + AvailabilityService
111 + MerklizedStateService
112 + RewardStateService
113 + StateSignatureService
114 + SubmitService
115 + ExplorerService
116 + CatchupService
117 + LightClientService
118 + Send
119 + Sync
120 + 'static,
121{
122 let listener = bind_api(port).await?;
123 let state = std::sync::Arc::new(state);
124 let mut router = axum::router_reward(state.clone())
125 .merge(axum::router_availability(state.clone()))
126 .merge(axum::router_block_state(state.clone()))
127 .merge(axum::router_fee_state(state.clone()))
128 .merge(axum::router_status(state.clone()))
129 .merge(axum::router_node(state.clone()))
130 .merge(axum::router_catchup(state.clone()))
131 .merge(axum::router_state_signature(state.clone()))
132 .merge(axum::router_token(state.clone()))
133 .merge(axum::router_database(state.clone()))
134 .merge(axum::router_light_client(state.clone()));
135 if modules.submit {
136 router = router.merge(axum::router_submit(state.clone()));
137 }
138 if modules.config {
139 router = router.merge(axum::router_config(state.clone()));
140 }
141 if modules.explorer {
142 router = router.merge(axum::router_explorer(state.clone()));
143 }
144
145 let router = axum::finish_v1_docs(router)
146 .merge(router_v2(state, modules))
147 .merge(axum::router_v2_docs());
148 serve_router(listener, "v1 and v2", router, max_connections).await
149}
150
151pub(crate) fn router_v2<S>(state: std::sync::Arc<S>, modules: OptionalModules) -> ::axum::Router
154where
155 S: StatusService
156 + TokenService
157 + NodeService
158 + ConfigService
159 + DatabaseService
160 + AvailabilityService
161 + MerklizedStateService
162 + RewardStateService
163 + StateSignatureService
164 + SubmitService
165 + ExplorerService
166 + CatchupService
167 + LightClientService
168 + Send
169 + Sync
170 + 'static,
171{
172 let router = rest::status_service_rest_router(state.clone())
173 .merge(rest::token_service_rest_router(state.clone()))
174 .merge(rest::node_service_rest_router(state.clone()))
175 .merge(rest::database_service_rest_router(state.clone()))
176 .merge(rest::availability_service_rest_router(state.clone()))
177 .merge(rest::merklized_state_service_rest_router(state.clone()))
178 .merge(rest::reward_state_service_rest_router(state.clone()))
179 .merge(rest::state_signature_service_rest_router(state.clone()))
180 .merge(rest::catchup_service_rest_router(state.clone()))
181 .merge(rest::light_client_service_rest_router(state.clone()));
182 let router = if modules.submit {
183 router.merge(rest::submit_service_rest_router(state.clone()))
184 } else {
185 router.merge(axum::router_module_disabled(
186 "submit",
187 routes::v2::SUBMIT_ROUTES,
188 ))
189 };
190 let router = if modules.explorer {
191 router.merge(rest::explorer_service_rest_router(state.clone()))
192 } else {
193 router.merge(axum::router_module_disabled(
194 "explorer",
195 routes::v2::EXPLORER_ROUTES,
196 ))
197 };
198 let router = if modules.config {
199 router.merge(rest::config_service_rest_router(state))
200 } else {
201 router.merge(axum::router_module_disabled(
202 "config",
203 routes::v2::CONFIG_ROUTES,
204 ))
205 };
206 router
207 .layer(::axum::middleware::from_fn(axum::v2_refuse_post_query))
208 .layer(::axum::middleware::from_fn(axum::v2_error_envelope))
209}
210
211#[derive(Default, Clone, Copy, Debug)]
214pub struct OptionalModules {
215 pub submit: bool,
216 pub catchup: bool,
217 pub config: bool,
218 pub explorer: bool,
219}
220
221pub async fn serve_axum_fs<S>(
227 port: u16,
228 state: S,
229 modules: OptionalModules,
230 max_connections: Option<usize>,
231) -> anyhow::Result<()>
232where
233 S: v1::StatusApi
234 + v1::AvailabilityApi
235 + v1::HotShotAvailabilityApi
236 + v1::NodeApi
237 + v1::TokenApi
238 + v1::CatchupApi
239 + v1::SubmitApi
240 + v1::StateSignatureApi
241 + v1::ConfigApi
242 + Send
243 + Sync
244 + 'static,
245{
246 let listener = bind_api(port).await?;
247 let state = std::sync::Arc::new(state);
248 let mut router = axum::router_status(state.clone())
249 .merge(axum::router_availability(state.clone()))
250 .merge(axum::router_node(state.clone()))
251 .merge(axum::router_token(state.clone()))
252 .merge(axum::router_catchup(state.clone()))
253 .merge(axum::router_state_signature(state.clone()));
254 if modules.submit {
255 router = router.merge(axum::router_submit(state.clone()));
256 }
257 if modules.config {
258 router = router.merge(axum::router_config(state.clone()));
259 }
260
261 serve_router(
262 listener,
263 "fs",
264 axum::finish_v1_docs(router),
265 max_connections,
266 )
267 .await
268}
269
270pub async fn serve_axum_status<S>(
274 port: u16,
275 state: S,
276 modules: OptionalModules,
277 max_connections: Option<usize>,
278) -> anyhow::Result<()>
279where
280 S: v1::StatusApi
281 + v1::SubmitApi
282 + v1::CatchupApi
283 + v1::StateSignatureApi
284 + v1::ConfigApi
285 + Send
286 + Sync
287 + 'static,
288{
289 let listener = bind_api(port).await?;
290 let state = std::sync::Arc::new(state);
291 let router =
292 axum::router_status(state.clone()).merge(axum::router_state_signature(state.clone()));
293 let router = merge_hotshot_modules(router, &state, modules);
294 serve_router(
295 listener,
296 "status",
297 axum::finish_v1_docs(router),
298 max_connections,
299 )
300 .await
301}
302
303pub async fn serve_axum_bare<S>(
307 port: u16,
308 state: S,
309 modules: OptionalModules,
310 max_connections: Option<usize>,
311) -> anyhow::Result<()>
312where
313 S: v1::SubmitApi
314 + v1::CatchupApi
315 + v1::StateSignatureApi
316 + v1::ConfigApi
317 + Send
318 + Sync
319 + 'static,
320{
321 let listener = bind_api(port).await?;
322 let state = std::sync::Arc::new(state);
323 let router = axum::router_state_signature(state.clone());
324 let router = merge_hotshot_modules(router, &state, modules);
325 serve_router(
326 listener,
327 "bare",
328 axum::finish_v1_docs(router),
329 max_connections,
330 )
331 .await
332}
333
334fn merge_hotshot_modules<S>(
335 mut router: aide::axum::ApiRouter,
336 state: &std::sync::Arc<S>,
337 modules: OptionalModules,
338) -> aide::axum::ApiRouter
339where
340 S: v1::SubmitApi + v1::CatchupApi + v1::ConfigApi + Send + Sync + 'static,
341{
342 if modules.submit {
343 router = router.merge(axum::router_submit(state.clone()));
344 }
345 if modules.catchup {
346 router = router.merge(axum::router_catchup(state.clone()));
347 }
348 if modules.config {
349 router = router.merge(axum::router_config(state.clone()));
350 }
351
352 router
353}
354
355async fn bind_api(port: u16) -> anyhow::Result<tokio::net::TcpListener> {
360 let addr = format!("0.0.0.0:{}", port);
361 tracing::info!("Binding to {}", addr);
362 Ok(tokio::net::TcpListener::bind(&addr).await?)
363}
364
365async fn serve_router(
366 listener: tokio::net::TcpListener,
367 mode: &str,
368 router: ::axum::Router,
369 max_connections: Option<usize>,
370) -> anyhow::Result<()> {
371 let mut router = axum::with_top_level_routes(router);
372 if let Some(limit) = max_connections {
373 router = apply_connection_limit(router, limit);
374 }
375 let router = router.layer(http_wire::body_limit_layer());
376 let router = router.layer(http_wire::cors_layer());
378 let router = tower::util::MapRequestLayer::new(axum::rewrite_legacy_uri).layer(router);
382
383 tracing::info!(
384 "Axum API server listening on {:?} ({} mode)",
385 listener.local_addr()?,
386 mode
387 );
388 ::axum::serve(listener, ::axum::ServiceExt::into_make_service(router)).await?;
389
390 tracing::info!("Axum server stopped");
391 Ok(())
392}
393
394fn apply_connection_limit(router: ::axum::Router, limit: usize) -> ::axum::Router {
398 let semaphore = std::sync::Arc::new(tokio::sync::Semaphore::new(limit));
399 router
400 .layer(::axum::middleware::from_fn(axum::limit_requests))
401 .layer(::axum::Extension(axum::RequestLimit(semaphore)))
402}
403
404pub async fn serve_tonic<S>(port: u16, state: S, modules: OptionalModules) -> anyhow::Result<()>
407where
408 S: StatusService
409 + TokenService
410 + NodeService
411 + ConfigService
412 + DatabaseService
413 + AvailabilityService
414 + MerklizedStateService
415 + RewardStateService
416 + StateSignatureService
417 + SubmitService
418 + ExplorerService
419 + CatchupService
420 + LightClientService
421 + Clone,
422{
423 use ::tonic::transport::Server;
424
425 let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port));
426
427 let reflection_service = tonic_reflection::server::Builder::configure()
429 .register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET)
430 .build_v1()?;
431
432 let router = Server::builder()
433 .add_service(StatusServiceServer::new(state.clone()))
434 .add_service(TokenServiceServer::new(state.clone()))
435 .add_service(NodeServiceServer::new(state.clone()))
436 .add_service(DatabaseServiceServer::new(state.clone()))
437 .add_service(AvailabilityServiceServer::new(state.clone()))
438 .add_service(MerklizedStateServiceServer::new(state.clone()))
439 .add_service(RewardStateServiceServer::new(state.clone()))
440 .add_service(StateSignatureServiceServer::new(state.clone()))
441 .add_service(CatchupServiceServer::new(state.clone()))
442 .add_service(LightClientServiceServer::new(state.clone()))
443 .add_service(reflection_service)
444 .add_optional_service(modules.submit.then(|| {
445 SubmitServiceServer::new(state.clone())
448 .max_decoding_message_size(http_wire::MAX_REQUEST_BODY_BYTES)
449 }))
450 .add_optional_service(
451 modules
452 .explorer
453 .then(|| ExplorerServiceServer::new(state.clone())),
454 )
455 .add_optional_service(modules.config.then(|| ConfigServiceServer::new(state)));
456
457 tracing::info!("gRPC server listening on {}", addr);
458 router.serve(addr).await?;
459
460 Ok(())
461}