Skip to main content

fedimint_server/net/api/
mod.rs

1pub 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    /// Get "active" secret - one that should be used to call other peers
66    pub fn get_active(&self) -> Option<String> {
67        self.0.first().cloned()
68    }
69
70    /// Get all secrets
71    pub fn get_all(&self) -> &[String] {
72        &self.0
73    }
74
75    /// Get empty value - meaning no secrets to use
76    pub fn none() -> ApiSecrets {
77        Self(vec![])
78    }
79}
80
81/// How long to wait before timing out client connections
82const API_ENDPOINT_TIMEOUT: Duration = Duration::from_mins(1);
83
84/// Has the context necessary for serving API endpoints
85///
86/// Returns the specific `State` the endpoint requires and the
87/// `ApiEndpointContext` which all endpoints can access.
88#[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(
112            RpcServiceBuilder::new()
113                .layer(metrics::jsonrpsee::MetricsLayer::new(module.method_names())),
114        )
115        .set_http_middleware(builder)
116        .build(&api_bind.to_string())
117        .await
118        .context(format!("Bind address: {api_bind}"))
119        .context(format!("API name: {name}"))
120        .expect("Could not build API server")
121        .start(module)
122}
123
124pub fn attach_endpoints<State, T>(
125    rpc_module: &mut RpcModule<T>,
126    endpoints: Vec<ApiEndpoint<State>>,
127    module_instance_id: Option<ModuleInstanceId>,
128) where
129    T: HasApiContext<State> + Sync + Send + 'static,
130    State: Sync + Send + 'static,
131{
132    for endpoint in endpoints {
133        let path = if let Some(module_instance_id) = module_instance_id {
134            // This memory leak is fine because it only happens on server startup
135            // and path has to live till the end of program anyways.
136            Box::leak(format!("module_{}_{}", module_instance_id, endpoint.path).into_boxed_str())
137        } else {
138            endpoint.path
139        };
140        // Check if paths contain any abnormal characters
141        assert!(
142            !path.contains(|c: char| !matches!(c, '0'..='9' | 'a'..='z' | '_')),
143            "Constructing bad path name {path}"
144        );
145
146        // Another memory leak that is fine because the function is only called once at
147        // startup
148        let handler: &'static _ = Box::leak(endpoint.handler);
149
150        rpc_module
151            .register_async_method(path, move |params, rpc_state, _extensions| async move {
152                let params = params.one::<serde_json::Value>()?;
153
154                // Using AssertUnwindSafe here is far from ideal. In theory this means we could
155                // end up with an inconsistent state in theory. In practice most API functions
156                // are only reading and the few that do write anything are atomic. Lastly, this
157                // is only the last line of defense
158                AssertUnwindSafe(tokio::time::timeout(API_ENDPOINT_TIMEOUT, async {
159                    let request = serde_json::from_value(params)
160                        .map_err(|e| ApiError::bad_request(e.to_string()))?;
161
162                    let (state, context) = rpc_state.context(&request, module_instance_id).await;
163
164                    (handler)(state, context, request).await
165                }))
166                .catch_unwind()
167                .await
168                .map_err(|_| {
169                    error!(
170                        target: LOG_NET_API,
171                        path, "API handler panicked, DO NOT IGNORE, FIX IT!!!"
172                    );
173                    ErrorObject::owned(500, "API handler panicked", None::<()>)
174                })?
175                .map_err(|tokio::time::error::Elapsed { .. }| {
176                    // TODO: find a better error for this, the error we used before:
177                    // jsonrpsee::core::Error::RequestTimeout
178                    // was moved to be client-side only
179                    ErrorObject::owned(-32000, "Request timeout", None::<()>)
180                })?
181                .map_err(|e| ErrorObject::owned(e.code, e.message, None::<()>))
182            })
183            .expect("Failed to register async method");
184    }
185}