Skip to content

Commit b583351

Browse files
committed
Support paying BOLT 12 invoices
Add a send_payment_for_bolt12_invoice method to OutboundPayments for initiating payment of a BOLT 12 invoice. This will be called from an OffersMessageHandler, after which any retries are handled using the Retryable logic.
1 parent cc60aa8 commit b583351

File tree

1 file changed

+108
-28
lines changed

1 file changed

+108
-28
lines changed

lightning/src/ln/outbound_payment.rs

Lines changed: 108 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::events::{self, PaymentFailureReason};
1818
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
1919
use crate::ln::channelmanager::{ChannelDetails, EventCompletionAction, HTLCSource, PaymentId};
2020
use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
21+
use crate::offers::invoice::Bolt12Invoice;
2122
use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
2223
use crate::util::errors::APIError;
2324
use crate::util::logger::Logger;
@@ -440,6 +441,14 @@ pub enum PaymentSendFailure {
440441
},
441442
}
442443

444+
/// An error when attempting to pay a BOLT 12 invoice.
445+
pub(super) enum Bolt12PaymentError {
446+
/// The invoice was not requested.
447+
UnexpectedInvoice,
448+
/// Payment for an invoice with the corresponding [`PaymentId`] was already initiated.
449+
DuplicateInvoice,
450+
}
451+
443452
/// Information which is provided, encrypted, to the payment recipient when sending HTLCs.
444453
///
445454
/// This should generally be constructed with data communicated to us from the recipient (via a
@@ -577,6 +586,8 @@ pub(super) struct SendAlongPathArgs<'a> {
577586
pub session_priv_bytes: [u8; 32],
578587
}
579588

589+
const BOLT_12_INVOICE_RETRY_STRATEGY: Retry = Retry::Attempts(3);
590+
580591
pub(super) struct OutboundPayments {
581592
pub(super) pending_outbound_payments: Mutex<HashMap<PaymentId, PendingOutboundPayment>>,
582593
pub(super) retry_lock: Mutex<()>,
@@ -677,6 +688,45 @@ impl OutboundPayments {
677688
}
678689
}
679690

691+
#[allow(unused)]
692+
pub(super) fn send_payment_for_bolt12_invoice<R: Deref, ES: Deref, NS: Deref, IH, SP, L: Deref>(
693+
&self, invoice: &Bolt12Invoice, payment_id: PaymentId, router: &R,
694+
first_hops: Vec<ChannelDetails>, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
695+
best_block_height: u32, logger: &L,
696+
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
697+
send_payment_along_path: SP,
698+
) -> Result<(), Bolt12PaymentError>
699+
where
700+
R::Target: Router,
701+
ES::Target: EntropySource,
702+
NS::Target: NodeSigner,
703+
L::Target: Logger,
704+
IH: Fn() -> InFlightHtlcs,
705+
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
706+
{
707+
let payment_hash = invoice.payment_hash();
708+
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
709+
hash_map::Entry::Occupied(entry) if entry.get().is_awaiting_invoice() => {
710+
*entry.into_mut() = PendingOutboundPayment::InvoiceReceived { payment_hash };
711+
},
712+
hash_map::Entry::Occupied(_) => return Err(Bolt12PaymentError::DuplicateInvoice),
713+
hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice),
714+
};
715+
716+
let route_params = RouteParameters {
717+
payment_params: PaymentParameters::from_bolt12_invoice(&invoice),
718+
final_value_msat: invoice.amount_msats(),
719+
};
720+
721+
self.retry_payment_internal(
722+
payment_hash, payment_id, route_params, router, first_hops, &inflight_htlcs,
723+
entropy_source, node_signer, best_block_height, logger, pending_events,
724+
&send_payment_along_path
725+
);
726+
727+
Ok(())
728+
}
729+
680730
pub(super) fn check_retry_payments<R: Deref, ES: Deref, NS: Deref, SP, IH, FH, L: Deref>(
681731
&self, router: &R, first_hops: FH, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
682732
best_block_height: u32,
@@ -902,12 +952,26 @@ impl OutboundPayments {
902952
log_error!(logger, "Unable to retry payments that were initially sent on LDK versions prior to 0.0.102");
903953
return
904954
},
905-
PendingOutboundPayment::AwaitingInvoice { .. } |
906-
PendingOutboundPayment::InvoiceReceived { .. } =>
907-
{
955+
PendingOutboundPayment::AwaitingInvoice { .. } => {
908956
log_error!(logger, "Payment not yet sent");
909957
return
910958
},
959+
PendingOutboundPayment::InvoiceReceived { payment_hash } => {
960+
let total_amount = route_params.final_value_msat;
961+
let recipient_onion = RecipientOnionFields {
962+
payment_secret: None,
963+
payment_metadata: None,
964+
custom_tlvs: vec![],
965+
};
966+
let retry_strategy = Some(BOLT_12_INVOICE_RETRY_STRATEGY);
967+
let payment_params = Some(route_params.payment_params.clone());
968+
let (retryable_payment, onion_session_privs) = self.create_pending_payment(
969+
*payment_hash, recipient_onion.clone(), None, &route,
970+
retry_strategy, payment_params, entropy_source, best_block_height
971+
);
972+
*payment.into_mut() = retryable_payment;
973+
(total_amount, recipient_onion, None, onion_session_privs)
974+
},
911975
PendingOutboundPayment::Fulfilled { .. } => {
912976
log_error!(logger, "Payment already completed");
913977
return
@@ -1070,40 +1134,56 @@ impl OutboundPayments {
10701134
keysend_preimage: Option<PaymentPreimage>, route: &Route, retry_strategy: Option<Retry>,
10711135
payment_params: Option<PaymentParameters>, entropy_source: &ES, best_block_height: u32
10721136
) -> Result<Vec<[u8; 32]>, PaymentSendFailure> where ES::Target: EntropySource {
1073-
let mut onion_session_privs = Vec::with_capacity(route.paths.len());
1074-
for _ in 0..route.paths.len() {
1075-
onion_session_privs.push(entropy_source.get_secure_random_bytes());
1076-
}
1077-
10781137
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
10791138
match pending_outbounds.entry(payment_id) {
10801139
hash_map::Entry::Occupied(_) => Err(PaymentSendFailure::DuplicatePayment),
10811140
hash_map::Entry::Vacant(entry) => {
1082-
let payment = entry.insert(PendingOutboundPayment::Retryable {
1083-
retry_strategy,
1084-
attempts: PaymentAttempts::new(),
1085-
payment_params,
1086-
session_privs: HashSet::new(),
1087-
pending_amt_msat: 0,
1088-
pending_fee_msat: Some(0),
1089-
payment_hash,
1090-
payment_secret: recipient_onion.payment_secret,
1091-
payment_metadata: recipient_onion.payment_metadata,
1092-
keysend_preimage,
1093-
custom_tlvs: recipient_onion.custom_tlvs,
1094-
starting_block_height: best_block_height,
1095-
total_msat: route.get_total_amount(),
1096-
});
1097-
1098-
for (path, session_priv_bytes) in route.paths.iter().zip(onion_session_privs.iter()) {
1099-
assert!(payment.insert(*session_priv_bytes, path));
1100-
}
1101-
1141+
let (payment, onion_session_privs) = self.create_pending_payment(
1142+
payment_hash, recipient_onion, keysend_preimage, route, retry_strategy,
1143+
payment_params, entropy_source, best_block_height
1144+
);
1145+
entry.insert(payment);
11021146
Ok(onion_session_privs)
11031147
},
11041148
}
11051149
}
11061150

1151+
fn create_pending_payment<ES: Deref>(
1152+
&self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
1153+
keysend_preimage: Option<PaymentPreimage>, route: &Route, retry_strategy: Option<Retry>,
1154+
payment_params: Option<PaymentParameters>, entropy_source: &ES, best_block_height: u32
1155+
) -> (PendingOutboundPayment, Vec<[u8; 32]>)
1156+
where
1157+
ES::Target: EntropySource,
1158+
{
1159+
let mut onion_session_privs = Vec::with_capacity(route.paths.len());
1160+
for _ in 0..route.paths.len() {
1161+
onion_session_privs.push(entropy_source.get_secure_random_bytes());
1162+
}
1163+
1164+
let mut payment = PendingOutboundPayment::Retryable {
1165+
retry_strategy,
1166+
attempts: PaymentAttempts::new(),
1167+
payment_params,
1168+
session_privs: HashSet::new(),
1169+
pending_amt_msat: 0,
1170+
pending_fee_msat: Some(0),
1171+
payment_hash,
1172+
payment_secret: recipient_onion.payment_secret,
1173+
payment_metadata: recipient_onion.payment_metadata,
1174+
keysend_preimage,
1175+
custom_tlvs: recipient_onion.custom_tlvs,
1176+
starting_block_height: best_block_height,
1177+
total_msat: route.get_total_amount(),
1178+
};
1179+
1180+
for (path, session_priv_bytes) in route.paths.iter().zip(onion_session_privs.iter()) {
1181+
assert!(payment.insert(*session_priv_bytes, path));
1182+
}
1183+
1184+
(payment, onion_session_privs)
1185+
}
1186+
11071187
#[allow(unused)]
11081188
pub(super) fn add_new_awaiting_invoice(&self, payment_id: PaymentId) -> Result<(), ()> {
11091189
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();

0 commit comments

Comments
 (0)