Skip to content

Commit 54d0f2b

Browse files
committed
ln/test: add tests for mpp accumulation of trampoline forwards
1 parent a595cb0 commit 54d0f2b

4 files changed

Lines changed: 226 additions & 11 deletions

File tree

lightning/src/ln/blinded_payment_tests.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2768,7 +2768,7 @@ fn send_trampoline_mpp_payment<'a, 'b, 'c>(
27682768
let trampoline_cltv = 42;
27692769
let excess_final_cltv = 70;
27702770

2771-
// Not we don't actually have an outgoing channel for Carol, we just use our default fee
2771+
// Note we don't actually have an outgoing channel for Carol, we just use our default fee
27722772
// policy.
27732773
let carol_relay = ChannelConfig::default();
27742774

@@ -3001,16 +3001,8 @@ fn do_trampoline_mpp_test(timeout: Option<TrampolineTimeout>) {
30013001
}
30023002

30033003
#[test]
3004-
fn test_trampoline_mpp_receive_success() {
3004+
fn test_trampoline_mpp_accumulation() {
30053005
do_trampoline_mpp_test(None);
3006-
}
3007-
3008-
#[test]
3009-
fn test_trampoline_mpp_timeout_partial() {
30103006
do_trampoline_mpp_test(Some(TrampolineTimeout::Ticks));
3011-
}
3012-
3013-
#[test]
3014-
fn test_trampoline_mpp_onchain_timeout() {
30153007
do_trampoline_mpp_test(Some(TrampolineTimeout::OnChain));
30163008
}

lightning/src/ln/channelmanager.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ enum OnionPayload {
527527
}
528528

529529
#[derive(PartialEq, Eq)]
530-
struct MppPart {
530+
pub(super) struct MppPart {
531531
prev_hop: HTLCPreviousHopData,
532532
cltv_expiry: u32,
533533
/// The amount (in msats) of this MPP part
@@ -542,6 +542,20 @@ struct MppPart {
542542
}
543543

544544
impl MppPart {
545+
#[cfg(test)]
546+
pub(super) fn new(
547+
prev_hop: HTLCPreviousHopData, value: u64, sender_intended_value: u64, cltv_expiry: u32,
548+
) -> Self {
549+
MppPart {
550+
prev_hop,
551+
cltv_expiry,
552+
value,
553+
sender_intended_value,
554+
timer_ticks: 0,
555+
total_value_received: None,
556+
}
557+
}
558+
545559
/// Returns a boolean indicating whether the HTLC has timed out on chain, accounting for a buffer
546560
/// that gives us time to resolve it.
547561
fn check_onchain_timeout(&self, height: u32) -> bool {
@@ -5776,6 +5790,20 @@ impl<
57765790
self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata);
57775791
}
57785792

5793+
#[cfg(test)]
5794+
pub(super) fn test_handle_trampoline_htlc(
5795+
&self, mpp_part: MppPart, onion_fields: RecipientOnionFields, payment_hash: PaymentHash,
5796+
next_hop_info: NextTrampolineHopInfo, next_node_id: PublicKey,
5797+
) -> Result<(), (HTLCSource, onion_utils::HTLCFailReason)> {
5798+
self.handle_trampoline_htlc(
5799+
mpp_part,
5800+
onion_fields,
5801+
payment_hash,
5802+
next_hop_info,
5803+
next_node_id,
5804+
)
5805+
}
5806+
57795807
/// Pays a [`Bolt11Invoice`] associated with the `payment_id`. See [`Self::send_payment`] for more info.
57805808
///
57815809
/// # Payment Id

lightning/src/ln/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ mod reorg_tests;
118118
mod shutdown_tests;
119119
#[cfg(any(feature = "_test_utils", test))]
120120
pub mod splicing_tests;
121+
#[cfg(test)]
122+
mod trampoline_forward_tests;
121123
#[cfg(any(test, feature = "_externalize_tests"))]
122124
#[allow(unused_mut)]
123125
pub mod update_fee_tests;
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
//! Tests for trampoline MPP accumulation and forwarding validation in
11+
//! [`ChannelManager::handle_trampoline_htlc`].
12+
13+
use crate::chain::transaction::OutPoint;
14+
use crate::events::HTLCHandlingFailureReason;
15+
use crate::ln::channelmanager::{HTLCPreviousHopData, MppPart};
16+
use crate::ln::functional_test_utils::*;
17+
use crate::ln::msgs;
18+
use crate::ln::onion_utils::LocalHTLCFailureReason;
19+
use crate::ln::outbound_payment::{NextTrampolineHopInfo, RecipientOnionFields};
20+
use crate::ln::types::ChannelId;
21+
use crate::types::payment::{PaymentHash, PaymentSecret};
22+
23+
use bitcoin::hashes::Hash;
24+
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
25+
26+
fn test_prev_hop_data(htlc_id: u64) -> HTLCPreviousHopData {
27+
HTLCPreviousHopData {
28+
prev_outbound_scid_alias: 0,
29+
user_channel_id: None,
30+
htlc_id,
31+
incoming_packet_shared_secret: [0; 32],
32+
phantom_shared_secret: None,
33+
trampoline_shared_secret: Some([0; 32]),
34+
blinded_failure: None,
35+
channel_id: ChannelId::from_bytes([0; 32]),
36+
outpoint: OutPoint { txid: bitcoin::Txid::all_zeros(), index: 0 },
37+
counterparty_node_id: None,
38+
cltv_expiry: None,
39+
}
40+
}
41+
42+
fn test_trampoline_onion_packet() -> msgs::TrampolineOnionPacket {
43+
let secp = Secp256k1::new();
44+
let test_secret = SecretKey::from_slice(&[42; 32]).unwrap();
45+
msgs::TrampolineOnionPacket {
46+
version: 0,
47+
public_key: PublicKey::from_secret_key(&secp, &test_secret),
48+
hop_data: vec![0; 650],
49+
hmac: [0; 32],
50+
}
51+
}
52+
53+
fn test_onion_fields(total_msat: u64) -> RecipientOnionFields {
54+
RecipientOnionFields {
55+
payment_secret: Some(PaymentSecret([0; 32])),
56+
total_mpp_amount_msat: total_msat,
57+
payment_metadata: None,
58+
custom_tlvs: Vec::new(),
59+
}
60+
}
61+
62+
enum TrampolineMppValidationTestCase {
63+
FeeInsufficient,
64+
CltvInsufficient,
65+
TrampolineAmountExceedsReceived,
66+
TrampolineCLTVExceedsReceived,
67+
MismatchedPaymentSecret,
68+
}
69+
70+
/// Sends two MPP parts through [`ChannelManager::handle_trampoline_htlc`], testing various MPP
71+
/// validation steps with a base case that succeeds.
72+
fn do_test_trampoline_mpp_validation(test_case: Option<TrampolineMppValidationTestCase>) {
73+
let update_add_value: u64 = 500_000; // Actual amount we received in update_add_htlc.
74+
let update_add_cltv: u32 = 500; // Actual CLTV we received in update_add_htlc.
75+
let sender_intended_incoming_value: u64 = 500_000; // Amount we expect for one HTLC, outer onion.
76+
let incoming_mpp_total: u64 = 1_000_000; // Total we expect to receive across MPP parts, outer onion.
77+
let mut next_trampoline_amount: u64 = 750_000; // Total next trampoline expects, inner onion.
78+
let mut next_trampoline_cltv: u32 = 100; // CLTV next trampoline expects, inner onion.
79+
80+
// By default, set our forwarding fee and CLTV delta to exactly what we're being offered
81+
// for this trampoline forward, so that we can force failures by just adding one.
82+
let mut forwarding_fee_base_msat = incoming_mpp_total - next_trampoline_amount;
83+
let mut cltv_delta = update_add_cltv - next_trampoline_cltv;
84+
let mut mismatch_payment_secret = false;
85+
86+
let expected = match test_case {
87+
Some(TrampolineMppValidationTestCase::FeeInsufficient) => {
88+
forwarding_fee_base_msat += 1;
89+
LocalHTLCFailureReason::TrampolineFeeOrExpiryInsufficient
90+
},
91+
Some(TrampolineMppValidationTestCase::CltvInsufficient) => {
92+
cltv_delta += 1;
93+
LocalHTLCFailureReason::TrampolineFeeOrExpiryInsufficient
94+
},
95+
Some(TrampolineMppValidationTestCase::TrampolineAmountExceedsReceived) => {
96+
next_trampoline_amount = incoming_mpp_total + 1;
97+
LocalHTLCFailureReason::TrampolineFeeOrExpiryInsufficient
98+
},
99+
Some(TrampolineMppValidationTestCase::TrampolineCLTVExceedsReceived) => {
100+
next_trampoline_cltv = update_add_cltv + 1;
101+
LocalHTLCFailureReason::TrampolineFeeOrExpiryInsufficient
102+
},
103+
Some(TrampolineMppValidationTestCase::MismatchedPaymentSecret) => {
104+
mismatch_payment_secret = true;
105+
LocalHTLCFailureReason::InvalidTrampolineForward
106+
},
107+
// We currently reject trampoline forwards once accumulated.
108+
None => LocalHTLCFailureReason::TemporaryTrampolineFailure,
109+
};
110+
111+
let chanmon_cfgs = create_chanmon_cfgs(1);
112+
let node_cfgs = create_node_cfgs(1, &chanmon_cfgs);
113+
let mut cfg = test_default_channel_config();
114+
cfg.channel_config.forwarding_fee_base_msat = forwarding_fee_base_msat as u32;
115+
cfg.channel_config.forwarding_fee_proportional_millionths = 0;
116+
cfg.channel_config.cltv_expiry_delta = cltv_delta as u16;
117+
let node_chanmgrs = create_node_chanmgrs(1, &node_cfgs, &[Some(cfg)]);
118+
let nodes = create_network(1, &node_cfgs, &node_chanmgrs);
119+
120+
let payment_hash = PaymentHash([1; 32]);
121+
122+
let secp = Secp256k1::new();
123+
let test_secret = SecretKey::from_slice(&[2; 32]).unwrap();
124+
let next_trampoline = PublicKey::from_secret_key(&secp, &test_secret);
125+
let next_hop_info = NextTrampolineHopInfo {
126+
onion_packet: test_trampoline_onion_packet(),
127+
blinding_point: None,
128+
amount_msat: next_trampoline_amount,
129+
cltv_expiry_height: next_trampoline_cltv,
130+
};
131+
132+
let htlc1 = MppPart::new(
133+
test_prev_hop_data(0),
134+
update_add_value,
135+
sender_intended_incoming_value,
136+
update_add_cltv,
137+
);
138+
assert!(nodes[0]
139+
.node
140+
.test_handle_trampoline_htlc(
141+
htlc1,
142+
test_onion_fields(incoming_mpp_total),
143+
payment_hash,
144+
next_hop_info.clone(),
145+
next_trampoline,
146+
)
147+
.is_ok());
148+
149+
let htlc2 = MppPart::new(
150+
test_prev_hop_data(1),
151+
update_add_value,
152+
sender_intended_incoming_value,
153+
update_add_cltv,
154+
);
155+
let onion2 = if mismatch_payment_secret {
156+
RecipientOnionFields {
157+
payment_secret: Some(PaymentSecret([1; 32])),
158+
total_mpp_amount_msat: incoming_mpp_total,
159+
payment_metadata: None,
160+
custom_tlvs: Vec::new(),
161+
}
162+
} else {
163+
test_onion_fields(incoming_mpp_total)
164+
};
165+
let result = nodes[0].node.test_handle_trampoline_htlc(
166+
htlc2,
167+
onion2,
168+
payment_hash,
169+
next_hop_info,
170+
next_trampoline,
171+
);
172+
173+
assert_eq!(
174+
HTLCHandlingFailureReason::from(&result.expect_err("expect trampoline failure").1),
175+
HTLCHandlingFailureReason::Local { reason: expected },
176+
);
177+
}
178+
179+
#[test]
180+
fn test_trampoline_mpp_validation() {
181+
do_test_trampoline_mpp_validation(Some(TrampolineMppValidationTestCase::FeeInsufficient));
182+
do_test_trampoline_mpp_validation(Some(TrampolineMppValidationTestCase::CltvInsufficient));
183+
do_test_trampoline_mpp_validation(Some(
184+
TrampolineMppValidationTestCase::TrampolineAmountExceedsReceived,
185+
));
186+
do_test_trampoline_mpp_validation(Some(
187+
TrampolineMppValidationTestCase::TrampolineCLTVExceedsReceived,
188+
));
189+
do_test_trampoline_mpp_validation(Some(
190+
TrampolineMppValidationTestCase::MismatchedPaymentSecret,
191+
));
192+
do_test_trampoline_mpp_validation(None);
193+
}

0 commit comments

Comments
 (0)