ln/test: add multi-purpose trampoline test helper
What changed, and why it matters
This commit is a test-only refactor. It moves a helper function that builds synthetic blinded payment paths for trampoline routing tests into a shared test utilities file and generalizes it so it can be reused for both forwarding and single-hop receiving test scenarios. There is no change to production code, no bug fix, and no security relevance.
No security action needed. This is a routine test refactor.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change removes a local create_blinded_tail helper from blinded_payment_tests.rs and adds a new shared helper create_trampoline_forward_blinded_tail in functional_test_utils.rs. The new helper uses BlindedPaymentPath::new_for_trampoline to construct a blinded path from supplied intermediate nodes and payee data, then derives the TrampolineHop fee via compute_fees and the aggregated payinfo. compute_fees in router.rs is changed from fn to pub(crate) fn so the test utility can call it. All modifications are confined to test code and test helpers.
Changed components
lightning/src/ln/blinded_payment_tests.rslightning/src/ln/functional_test_utils.rslightning/src/routing/router.rsInspect captured patch +61 / −50
diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs
index d62f799..621c510 100644
--- a/lightning/src/ln/blinded_payment_tests.rs
+++ b/lightning/src/ln/blinded_payment_tests.rs
@@ -2428,50 +2428,6 @@ fn test_trampoline_blinded_receive() {
do_test_trampoline_relay(true, TrampolineTestCase::OuterCLTVLessThanTrampoline);
}
-/// Creates a blinded tail where Carol receives via a blinded path.
-fn create_blinded_tail(
- secp_ctx: &Secp256k1<All>, override_random_bytes: [u8; 32], carol_node_id: PublicKey,
- carol_auth_key: ReceiveAuthKey, trampoline_cltv_expiry_delta: u32,
- excess_final_cltv_delta: u32, final_value_msat: u64, payment_secret: PaymentSecret,
-) -> BlindedTail {
- let outer_session_priv = SecretKey::from_slice(&override_random_bytes).unwrap();
- let trampoline_session_priv = onion_utils::compute_trampoline_session_priv(&outer_session_priv);
-
- let carol_blinding_point = PublicKey::from_secret_key(&secp_ctx, &trampoline_session_priv);
- let carol_blinded_hops = {
- let payee_tlvs = ReceiveTlvs {
- payment_secret,
- payment_constraints: PaymentConstraints {
- max_cltv_expiry: u32::max_value(),
- htlc_minimum_msat: final_value_msat,
- },
- payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
- }
- .encode();
-
- let path = [((carol_node_id, Some(carol_auth_key)), WithoutLength(&payee_tlvs))];
-
- blinded_path::utils::construct_blinded_hops(
- &secp_ctx,
- path.into_iter(),
- &trampoline_session_priv,
- )
- };
-
- BlindedTail {
- trampoline_hops: vec![TrampolineHop {
- pubkey: carol_node_id,
- node_features: Features::empty(),
- fee_msat: final_value_msat,
- cltv_expiry_delta: trampoline_cltv_expiry_delta + excess_final_cltv_delta,
- }],
- hops: carol_blinded_hops,
- blinding_point: carol_blinding_point,
- excess_final_cltv_expiry_delta: excess_final_cltv_delta,
- final_value_msat,
- }
-}
-
// Creates a replacement onion that is used to produce scenarios that we don't support, specifically
// payloads that send to unblinded receives and invalid payloads.
fn replacement_onion(
@@ -2639,15 +2595,23 @@ fn do_test_trampoline_relay(blinded: bool, test_case: TrampolineTestCase) {
// Create a blinded tail where Carol is receiving. In our unblinded test cases, we'll
// override this anyway (with a tail sending to an unblinded receive, which LDK doesn't
// allow).
- blinded_tail: Some(create_blinded_tail(
+ blinded_tail: Some(create_trampoline_forward_blinded_tail(
&secp_ctx,
- override_random_bytes,
+ &nodes[2].keys_manager,
+ &[],
carol_node_id,
nodes[2].keys_manager.get_receive_auth_key(),
+ ReceiveTlvs {
+ payment_secret,
+ payment_constraints: PaymentConstraints {
+ max_cltv_expiry: u32::max_value(),
+ htlc_minimum_msat: original_amt_msat,
+ },
+ payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
+ },
original_trampoline_cltv,
excess_final_cltv,
original_amt_msat,
- payment_secret,
)),
}],
route_params: None,
diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs
index d39cee7..c192373 100644
--- a/lightning/src/ln/functional_test_utils.rs
+++ b/lightning/src/ln/functional_test_utils.rs
@@ -10,7 +10,9 @@
//! A bunch of useful utilities for building networks of nodes and exchanging messages between
//! nodes for functional tests.
-use crate::blinded_path::payment::DummyTlvs;
+use crate::blinded_path::payment::{
+ BlindedPaymentPath, DummyTlvs, ForwardNode, ReceiveTlvs, TrampolineForwardTlvs,
+};
use crate::chain::channelmonitor::{ChannelMonitor, HTLC_FAIL_BACK_BUFFER};
use crate::chain::transaction::OutPoint;
use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch};
@@ -40,7 +42,8 @@ use crate::ln::types::ChannelId;
use crate::onion_message::messenger::OnionMessenger;
use crate::routing::gossip::{NetworkGraph, NetworkUpdate, P2PGossipSync};
use crate::routing::router::{self, PaymentParameters, Route, RouteParameters};
-use crate::sign::{EntropySource, RandomBytes};
+use crate::routing::router::{compute_fees, BlindedTail, TrampolineHop};
+use crate::sign::{EntropySource, RandomBytes, ReceiveAuthKey};
use crate::types::features::ChannelTypeFeatures;
use crate::types::features::InitFeatures;
use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
@@ -5768,3 +5771,47 @@ pub fn get_scid_from_channel_id<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, channel_id:
.short_channel_id
.unwrap()
}
+
+/// Creates a [`BlindedTail`] for a trampoline forward through a single intermediate node.
+///
+/// The resulting tail contains blinded hops built from `intermediate_nodes` plus a dummy receive
+/// TLV, with the `TrampolineHop` fee and CLTV derived from the blinded path's aggregated payinfo.
+pub fn create_trampoline_forward_blinded_tail<ES: EntropySource>(
+ secp_ctx: &bitcoin::secp256k1::Secp256k1<bitcoin::secp256k1::All>, entropy_source: ES,
+ intermediate_nodes: &[ForwardNode<TrampolineForwardTlvs>], payee_node_id: PublicKey,
+ payee_receive_key: ReceiveAuthKey, payee_tlvs: ReceiveTlvs, min_final_cltv_expiry_delta: u32,
+ excess_final_cltv_delta: u32, final_value_msat: u64,
+) -> BlindedTail {
+ let blinded_path = BlindedPaymentPath::new_for_trampoline(
+ intermediate_nodes,
+ payee_node_id,
+ payee_receive_key,
+ payee_tlvs,
+ u64::max_value(),
+ min_final_cltv_expiry_delta as u16,
+ entropy_source,
+ secp_ctx,
+ )
+ .unwrap();
+
+ BlindedTail {
+ trampoline_hops: vec![TrampolineHop {
+ pubkey: intermediate_nodes.first().map(|n| n.node_id).unwrap_or(payee_node_id),
+ node_features: types::features::Features::empty(),
+ fee_msat: compute_fees(
+ final_value_msat,
+ lightning_types::routing::RoutingFees {
+ base_msat: blinded_path.payinfo.fee_base_msat,
+ proportional_millionths: blinded_path.payinfo.fee_proportional_millionths,
+ },
+ )
+ .unwrap(),
+ cltv_expiry_delta: blinded_path.payinfo.cltv_expiry_delta as u32
+ + excess_final_cltv_delta,
+ }],
+ hops: blinded_path.blinded_hops().to_vec(),
+ blinding_point: blinded_path.blinding_point(),
+ excess_final_cltv_expiry_delta: excess_final_cltv_delta,
+ final_value_msat,
+ }
+}
diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs
index 0c0d14b..edb048c 100644
--- a/lightning/src/routing/router.rs
+++ b/lightning/src/routing/router.rs
@@ -2464,7 +2464,7 @@ impl<'a> PaymentPath<'a> {
#[inline(always)]
/// Calculate the fees required to route the given amount over a channel with the given fees.
#[rustfmt::skip]
-fn compute_fees(amount_msat: u64, channel_fees: RoutingFees) -> Option<u64> {
+pub(crate) fn compute_fees(amount_msat: u64, channel_fees: RoutingFees) -> Option<u64> {
amount_msat.checked_mul(channel_fees.proportional_millionths as u64)
.and_then(|part| (channel_fees.base_msat as u64).checked_add(part / 1_000_000))
}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.