Skip to content

Commit 2abd54b

Browse files
committed
Allow BOLT 11 payments to be a part of a larger MPP payment
In some uses of LDK we need the ability to send HTLCs for only a portion of some larger MPP payment. This allows payers to make single payments which spend funds from multiple wallets, which may be important for ecash wallets holding funds in multiple mints or graduated wallets which hold funds across a trusted wallet and a self-custodial wallet. In the previous few commits we added support for making these kinds of payments when using the payment methods which explicitly accepted a `RecipientOnionFields`. Here we also add support for such payments made via the `pay_for_bolt11_invoice` method, utilizing the new `OptionalBolt11PaymentParams` to hide the parameter from most calls. Test mostly by Claude
1 parent 05e5072 commit 2abd54b

File tree

4 files changed

+205
-6
lines changed

4 files changed

+205
-6
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,20 @@ pub struct OptionalBolt11PaymentParams {
685685
/// will ultimately fail once all pending paths have failed (generating an
686686
/// [`Event::PaymentFailed`]).
687687
pub retry_strategy: Retry,
688+
/// If the payment being made from this node is part of a larger MPP payment from multiple
689+
/// nodes (i.e. because a single payment is being made from multiple wallets), you can specify
690+
/// the total amount being paid here.
691+
///
692+
/// If this is set, it must be at least the [`Bolt11Invoice::amount_milli_satoshis`] for the
693+
/// invoice provided to [`ChannelManager::pay_for_bolt11_invoice`]. Further, if this is set,
694+
/// the `amount_msats` provided to [`ChannelManager::pay_for_bolt11_invoice`] is allowed to be
695+
/// lower than [`Bolt11Invoice::amount_milli_satoshis`] (as the payment we're making may be a
696+
/// small part of the amount needed to meet the invoice's minimum).
697+
///
698+
/// If this is lower than the `amount_msats` passed to
699+
/// [`ChannelManager::pay_for_bolt11_invoice`] the call will fail with
700+
/// [`Bolt11PaymentError::InvalidAmount`].
701+
pub declared_total_mpp_value_override: Option<u64>,
688702
}
689703

690704
impl Default for OptionalBolt11PaymentParams {
@@ -696,6 +710,7 @@ impl Default for OptionalBolt11PaymentParams {
696710
retry_strategy: Retry::Timeout(core::time::Duration::from_secs(2)),
697711
#[cfg(not(feature = "std"))]
698712
retry_strategy: Retry::Attempts(3),
713+
declared_total_mpp_value_override: None,
699714
}
700715
}
701716
}
@@ -5451,10 +5466,18 @@ impl<
54515466
/// The invoice's `payment_hash().0` serves as a reliable choice for the `payment_id`.
54525467
///
54535468
/// # Handling Invoice Amounts
5454-
/// Some invoices include a specific amount, while others require you to specify one.
5455-
/// - If the invoice **includes** an amount, user may provide an amount greater or equal to it
5456-
/// to allow for overpayments.
5457-
/// - If the invoice **doesn't include** an amount, you'll need to specify `amount_msats`.
5469+
/// Some invoices require a specific amount (which can be fetched with
5470+
/// [`Bolt11Invoice::amount_milli_satoshis`]) while others allow you to pay amount amount.
5471+
///
5472+
/// - If the invoice **includes** an amount, `amount_msats` may be `None` to pay exactly
5473+
/// [`Bolt11Invoice::amount_milli_satoshis`] or may be `Some` with a value greater than or
5474+
/// equal to the [`Bolt11Invoice::amount_milli_satoshis`] to allow for deliberate overpayment
5475+
/// (e.g. for "tips").
5476+
/// - If the invoice **doesn't include** an amount, `amount_msats` must be `Some`.
5477+
///
5478+
/// In the special case that [`OptionalBolt11PaymentParams::declared_total_mpp_value_override`]
5479+
/// is set, `amount_msats` may be `Some` and lower than
5480+
/// [`Bolt11Invoice::amount_milli_satoshis`]. See the parameter for more details.
54585481
///
54595482
/// If these conditions aren’t met, the function will return [`Bolt11PaymentError::InvalidAmount`].
54605483
///

lightning/src/ln/invoice_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ mod test {
691691
custom_tlvs: custom_tlvs.clone(),
692692
route_params_config: RouteParametersConfig::default(),
693693
retry_strategy: Retry::Attempts(0),
694+
declared_total_mpp_value_override: None,
694695
};
695696

696697
nodes[0]

lightning/src/ln/outbound_payment.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,11 @@ pub(crate) enum PaymentSendFailure {
627627
#[derive(Debug)]
628628
pub enum Bolt11PaymentError {
629629
/// Incorrect amount was provided to [`ChannelManager::pay_for_bolt11_invoice`].
630-
/// This happens when the user-provided amount is less than an amount specified in the [`Bolt11Invoice`].
630+
///
631+
/// This happens when the user-provided amount is less than an amount specified in the
632+
/// [`Bolt11Invoice`] or the amount set at
633+
/// [`OptionalBolt11PaymentParams::declared_total_mpp_value_override`] was lower than the
634+
/// explicit amount provided to [`ChannelManager::pay_for_bolt11_invoice`].
631635
///
632636
/// [`Bolt11Invoice`]: lightning_invoice::Bolt11Invoice
633637
/// [`ChannelManager::pay_for_bolt11_invoice`]: crate::ln::channelmanager::ChannelManager::pay_for_bolt11_invoice
@@ -1031,9 +1035,11 @@ impl OutboundPayments {
10311035
{
10321036
let payment_hash = invoice.payment_hash();
10331037

1038+
let partial_payment = optional_params.declared_total_mpp_value_override.is_some();
10341039
let amount = match (invoice.amount_milli_satoshis(), amount_msats) {
10351040
(Some(amt), None) | (None, Some(amt)) => amt,
1036-
(Some(inv_amt), Some(user_amt)) if user_amt < inv_amt => return Err(Bolt11PaymentError::InvalidAmount),
1041+
(Some(inv_amt), Some(user_amt)) if user_amt < inv_amt && !partial_payment =>
1042+
return Err(Bolt11PaymentError::InvalidAmount),
10371043
(Some(_), Some(user_amt)) => user_amt,
10381044
(None, None) => return Err(Bolt11PaymentError::InvalidAmount),
10391045
};
@@ -1043,6 +1049,18 @@ impl OutboundPayments {
10431049
.with_custom_tlvs(optional_params.custom_tlvs);
10441050
recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone());
10451051

1052+
if let Some(mpp_amt) = optional_params.declared_total_mpp_value_override {
1053+
if mpp_amt < amount {
1054+
return Err(Bolt11PaymentError::InvalidAmount);
1055+
}
1056+
if let Some(invoice_amount) = invoice.amount_milli_satoshis() {
1057+
if mpp_amt < invoice_amount {
1058+
return Err(Bolt11PaymentError::InvalidAmount);
1059+
}
1060+
}
1061+
recipient_onion.total_mpp_amount_msat = mpp_amt;
1062+
}
1063+
10461064
let payment_params = PaymentParameters::from_bolt11_invoice(invoice)
10471065
.with_user_config_ignoring_fee_limit(optional_params.route_params_config);
10481066

lightning/src/ln/payment_tests.rs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5423,3 +5423,160 @@ fn max_out_mpp_path() {
54235423
check_added_monitors(&nodes[0], 2); // one monitor update per MPP part
54245424
nodes[0].node.get_and_clear_pending_msg_events();
54255425
}
5426+
5427+
#[test]
5428+
fn bolt11_multi_node_mpp() {
5429+
// Test that multiple nodes can collaborate to pay a single BOLT 11 invoice, with each node
5430+
// paying a portion of the total invoice amount. This is useful for scenarios like:
5431+
// - Paying from multiple wallets (e.g., ecash wallets with funds in multiple mints)
5432+
// - Graduated wallets (funds split between trusted and self-custodial wallets)
5433+
5434+
let chanmon_cfgs = create_chanmon_cfgs(3);
5435+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
5436+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
5437+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
5438+
5439+
// Create channels: A<>C and B<>C
5440+
create_announced_chan_between_nodes(&nodes, 0, 2);
5441+
create_announced_chan_between_nodes(&nodes, 1, 2);
5442+
5443+
// Node C creates a BOLT 11 invoice for 100_000 msat
5444+
let invoice_amt_msat = 100_000;
5445+
let invoice_params = crate::ln::channelmanager::Bolt11InvoiceParameters {
5446+
amount_msats: Some(invoice_amt_msat),
5447+
..Default::default()
5448+
};
5449+
let invoice = nodes[2].node.create_bolt11_invoice(invoice_params).unwrap();
5450+
5451+
// Node A pays 60_000 msat (part of the total)
5452+
let node_a_payment_amt = 60_000;
5453+
let payment_id_a = PaymentId([1; 32]);
5454+
let optional_params_a = crate::ln::channelmanager::OptionalBolt11PaymentParams {
5455+
declared_total_mpp_value_override: Some(invoice_amt_msat),
5456+
..Default::default()
5457+
};
5458+
nodes[0]
5459+
.node
5460+
.pay_for_bolt11_invoice(&invoice, payment_id_a, Some(node_a_payment_amt), optional_params_a)
5461+
.unwrap();
5462+
check_added_monitors(&nodes[0], 1);
5463+
5464+
// Node B pays 40_000 msat (the remaining part)
5465+
let node_b_payment_amt = 40_000;
5466+
let payment_id_b = PaymentId([2; 32]);
5467+
let optional_params_b = crate::ln::channelmanager::OptionalBolt11PaymentParams {
5468+
declared_total_mpp_value_override: Some(invoice_amt_msat),
5469+
..Default::default()
5470+
};
5471+
nodes[1]
5472+
.node
5473+
.pay_for_bolt11_invoice(&invoice, payment_id_b, Some(node_b_payment_amt), optional_params_b)
5474+
.unwrap();
5475+
check_added_monitors(&nodes[1], 1);
5476+
5477+
let payment_event_a = SendEvent::from_node(&nodes[0]);
5478+
nodes[2].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &payment_event_a.msgs[0]);
5479+
do_commitment_signed_dance(&nodes[2], &nodes[0], &payment_event_a.commitment_msg, false, false);
5480+
5481+
let payment_event_b = SendEvent::from_node(&nodes[1]);
5482+
nodes[2].node.handle_update_add_htlc(nodes[1].node.get_our_node_id(), &payment_event_b.msgs[0]);
5483+
do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event_b.commitment_msg, false, false);
5484+
5485+
// Process the pending HTLCs on node C and generate the PaymentClaimable event
5486+
assert!(nodes[2].node.get_and_clear_pending_events().is_empty());
5487+
expect_and_process_pending_htlcs(&nodes[2], false);
5488+
let events = nodes[2].node.get_and_clear_pending_events();
5489+
assert_eq!(events.len(), 1);
5490+
let payment_preimage = match &events[0] {
5491+
Event::PaymentClaimable {
5492+
payment_hash,
5493+
amount_msat,
5494+
purpose: PaymentPurpose::Bolt11InvoicePayment { payment_preimage, .. },
5495+
..
5496+
} => {
5497+
assert_eq!(*payment_hash, invoice.payment_hash());
5498+
assert_eq!(*amount_msat, invoice_amt_msat);
5499+
payment_preimage.unwrap()
5500+
},
5501+
_ => panic!("Unexpected event: {:?}", events[0]),
5502+
};
5503+
5504+
nodes[2].node.claim_funds(payment_preimage);
5505+
5506+
expect_payment_claimed!(nodes[2], invoice.payment_hash(), invoice_amt_msat);
5507+
check_added_monitors(&nodes[2], 2);
5508+
5509+
// Get the fulfill messages from C to both A and B
5510+
let mut events_c = nodes[2].node.get_and_clear_pending_msg_events();
5511+
assert_eq!(events_c.len(), 2);
5512+
5513+
// Handle fulfill message from C to A
5514+
let fulfill_idx_a = events_c
5515+
.iter()
5516+
.position(|ev| {
5517+
if let MessageSendEvent::UpdateHTLCs { node_id, .. } = ev {
5518+
*node_id == nodes[0].node.get_our_node_id()
5519+
} else {
5520+
false
5521+
}
5522+
})
5523+
.unwrap();
5524+
let fulfill_idx_b = 1 - fulfill_idx_a;
5525+
5526+
if let MessageSendEvent::UpdateHTLCs { ref updates, .. } = events_c[fulfill_idx_a] {
5527+
nodes[0].node.handle_update_fulfill_htlc(
5528+
nodes[2].node.get_our_node_id(),
5529+
updates.update_fulfill_htlcs[0].clone(),
5530+
);
5531+
do_commitment_signed_dance(&nodes[0], &nodes[2], &updates.commitment_signed, false, false);
5532+
}
5533+
5534+
let payment_sent = nodes[0].node.get_and_clear_pending_events();
5535+
check_added_monitors(&nodes[0], 1);
5536+
5537+
assert_eq!(payment_sent.len(), 2, "{payment_sent:?}");
5538+
if let Event::PaymentSent { payment_id, payment_hash, amount_msat, fee_paid_msat, .. } =
5539+
&payment_sent[0]
5540+
{
5541+
assert_eq!(*payment_id, Some(payment_id_a));
5542+
assert_eq!(*payment_hash, invoice.payment_hash());
5543+
assert_eq!(*amount_msat, Some(node_a_payment_amt));
5544+
assert_eq!(*fee_paid_msat, Some(0));
5545+
} else {
5546+
panic!("{payment_sent:?}");
5547+
}
5548+
if let Event::PaymentPathSuccessful { payment_id, .. } = &payment_sent[1] {
5549+
assert_eq!(*payment_id, payment_id_a);
5550+
} else {
5551+
panic!("{payment_sent:?}");
5552+
}
5553+
5554+
// Handle fulfill message from C to B
5555+
if let MessageSendEvent::UpdateHTLCs { ref updates, .. } = events_c[fulfill_idx_b] {
5556+
nodes[1].node.handle_update_fulfill_htlc(
5557+
nodes[2].node.get_our_node_id(),
5558+
updates.update_fulfill_htlcs[0].clone(),
5559+
);
5560+
do_commitment_signed_dance(&nodes[1], &nodes[2], &updates.commitment_signed, false, false);
5561+
}
5562+
5563+
let payment_sent = nodes[1].node.get_and_clear_pending_events();
5564+
check_added_monitors(&nodes[1], 1);
5565+
5566+
assert_eq!(payment_sent.len(), 2, "{payment_sent:?}");
5567+
if let Event::PaymentSent { payment_id, payment_hash, amount_msat, fee_paid_msat, .. } =
5568+
&payment_sent[0]
5569+
{
5570+
assert_eq!(*payment_id, Some(payment_id_b));
5571+
assert_eq!(*payment_hash, invoice.payment_hash());
5572+
assert_eq!(*amount_msat, Some(node_b_payment_amt));
5573+
assert_eq!(*fee_paid_msat, Some(0));
5574+
} else {
5575+
panic!("{payment_sent:?}");
5576+
}
5577+
if let Event::PaymentPathSuccessful { payment_id, .. } = &payment_sent[1] {
5578+
assert_eq!(*payment_id, payment_id_b);
5579+
} else {
5580+
panic!("{payment_sent:?}");
5581+
}
5582+
}

0 commit comments

Comments
 (0)