1use std::collections::{BTreeMap, BTreeSet, HashMap};
2use std::pin::Pin;
3use std::sync::Arc;
4
5use anyhow::anyhow;
6use axum::extract::{Path, Query};
7use axum::{Extension, Json};
8use bitcoin::hashes::sha256;
9use fedimint_core::net::iroh::build_iroh_endpoint;
10use fedimint_core::task::TaskGroup;
11use fedimint_gateway_common::{
12 FEDIMINT_GATEWAY_ALPN, IrohGatewayRequest, IrohGatewayResponse, STOP_ENDPOINT,
13};
14use fedimint_logging::LOG_GATEWAY;
15use iroh::endpoint::Incoming;
16use reqwest::StatusCode;
17use serde::de::DeserializeOwned;
18use serde_json::json;
19use tracing::info;
20use url::Url;
21
22use crate::Gateway;
23use crate::error::{GatewayError, PublicGatewayError};
24use crate::rpc_server::verify_bolt11_preimage_v2_get;
25
26type GetHandler = Box<
29 dyn Fn(
30 Extension<Arc<Gateway>>,
31 )
32 -> Pin<Box<dyn Future<Output = Result<Json<serde_json::Value>, GatewayError>> + Send>>
33 + Send
34 + Sync,
35>;
36
37type PostHandler = Box<
40 dyn Fn(
41 Extension<Arc<Gateway>>,
42 serde_json::Value,
43 )
44 -> Pin<Box<dyn Future<Output = Result<Json<serde_json::Value>, GatewayError>> + Send>>
45 + Send
46 + Sync,
47>;
48
49fn make_get_handler<F, Fut>(f: F) -> GetHandler
51where
52 F: Fn(Extension<Arc<Gateway>>) -> Fut + Clone + Send + Sync + 'static,
53 Fut: Future<Output = Result<Json<serde_json::Value>, GatewayError>> + Send + 'static,
54{
55 Box::new(move |gateway: Extension<Arc<Gateway>>| {
56 let f = f.clone();
57 Box::pin(async move {
58 let res = f(gateway).await?;
59 Ok(res)
60 })
61 })
62}
63
64fn make_post_handler<P, F, Fut>(f: F) -> PostHandler
66where
67 P: DeserializeOwned + Send + 'static,
68 F: Fn(Extension<Arc<Gateway>>, Json<P>) -> Fut + Clone + Send + Sync + 'static,
69 Fut: Future<Output = Result<Json<serde_json::Value>, GatewayError>> + Send + 'static,
70{
71 Box::new(
72 move |gateway: Extension<Arc<Gateway>>, value: serde_json::Value| {
73 let f = f.clone();
74 Box::pin(async move {
75 let payload: P = serde_json::from_value(value)
76 .map_err(|e| PublicGatewayError::Unexpected(anyhow!(e.to_string())))?;
77 let res = f(gateway, Json(payload)).await?;
78 Ok(res)
79 })
80 },
81 )
82}
83
84pub struct Handlers {
90 get_handlers: BTreeMap<String, GetHandler>,
91 post_handlers: BTreeMap<String, PostHandler>,
92 authenticated_routes: BTreeSet<String>,
93}
94
95impl Handlers {
96 pub fn new() -> Self {
97 let mut authenticated_routes = BTreeSet::new();
98 authenticated_routes.insert(STOP_ENDPOINT.to_string());
99 Handlers {
100 get_handlers: BTreeMap::new(),
101 post_handlers: BTreeMap::new(),
102 authenticated_routes,
103 }
104 }
105
106 pub fn add_handler<F, Fut>(&mut self, route: &str, f: F, is_authenticated: bool)
107 where
108 F: Fn(Extension<Arc<Gateway>>) -> Fut + Clone + Send + Sync + 'static,
109 Fut: Future<Output = Result<Json<serde_json::Value>, GatewayError>> + Send + 'static,
110 {
111 if is_authenticated {
112 self.authenticated_routes.insert(route.to_string());
113 }
114 self.get_handlers
115 .insert(route.to_string(), make_get_handler(f));
116 }
117
118 pub fn get_handler(&self, route: &str) -> Option<&GetHandler> {
119 self.get_handlers.get(route)
120 }
121
122 pub fn add_handler_with_payload<P, F, Fut>(&mut self, route: &str, f: F, is_authenticated: bool)
123 where
124 P: DeserializeOwned + Send + 'static,
125 F: Fn(Extension<Arc<Gateway>>, Json<P>) -> Fut + Clone + Send + Sync + 'static,
126 Fut: Future<Output = Result<Json<serde_json::Value>, GatewayError>> + Send + 'static,
127 {
128 if is_authenticated {
129 self.authenticated_routes.insert(route.to_string());
130 }
131
132 self.post_handlers
133 .insert(route.to_string(), make_post_handler(f));
134 }
135
136 pub fn get_handler_with_payload(&self, route: &str) -> Option<&PostHandler> {
137 self.post_handlers.get(route)
138 }
139
140 pub fn is_authenticated(&self, route: &str) -> bool {
141 self.authenticated_routes.contains(route)
142 }
143}
144
145pub async fn start_iroh_endpoint(
148 gateway: &Arc<Gateway>,
149 task_group: TaskGroup,
150 handlers: Arc<Handlers>,
151) -> anyhow::Result<()> {
152 info!("Building Iroh Endpoint...");
153 let iroh_endpoint = build_iroh_endpoint(
154 gateway.iroh_sk.clone(),
155 gateway.iroh_listen,
156 gateway.iroh_dns.clone(),
157 gateway.iroh_relays.clone(),
158 FEDIMINT_GATEWAY_ALPN,
159 )
160 .await?;
161 let gw_clone = gateway.clone();
162 let tg_clone = task_group.clone();
163 let handlers_clone = handlers.clone();
164 info!("Spawning accept loop...");
165 task_group.spawn("Gateway Iroh", |_| async move {
166 while let Some(incoming) = iroh_endpoint.accept().await {
167 info!("Accepted new connection. Spawning handler...");
168 tg_clone.spawn_cancellable_silent(
169 "handle endpoint accept",
170 handle_incoming_iroh_request(
171 incoming,
172 gw_clone.clone(),
173 handlers_clone.clone(),
174 tg_clone.clone(),
175 ),
176 );
177 }
178 });
179
180 info!(target: LOG_GATEWAY, "Successfully started iroh endpoint");
181
182 Ok(())
183}
184
185async fn handle_incoming_iroh_request(
188 incoming: Incoming,
189 gateway: Arc<Gateway>,
190 handlers: Arc<Handlers>,
191 task_group: TaskGroup,
192) -> anyhow::Result<()> {
193 let connection = incoming.accept()?.await?;
194 let remote_node_id = &connection.remote_node_id()?;
195 info!(%remote_node_id, "Handler received connection");
196 while let Ok((mut send, mut recv)) = connection.accept_bi().await {
197 let request = recv.read_to_end(100_000).await?;
198 let request = serde_json::from_slice::<IrohGatewayRequest>(&request)?;
199
200 let (status, body) = handle_request(
201 &request,
202 gateway.clone(),
203 handlers.clone(),
204 task_group.clone(),
205 )
206 .await?;
207
208 let response = IrohGatewayResponse {
209 status: status.as_u16(),
210 body: body.0,
211 };
212 let response = serde_json::to_vec(&response)?;
213
214 send.write_all(&response).await?;
215 send.finish()?;
216 }
217 Ok(())
218}
219
220async fn handle_request(
225 request: &IrohGatewayRequest,
226 gateway: Arc<Gateway>,
227 handlers: Arc<Handlers>,
228 task_group: TaskGroup,
229) -> anyhow::Result<(StatusCode, Json<serde_json::Value>)> {
230 if handlers.is_authenticated(&request.route) && iroh_verify_password(&gateway, request).is_err()
231 {
232 return Ok((StatusCode::UNAUTHORIZED, Json(json!(()))));
233 }
234
235 if request.route == STOP_ENDPOINT {
238 let body = crate::rpc_server::stop(Extension(task_group), Extension(gateway)).await?;
239 return Ok((StatusCode::OK, body));
240 }
241
242 if request.route.starts_with("/verify") {
246 let url = Url::parse(&format!("http://localhost{}", request.route))?;
248 let mut segments = url.path_segments().unwrap();
250 let hash_str = segments.next();
251
252 let payment_hash: sha256::Hash = hash_str.ok_or(anyhow!("No has present"))?.parse()?;
253
254 let query_map: HashMap<String, String> = url.query_pairs().into_owned().collect();
256
257 let body =
258 verify_bolt11_preimage_v2_get(Extension(gateway), Path(payment_hash), Query(query_map))
259 .await?;
260
261 return Ok((StatusCode::OK, body));
262 }
263
264 let (status, body) = match &request.params {
265 Some(params) => {
266 if let Some(handler) = handlers.get_handler_with_payload(&request.route) {
267 (
268 StatusCode::OK,
269 handler(Extension(gateway), params.clone()).await?,
270 )
271 } else {
272 return Err(anyhow!("Iroh handler received request with unknown route"));
273 }
274 }
275 None => {
276 if let Some(handler) = handlers.get_handler(&request.route) {
277 (StatusCode::OK, handler(Extension(gateway)).await?)
278 } else {
279 return Err(anyhow!("Iroh handler received request with unknown route"));
280 }
281 }
282 };
283
284 Ok((status, body))
285}
286
287fn iroh_verify_password(
290 gateway: &Arc<Gateway>,
291 request: &IrohGatewayRequest,
292) -> anyhow::Result<()> {
293 if let Some(password) = request.password.as_ref()
294 && bcrypt::verify(password, &gateway.bcrypt_password_hash.to_string())?
295 {
296 return Ok(());
297 }
298
299 Err(anyhow!("Invalid password"))
300}