Skip to content

Commit 490caa7

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 8ba1bdb commit 490caa7

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

lightning/src/ln/channelmanager.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5166,15 +5166,32 @@ impl<
51665166
fn can_forward_htlc_should_intercept(
51675167
&self, msg: &msgs::UpdateAddHTLC, prev_chan_public: bool, next_hop: &NextPacketDetails,
51685168
) -> Result<bool, LocalHTLCFailureReason> {
5169+
let cur_height = self.best_block.read().unwrap().height + 1;
51695170
let outgoing_scid = match next_hop.outgoing_connector {
51705171
HopConnector::ShortChannelId(scid) => scid,
51715172
HopConnector::Dummy => {
51725173
// Dummy hops are only used for path padding and must not reach HTLC processing.
51735174
debug_assert!(false, "Dummy hop reached HTLC handling.");
51745175
return Err(LocalHTLCFailureReason::InvalidOnionPayload);
51755176
},
5177+
// We can't make forwarding checks on trampoline forwards where we don't know the
5178+
// outgoing channel on receipt of the incoming htlc. Our trampoline logic will check
5179+
// our required delta and fee later on, so here we just check that the forwarding node
5180+
// did not "skim" off some of the sender's intended fee/cltv.
51765181
HopConnector::Trampoline(_) => {
5177-
return Err(LocalHTLCFailureReason::InvalidTrampolineForward);
5182+
if msg.amount_msat < next_hop.outgoing_amt_msat {
5183+
return Err(LocalHTLCFailureReason::FeeInsufficient);
5184+
}
5185+
5186+
check_incoming_htlc_cltv(
5187+
cur_height,
5188+
next_hop.outgoing_cltv_value,
5189+
msg.cltv_expiry,
5190+
0,
5191+
)?;
5192+
5193+
// TODO: add interception flag specifically for trampoline
5194+
return Ok(false);
51785195
},
51795196
};
51805197
// TODO: We do the fake SCID namespace check a bunch of times here (and indirectly via
@@ -5213,7 +5230,6 @@ impl<
52135230
},
52145231
};
52155232

5216-
let cur_height = self.best_block.read().unwrap().height + 1;
52175233
check_incoming_htlc_cltv(
52185234
cur_height,
52195235
next_hop.outgoing_cltv_value,

0 commit comments

Comments
 (0)