Skip to content

Commit a04e07a

Browse files
committed
Handle Trampoline receives
Handle payment receives with payment details located inside an inner onion.
1 parent 7338c0f commit a04e07a

File tree

2 files changed

+134
-1
lines changed

2 files changed

+134
-1
lines changed

lightning/src/ln/blinded_payment_tests.rs

+103
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ use lightning_invoice::RawBolt11Invoice;
4141
use types::features::Features;
4242
use crate::blinded_path::BlindedHop;
4343

44+
#[cfg(trampoline)]
45+
use crate::routing::router::Route;
46+
4447
pub fn blinded_payment_path(
4548
payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64,
4649
node_ids: Vec<PublicKey>, channel_upds: &[&msgs::UnsignedChannelUpdate],
@@ -1958,3 +1961,103 @@ fn test_trampoline_inbound_payment_decoding() {
19581961
panic!();
19591962
};
19601963
}
1964+
1965+
#[test]
1966+
#[cfg(trampoline)]
1967+
fn test_successful_trampoline_single_hop_receive() {
1968+
// Simulate a payment of A (0) -> B (1) -> C(Trampoline (blinded intro)) (2)
1969+
1970+
const TOTAL_NODE_COUNT: usize = 3;
1971+
let secp_ctx = Secp256k1::new();
1972+
1973+
let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT);
1974+
let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs);
1975+
let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]);
1976+
let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs);
1977+
1978+
let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
1979+
let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
1980+
1981+
for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks
1982+
connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1);
1983+
}
1984+
1985+
let bob_node_id = nodes[1].node().get_our_node_id();
1986+
let carol_node_id = nodes[2].node().get_our_node_id();
1987+
1988+
let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap();
1989+
let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap();
1990+
1991+
let amt_msat = 1000;
1992+
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None);
1993+
let payee_tlvs = UnauthenticatedReceiveTlvs {
1994+
payment_secret,
1995+
payment_constraints: PaymentConstraints {
1996+
max_cltv_expiry: u32::max_value(),
1997+
htlc_minimum_msat: amt_msat,
1998+
},
1999+
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
2000+
};
2001+
2002+
let nonce = Nonce([42u8; 16]);
2003+
let expanded_key = nodes[2].keys_manager.get_inbound_payment_key();
2004+
let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key);
2005+
let carol_unblinded_tlvs = payee_tlvs.encode();
2006+
2007+
let path = [(carol_node_id, WithoutLength(&carol_unblinded_tlvs))];
2008+
let carol_alice_trampoline_session_priv = secret_from_hex("a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03");
2009+
let carol_blinding_point = PublicKey::from_secret_key(&secp_ctx, &carol_alice_trampoline_session_priv);
2010+
let carol_blinded_hops = blinded_path::utils::construct_blinded_hops(
2011+
&secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv
2012+
).unwrap();
2013+
2014+
let route = Route {
2015+
paths: vec![Path {
2016+
hops: vec![
2017+
// Bob
2018+
RouteHop {
2019+
pubkey: bob_node_id,
2020+
node_features: NodeFeatures::empty(),
2021+
short_channel_id: alice_bob_scid,
2022+
channel_features: ChannelFeatures::empty(),
2023+
fee_msat: 1000,
2024+
cltv_expiry_delta: 48,
2025+
maybe_announced_channel: false,
2026+
},
2027+
2028+
// Carol
2029+
RouteHop {
2030+
pubkey: carol_node_id,
2031+
node_features: NodeFeatures::empty(),
2032+
short_channel_id: bob_carol_scid,
2033+
channel_features: ChannelFeatures::empty(),
2034+
fee_msat: 0,
2035+
cltv_expiry_delta: 48,
2036+
maybe_announced_channel: false,
2037+
}
2038+
],
2039+
blinded_tail: Some(BlindedTail {
2040+
trampoline_hops: vec![
2041+
// Carol
2042+
TrampolineHop {
2043+
pubkey: carol_node_id,
2044+
node_features: Features::empty(),
2045+
fee_msat: amt_msat,
2046+
cltv_expiry_delta: 24,
2047+
},
2048+
],
2049+
hops: carol_blinded_hops,
2050+
blinding_point: carol_blinding_point,
2051+
excess_final_cltv_expiry_delta: 39,
2052+
final_value_msat: amt_msat,
2053+
})
2054+
}],
2055+
route_params: None,
2056+
};
2057+
2058+
nodes[0].node.send_payment_with_route(route, payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap();
2059+
2060+
check_added_monitors!(&nodes[0], 1);
2061+
pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], amt_msat, payment_hash, payment_secret);
2062+
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
2063+
}

lightning/src/ln/onion_payment.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,37 @@ pub(super) fn create_recv_pending_htlc_info(
273273
intro_node_blinding_point.is_none(), true, invoice_request)
274274
}
275275
#[cfg(trampoline)]
276-
onion_utils::Hop::TrampolineReceive { .. } | onion_utils::Hop::TrampolineBlindedReceive { .. } => todo!(),
276+
onion_utils::Hop::TrampolineReceive {
277+
trampoline_hop_data: msgs::InboundOnionReceivePayload {
278+
payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat,
279+
cltv_expiry_height, payment_metadata, ..
280+
}, ..
281+
} =>
282+
(payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat,
283+
cltv_expiry_height, payment_metadata, None, false, keysend_preimage.is_none(), None),
284+
#[cfg(trampoline)]
285+
onion_utils::Hop::TrampolineBlindedReceive {
286+
trampoline_hop_data: msgs::InboundOnionBlindedReceivePayload {
287+
sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret,
288+
intro_node_blinding_point, payment_constraints, payment_context, keysend_preimage,
289+
custom_tlvs, invoice_request
290+
}, ..
291+
} => {
292+
check_blinded_payment_constraints(
293+
sender_intended_htlc_amt_msat, cltv_expiry, &payment_constraints,
294+
)
295+
.map_err(|()| {
296+
InboundHTLCErr {
297+
err_code: INVALID_ONION_BLINDING,
298+
err_data: vec![0; 32],
299+
msg: "Amount or cltv_expiry violated blinded payment constraints",
300+
}
301+
})?;
302+
let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat };
303+
(Some(payment_data), keysend_preimage, custom_tlvs,
304+
sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context),
305+
intro_node_blinding_point.is_none(), true, invoice_request)
306+
},
277307
onion_utils::Hop::Forward { .. } => {
278308
return Err(InboundHTLCErr {
279309
err_code: 0x4000|22,

0 commit comments

Comments
 (0)