fedimint_server/net/api/
mod.rs1pub mod announcement;
2pub mod guardian_metadata;
3mod http_auth;
4pub mod pkarr_publish;
5
6use std::fmt::{self, Formatter};
7use std::net::SocketAddr;
8use std::panic::AssertUnwindSafe;
9use std::str::FromStr;
10use std::time::Duration;
11
12use anyhow::{Context, bail};
13use async_trait::async_trait;
14use fedimint_core::core::ModuleInstanceId;
15use fedimint_core::encoding::{Decodable, Encodable};
16use fedimint_core::module::{ApiEndpoint, ApiEndpointContext, ApiError, ApiRequestErased};
17use fedimint_logging::LOG_NET_API;
18use futures::FutureExt;
19use jsonrpsee::RpcModule;
20use jsonrpsee::server::{PingConfig, RpcServiceBuilder, ServerBuilder, ServerHandle};
21use jsonrpsee::types::ErrorObject;
22use tracing::{error, info};
23
24use crate::metrics;
25use crate::net::api::http_auth::HttpAuthLayer;
26
27#[derive(Clone, Encodable, Decodable, Default)]
28pub struct ApiSecrets(Vec<String>);
29
30impl fmt::Debug for ApiSecrets {
31 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
32 f.debug_struct("ApiSecrets")
33 .field("num_secrets", &self.0.len())
34 .finish()
35 }
36}
37
38impl FromStr for ApiSecrets {
39 type Err = anyhow::Error;
40
41 fn from_str(s: &str) -> anyhow::Result<Self> {
42 if s.is_empty() {
43 return Ok(Self(vec![]));
44 }
45
46 let secrets = s
47 .split(',')
48 .map(str::trim)
49 .map(|s| {
50 if s.is_empty() {
51 bail!("Empty Api Secret is not allowed")
52 }
53 Ok(s.to_string())
54 })
55 .collect::<anyhow::Result<_>>()?;
56 Ok(ApiSecrets(secrets))
57 }
58}
59
60impl ApiSecrets {
61 pub fn is_empty(&self) -> bool {
62 self.0.is_empty()
63 }
64
65 pub fn get_active(&self) -> Option<String> {
67 self.0.first().cloned()
68 }
69
70 pub fn get_all(&self) -> &[String] {
72 &self.0
73 }
74
75 pub fn none() -> ApiSecrets {
77 Self(vec![])
78 }
79}
80
81const API_ENDPOINT_TIMEOUT: Duration = Duration::from_mins(1);
83
84#[async_trait]
89pub trait HasApiContext<State> {
90 async fn context(
91 &self,
92 request: &ApiRequestErased,
93 id: Option<ModuleInstanceId>,
94 ) -> (&State, ApiEndpointContext);
95}
96
97pub async fn spawn<T>(
98 name: &'static str,
99 api_bind: SocketAddr,
100 module: RpcModule<T>,
101 max_connections: u32,
102 api_secrets: ApiSecrets,
103) -> ServerHandle {
104 info!(target: LOG_NET_API, "Starting http api on ws://{api_bind}");
105
106 let builder = tower::ServiceBuilder::new().layer(HttpAuthLayer::new(api_secrets.get_all()));
107
108 ServerBuilder::new()
109 .max_connections(max_connections)
110 .enable_ws_ping(PingConfig::new().ping_interval(Duration::from_secs(10)))
111 .set_rpc_middleware(RpcServiceBuilder::new().layer(metrics::jsonrpsee::MetricsLayer))
112 .set_http_middleware(builder)
113 .build(&api_bind.to_string())
114 .await
115 .context(format!("Bind address: {api_bind}"))
116 .context(format!("API name: {name}"))
117 .expect("Could not build API server")
118 .start(module)
119}
120
121pub fn attach_endpoints<State, T>(
122 rpc_module: &mut RpcModule<T>,
123 endpoints: Vec<ApiEndpoint<State>>,
124 module_instance_id: Option<ModuleInstanceId>,
125) where
126 T: HasApiContext<State> + Sync + Send + 'static,
127 State: Sync + Send + 'static,
128{
129 for endpoint in endpoints {
130 let path = if let Some(module_instance_id) = module_instance_id {
131 Box::leak(format!("module_{}_{}", module_instance_id, endpoint.path).into_boxed_str())
134 } else {
135 endpoint.path
136 };
137 assert!(
139 !path.contains(|c: char| !matches!(c, '0'..='9' | 'a'..='z' | '_')),
140 "Constructing bad path name {path}"
141 );
142
143 let handler: &'static _ = Box::leak(endpoint.handler);
146
147 rpc_module
148 .register_async_method(path, move |params, rpc_state, _extensions| async move {
149 let params = params.one::<serde_json::Value>()?;
150
151 AssertUnwindSafe(tokio::time::timeout(API_ENDPOINT_TIMEOUT, async {
156 let request = serde_json::from_value(params)
157 .map_err(|e| ApiError::bad_request(e.to_string()))?;
158
159 let (state, context) = rpc_state.context(&request, module_instance_id).await;
160
161 (handler)(state, context, request).await
162 }))
163 .catch_unwind()
164 .await
165 .map_err(|_| {
166 error!(
167 target: LOG_NET_API,
168 path, "API handler panicked, DO NOT IGNORE, FIX IT!!!"
169 );
170 ErrorObject::owned(500, "API handler panicked", None::<()>)
171 })?
172 .map_err(|tokio::time::error::Elapsed { .. }| {
173 ErrorObject::owned(-32000, "Request timeout", None::<()>)
177 })?
178 .map_err(|e| ErrorObject::owned(e.code, e.message, None::<()>))
179 })
180 .expect("Failed to register async method");
181 }
182}