Skip to content

Commit a595cb0

Browse files
committed
ln/test: add test coverage for MPP trampoline
1 parent b410d03 commit a595cb0

1 file changed

Lines changed: 295 additions & 4 deletions

File tree

lightning/src/ln/blinded_payment_tests.rs

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

0 commit comments

Comments
 (0)