Skip to content

Commit 6abf36e

Browse files
committed
Update PaymentPath, and ClaimAlongRoute arguments
Upcoming commits will need the ability to specify whether a blinded path contains dummy hops. This change adds that support to the testing framework ahead of time, so later tests can express dummy-hop scenarios explicitly.
1 parent d2def54 commit 6abf36e

File tree

2 files changed

+93
-17
lines changed

2 files changed

+93
-17
lines changed

lightning/src/ln/functional_test_utils.rs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//! A bunch of useful utilities for building networks of nodes and exchanging messages between
1111
//! nodes for functional tests.
1212
13+
use crate::blinded_path::payment::DummyTlvs;
1314
use crate::chain::channelmonitor::ChannelMonitor;
1415
use crate::chain::transaction::OutPoint;
1516
use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch};
@@ -3435,6 +3436,7 @@ fn fail_payment_along_path<'a, 'b, 'c>(expected_path: &[&Node<'a, 'b, 'c>]) {
34353436
pub struct PassAlongPathArgs<'a, 'b, 'c, 'd> {
34363437
pub origin_node: &'a Node<'b, 'c, 'd>,
34373438
pub expected_path: &'a [&'a Node<'b, 'c, 'd>],
3439+
pub dummy_tlvs: Vec<DummyTlvs>,
34383440
pub recv_value: u64,
34393441
pub payment_hash: PaymentHash,
34403442
pub payment_secret: Option<PaymentSecret>,
@@ -3456,6 +3458,7 @@ impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> {
34563458
Self {
34573459
origin_node,
34583460
expected_path,
3461+
dummy_tlvs: vec![],
34593462
recv_value,
34603463
payment_hash,
34613464
payment_secret: None,
@@ -3503,12 +3506,17 @@ impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> {
35033506
self.expected_failure = Some(failure);
35043507
self
35053508
}
3509+
pub fn with_dummy_tlvs(mut self, dummy_tlvs: &[DummyTlvs]) -> Self {
3510+
self.dummy_tlvs = dummy_tlvs.to_vec();
3511+
self
3512+
}
35063513
}
35073514

35083515
pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option<Event> {
35093516
let PassAlongPathArgs {
35103517
origin_node,
35113518
expected_path,
3519+
dummy_tlvs,
35123520
recv_value,
35133521
payment_hash: our_payment_hash,
35143522
payment_secret: our_payment_secret,
@@ -3543,6 +3551,16 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option<Event>
35433551
node.node.process_pending_htlc_forwards();
35443552
}
35453553

3554+
if is_last_hop {
3555+
// At the final hop, the incoming packet contains N dummy-hop layers
3556+
// before the real HTLC. Each call to `process_pending_htlc_forwards`
3557+
// strips exactly one dummy layer, so we call it N times.
3558+
for _ in 0..dummy_tlvs.len() {
3559+
assert!(node.node.needs_pending_htlc_processing());
3560+
node.node.process_pending_htlc_forwards();
3561+
}
3562+
}
3563+
35463564
if is_last_hop && clear_recipient_events {
35473565
let events_2 = node.node.get_and_clear_pending_events();
35483566
if payment_claimable_expected {
@@ -3755,6 +3773,29 @@ pub struct ClaimAlongRouteArgs<'a, 'b, 'c, 'd> {
37553773
pub origin_node: &'a Node<'b, 'c, 'd>,
37563774
pub expected_paths: &'a [&'a [&'a Node<'b, 'c, 'd>]],
37573775
pub expected_extra_fees: Vec<u32>,
3776+
/// A one-off adjustment used only in tests to account for an existing
3777+
/// fee-handling trade-off in LDK.
3778+
///
3779+
/// When the payer is the introduction node of a blinded path, LDK does not
3780+
/// subtract the forward fee for the `payer -> next_hop` channel
3781+
/// (see [`BlindedPaymentPath::advance_path_by_one`]). This keeps the fee
3782+
/// logic simpler at the cost of a small, intentional overpayment.
3783+
///
3784+
/// In the simple two-hop case (payer as introduction node → payee),
3785+
/// this overpayment has historically been avoided by simply not charging
3786+
/// the payer the forward fee, since the payer knows there is only
3787+
/// a single hop after them.
3788+
///
3789+
/// However, with the introduction of dummy hops in LDK v0.3, even a
3790+
/// two-node real path (payer as introduction node → payee) may appear as a
3791+
/// multi-hop blinded path. This makes the existing overpayment surface in
3792+
/// tests.
3793+
///
3794+
/// Until the fee-handling trade-off is revisited, this field allows tests
3795+
/// to compensate for that expected difference.
3796+
///
3797+
/// [`BlindedPaymentPath::advance_path_by_one`]: crate::blinded_path::payment::BlindedPaymentPath::advance_path_by_one
3798+
pub expected_extra_total_fees_msat: u64,
37583799
pub expected_min_htlc_overpay: Vec<u32>,
37593800
pub skip_last: bool,
37603801
pub payment_preimage: PaymentPreimage,
@@ -3778,6 +3819,7 @@ impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> {
37783819
origin_node,
37793820
expected_paths,
37803821
expected_extra_fees: vec![0; expected_paths.len()],
3822+
expected_extra_total_fees_msat: 0,
37813823
expected_min_htlc_overpay: vec![0; expected_paths.len()],
37823824
skip_last: false,
37833825
payment_preimage,
@@ -3793,6 +3835,10 @@ impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> {
37933835
self.expected_extra_fees = extra_fees;
37943836
self
37953837
}
3838+
pub fn with_expected_extra_total_fees_msat(mut self, extra_total_fees: u64) -> Self {
3839+
self.expected_extra_total_fees_msat = extra_total_fees;
3840+
self
3841+
}
37963842
pub fn with_expected_min_htlc_overpay(mut self, extra_fees: Vec<u32>) -> Self {
37973843
self.expected_min_htlc_overpay = extra_fees;
37983844
self
@@ -4060,13 +4106,21 @@ pub fn pass_claimed_payment_along_route_from_ev(
40604106

40614107
expected_total_fee_msat
40624108
}
4109+
40634110
pub fn claim_payment_along_route(
40644111
args: ClaimAlongRouteArgs,
40654112
) -> (Option<PaidBolt12Invoice>, Vec<Event>) {
4066-
let origin_node = args.origin_node;
4067-
let payment_preimage = args.payment_preimage;
4068-
let skip_last = args.skip_last;
4069-
let expected_total_fee_msat = do_claim_payment_along_route(args);
4113+
let ClaimAlongRouteArgs {
4114+
origin_node,
4115+
payment_preimage,
4116+
skip_last,
4117+
expected_extra_total_fees_msat,
4118+
..
4119+
} = args;
4120+
4121+
let expected_total_fee_msat =
4122+
do_claim_payment_along_route(args) + expected_extra_total_fees_msat;
4123+
40704124
if !skip_last {
40714125
expect_payment_sent!(origin_node, payment_preimage, Some(expected_total_fee_msat))
40724126
} else {

lightning/src/ln/offers_tests.rs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,20 @@ fn route_bolt12_payment<'a, 'b, 'c>(
185185
fn claim_bolt12_payment<'a, 'b, 'c>(
186186
node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], expected_payment_context: PaymentContext, invoice: &Bolt12Invoice
187187
) {
188-
let recipient = &path[path.len() - 1];
188+
claim_bolt12_payment_with_extra_fees(
189+
node,
190+
path,
191+
expected_payment_context,
192+
invoice,
193+
None,
194+
)
195+
}
196+
197+
fn claim_bolt12_payment_with_extra_fees<'a, 'b, 'c>(
198+
node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], expected_payment_context: PaymentContext, invoice: &Bolt12Invoice,
199+
expected_extra_fees_msat: Option<u64>,
200+
) {
201+
let recipient = path.last().expect("Empty path?");
189202
let payment_purpose = match get_event!(recipient, Event::PaymentClaimable) {
190203
Event::PaymentClaimable { purpose, .. } => purpose,
191204
_ => panic!("No Event::PaymentClaimable"),
@@ -194,20 +207,29 @@ fn claim_bolt12_payment<'a, 'b, 'c>(
194207
Some(preimage) => preimage,
195208
None => panic!("No preimage in Event::PaymentClaimable"),
196209
};
197-
match payment_purpose {
198-
PaymentPurpose::Bolt12OfferPayment { payment_context, .. } => {
199-
assert_eq!(PaymentContext::Bolt12Offer(payment_context), expected_payment_context);
200-
},
201-
PaymentPurpose::Bolt12RefundPayment { payment_context, .. } => {
202-
assert_eq!(PaymentContext::Bolt12Refund(payment_context), expected_payment_context);
203-
},
210+
let context = match payment_purpose {
211+
PaymentPurpose::Bolt12OfferPayment { payment_context, .. } =>
212+
PaymentContext::Bolt12Offer(payment_context),
213+
PaymentPurpose::Bolt12RefundPayment { payment_context, .. } =>
214+
PaymentContext::Bolt12Refund(payment_context),
204215
_ => panic!("Unexpected payment purpose: {:?}", payment_purpose),
205-
}
206-
if let Some(inv) = claim_payment(node, path, payment_preimage) {
207-
assert_eq!(inv, PaidBolt12Invoice::Bolt12Invoice(invoice.to_owned()));
208-
} else {
209-
panic!("Expected PaidInvoice::Bolt12Invoice");
210216
};
217+
218+
assert_eq!(context, expected_payment_context);
219+
220+
let expected_paths = [path];
221+
let mut args = ClaimAlongRouteArgs::new(
222+
node,
223+
&expected_paths,
224+
payment_preimage,
225+
);
226+
227+
if let Some(extra) = expected_extra_fees_msat {
228+
args = args.with_expected_extra_total_fees_msat(extra);
229+
}
230+
231+
let (inv, _) = claim_payment_along_route(args);
232+
assert_eq!(inv, Some(PaidBolt12Invoice::Bolt12Invoice(invoice.clone())));
211233
}
212234

213235
fn extract_offer_nonce<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) -> Nonce {

0 commit comments

Comments
 (0)