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