Skip to content

Commit 7ce7d5e

Browse files
committed
Add custom TLV support to PendingOutboundPayment for Bolt12 flows
Bolt11 payments now allow attaching custom TLVs to the onion packet, giving payers a way to pass metadata ranging from descriptions to custom authentication. Bolt12 lacked an equivalent path, leaving its outbound flow less extensible. This commit adds a `custom_tlvs` field to the Bolt12-related `PendingOutboundPayment` variants, bringing the internal Bolt12 payment state in line with the Bolt11 changes and preparing the ground for user-facing API support. A follow-up commit will expose a way for users to set these TLVs.
1 parent ce467a4 commit 7ce7d5e

File tree

2 files changed

+54
-36
lines changed

2 files changed

+54
-36
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12874,7 +12874,8 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
1287412874
/// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments
1287512875
pub fn create_refund_builder(
1287612876
&$self, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId,
12877-
retry_strategy: Retry, route_params_config: RouteParametersConfig
12877+
custom_tlvs: Vec<(u64, Vec<u8>)>, retry_strategy: Retry,
12878+
route_params_config: RouteParametersConfig
1287812879
) -> Result<$builder, Bolt12SemanticError> {
1287912880
let entropy = &*$self.entropy_source;
1288012881

@@ -12888,7 +12889,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
1288812889
let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry);
1288912890
$self.pending_outbound_payments
1289012891
.add_new_awaiting_invoice(
12891-
payment_id, expiration, retry_strategy, route_params_config, None,
12892+
payment_id, custom_tlvs, expiration, retry_strategy, route_params_config, None,
1289212893
)
1289312894
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
1289412895

@@ -12915,7 +12916,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
1291512916
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
1291612917
pub fn create_refund_builder_using_router<ME: Deref>(
1291712918
&$self, router: ME, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId,
12918-
retry_strategy: Retry, route_params_config: RouteParametersConfig
12919+
custom_tlvs: Vec<(u64, Vec<u8>)>, retry_strategy: Retry, route_params_config: RouteParametersConfig
1291912920
) -> Result<$builder, Bolt12SemanticError>
1292012921
where
1292112922
ME::Target: MessageRouter,
@@ -12932,7 +12933,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
1293212933
let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry);
1293312934
$self.pending_outbound_payments
1293412935
.add_new_awaiting_invoice(
12935-
payment_id, expiration, retry_strategy, route_params_config, None,
12936+
payment_id, custom_tlvs, expiration, retry_strategy, route_params_config, None,
1293612937
)
1293712938
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
1293812939

@@ -13054,6 +13055,7 @@ where
1305413055
self.pending_outbound_payments
1305513056
.add_new_awaiting_invoice(
1305613057
payment_id,
13058+
vec![],
1305713059
StaleExpiration::TimerTicks(1),
1305813060
optional_params.retry_strategy,
1305913061
optional_params.route_params_config,
@@ -13083,6 +13085,7 @@ where
1308313085
self.pending_outbound_payments
1308413086
.add_new_awaiting_invoice(
1308513087
payment_id,
13088+
vec![],
1308613089
StaleExpiration::TimerTicks(1),
1308713090
optional_params.retry_strategy,
1308813091
optional_params.route_params_config,
@@ -13125,6 +13128,7 @@ where
1312513128
self.pending_outbound_payments
1312613129
.add_new_awaiting_invoice(
1312713130
payment_id,
13131+
vec![],
1312813132
StaleExpiration::TimerTicks(1),
1312913133
optional_params.retry_strategy,
1313013134
optional_params.route_params_config,
@@ -15778,7 +15782,7 @@ where
1577815782
self.pay_for_offer_intern(&offer, None, Some(amt_msats), payer_note, payment_id, Some(name),
1577915783
|retryable_invoice_request| {
1578015784
self.pending_outbound_payments
15781-
.received_offer(payment_id, Some(retryable_invoice_request))
15785+
.received_offer(payment_id, vec![], Some(retryable_invoice_request))
1578215786
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)
1578315787
});
1578415788
if offer_pay_res.is_err() {

lightning/src/ln/outbound_payment.rs

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ pub(crate) enum PendingOutboundPayment {
8282
retry_strategy: Retry,
8383
route_params_config: RouteParametersConfig,
8484
retryable_invoice_request: Option<RetryableInvoiceRequest>,
85+
custom_tlvs: Vec<(u64, Vec<u8>)>,
8586
},
8687
// Represents the state after the invoice has been received, transitioning from the corresponding
8788
// `AwaitingInvoice` state.
@@ -93,6 +94,7 @@ pub(crate) enum PendingOutboundPayment {
9394
// race conditions where this field might be missing upon reload. It may be required
9495
// for future retries.
9596
route_params_config: RouteParametersConfig,
97+
custom_tlvs: Vec<(u64, Vec<u8>)>,
9698
},
9799
// This state applies when we are paying an often-offline recipient and another node on the
98100
// network served us a static invoice on the recipient's behalf in response to our invoice
@@ -986,7 +988,7 @@ where
986988
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
987989
{
988990

989-
let (payment_hash, retry_strategy, params_config, _) = self
991+
let (payment_hash, retry_strategy, params_config, custom_tlvs, _) = self
990992
.mark_invoice_received_and_get_details(invoice, payment_id)?;
991993

992994
if invoice.invoice_features().requires_unknown_bits_from(&features) {
@@ -1004,10 +1006,16 @@ where
10041006
route_params.max_total_routing_fee_msat = Some(max_fee_msat);
10051007
}
10061008
let invoice = PaidBolt12Invoice::Bolt12Invoice(invoice.clone());
1009+
1010+
let recipient_onion = RecipientOnionFields::spontaneous_empty()
1011+
.with_custom_tlvs(custom_tlvs)
1012+
.map_err(|_| Bolt12PaymentError::SendingFailed(RetryableSendFailure::InvalidCustomTlvs))?;
1013+
10071014
self.send_payment_for_bolt12_invoice_internal(
1008-
payment_id, payment_hash, None, None, invoice, route_params, retry_strategy, false, router,
1009-
first_hops, inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx,
1010-
best_block_height, pending_events, send_payment_along_path
1015+
payment_id, payment_hash, None, None, invoice, recipient_onion,
1016+
route_params, retry_strategy, false, router, first_hops, inflight_htlcs,
1017+
entropy_source, node_signer, node_id_lookup, secp_ctx, best_block_height, pending_events,
1018+
send_payment_along_path
10111019
)
10121020
}
10131021

@@ -1017,7 +1025,7 @@ where
10171025
>(
10181026
&self, payment_id: PaymentId, payment_hash: PaymentHash,
10191027
keysend_preimage: Option<PaymentPreimage>, invoice_request: Option<&InvoiceRequest>,
1020-
bolt12_invoice: PaidBolt12Invoice,
1028+
bolt12_invoice: PaidBolt12Invoice, recipient_onion: RecipientOnionFields,
10211029
mut route_params: RouteParameters, retry_strategy: Retry, hold_htlcs_at_next_hop: bool, router: &R,
10221030
first_hops: Vec<ChannelDetails>, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
10231031
node_id_lookup: &NL, secp_ctx: &Secp256k1<secp256k1::All>, best_block_height: u32,
@@ -1050,11 +1058,6 @@ where
10501058
}
10511059
}
10521060

1053-
let recipient_onion = RecipientOnionFields {
1054-
payment_secret: None,
1055-
payment_metadata: None,
1056-
custom_tlvs: vec![],
1057-
};
10581061
let route = match self.find_initial_route(
10591062
payment_id, payment_hash, &recipient_onion, keysend_preimage, invoice_request,
10601063
&mut route_params, router, &first_hops, &inflight_htlcs, node_signer, best_block_height,
@@ -1289,13 +1292,16 @@ where
12891292
retry_strategy = Retry::Attempts(0);
12901293
}
12911294

1295+
let recipient_onion = RecipientOnionFields::spontaneous_empty();
1296+
12921297
let invoice = PaidBolt12Invoice::StaticInvoice(invoice);
12931298
self.send_payment_for_bolt12_invoice_internal(
12941299
payment_id,
12951300
payment_hash,
12961301
Some(keysend_preimage),
12971302
Some(&invoice_request),
12981303
invoice,
1304+
recipient_onion,
12991305
route_params,
13001306
retry_strategy,
13011307
hold_htlcs_at_next_hop,
@@ -1980,7 +1986,8 @@ where
19801986
#[cfg(feature = "dnssec")]
19811987
#[rustfmt::skip]
19821988
pub(super) fn received_offer(
1983-
&self, payment_id: PaymentId, retryable_invoice_request: Option<RetryableInvoiceRequest>,
1989+
&self, payment_id: PaymentId, custom_tlvs: Vec<(u64, Vec<u8>)>,
1990+
retryable_invoice_request: Option<RetryableInvoiceRequest>,
19841991
) -> Result<(), ()> {
19851992
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
19861993
hash_map::Entry::Occupied(entry) => match entry.get() {
@@ -1992,6 +1999,7 @@ where
19921999
retry_strategy: *retry_strategy,
19932000
route_params_config: *route_params_config,
19942001
retryable_invoice_request,
2002+
custom_tlvs,
19952003
};
19962004
core::mem::swap(&mut new_val, entry.into_mut());
19972005
Ok(())
@@ -2003,7 +2011,8 @@ where
20032011
}
20042012

20052013
pub(super) fn add_new_awaiting_invoice(
2006-
&self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry,
2014+
&self, payment_id: PaymentId, custom_tlvs: Vec<(u64, Vec<u8>)>,
2015+
expiration: StaleExpiration, retry_strategy: Retry,
20072016
route_params_config: RouteParametersConfig,
20082017
retryable_invoice_request: Option<RetryableInvoiceRequest>,
20092018
) -> Result<(), ()> {
@@ -2019,6 +2028,7 @@ where
20192028
retry_strategy,
20202029
route_params_config,
20212030
retryable_invoice_request,
2031+
custom_tlvs,
20222032
});
20232033

20242034
Ok(())
@@ -2031,7 +2041,7 @@ where
20312041
&self, invoice: &Bolt12Invoice, payment_id: PaymentId
20322042
) -> Result<(), Bolt12PaymentError> {
20332043
self.mark_invoice_received_and_get_details(invoice, payment_id)
2034-
.and_then(|(_, _, _, is_newly_marked)| {
2044+
.and_then(|(_, _, _, _, is_newly_marked)| {
20352045
is_newly_marked
20362046
.then_some(())
20372047
.ok_or(Bolt12PaymentError::DuplicateInvoice)
@@ -2040,32 +2050,34 @@ where
20402050

20412051
#[rustfmt::skip]
20422052
fn mark_invoice_received_and_get_details(
2043-
&self, invoice: &Bolt12Invoice, payment_id: PaymentId
2044-
) -> Result<(PaymentHash, Retry, RouteParametersConfig, bool), Bolt12PaymentError> {
2053+
&self, invoice: &Bolt12Invoice, payment_id: PaymentId,
2054+
) -> Result<(PaymentHash, Retry, RouteParametersConfig, Vec<(u64, Vec<u8>)>, bool), Bolt12PaymentError> {
20452055
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
2046-
hash_map::Entry::Occupied(entry) => match entry.get() {
2056+
hash_map::Entry::Occupied(mut entry) => match entry.get_mut() {
20472057
PendingOutboundPayment::AwaitingInvoice {
2048-
retry_strategy: retry, route_params_config, ..
2058+
retry_strategy: retry, route_params_config, custom_tlvs, ..
20492059
} => {
20502060
let payment_hash = invoice.payment_hash();
20512061
let retry = *retry;
20522062
let config = *route_params_config;
2053-
*entry.into_mut() = PendingOutboundPayment::InvoiceReceived {
2063+
let custom = core::mem::take(custom_tlvs);
2064+
*entry.get_mut() = PendingOutboundPayment::InvoiceReceived {
20542065
payment_hash,
20552066
retry_strategy: retry,
20562067
route_params_config: config,
2068+
custom_tlvs: custom.clone(),
20572069
};
20582070

2059-
Ok((payment_hash, retry, config, true))
2071+
Ok((payment_hash, retry, config, custom, true))
20602072
},
20612073
// When manual invoice handling is enabled, the corresponding `PendingOutboundPayment` entry
20622074
// is already updated at the time the invoice is received. This ensures that `InvoiceReceived`
20632075
// event generation remains idempotent, even if the same invoice is received again before the
20642076
// event is handled by the user.
20652077
PendingOutboundPayment::InvoiceReceived {
2066-
retry_strategy, route_params_config, ..
2078+
retry_strategy, route_params_config, custom_tlvs, ..
20672079
} => {
2068-
Ok((invoice.payment_hash(), *retry_strategy, *route_params_config, false))
2080+
Ok((invoice.payment_hash(), *retry_strategy, *route_params_config, custom_tlvs.clone(), false))
20692081
},
20702082
_ => Err(Bolt12PaymentError::DuplicateInvoice),
20712083
},
@@ -2757,6 +2769,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
27572769
|fee_msat| RouteParametersConfig::default().with_max_total_routing_fee_msat(fee_msat)
27582770
)
27592771
))),
2772+
(9, custom_tlvs, optional_vec),
27602773
},
27612774
(7, InvoiceReceived) => {
27622775
(0, payment_hash, required),
@@ -2773,6 +2786,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
27732786
|fee_msat| RouteParametersConfig::default().with_max_total_routing_fee_msat(fee_msat)
27742787
)
27752788
))),
2789+
(7, custom_tlvs, optional_vec),
27762790
},
27772791
// Added in 0.1. Prior versions will drop these outbounds on downgrade, which is safe because no
27782792
// HTLCs are in-flight.
@@ -3053,7 +3067,7 @@ mod tests {
30533067
assert!(!outbound_payments.has_pending_payments());
30543068
assert!(
30553069
outbound_payments.add_new_awaiting_invoice(
3056-
payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
3070+
payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
30573071
).is_ok()
30583072
);
30593073
assert!(outbound_payments.has_pending_payments());
@@ -3083,14 +3097,14 @@ mod tests {
30833097

30843098
assert!(
30853099
outbound_payments.add_new_awaiting_invoice(
3086-
payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
3100+
payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
30873101
).is_ok()
30883102
);
30893103
assert!(outbound_payments.has_pending_payments());
30903104

30913105
assert!(
30923106
outbound_payments.add_new_awaiting_invoice(
3093-
payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
3107+
payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
30943108
).is_err()
30953109
);
30963110
}
@@ -3108,7 +3122,7 @@ mod tests {
31083122
assert!(!outbound_payments.has_pending_payments());
31093123
assert!(
31103124
outbound_payments.add_new_awaiting_invoice(
3111-
payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
3125+
payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
31123126
).is_ok()
31133127
);
31143128
assert!(outbound_payments.has_pending_payments());
@@ -3138,14 +3152,14 @@ mod tests {
31383152

31393153
assert!(
31403154
outbound_payments.add_new_awaiting_invoice(
3141-
payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
3155+
payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
31423156
).is_ok()
31433157
);
31443158
assert!(outbound_payments.has_pending_payments());
31453159

31463160
assert!(
31473161
outbound_payments.add_new_awaiting_invoice(
3148-
payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
3162+
payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
31493163
).is_err()
31503164
);
31513165
}
@@ -3162,7 +3176,7 @@ mod tests {
31623176
assert!(!outbound_payments.has_pending_payments());
31633177
assert!(
31643178
outbound_payments.add_new_awaiting_invoice(
3165-
payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
3179+
payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
31663180
).is_ok()
31673181
);
31683182
assert!(outbound_payments.has_pending_payments());
@@ -3201,7 +3215,7 @@ mod tests {
32013215

32023216
assert!(
32033217
outbound_payments.add_new_awaiting_invoice(
3204-
payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
3218+
payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None,
32053219
).is_ok()
32063220
);
32073221
assert!(outbound_payments.has_pending_payments());
@@ -3267,7 +3281,7 @@ mod tests {
32673281

32683282
assert!(
32693283
outbound_payments.add_new_awaiting_invoice(
3270-
payment_id, expiration, Retry::Attempts(0),
3284+
payment_id, vec![], expiration, Retry::Attempts(0),
32713285
route_params_config, None,
32723286
).is_ok()
32733287
);
@@ -3370,7 +3384,7 @@ mod tests {
33703384

33713385
assert!(
33723386
outbound_payments.add_new_awaiting_invoice(
3373-
payment_id, expiration, Retry::Attempts(0), route_params_config, None,
3387+
payment_id, vec![], expiration, Retry::Attempts(0), route_params_config, None,
33743388
).is_ok()
33753389
);
33763390
assert!(outbound_payments.has_pending_payments());

0 commit comments

Comments
 (0)