1mod axum;
5pub mod error;
6pub mod v1;
7
8pub mod proto {
12 #![allow(clippy::useless_borrows_in_formatting)]
14
15 include!("generated/espresso.api.v2.rs");
16 include!("generated/espresso.api.v2.serde.rs");
17}
18
19pub mod rest {
22 #![allow(unfulfilled_lint_expectations)]
24
25 include!("generated/espresso.api.v2.rest.rs");
26}
27
28pub const FILE_DESCRIPTOR_SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/descriptor.bin"));
30
31use tower::Layer;
32
33pub use self::axum::{create_router_v1, routes};
35use self::proto::{
36 status_service_server::{StatusService, StatusServiceServer},
37 token_service_server::{TokenService, TokenServiceServer},
38};
39
40pub fn url(base: &::url::Url, path: impl AsRef<str>) -> ::url::Url {
47 base.join(path.as_ref())
48 .expect("path produced by routes::*::path_fn is always a valid relative URL")
49}
50
51pub async fn serve_axum<S>(
60 port: u16,
61 state: S,
62 modules: OptionalModules,
63 max_connections: Option<usize>,
64) -> anyhow::Result<()>
65where
66 S: v1::RewardApi
67 + v1::AvailabilityApi
68 + v1::HotShotAvailabilityApi
69 + v1::BlockStateApi
70 + v1::FeeStateApi
71 + v1::StatusApi
72 + v1::ConfigApi
73 + v1::NodeApi
74 + v1::CatchupApi
75 + v1::SubmitApi
76 + v1::StateSignatureApi
77 + v1::HotShotEventsApi
78 + v1::LightClientApi
79 + v1::ExplorerApi
80 + v1::TokenApi
81 + v1::DatabaseApi
82 + StatusService
83 + TokenService
84 + Clone
85 + Send
86 + Sync
87 + 'static,
88{
89 let listener = bind_api(port).await?;
90 let mut router = axum::router_reward(state.clone())
91 .merge(axum::router_availability(state.clone()))
92 .merge(axum::router_block_state(state.clone()))
93 .merge(axum::router_fee_state(state.clone()))
94 .merge(axum::router_status(state.clone()))
95 .merge(axum::router_node(state.clone()))
96 .merge(axum::router_catchup(state.clone()))
97 .merge(axum::router_state_signature(state.clone()))
98 .merge(axum::router_token(state.clone()))
99 .merge(axum::router_database(state.clone()));
100 if modules.submit {
101 router = router.merge(axum::router_submit(state.clone()));
102 }
103 if modules.config {
104 router = router.merge(axum::router_config(state.clone()));
105 }
106 if modules.explorer {
107 router = router.merge(axum::router_explorer(state.clone()));
108 }
109 if modules.light_client {
110 router = router.merge(axum::router_light_client(state.clone()));
111 }
112 if modules.hotshot_events {
113 router = router.merge(axum::router_hotshot_events(state.clone()));
114 }
115 let router = axum::finish_v1_docs(router)
116 .merge(router_v2(std::sync::Arc::new(state)))
117 .merge(axum::router_v2_docs());
118 serve_router(listener, "v1 and v2", router, max_connections).await
119}
120
121pub(crate) fn router_v2<S>(state: std::sync::Arc<S>) -> ::axum::Router
124where
125 S: StatusService + TokenService + Send + Sync + 'static,
126{
127 rest::status_service_rest_router(state.clone())
128 .merge(rest::token_service_rest_router(state))
129 .layer(::axum::middleware::from_fn(axum::v2_error_envelope))
130}
131
132#[derive(Default, Clone, Copy, Debug)]
136pub struct OptionalModules {
137 pub submit: bool,
138 pub catchup: bool,
139 pub config: bool,
140 pub hotshot_events: bool,
141 pub explorer: bool,
142 pub light_client: bool,
143}
144
145pub async fn serve_axum_fs<S>(
151 port: u16,
152 state: S,
153 modules: OptionalModules,
154 max_connections: Option<usize>,
155) -> anyhow::Result<()>
156where
157 S: v1::StatusApi
158 + v1::AvailabilityApi
159 + v1::HotShotAvailabilityApi
160 + v1::NodeApi
161 + v1::TokenApi
162 + v1::CatchupApi
163 + v1::SubmitApi
164 + v1::StateSignatureApi
165 + v1::ConfigApi
166 + v1::HotShotEventsApi
167 + Clone
168 + Send
169 + Sync
170 + 'static,
171{
172 let listener = bind_api(port).await?;
173 let mut router = axum::router_status(state.clone())
174 .merge(axum::router_availability(state.clone()))
175 .merge(axum::router_node(state.clone()))
176 .merge(axum::router_token(state.clone()))
177 .merge(axum::router_catchup(state.clone()))
178 .merge(axum::router_state_signature(state.clone()));
179 if modules.submit {
180 router = router.merge(axum::router_submit(state.clone()));
181 }
182 if modules.config {
183 router = router.merge(axum::router_config(state.clone()));
184 }
185 if modules.hotshot_events {
186 router = router.merge(axum::router_hotshot_events(state));
187 }
188 serve_router(
189 listener,
190 "fs",
191 axum::finish_v1_docs(router),
192 max_connections,
193 )
194 .await
195}
196
197pub async fn serve_axum_status<S>(
201 port: u16,
202 state: S,
203 modules: OptionalModules,
204 max_connections: Option<usize>,
205) -> anyhow::Result<()>
206where
207 S: v1::StatusApi
208 + v1::SubmitApi
209 + v1::CatchupApi
210 + v1::StateSignatureApi
211 + v1::ConfigApi
212 + v1::HotShotEventsApi
213 + Clone
214 + Send
215 + Sync
216 + 'static,
217{
218 let listener = bind_api(port).await?;
219 let router =
220 axum::router_status(state.clone()).merge(axum::router_state_signature(state.clone()));
221 let router = merge_hotshot_modules(router, &state, modules);
222 serve_router(
223 listener,
224 "status",
225 axum::finish_v1_docs(router),
226 max_connections,
227 )
228 .await
229}
230
231pub async fn serve_axum_bare<S>(
235 port: u16,
236 state: S,
237 modules: OptionalModules,
238 max_connections: Option<usize>,
239) -> anyhow::Result<()>
240where
241 S: v1::SubmitApi
242 + v1::CatchupApi
243 + v1::StateSignatureApi
244 + v1::ConfigApi
245 + v1::HotShotEventsApi
246 + Clone
247 + Send
248 + Sync
249 + 'static,
250{
251 let listener = bind_api(port).await?;
252 let router = axum::router_state_signature(state.clone());
253 let router = merge_hotshot_modules(router, &state, modules);
254 serve_router(
255 listener,
256 "bare",
257 axum::finish_v1_docs(router),
258 max_connections,
259 )
260 .await
261}
262
263fn merge_hotshot_modules<S>(
264 mut router: aide::axum::ApiRouter,
265 state: &S,
266 modules: OptionalModules,
267) -> aide::axum::ApiRouter
268where
269 S: v1::SubmitApi
270 + v1::CatchupApi
271 + v1::ConfigApi
272 + v1::HotShotEventsApi
273 + Clone
274 + Send
275 + Sync
276 + 'static,
277{
278 if modules.submit {
279 router = router.merge(axum::router_submit(state.clone()));
280 }
281 if modules.catchup {
282 router = router.merge(axum::router_catchup(state.clone()));
283 }
284 if modules.config {
285 router = router.merge(axum::router_config(state.clone()));
286 }
287 if modules.hotshot_events {
288 router = router.merge(axum::router_hotshot_events(state.clone()));
289 }
290 router
291}
292
293async fn bind_api(port: u16) -> anyhow::Result<tokio::net::TcpListener> {
298 let addr = format!("0.0.0.0:{}", port);
299 tracing::info!("Binding to {}", addr);
300 Ok(tokio::net::TcpListener::bind(&addr).await?)
301}
302
303async fn serve_router(
304 listener: tokio::net::TcpListener,
305 mode: &str,
306 router: ::axum::Router,
307 max_connections: Option<usize>,
308) -> anyhow::Result<()> {
309 let mut router = axum::with_top_level_routes(router);
310 if let Some(limit) = max_connections {
311 router = apply_connection_limit(router, limit);
312 }
313 let router = router.layer(http_wire::body_limit_layer());
314 let router = router.layer(http_wire::cors_layer());
316 let router = tower::util::MapRequestLayer::new(axum::rewrite_legacy_uri).layer(router);
320
321 tracing::info!(
322 "Axum API server listening on {:?} ({} mode)",
323 listener.local_addr()?,
324 mode
325 );
326 ::axum::serve(listener, ::axum::ServiceExt::into_make_service(router)).await?;
327
328 tracing::info!("Axum server stopped");
329 Ok(())
330}
331
332fn apply_connection_limit(router: ::axum::Router, limit: usize) -> ::axum::Router {
335 let semaphore = std::sync::Arc::new(tokio::sync::Semaphore::new(limit));
336 router
337 .layer(::axum::middleware::from_fn(axum::limit_requests))
338 .layer(::axum::Extension(axum::RequestLimit(semaphore)))
339}
340
341pub async fn serve_tonic<S>(port: u16, state: S) -> anyhow::Result<()>
343where
344 S: StatusService + TokenService + Clone,
345{
346 use ::tonic::transport::Server;
347
348 let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port));
349
350 let status_service = StatusServiceServer::new(state.clone());
351 let token_service = TokenServiceServer::new(state);
352
353 let reflection_service = tonic_reflection::server::Builder::configure()
355 .register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET)
356 .build_v1()?;
357
358 tracing::info!("gRPC server listening on {}", addr);
359 Server::builder()
360 .add_service(status_service)
361 .add_service(token_service)
362 .add_service(reflection_service)
363 .serve(addr)
364 .await?;
365
366 Ok(())
367}