fedimint_gateway_server/
rpc_server.rs

1use std::sync::Arc;
2
3use axum::extract::Request;
4use axum::http::{StatusCode, header};
5use axum::middleware::{self, Next};
6use axum::response::IntoResponse;
7use axum::routing::{get, post};
8use axum::{Extension, Json, Router};
9use fedimint_core::config::FederationId;
10use fedimint_core::task::TaskGroup;
11use fedimint_core::util::FmtCompact;
12use fedimint_gateway_common::{
13    ADDRESS_ENDPOINT, ADDRESS_RECHECK_ENDPOINT, BACKUP_ENDPOINT, BackupPayload,
14    CLOSE_CHANNELS_WITH_PEER_ENDPOINT, CONFIGURATION_ENDPOINT, CONNECT_FED_ENDPOINT,
15    CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT, CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT,
16    CloseChannelsWithPeerRequest, ConfigPayload, ConnectFedPayload,
17    CreateInvoiceForOperatorPayload, CreateOfferPayload, DepositAddressPayload,
18    DepositAddressRecheckPayload, GATEWAY_INFO_ENDPOINT, GATEWAY_INFO_POST_ENDPOINT,
19    GET_BALANCES_ENDPOINT, GET_INVOICE_ENDPOINT, GET_LN_ONCHAIN_ADDRESS_ENDPOINT,
20    GetInvoiceRequest, InfoPayload, LEAVE_FED_ENDPOINT, LIST_ACTIVE_CHANNELS_ENDPOINT,
21    LIST_TRANSACTIONS_ENDPOINT, LeaveFedPayload, ListTransactionsPayload, MNEMONIC_ENDPOINT,
22    OPEN_CHANNEL_ENDPOINT, OpenChannelRequest, PAY_INVOICE_FOR_OPERATOR_ENDPOINT,
23    PAY_OFFER_FOR_OPERATOR_ENDPOINT, PAYMENT_LOG_ENDPOINT, PAYMENT_SUMMARY_ENDPOINT,
24    PayInvoiceForOperatorPayload, PayOfferPayload, PaymentLogPayload, PaymentSummaryPayload,
25    RECEIVE_ECASH_ENDPOINT, ReceiveEcashPayload, SEND_ONCHAIN_ENDPOINT, SET_FEES_ENDPOINT,
26    SPEND_ECASH_ENDPOINT, STOP_ENDPOINT, SendOnchainRequest, SetFeesPayload, SpendEcashPayload,
27    V1_API_ENDPOINT, WITHDRAW_ENDPOINT, WithdrawPayload,
28};
29use fedimint_ln_common::gateway_endpoint_constants::{
30    GET_GATEWAY_ID_ENDPOINT, PAY_INVOICE_ENDPOINT,
31};
32use fedimint_lnv2_common::endpoint_constants::{
33    CREATE_BOLT11_INVOICE_ENDPOINT, ROUTING_INFO_ENDPOINT, SEND_PAYMENT_ENDPOINT,
34};
35use fedimint_lnv2_common::gateway_api::{CreateBolt11InvoicePayload, SendPaymentPayload};
36use fedimint_logging::LOG_GATEWAY;
37use hex::ToHex;
38use serde_json::json;
39use tokio::net::TcpListener;
40use tower_http::cors::CorsLayer;
41use tracing::{info, instrument, warn};
42
43use crate::Gateway;
44use crate::error::{AdminGatewayError, PublicGatewayError};
45
46/// Creates the webserver's routes and spawns the webserver in a separate task.
47pub async fn run_webserver(gateway: Arc<Gateway>) -> anyhow::Result<()> {
48    let task_group = gateway.task_group.clone();
49    let v1_routes = v1_routes(gateway.clone(), task_group.clone());
50    let api_v1 = Router::new()
51        .nest(&format!("/{V1_API_ENDPOINT}"), v1_routes.clone())
52        // Backwards compatibility: Continue supporting gateway APIs without versioning
53        .merge(v1_routes);
54
55    let handle = task_group.make_handle();
56    let shutdown_rx = handle.make_shutdown_rx();
57    let listener = TcpListener::bind(&gateway.listen).await?;
58    let serve = axum::serve(listener, api_v1.into_make_service());
59    task_group.spawn("Gateway Webserver", |_| async {
60        let graceful = serve.with_graceful_shutdown(async {
61            shutdown_rx.await;
62        });
63
64        match graceful.await {
65            Err(err) => {
66                warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Error shutting down gatewayd webserver");
67            }
68            _ => {
69                info!(target: LOG_GATEWAY, "Successfully shutdown webserver");
70            }
71        }
72    });
73
74    info!(target: LOG_GATEWAY, listen = %gateway.listen, "Successfully started webserver");
75    Ok(())
76}
77
78/// Extracts the Bearer token from the Authorization header of the request.
79fn extract_bearer_token(request: &Request) -> Result<String, StatusCode> {
80    let headers = request.headers();
81    let auth_header = headers.get(header::AUTHORIZATION);
82    if let Some(header_value) = auth_header {
83        let auth_str = header_value
84            .to_str()
85            .map_err(|_| StatusCode::UNAUTHORIZED)?;
86        let token = auth_str.trim_start_matches("Bearer ").to_string();
87        return Ok(token);
88    }
89
90    Err(StatusCode::UNAUTHORIZED)
91}
92
93/// Middleware to authenticate an incoming request. Routes that are
94/// authenticated with this middleware always require a Bearer token to be
95/// supplied in the Authorization header.
96async fn auth_middleware(
97    Extension(gateway): Extension<Arc<Gateway>>,
98    request: Request,
99    next: Next,
100) -> Result<impl IntoResponse, StatusCode> {
101    let token = extract_bearer_token(&request)?;
102    if bcrypt::verify(token, &gateway.bcrypt_password_hash.to_string())
103        .expect("Bcrypt hash is valid since we just stringified it")
104    {
105        return Ok(next.run(request).await);
106    }
107
108    Err(StatusCode::UNAUTHORIZED)
109}
110
111/// Public routes that are used in the LNv1 protocol
112fn lnv1_routes() -> Router {
113    Router::new()
114        .route(PAY_INVOICE_ENDPOINT, post(pay_invoice))
115        .route(GET_GATEWAY_ID_ENDPOINT, get(get_gateway_id))
116}
117
118/// Public routes that are used in the LNv2 protocol
119fn lnv2_routes() -> Router {
120    Router::new()
121        .route(ROUTING_INFO_ENDPOINT, post(routing_info_v2))
122        .route(SEND_PAYMENT_ENDPOINT, post(pay_bolt11_invoice_v2))
123        .route(
124            CREATE_BOLT11_INVOICE_ENDPOINT,
125            post(create_bolt11_invoice_v2),
126        )
127}
128
129/// Gateway Webserver Routes. The gateway supports three types of routes
130/// - Always Authenticated: these routes always require a Bearer token. Used by
131///   gateway administrators.
132/// - Authenticated after config: these routes are unauthenticated before
133///   configuring the gateway to allow the user to set a password. After setting
134///   the password, they become authenticated.
135/// - Un-authenticated: anyone can request these routes. Used by fedimint
136///   clients.
137fn v1_routes(gateway: Arc<Gateway>, task_group: TaskGroup) -> Router {
138    // Public routes on gateway webserver
139    let mut public_routes = Router::new().route(RECEIVE_ECASH_ENDPOINT, post(receive_ecash));
140
141    if gateway.is_running_lnv1() {
142        public_routes = public_routes.merge(lnv1_routes());
143    }
144
145    if gateway.is_running_lnv2() {
146        public_routes = public_routes.merge(lnv2_routes());
147    }
148
149    // Authenticated routes used for gateway administration
150    let authenticated_routes = Router::new()
151        .route(ADDRESS_ENDPOINT, post(address))
152        .route(WITHDRAW_ENDPOINT, post(withdraw))
153        .route(CONNECT_FED_ENDPOINT, post(connect_fed))
154        .route(LEAVE_FED_ENDPOINT, post(leave_fed))
155        .route(BACKUP_ENDPOINT, post(backup))
156        .route(
157            CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT,
158            post(create_invoice_for_operator),
159        )
160        .route(
161            CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT,
162            post(create_offer_for_operator),
163        )
164        .route(
165            PAY_INVOICE_FOR_OPERATOR_ENDPOINT,
166            post(pay_invoice_operator),
167        )
168        .route(PAY_OFFER_FOR_OPERATOR_ENDPOINT, post(pay_offer_operator))
169        .route(GET_INVOICE_ENDPOINT, post(get_invoice))
170        .route(GET_LN_ONCHAIN_ADDRESS_ENDPOINT, get(get_ln_onchain_address))
171        .route(OPEN_CHANNEL_ENDPOINT, post(open_channel))
172        .route(
173            CLOSE_CHANNELS_WITH_PEER_ENDPOINT,
174            post(close_channels_with_peer),
175        )
176        .route(LIST_ACTIVE_CHANNELS_ENDPOINT, get(list_active_channels))
177        .route(LIST_TRANSACTIONS_ENDPOINT, post(list_transactions))
178        .route(SEND_ONCHAIN_ENDPOINT, post(send_onchain))
179        .route(ADDRESS_RECHECK_ENDPOINT, post(recheck_address))
180        .route(GET_BALANCES_ENDPOINT, get(get_balances))
181        .route(SPEND_ECASH_ENDPOINT, post(spend_ecash))
182        .route(MNEMONIC_ENDPOINT, get(mnemonic))
183        .route(STOP_ENDPOINT, get(stop))
184        .route(PAYMENT_LOG_ENDPOINT, post(payment_log))
185        .route(PAYMENT_SUMMARY_ENDPOINT, post(payment_summary))
186        .route(SET_FEES_ENDPOINT, post(set_fees))
187        .route(CONFIGURATION_ENDPOINT, post(configuration))
188        // FIXME: deprecated >= 0.3.0
189        .route(GATEWAY_INFO_POST_ENDPOINT, post(handle_post_info))
190        .route(GATEWAY_INFO_ENDPOINT, get(info))
191        .layer(middleware::from_fn(auth_middleware));
192
193    Router::new()
194        .merge(public_routes)
195        .merge(authenticated_routes)
196        .layer(Extension(gateway))
197        .layer(Extension(task_group))
198        .layer(CorsLayer::permissive())
199}
200
201/// Display high-level information about the Gateway
202// FIXME: deprecated >= 0.3.0
203// This endpoint exists only to remain backwards-compatible with the original POST endpoint
204#[instrument(target = LOG_GATEWAY, skip_all, err)]
205async fn handle_post_info(
206    Extension(gateway): Extension<Arc<Gateway>>,
207    Json(_payload): Json<InfoPayload>,
208) -> Result<impl IntoResponse, AdminGatewayError> {
209    let info = gateway.handle_get_info().await?;
210    Ok(Json(json!(info)))
211}
212
213/// Display high-level information about the Gateway
214#[instrument(target = LOG_GATEWAY, skip_all, err)]
215async fn info(
216    Extension(gateway): Extension<Arc<Gateway>>,
217) -> Result<impl IntoResponse, AdminGatewayError> {
218    let info = gateway.handle_get_info().await?;
219    Ok(Json(json!(info)))
220}
221
222/// Display high-level information about the Gateway config
223#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
224async fn configuration(
225    Extension(gateway): Extension<Arc<Gateway>>,
226    Json(payload): Json<ConfigPayload>,
227) -> Result<impl IntoResponse, AdminGatewayError> {
228    let gateway_fed_config = gateway
229        .handle_get_federation_config(payload.federation_id)
230        .await?;
231    Ok(Json(json!(gateway_fed_config)))
232}
233
234/// Generate deposit address
235#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
236async fn address(
237    Extension(gateway): Extension<Arc<Gateway>>,
238    Json(payload): Json<DepositAddressPayload>,
239) -> Result<impl IntoResponse, AdminGatewayError> {
240    let address = gateway.handle_address_msg(payload).await?;
241    Ok(Json(json!(address)))
242}
243
244/// Withdraw from a gateway federation.
245#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
246async fn withdraw(
247    Extension(gateway): Extension<Arc<Gateway>>,
248    Json(payload): Json<WithdrawPayload>,
249) -> Result<impl IntoResponse, AdminGatewayError> {
250    let txid = gateway.handle_withdraw_msg(payload).await?;
251    Ok(Json(json!(txid)))
252}
253
254#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
255async fn create_invoice_for_operator(
256    Extension(gateway): Extension<Arc<Gateway>>,
257    Json(payload): Json<CreateInvoiceForOperatorPayload>,
258) -> Result<impl IntoResponse, AdminGatewayError> {
259    let invoice = gateway
260        .handle_create_invoice_for_operator_msg(payload)
261        .await?;
262    Ok(Json(json!(invoice)))
263}
264
265#[instrument(target = LOG_GATEWAY, skip_all, err)]
266async fn pay_invoice_operator(
267    Extension(gateway): Extension<Arc<Gateway>>,
268    Json(payload): Json<PayInvoiceForOperatorPayload>,
269) -> Result<impl IntoResponse, AdminGatewayError> {
270    let preimage = gateway.handle_pay_invoice_for_operator_msg(payload).await?;
271    Ok(Json(json!(preimage.0.encode_hex::<String>())))
272}
273
274#[instrument(target = LOG_GATEWAY, skip_all, err)]
275async fn pay_invoice(
276    Extension(gateway): Extension<Arc<Gateway>>,
277    Json(payload): Json<fedimint_ln_client::pay::PayInvoicePayload>,
278) -> Result<impl IntoResponse, PublicGatewayError> {
279    let preimage = gateway.handle_pay_invoice_msg(payload).await?;
280    Ok(Json(json!(preimage.0.encode_hex::<String>())))
281}
282
283/// Connect a new federation
284#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
285async fn connect_fed(
286    Extension(gateway): Extension<Arc<Gateway>>,
287    Json(payload): Json<ConnectFedPayload>,
288) -> Result<impl IntoResponse, AdminGatewayError> {
289    let fed = gateway.handle_connect_federation(payload).await?;
290    Ok(Json(json!(fed)))
291}
292
293/// Leave a federation
294#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
295async fn leave_fed(
296    Extension(gateway): Extension<Arc<Gateway>>,
297    Json(payload): Json<LeaveFedPayload>,
298) -> Result<impl IntoResponse, AdminGatewayError> {
299    let fed = gateway.handle_leave_federation(payload).await?;
300    Ok(Json(json!(fed)))
301}
302
303/// Backup a gateway actor state
304#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
305async fn backup(
306    Extension(gateway): Extension<Arc<Gateway>>,
307    Json(payload): Json<BackupPayload>,
308) -> Result<impl IntoResponse, AdminGatewayError> {
309    gateway.handle_backup_msg(payload).await?;
310    Ok(Json(json!(())))
311}
312
313#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
314async fn set_fees(
315    Extension(gateway): Extension<Arc<Gateway>>,
316    Json(payload): Json<SetFeesPayload>,
317) -> Result<impl IntoResponse, AdminGatewayError> {
318    gateway.handle_set_fees_msg(payload).await?;
319    Ok(Json(json!(())))
320}
321
322#[instrument(target = LOG_GATEWAY, skip_all, err)]
323async fn get_ln_onchain_address(
324    Extension(gateway): Extension<Arc<Gateway>>,
325) -> Result<impl IntoResponse, AdminGatewayError> {
326    let address = gateway.handle_get_ln_onchain_address_msg().await?;
327    Ok(Json(json!(address.to_string())))
328}
329
330#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
331async fn open_channel(
332    Extension(gateway): Extension<Arc<Gateway>>,
333    Json(payload): Json<OpenChannelRequest>,
334) -> Result<impl IntoResponse, AdminGatewayError> {
335    let funding_txid = gateway.handle_open_channel_msg(payload).await?;
336    Ok(Json(json!(funding_txid)))
337}
338
339#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
340async fn close_channels_with_peer(
341    Extension(gateway): Extension<Arc<Gateway>>,
342    Json(payload): Json<CloseChannelsWithPeerRequest>,
343) -> Result<impl IntoResponse, AdminGatewayError> {
344    let response = gateway.handle_close_channels_with_peer_msg(payload).await?;
345    Ok(Json(json!(response)))
346}
347
348#[instrument(target = LOG_GATEWAY, skip_all, err)]
349async fn list_active_channels(
350    Extension(gateway): Extension<Arc<Gateway>>,
351) -> Result<impl IntoResponse, AdminGatewayError> {
352    let channels = gateway.handle_list_active_channels_msg().await?;
353    Ok(Json(json!(channels)))
354}
355
356#[instrument(target = LOG_GATEWAY, skip_all, err)]
357async fn send_onchain(
358    Extension(gateway): Extension<Arc<Gateway>>,
359    Json(payload): Json<SendOnchainRequest>,
360) -> Result<impl IntoResponse, AdminGatewayError> {
361    let txid = gateway.handle_send_onchain_msg(payload).await?;
362    Ok(Json(json!(txid)))
363}
364
365#[instrument(target = LOG_GATEWAY, skip_all, err)]
366async fn recheck_address(
367    Extension(gateway): Extension<Arc<Gateway>>,
368    Json(payload): Json<DepositAddressRecheckPayload>,
369) -> Result<impl IntoResponse, AdminGatewayError> {
370    gateway.handle_recheck_address_msg(payload).await?;
371    Ok(Json(json!({})))
372}
373
374#[instrument(target = LOG_GATEWAY, skip_all, err)]
375async fn get_balances(
376    Extension(gateway): Extension<Arc<Gateway>>,
377) -> Result<impl IntoResponse, AdminGatewayError> {
378    let balances = gateway.handle_get_balances_msg().await?;
379    Ok(Json(json!(balances)))
380}
381
382#[instrument(target = LOG_GATEWAY, skip_all, err)]
383async fn get_gateway_id(
384    Extension(gateway): Extension<Arc<Gateway>>,
385) -> Result<impl IntoResponse, PublicGatewayError> {
386    Ok(Json(json!(gateway.gateway_id)))
387}
388
389#[instrument(target = LOG_GATEWAY, skip_all, err)]
390async fn routing_info_v2(
391    Extension(gateway): Extension<Arc<Gateway>>,
392    Json(federation_id): Json<FederationId>,
393) -> Result<impl IntoResponse, PublicGatewayError> {
394    let routing_info = gateway.routing_info_v2(&federation_id).await?;
395    Ok(Json(json!(routing_info)))
396}
397
398#[instrument(target = LOG_GATEWAY, skip_all, err)]
399async fn pay_bolt11_invoice_v2(
400    Extension(gateway): Extension<Arc<Gateway>>,
401    Json(payload): Json<SendPaymentPayload>,
402) -> Result<impl IntoResponse, PublicGatewayError> {
403    let payment_result = gateway.send_payment_v2(payload).await?;
404    Ok(Json(json!(payment_result)))
405}
406
407#[instrument(target = LOG_GATEWAY, skip_all, err)]
408async fn create_bolt11_invoice_v2(
409    Extension(gateway): Extension<Arc<Gateway>>,
410    Json(payload): Json<CreateBolt11InvoicePayload>,
411) -> Result<impl IntoResponse, PublicGatewayError> {
412    let invoice = gateway.create_bolt11_invoice_v2(payload).await?;
413    Ok(Json(json!(invoice)))
414}
415
416#[instrument(target = LOG_GATEWAY, skip_all, err)]
417async fn spend_ecash(
418    Extension(gateway): Extension<Arc<Gateway>>,
419    Json(payload): Json<SpendEcashPayload>,
420) -> Result<impl IntoResponse, AdminGatewayError> {
421    Ok(Json(json!(gateway.handle_spend_ecash_msg(payload).await?)))
422}
423
424#[instrument(target = LOG_GATEWAY, skip_all, err)]
425async fn receive_ecash(
426    Extension(gateway): Extension<Arc<Gateway>>,
427    Json(payload): Json<ReceiveEcashPayload>,
428) -> Result<impl IntoResponse, PublicGatewayError> {
429    Ok(Json(json!(
430        gateway.handle_receive_ecash_msg(payload).await?
431    )))
432}
433
434#[instrument(target = LOG_GATEWAY, skip_all, err)]
435async fn mnemonic(
436    Extension(gateway): Extension<Arc<Gateway>>,
437) -> Result<impl IntoResponse, AdminGatewayError> {
438    let words = gateway.handle_mnemonic_msg().await?;
439    Ok(Json(json!(words)))
440}
441
442#[instrument(target = LOG_GATEWAY, skip_all, err)]
443async fn stop(
444    Extension(task_group): Extension<TaskGroup>,
445    Extension(gateway): Extension<Arc<Gateway>>,
446) -> Result<impl IntoResponse, AdminGatewayError> {
447    gateway.handle_shutdown_msg(task_group).await?;
448    Ok(Json(json!(())))
449}
450
451#[instrument(target = LOG_GATEWAY, skip_all, err)]
452async fn payment_log(
453    Extension(gateway): Extension<Arc<Gateway>>,
454    Json(payload): Json<PaymentLogPayload>,
455) -> Result<impl IntoResponse, AdminGatewayError> {
456    let payment_log = gateway.handle_payment_log_msg(payload).await?;
457    Ok(Json(json!(payment_log)))
458}
459
460#[instrument(target = LOG_GATEWAY, skip_all, err)]
461async fn payment_summary(
462    Extension(gateway): Extension<Arc<Gateway>>,
463    Json(payload): Json<PaymentSummaryPayload>,
464) -> Result<impl IntoResponse, AdminGatewayError> {
465    let payment_summary = gateway.handle_payment_summary_msg(payload).await?;
466    Ok(Json(json!(payment_summary)))
467}
468
469#[instrument(target = LOG_GATEWAY, skip_all, err)]
470async fn get_invoice(
471    Extension(gateway): Extension<Arc<Gateway>>,
472    Json(payload): Json<GetInvoiceRequest>,
473) -> Result<impl IntoResponse, AdminGatewayError> {
474    let invoice = gateway.handle_get_invoice_msg(payload).await?;
475    Ok(Json(json!(invoice)))
476}
477
478#[instrument(target = LOG_GATEWAY, skip_all, err)]
479async fn list_transactions(
480    Extension(gateway): Extension<Arc<Gateway>>,
481    Json(payload): Json<ListTransactionsPayload>,
482) -> Result<impl IntoResponse, AdminGatewayError> {
483    let transactions = gateway.handle_list_transactions_msg(payload).await?;
484    Ok(Json(json!(transactions)))
485}
486
487#[instrument(target = LOG_GATEWAY, skip_all, err)]
488async fn create_offer_for_operator(
489    Extension(gateway): Extension<Arc<Gateway>>,
490    Json(payload): Json<CreateOfferPayload>,
491) -> Result<impl IntoResponse, AdminGatewayError> {
492    let offer = gateway
493        .handle_create_offer_for_operator_msg(payload)
494        .await?;
495    Ok(Json(json!(offer)))
496}
497
498#[instrument(target = LOG_GATEWAY, skip_all, err)]
499async fn pay_offer_operator(
500    Extension(gateway): Extension<Arc<Gateway>>,
501    Json(payload): Json<PayOfferPayload>,
502) -> Result<impl IntoResponse, AdminGatewayError> {
503    let response = gateway.handle_pay_offer_for_operator_msg(payload).await?;
504    Ok(Json(json!(response)))
505}