Skip to content

Commit

Permalink
node: api: add POST /app/preflight_pay_invoice
Browse files Browse the repository at this point in the history
This endpoint lets the app ask its node to "pre-flight" a BOLT11 invoice
payment without going through with the actual payment. We verify as much
as we can, find a route, and get the fee estimates.
  • Loading branch information
phlip9 committed May 21, 2024
1 parent f5f1fbe commit 8967ed2
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 14 deletions.
9 changes: 8 additions & 1 deletion app-rs/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,8 @@ mod test {
command::{
CreateInvoiceRequest, CreateInvoiceResponse,
EstimateFeeSendOnchainRequest, EstimateFeeSendOnchainResponse,
NodeInfo, PayInvoiceRequest, SendOnchainRequest,
NodeInfo, PayInvoiceRequest, PreflightPayInvoiceRequest,
PreflightPayInvoiceResponse, SendOnchainRequest,
},
error::NodeApiError,
Empty,
Expand Down Expand Up @@ -1086,6 +1087,12 @@ mod test {
) -> Result<Empty, NodeApiError> {
unimplemented!()
}
async fn preflight_pay_invoice(
&self,
_req: PreflightPayInvoiceRequest,
) -> Result<PreflightPayInvoiceResponse, NodeApiError> {
unimplemented!()
}
async fn send_onchain(
&self,
_req: SendOnchainRequest,
Expand Down
21 changes: 21 additions & 0 deletions common/src/api/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,27 @@ pub struct PayInvoiceRequest {
pub note: Option<String>,
}

#[derive(Serialize, Deserialize)]
pub struct PreflightPayInvoiceRequest {
/// The invoice we want to pay.
pub invoice: LxInvoice,
/// Specifies the amount we will pay if the invoice to be paid is
/// amountless. This field must be [`Some`] for amountless invoices.
pub fallback_amount: Option<Amount>,
}

#[derive(Serialize, Deserialize)]
pub struct PreflightPayInvoiceResponse {
/// The total amount to-be-paid for the pre-flighted [`LxInvoice`],
/// excluding the fees.
///
/// This value may be different from the value originally requested if some
/// we had to reach `htlc_minimum_msat` for some intermediate hops.
pub amount: Amount,
/// The total amount of fees to-be-paid for the pre-flighted [`LxInvoice`].
pub fees: Amount,
}

#[derive(Serialize, Deserialize)]
pub struct SendOnchainRequest {
/// The identifier to use for this payment.
Expand Down
20 changes: 16 additions & 4 deletions common/src/api/def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

use async_trait::async_trait;

use super::command::CreateInvoiceResponse;
#[cfg(doc)]
use crate::api::qs::{GetByMeasurement, GetByUserPk};
use crate::{
Expand All @@ -34,9 +33,11 @@ use crate::{
UserSignupRequest,
},
command::{
CreateInvoiceRequest, EstimateFeeSendOnchainRequest,
EstimateFeeSendOnchainResponse, NodeInfo, OpenChannelRequest,
PayInvoiceRequest, SendOnchainRequest,
CreateInvoiceRequest, CreateInvoiceResponse,
EstimateFeeSendOnchainRequest, EstimateFeeSendOnchainResponse,
NodeInfo, OpenChannelRequest, PayInvoiceRequest,
PreflightPayInvoiceRequest, PreflightPayInvoiceResponse,
SendOnchainRequest,
},
error::{
BackendApiError, GatewayApiError, LspApiError, NodeApiError,
Expand Down Expand Up @@ -355,6 +356,17 @@ pub trait AppNodeRunApi {
req: PayInvoiceRequest,
) -> Result<Empty, NodeApiError>;

/// POST /app/preflight_pay_invoice [`PreflightPayInvoiceRequest`]
/// -> [`PreflightPayInvoiceResponse`]
///
/// This endpoint lets the app ask its node to "pre-flight" a BOLT11 invoice
/// payment without going through with the actual payment. We verify as much
/// as we can, find a route, and get the fee estimates.
async fn preflight_pay_invoice(
&self,
req: PreflightPayInvoiceRequest,
) -> Result<PreflightPayInvoiceResponse, NodeApiError>;

/// POST /app/send_onchain [`SendOnchainRequest`] -> [`LxTxid`]
///
/// Returns the [`LxTxid`] of the newly broadcasted transaction.
Expand Down
14 changes: 13 additions & 1 deletion common/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ use crate::{
command::{
CreateInvoiceRequest, CreateInvoiceResponse,
EstimateFeeSendOnchainRequest, EstimateFeeSendOnchainResponse,
NodeInfo, PayInvoiceRequest, SendOnchainRequest,
NodeInfo, PayInvoiceRequest, PreflightPayInvoiceRequest,
PreflightPayInvoiceResponse, SendOnchainRequest,
},
def::{
AppBackendApi, AppGatewayApi, AppNodeProvisionApi, AppNodeRunApi,
Expand Down Expand Up @@ -430,6 +431,17 @@ impl AppNodeRunApi for NodeClient {
self.run_rest.send(req).await
}

async fn preflight_pay_invoice(
&self,
req: PreflightPayInvoiceRequest,
) -> Result<PreflightPayInvoiceResponse, NodeApiError> {
self.ensure_authed().await?;
let run_url = &self.run_url;
let url = format!("{run_url}/app/preflight_pay_invoice");
let req = self.run_rest.post(url, &req);
self.run_rest.send(req).await
}

async fn send_onchain(
&self,
req: SendOnchainRequest,
Expand Down
33 changes: 32 additions & 1 deletion lexe-ln/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use common::{
command::{
CreateInvoiceRequest, CreateInvoiceResponse,
EstimateFeeSendOnchainRequest, EstimateFeeSendOnchainResponse,
NodeInfo, PayInvoiceRequest, SendOnchainRequest,
NodeInfo, PayInvoiceRequest, PreflightPayInvoiceRequest,
PreflightPayInvoiceResponse, SendOnchainRequest,
},
Empty, NodePk, Scid,
},
Expand Down Expand Up @@ -355,6 +356,36 @@ where
}
}

#[instrument(skip_all, name = "(preflight-pay-invoice)")]
pub async fn preflight_pay_invoice<CM, PS>(
req: PreflightPayInvoiceRequest,
router: Arc<RouterType>,
channel_manager: CM,
payments_manager: PaymentsManager<CM, PS>,
) -> anyhow::Result<PreflightPayInvoiceResponse>
where
CM: LexeChannelManager<PS>,
PS: LexePersister,
{
let req = PayInvoiceRequest {
invoice: req.invoice,
fallback_amount: req.fallback_amount,
// User note not relevant for pre-flight.
note: None,
};
let preflight = preflight_pay_invoice_inner(
req,
router,
&channel_manager,
&payments_manager,
)
.await?;
Ok(PreflightPayInvoiceResponse {
amount: preflight.payment.amount,
fees: preflight.payment.fees,
})
}

#[instrument(skip_all, name = "(send-onchain)")]
pub async fn send_onchain<CM, PS>(
req: SendOnchainRequest,
Expand Down
18 changes: 17 additions & 1 deletion node/src/server/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use common::{
command::{
CreateInvoiceRequest, CreateInvoiceResponse,
EstimateFeeSendOnchainRequest, EstimateFeeSendOnchainResponse,
NodeInfo, PayInvoiceRequest, SendOnchainRequest,
NodeInfo, PayInvoiceRequest, PreflightPayInvoiceRequest,
PreflightPayInvoiceResponse, SendOnchainRequest,
},
error::NodeApiError,
qs::{GetNewPayments, GetPaymentsByIds, UpdatePaymentNote},
Expand Down Expand Up @@ -71,6 +72,21 @@ pub(super) async fn pay_invoice(
.map_err(NodeApiError::command)
}

pub(super) async fn preflight_pay_invoice(
State(state): State<Arc<AppRouterState>>,
LxJson(req): LxJson<PreflightPayInvoiceRequest>,
) -> Result<LxJson<PreflightPayInvoiceResponse>, NodeApiError> {
lexe_ln::command::preflight_pay_invoice(
req,
state.router.clone(),
state.channel_manager.clone(),
state.payments_manager.clone(),
)
.await
.map(LxJson)
.map_err(NodeApiError::command)
}

pub(super) async fn send_onchain(
State(state): State<Arc<AppRouterState>>,
LxJson(req): LxJson<SendOnchainRequest>,
Expand Down
12 changes: 6 additions & 6 deletions node/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,14 @@ pub(crate) struct AppRouterState {
/// [`AppNodeRunApi`]: common::api::def::AppNodeRunApi
pub(crate) fn app_router(state: Arc<AppRouterState>) -> Router<()> {
let activity_tx = state.activity_tx.clone();
Router::new()
#[rustfmt::skip]
let router = Router::new()
.route("/app/node_info", get(app::node_info))
.route("/app/create_invoice", post(app::create_invoice))
.route("/app/pay_invoice", post(app::pay_invoice))
.route("/app/preflight_pay_invoice", post(app::preflight_pay_invoice))
.route("/app/send_onchain", post(app::send_onchain))
.route(
"/app/estimate_fee_send_onchain",
get(app::estimate_fee_send_onchain),
)
.route("/app/estimate_fee_send_onchain", get(app::estimate_fee_send_onchain))
.route("/app/get_address", post(app::get_address))
.route("/app/payments/ids", post(app::get_payments_by_ids))
.route("/app/payments/new", get(app::get_new_payments))
Expand All @@ -80,7 +79,8 @@ pub(crate) fn app_router(state: Arc<AppRouterState>) -> Router<()> {
debug!("Sending activity event");
let _ = activity_tx.try_send(());
request
}))
}));
router
}

pub(crate) struct LexeRouterState {
Expand Down

0 comments on commit 8967ed2

Please sign in to comment.