Skip to content

Commit 9f2af61

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 e199f36 commit 9f2af61

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
@@ -2751,123 +2751,3 @@ fn do_test_trampoline_relay(blinded: bool, test_case: TrampolineTestCase) {
27512751
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
27522752
}
27532753
}
2754-
2755-
#[test]
2756-
#[rustfmt::skip]
2757-
fn test_trampoline_forward_rejection() {
2758-
const TOTAL_NODE_COUNT: usize = 3;
2759-
2760-
let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT);
2761-
let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs);
2762-
let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]);
2763-
let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs);
2764-
2765-
let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
2766-
let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
2767-
2768-
for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks
2769-
connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1);
2770-
}
2771-
2772-
let alice_node_id = nodes[0].node().get_our_node_id();
2773-
let bob_node_id = nodes[1].node().get_our_node_id();
2774-
let carol_node_id = nodes[2].node().get_our_node_id();
2775-
2776-
let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap();
2777-
let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap();
2778-
2779-
let amt_msat = 1000;
2780-
let (payment_preimage, payment_hash, _) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
2781-
2782-
let route = Route {
2783-
paths: vec![Path {
2784-
hops: vec![
2785-
// Bob
2786-
RouteHop {
2787-
pubkey: bob_node_id,
2788-
node_features: NodeFeatures::empty(),
2789-
short_channel_id: alice_bob_scid,
2790-
channel_features: ChannelFeatures::empty(),
2791-
fee_msat: 1000,
2792-
cltv_expiry_delta: 48,
2793-
maybe_announced_channel: false,
2794-
},
2795-
2796-
// Carol
2797-
RouteHop {
2798-
pubkey: carol_node_id,
2799-
node_features: NodeFeatures::empty(),
2800-
short_channel_id: bob_carol_scid,
2801-
channel_features: ChannelFeatures::empty(),
2802-
fee_msat: 0,
2803-
cltv_expiry_delta: 24 + 24 + 39,
2804-
maybe_announced_channel: false,
2805-
}
2806-
],
2807-
blinded_tail: Some(BlindedTail {
2808-
trampoline_hops: vec![
2809-
// Carol
2810-
TrampolineHop {
2811-
pubkey: carol_node_id,
2812-
node_features: Features::empty(),
2813-
fee_msat: amt_msat,
2814-
cltv_expiry_delta: 24,
2815-
},
2816-
2817-
// Alice (unreachable)
2818-
TrampolineHop {
2819-
pubkey: alice_node_id,
2820-
node_features: Features::empty(),
2821-
fee_msat: amt_msat,
2822-
cltv_expiry_delta: 24 + 39,
2823-
},
2824-
],
2825-
hops: vec![BlindedHop{
2826-
// Fake public key
2827-
blinded_node_id: alice_node_id,
2828-
encrypted_payload: vec![],
2829-
}],
2830-
blinding_point: alice_node_id,
2831-
excess_final_cltv_expiry_delta: 39,
2832-
final_value_msat: amt_msat,
2833-
})
2834-
}],
2835-
route_params: None,
2836-
};
2837-
2838-
nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(amt_msat), PaymentId(payment_hash.0)).unwrap();
2839-
2840-
check_added_monitors(&nodes[0], 1);
2841-
2842-
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
2843-
assert_eq!(events.len(), 1);
2844-
let first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
2845-
2846-
let route: &[&Node] = &[&nodes[1], &nodes[2]];
2847-
let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event)
2848-
.with_payment_preimage(payment_preimage)
2849-
.without_claimable_event()
2850-
.expect_failure(HTLCHandlingFailureType::Receive { payment_hash });
2851-
do_pass_along_path(args);
2852-
2853-
{
2854-
let unblinded_node_updates = get_htlc_update_msgs(&nodes[2], &nodes[1].node.get_our_node_id());
2855-
nodes[1].node.handle_update_fail_htlc(
2856-
nodes[2].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0]
2857-
);
2858-
do_commitment_signed_dance(&nodes[1], &nodes[2], &unblinded_node_updates.commitment_signed, true, false);
2859-
}
2860-
{
2861-
let unblinded_node_updates = get_htlc_update_msgs(&nodes[1], &nodes[0].node.get_our_node_id());
2862-
nodes[0].node.handle_update_fail_htlc(
2863-
nodes[1].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0]
2864-
);
2865-
do_commitment_signed_dance(&nodes[0], &nodes[1], &unblinded_node_updates.commitment_signed, false, false);
2866-
}
2867-
{
2868-
// Expect UnknownNextPeer error while we are unable to route forwarding Trampoline payments.
2869-
let payment_failed_conditions = PaymentFailedConditions::new()
2870-
.expected_htlc_error_data(LocalHTLCFailureReason::UnknownNextPeer, &[0; 0]);
2871-
expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions);
2872-
}
2873-
}

lightning/src/ln/channelmanager.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5178,15 +5178,32 @@ impl<
51785178
fn can_forward_htlc_should_intercept(
51795179
&self, msg: &msgs::UpdateAddHTLC, prev_chan_public: bool, next_hop: &NextPacketDetails,
51805180
) -> Result<bool, LocalHTLCFailureReason> {
5181+
let cur_height = self.best_block.read().unwrap().height + 1;
51815182
let outgoing_scid = match next_hop.outgoing_connector {
51825183
HopConnector::ShortChannelId(scid) => scid,
51835184
HopConnector::Dummy => {
51845185
// Dummy hops are only used for path padding and must not reach HTLC processing.
51855186
debug_assert!(false, "Dummy hop reached HTLC handling.");
51865187
return Err(LocalHTLCFailureReason::InvalidOnionPayload);
51875188
},
5189+
// We can't make forwarding checks on trampoline forwards where we don't know the
5190+
// outgoing channel on receipt of the incoming htlc. Our trampoline logic will check
5191+
// our required delta and fee later on, so here we just check that the forwarding node
5192+
// did not "skim" off some of the sender's intended fee/cltv.
51885193
HopConnector::Trampoline(_) => {
5189-
return Err(LocalHTLCFailureReason::InvalidTrampolineForward);
5194+
if msg.amount_msat < next_hop.outgoing_amt_msat {
5195+
return Err(LocalHTLCFailureReason::FeeInsufficient);
5196+
}
5197+
5198+
check_incoming_htlc_cltv(
5199+
cur_height,
5200+
next_hop.outgoing_cltv_value,
5201+
msg.cltv_expiry,
5202+
0,
5203+
)?;
5204+
5205+
// TODO: add interception flag specifically for trampoline
5206+
return Ok(false);
51905207
},
51915208
};
51925209
// TODO: We do the fake SCID namespace check a bunch of times here (and indirectly via
@@ -5225,7 +5242,6 @@ impl<
52255242
},
52265243
};
52275244

5228-
let cur_height = self.best_block.read().unwrap().height + 1;
52295245
check_incoming_htlc_cltv(
52305246
cur_height,
52315247
next_hop.outgoing_cltv_value,

0 commit comments

Comments
 (0)