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