Skip to content

Commit 52bc83f

Browse files
committed
ln: process added trampoline htlcs with CLTV validation
We can't perform proper validation because we don't know the outgoing channel id until we forward the HTLC, so we just perform a basic CLTV check. Now that we've got rejection on inbound MPP accumulation, we relax this check to allow testing of inbound MPP trampoline processing.
1 parent e75a047 commit 52bc83f

2 files changed

Lines changed: 18 additions & 122 deletions

File tree

lightning/src/ln/blinded_payment_tests.rs

Lines changed: 0 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,123 +2715,3 @@ fn do_test_trampoline_relay(blinded: bool, test_case: TrampolineTestCase) {
27152715
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
27162716
}
27172717
}
2718-
2719-
#[test]
2720-
#[rustfmt::skip]
2721-
fn test_trampoline_forward_rejection() {
2722-
const TOTAL_NODE_COUNT: usize = 3;
2723-
2724-
let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT);
2725-
let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs);
2726-
let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]);
2727-
let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs);
2728-
2729-
let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
2730-
let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
2731-
2732-
for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks
2733-
connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1);
2734-
}
2735-
2736-
let alice_node_id = nodes[0].node().get_our_node_id();
2737-
let bob_node_id = nodes[1].node().get_our_node_id();
2738-
let carol_node_id = nodes[2].node().get_our_node_id();
2739-
2740-
let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap();
2741-
let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap();
2742-
2743-
let amt_msat = 1000;
2744-
let (payment_preimage, payment_hash, _) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
2745-
2746-
let route = Route {
2747-
paths: vec![Path {
2748-
hops: vec![
2749-
// Bob
2750-
RouteHop {
2751-
pubkey: bob_node_id,
2752-
node_features: NodeFeatures::empty(),
2753-
short_channel_id: alice_bob_scid,
2754-
channel_features: ChannelFeatures::empty(),
2755-
fee_msat: 1000,
2756-
cltv_expiry_delta: 48,
2757-
maybe_announced_channel: false,
2758-
},
2759-
2760-
// Carol
2761-
RouteHop {
2762-
pubkey: carol_node_id,
2763-
node_features: NodeFeatures::empty(),
2764-
short_channel_id: bob_carol_scid,
2765-
channel_features: ChannelFeatures::empty(),
2766-
fee_msat: 0,
2767-
cltv_expiry_delta: 24 + 24 + 39,
2768-
maybe_announced_channel: false,
2769-
}
2770-
],
2771-
blinded_tail: Some(BlindedTail {
2772-
trampoline_hops: vec![
2773-
// Carol
2774-
TrampolineHop {
2775-
pubkey: carol_node_id,
2776-
node_features: Features::empty(),
2777-
fee_msat: amt_msat,
2778-
cltv_expiry_delta: 24,
2779-
},
2780-
2781-
// Alice (unreachable)
2782-
TrampolineHop {
2783-
pubkey: alice_node_id,
2784-
node_features: Features::empty(),
2785-
fee_msat: amt_msat,
2786-
cltv_expiry_delta: 24 + 39,
2787-
},
2788-
],
2789-
hops: vec![BlindedHop{
2790-
// Fake public key
2791-
blinded_node_id: alice_node_id,
2792-
encrypted_payload: vec![],
2793-
}],
2794-
blinding_point: alice_node_id,
2795-
excess_final_cltv_expiry_delta: 39,
2796-
final_value_msat: amt_msat,
2797-
})
2798-
}],
2799-
route_params: None,
2800-
};
2801-
2802-
nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(amt_msat), PaymentId(payment_hash.0)).unwrap();
2803-
2804-
check_added_monitors(&nodes[0], 1);
2805-
2806-
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
2807-
assert_eq!(events.len(), 1);
2808-
let first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
2809-
2810-
let route: &[&Node] = &[&nodes[1], &nodes[2]];
2811-
let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event)
2812-
.with_payment_preimage(payment_preimage)
2813-
.without_claimable_event()
2814-
.expect_failure(HTLCHandlingFailureType::Receive { payment_hash });
2815-
do_pass_along_path(args);
2816-
2817-
{
2818-
let unblinded_node_updates = get_htlc_update_msgs(&nodes[2], &nodes[1].node.get_our_node_id());
2819-
nodes[1].node.handle_update_fail_htlc(
2820-
nodes[2].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0]
2821-
);
2822-
do_commitment_signed_dance(&nodes[1], &nodes[2], &unblinded_node_updates.commitment_signed, true, false);
2823-
}
2824-
{
2825-
let unblinded_node_updates = get_htlc_update_msgs(&nodes[1], &nodes[0].node.get_our_node_id());
2826-
nodes[0].node.handle_update_fail_htlc(
2827-
nodes[1].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0]
2828-
);
2829-
do_commitment_signed_dance(&nodes[0], &nodes[1], &unblinded_node_updates.commitment_signed, false, false);
2830-
}
2831-
{
2832-
// Expect UnknownNextPeer error while we are unable to route forwarding Trampoline payments.
2833-
let payment_failed_conditions = PaymentFailedConditions::new()
2834-
.expected_htlc_error_data(LocalHTLCFailureReason::UnknownNextPeer, &[0; 0]);
2835-
expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions);
2836-
}
2837-
}

lightning/src/ln/channelmanager.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5239,15 +5239,32 @@ impl<
52395239
fn can_forward_htlc_should_intercept(
52405240
&self, msg: &msgs::UpdateAddHTLC, prev_chan_public: bool, next_hop: &NextPacketDetails,
52415241
) -> Result<bool, LocalHTLCFailureReason> {
5242+
let cur_height = self.best_block.read().unwrap().height + 1;
52425243
let outgoing_scid = match next_hop.outgoing_connector {
52435244
HopConnector::ShortChannelId(scid) => scid,
52445245
HopConnector::Dummy => {
52455246
// Dummy hops are only used for path padding and must not reach HTLC processing.
52465247
debug_assert!(false, "Dummy hop reached HTLC handling.");
52475248
return Err(LocalHTLCFailureReason::InvalidOnionPayload);
52485249
},
5250+
// We can't make forwarding checks on trampoline forwards where we don't know the
5251+
// outgoing channel on receipt of the incoming htlc. Our trampoline logic will check
5252+
// our required delta and fee later on, so here we just check that the forwarding node
5253+
// did not "skim" off some of the sender's intended fee/cltv.
52495254
HopConnector::Trampoline(_) => {
5250-
return Err(LocalHTLCFailureReason::InvalidTrampolineForward);
5255+
if msg.amount_msat < next_hop.outgoing_amt_msat {
5256+
return Err(LocalHTLCFailureReason::FeeInsufficient);
5257+
}
5258+
5259+
check_incoming_htlc_cltv(
5260+
cur_height,
5261+
next_hop.outgoing_cltv_value,
5262+
msg.cltv_expiry,
5263+
0,
5264+
)?;
5265+
5266+
// TODO: add interception flag specifically for trampoline
5267+
return Ok(false);
52515268
},
52525269
};
52535270
// TODO: We do the fake SCID namespace check a bunch of times here (and indirectly via
@@ -5286,7 +5303,6 @@ impl<
52865303
},
52875304
};
52885305

5289-
let cur_height = self.best_block.read().unwrap().height + 1;
52905306
check_incoming_htlc_cltv(
52915307
cur_height,
52925308
next_hop.outgoing_cltv_value,

0 commit comments

Comments
 (0)