Skip to content

Commit 314d03b

Browse files
committed
ln/test: add test coverage for MPP trampoline
1 parent 98f8ac2 commit 314d03b

1 file changed

Lines changed: 292 additions & 4 deletions

File tree

lightning/src/ln/blinded_payment_tests.rs

Lines changed: 292 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
// licenses.
99

1010
use crate::blinded_path::payment::{
11-
BlindedPaymentPath, Bolt12RefundContext, DummyTlvs, ForwardTlvs, PaymentConstraints,
12-
PaymentContext, PaymentForwardNode, PaymentRelay, ReceiveTlvs, PAYMENT_PADDING_ROUND_OFF,
11+
BlindedPaymentPath, Bolt12RefundContext, DummyTlvs, ForwardNode, ForwardTlvs,
12+
PaymentConstraints, PaymentContext, PaymentForwardNode, PaymentRelay, ReceiveTlvs,
13+
PAYMENT_PADDING_ROUND_OFF,
1314
};
1415
use crate::blinded_path::utils::is_padded;
1516
use crate::blinded_path::{self, BlindedHop};
1617
use crate::events::{Event, HTLCHandlingFailureType, PaymentFailureReason};
17-
use crate::ln::channelmanager::{self, HTLCFailureMsg, PaymentId};
18+
use crate::ln::channelmanager::{self, HTLCFailureMsg, PaymentId, MPP_TIMEOUT_TICKS};
1819
use crate::ln::functional_test_utils::*;
1920
use crate::ln::inbound_payment::ExpandedKey;
2021
use crate::ln::msgs::{
@@ -34,7 +35,7 @@ use crate::routing::router::{
3435
use crate::sign::{NodeSigner, PeerStorageKey, ReceiveAuthKey, Recipient};
3536
use crate::types::features::{BlindedHopFeatures, ChannelFeatures, NodeFeatures};
3637
use crate::types::payment::{PaymentHash, PaymentSecret};
37-
use crate::util::config::{HTLCInterceptionFlags, UserConfig};
38+
use crate::util::config::{ChannelConfig, HTLCInterceptionFlags, UserConfig};
3839
use crate::util::ser::{WithoutLength, Writeable};
3940
use crate::util::test_utils::{self, bytes_from_hex, pubkey_from_hex, secret_from_hex};
4041
use bitcoin::hex::DisplayHex;
@@ -2715,3 +2716,290 @@ fn do_test_trampoline_relay(blinded: bool, test_case: TrampolineTestCase) {
27152716
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
27162717
}
27172718
}
2719+
2720+
/// Sets up channels and sends a trampoline MPP payment across two paths.
2721+
///
2722+
/// Topology:
2723+
/// Alice (0) --> Bob (1) --> Carol (2, trampoline node)
2724+
/// Alice (0) --> Barry (3) --> Carol (2, trampoline node)
2725+
///
2726+
/// Carol's inner trampoline onion is a forward to an unknown next node. We don't need the
2727+
/// next hop as a real node since forwarding isn't implemented yet -- we just need the onion to
2728+
/// contain a valid forward payload.
2729+
///
2730+
/// Returns (payment_hash, per_path_amount, ev_to_bob, ev_to_barry).
2731+
fn send_trampoline_mpp_payment<'a, 'b, 'c>(
2732+
nodes: &'a Vec<Node<'a, 'b, 'c>>,
2733+
) -> (PaymentHash, u64, MessageSendEvent, MessageSendEvent) {
2734+
let secp_ctx = Secp256k1::new();
2735+
2736+
let alice_bob_chan =
2737+
create_announced_chan_between_nodes_with_value(nodes, 0, 1, 1_000_000, 0).2;
2738+
let bob_carol_chan =
2739+
create_announced_chan_between_nodes_with_value(nodes, 1, 2, 1_000_000, 0).2;
2740+
let alice_barry_chan =
2741+
create_announced_chan_between_nodes_with_value(nodes, 0, 3, 1_000_000, 0).2;
2742+
let barry_carol_chan =
2743+
create_announced_chan_between_nodes_with_value(nodes, 3, 2, 1_000_000, 0).2;
2744+
2745+
let per_path_amt = 500_000;
2746+
let total_amt = per_path_amt * 2;
2747+
let (_, payment_hash, payment_secret) =
2748+
get_payment_preimage_hash(&nodes[2], Some(total_amt), None);
2749+
2750+
let bob_node_id = nodes[1].node.get_our_node_id();
2751+
let carol_node_id = nodes[2].node.get_our_node_id();
2752+
let barry_node_id = nodes[3].node.get_our_node_id();
2753+
2754+
let alice_bob_scid = get_scid_from_channel_id(&nodes[0], alice_bob_chan);
2755+
let bob_carol_scid = get_scid_from_channel_id(&nodes[1], bob_carol_chan);
2756+
let alice_barry_scid = get_scid_from_channel_id(&nodes[0], alice_barry_chan);
2757+
let barry_carol_scid = get_scid_from_channel_id(&nodes[3], barry_carol_chan);
2758+
2759+
let trampoline_cltv = 42;
2760+
let excess_final_cltv = 70;
2761+
2762+
// Not we don't actually have an outgoing channel for Carol, we just use our default fee
2763+
// policy.
2764+
let carol_relay = ChannelConfig::default();
2765+
2766+
let next_trampoline = PublicKey::from_slice(&[2; 33]).unwrap();
2767+
let fwd_tail = || {
2768+
let intermediate_nodes = [ForwardNode {
2769+
tlvs: blinded_path::payment::TrampolineForwardTlvs {
2770+
next_trampoline,
2771+
payment_constraints: PaymentConstraints {
2772+
max_cltv_expiry: u32::max_value(),
2773+
htlc_minimum_msat: 1,
2774+
},
2775+
features: BlindedHopFeatures::empty(),
2776+
payment_relay: PaymentRelay {
2777+
cltv_expiry_delta: carol_relay.cltv_expiry_delta,
2778+
fee_proportional_millionths: carol_relay.forwarding_fee_proportional_millionths,
2779+
fee_base_msat: carol_relay.forwarding_fee_base_msat,
2780+
},
2781+
next_blinding_override: None,
2782+
},
2783+
node_id: carol_node_id,
2784+
htlc_maximum_msat: u64::max_value(),
2785+
}];
2786+
let payee_tlvs = ReceiveTlvs {
2787+
payment_secret: PaymentSecret([0; 32]),
2788+
payment_constraints: PaymentConstraints {
2789+
max_cltv_expiry: u32::max_value(),
2790+
htlc_minimum_msat: 1,
2791+
},
2792+
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
2793+
};
2794+
create_trampoline_forward_blinded_tail(
2795+
&secp_ctx,
2796+
&nodes[2].keys_manager,
2797+
&intermediate_nodes,
2798+
next_trampoline,
2799+
ReceiveAuthKey([0; 32]),
2800+
payee_tlvs,
2801+
trampoline_cltv,
2802+
excess_final_cltv,
2803+
per_path_amt,
2804+
)
2805+
};
2806+
2807+
let hop = |pubkey, short_channel_id, fee_msat, cltv_expiry_delta| RouteHop {
2808+
pubkey,
2809+
node_features: NodeFeatures::empty(),
2810+
short_channel_id,
2811+
channel_features: ChannelFeatures::empty(),
2812+
fee_msat,
2813+
cltv_expiry_delta,
2814+
maybe_announced_channel: true,
2815+
};
2816+
let build_path_hops = |first_hop_node_id, first_hop_scid, second_hop_scid| {
2817+
vec![
2818+
hop(first_hop_node_id, first_hop_scid, 1000, 48),
2819+
hop(carol_node_id, second_hop_scid, 0, trampoline_cltv + excess_final_cltv),
2820+
]
2821+
};
2822+
2823+
let placeholder_tail = fwd_tail();
2824+
let mut route = Route {
2825+
paths: vec![
2826+
Path {
2827+
hops: build_path_hops(bob_node_id, alice_bob_scid, bob_carol_scid),
2828+
blinded_tail: Some(placeholder_tail.clone()),
2829+
},
2830+
Path {
2831+
hops: build_path_hops(barry_node_id, alice_barry_scid, barry_carol_scid),
2832+
blinded_tail: Some(placeholder_tail),
2833+
},
2834+
],
2835+
route_params: None,
2836+
};
2837+
2838+
let cur_height = nodes[0].best_block_info().1 + 1;
2839+
let payment_id = PaymentId(payment_hash.0);
2840+
let onion = RecipientOnionFields::secret_only(payment_secret, total_amt);
2841+
let session_privs = nodes[0]
2842+
.node
2843+
.test_add_new_pending_payment(payment_hash, onion.clone(), payment_id, &route)
2844+
.unwrap();
2845+
2846+
route.paths[0].blinded_tail = Some(fwd_tail());
2847+
route.paths[1].blinded_tail = Some(fwd_tail());
2848+
2849+
for (i, path) in route.paths.iter().enumerate() {
2850+
nodes[0]
2851+
.node
2852+
.test_send_payment_along_path(
2853+
path,
2854+
&payment_hash,
2855+
onion.clone(),
2856+
cur_height,
2857+
payment_id,
2858+
&None,
2859+
session_privs[i],
2860+
)
2861+
.unwrap();
2862+
check_added_monitors(&nodes[0], 1);
2863+
}
2864+
2865+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
2866+
assert_eq!(events.len(), 2);
2867+
let ev_bob = remove_first_msg_event_to_node(&bob_node_id, &mut events);
2868+
let ev_barry = remove_first_msg_event_to_node(&barry_node_id, &mut events);
2869+
(payment_hash, per_path_amt, ev_bob, ev_barry)
2870+
}
2871+
2872+
/// How an incomplete trampoline MPP times out (if at all).
2873+
enum TrampolineTimeout {
2874+
/// Tick timers until MPP timeout fires.
2875+
Ticks,
2876+
/// Mine blocks until on-chain CLTV timeout fires.
2877+
OnChain,
2878+
}
2879+
2880+
fn do_trampoline_mpp_test(timeout: Option<TrampolineTimeout>) {
2881+
let chanmon_cfgs = create_chanmon_cfgs(4);
2882+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
2883+
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &vec![None; 4]);
2884+
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
2885+
2886+
let (payment_hash, per_path_amt, ev_bob, ev_barry) = send_trampoline_mpp_payment(&nodes);
2887+
let send_both = timeout.is_none();
2888+
2889+
let bob_path: &[&Node] = &[&nodes[1], &nodes[2]];
2890+
let barry_path: &[&Node] = &[&nodes[3], &nodes[2]];
2891+
2892+
// Pass first part along Alice -> Bob -> Carol.
2893+
let args = PassAlongPathArgs::new(&nodes[0], bob_path, per_path_amt, payment_hash, ev_bob)
2894+
.without_claimable_event();
2895+
do_pass_along_path(args);
2896+
2897+
// Either complete the MPP (triggering trampoline rejection) or trigger a timeout.
2898+
let expected_reason = match timeout {
2899+
None => {
2900+
let args =
2901+
PassAlongPathArgs::new(&nodes[0], barry_path, per_path_amt, payment_hash, ev_barry)
2902+
.without_clearing_recipient_events();
2903+
do_pass_along_path(args);
2904+
LocalHTLCFailureReason::TemporaryTrampolineFailure
2905+
},
2906+
Some(TrampolineTimeout::Ticks) => {
2907+
for _ in 0..MPP_TIMEOUT_TICKS {
2908+
nodes[2].node.timer_tick_occurred();
2909+
}
2910+
LocalHTLCFailureReason::MPPTimeout
2911+
},
2912+
Some(TrampolineTimeout::OnChain) => {
2913+
let current_height = nodes[2].best_block_info().1;
2914+
connect_blocks(&nodes[2], 200 - current_height);
2915+
LocalHTLCFailureReason::CLTVExpiryTooSoon
2916+
},
2917+
};
2918+
2919+
// Carol rejects the trampoline forward (either after MPP completion or timeout).
2920+
let events = nodes[2].node.get_and_clear_pending_events();
2921+
assert_eq!(events.len(), 1);
2922+
match events[0] {
2923+
crate::events::Event::HTLCHandlingFailed {
2924+
ref failure_type, ref failure_reason, ..
2925+
} => {
2926+
assert_eq!(failure_type, &HTLCHandlingFailureType::TrampolineForward {});
2927+
match failure_reason {
2928+
Some(crate::events::HTLCHandlingFailureReason::Local { reason }) => {
2929+
assert_eq!(*reason, expected_reason)
2930+
},
2931+
Some(_) | None => panic!("expected failure_reason for failed trampoline"),
2932+
}
2933+
},
2934+
_ => panic!("Unexpected destination"),
2935+
}
2936+
expect_and_process_pending_htlcs(&nodes[2], false);
2937+
assert!(nodes[2].node.get_and_clear_pending_events().is_empty());
2938+
2939+
// Propagate failures back through each forwarded path to Alice.
2940+
let both: [&[&Node]; 2] = [bob_path, barry_path];
2941+
let one: [&[&Node]; 1] = [bob_path];
2942+
let forwarded: &[&[&Node]] = if send_both { &both } else { &one };
2943+
let carol_id = nodes[2].node.get_our_node_id();
2944+
check_added_monitors(&nodes[2], forwarded.len());
2945+
let mut carol_msgs = nodes[2].node.get_and_clear_pending_msg_events();
2946+
assert_eq!(carol_msgs.len(), forwarded.len());
2947+
for path in forwarded {
2948+
let hop = path[0];
2949+
let hop_id = hop.node.get_our_node_id();
2950+
let ev = remove_first_msg_event_to_node(&hop_id, &mut carol_msgs);
2951+
let updates = match ev {
2952+
MessageSendEvent::UpdateHTLCs { updates, .. } => updates,
2953+
_ => panic!("Expected UpdateHTLCs"),
2954+
};
2955+
hop.node.handle_update_fail_htlc(carol_id, &updates.update_fail_htlcs[0]);
2956+
do_commitment_signed_dance(hop, &nodes[2], &updates.commitment_signed, true, false);
2957+
2958+
let fwd = get_htlc_update_msgs(hop, &nodes[0].node.get_our_node_id());
2959+
nodes[0].node.handle_update_fail_htlc(hop_id, &fwd.update_fail_htlcs[0]);
2960+
do_commitment_signed_dance(&nodes[0], hop, &fwd.commitment_signed, false, false);
2961+
}
2962+
2963+
// Check Alice's failure events.
2964+
let events = nodes[0].node.get_and_clear_pending_events();
2965+
assert_eq!(events.len(), if send_both { 3 } else { 1 });
2966+
for ev in &events[..forwarded.len()] {
2967+
match ev {
2968+
Event::PaymentPathFailed { payment_hash: h, payment_failed_permanently, .. } => {
2969+
assert_eq!(*h, payment_hash);
2970+
assert!(!payment_failed_permanently);
2971+
},
2972+
_ => panic!("Expected PaymentPathFailed, got {:?}", ev),
2973+
}
2974+
}
2975+
if send_both {
2976+
match &events[2] {
2977+
Event::PaymentFailed { payment_hash: h, reason, .. } => {
2978+
assert_eq!(*h, Some(payment_hash));
2979+
assert_eq!(*reason, Some(PaymentFailureReason::RetriesExhausted));
2980+
},
2981+
_ => panic!("Expected PaymentFailed, got {:?}", events[2]),
2982+
}
2983+
2984+
// Verify no spurious timeout fires after the MPP set was dispatched.
2985+
for _ in 0..(MPP_TIMEOUT_TICKS * 3) {
2986+
nodes[2].node.timer_tick_occurred();
2987+
}
2988+
assert!(nodes[2].node.get_and_clear_pending_events().is_empty());
2989+
}
2990+
}
2991+
2992+
#[test]
2993+
fn test_trampoline_mpp_receive_success() {
2994+
do_trampoline_mpp_test(None);
2995+
}
2996+
2997+
#[test]
2998+
fn test_trampoline_mpp_timeout_partial() {
2999+
do_trampoline_mpp_test(Some(TrampolineTimeout::Ticks));
3000+
}
3001+
3002+
#[test]
3003+
fn test_trampoline_mpp_onchain_timeout() {
3004+
do_trampoline_mpp_test(Some(TrampolineTimeout::OnChain));
3005+
}

0 commit comments

Comments
 (0)