Skip to content

Commit ded194f

Browse files
authored
Merge pull request #4682 from jkczyz/2026-06-onion-message-origin
Report the sending peer in `Event::OnionMessageIntercepted`
2 parents 5049f7c + 1a0e530 commit ded194f

6 files changed

Lines changed: 383 additions & 31 deletions

File tree

lightning-tests/src/upgrade_downgrade_tests.rs

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ use lightning_0_2::ln::channelmanager::PaymentId as PaymentId_0_2;
1717
use lightning_0_2::ln::channelmanager::RecipientOnionFields as RecipientOnionFields_0_2;
1818
use lightning_0_2::ln::functional_test_utils as lightning_0_2_utils;
1919
use lightning_0_2::ln::msgs::ChannelMessageHandler as _;
20+
use lightning_0_2::ln::msgs::OnionMessage as OnionMessage_0_2;
21+
use lightning_0_2::onion_message::packet::Packet as Packet_0_2;
2022
use lightning_0_2::routing::router as router_0_2;
23+
use lightning_0_2::util::ser::MaybeReadable as MaybeReadable_0_2;
2124
use lightning_0_2::util::ser::Writeable as _;
2225

2326
use lightning_0_1::commitment_signed_dance as commitment_signed_dance_0_1;
@@ -45,23 +48,29 @@ use lightning_0_0_125::ln::msgs::ChannelMessageHandler as _;
4548
use lightning_0_0_125::routing::router as router_0_0_125;
4649
use lightning_0_0_125::util::ser::Writeable as _;
4750

51+
use lightning::blinded_path::message::NextMessageHop;
4852
use lightning::chain::channelmonitor::{ANTI_REORG_DELAY, HTLC_FAIL_BACK_BUFFER};
4953
use lightning::events::{ClosureReason, Event, HTLCHandlingFailureType};
5054
use lightning::ln::functional_test_utils::*;
55+
use lightning::ln::msgs;
5156
use lightning::ln::msgs::BaseMessageHandler as _;
5257
use lightning::ln::msgs::ChannelMessageHandler as _;
5358
use lightning::ln::msgs::MessageSendEvent;
5459
use lightning::ln::splicing_tests::*;
5560
use lightning::ln::types::ChannelId;
61+
use lightning::onion_message::packet::Packet;
5662
use lightning::sign::OutputSpender;
63+
use lightning::util::ser::{MaybeReadable, Writeable};
5764
use lightning::util::wallet_utils::WalletSourceSync;
5865

5966
use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
6067

6168
use bitcoin::script::Builder;
62-
use bitcoin::secp256k1::Secp256k1;
69+
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
6370
use bitcoin::{opcodes, Amount, TxOut};
6471

72+
use lightning::io::Cursor;
73+
6574
use std::sync::Arc;
6675

6776
#[test]
@@ -700,3 +709,113 @@ fn do_upgrade_mid_htlc_forward(test: MidHtlcForwardCase) {
700709
expect_payment_claimable!(nodes[2], pay_hash, pay_secret, 1_000_000);
701710
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], pay_preimage);
702711
}
712+
713+
/// Constructs a dummy `OnionMessage` (current version) for use in serialization tests.
714+
fn dummy_onion_message() -> msgs::OnionMessage {
715+
let pubkey =
716+
PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[42; 32]).unwrap());
717+
msgs::OnionMessage {
718+
blinding_point: pubkey,
719+
onion_routing_packet: Packet {
720+
version: 0,
721+
public_key: pubkey,
722+
hop_data: vec![1; 64],
723+
hmac: [2; 32],
724+
},
725+
}
726+
}
727+
728+
/// Constructs a dummy `OnionMessage` (0.2 version) for use in serialization tests.
729+
fn dummy_onion_message_0_2() -> OnionMessage_0_2 {
730+
let pubkey = bitcoin::secp256k1::PublicKey::from_secret_key(
731+
&Secp256k1::new(),
732+
&SecretKey::from_slice(&[42; 32]).unwrap(),
733+
);
734+
OnionMessage_0_2 {
735+
blinding_point: pubkey,
736+
onion_routing_packet: Packet_0_2 {
737+
version: 0,
738+
public_key: pubkey,
739+
hop_data: vec![1; 64],
740+
hmac: [2; 32],
741+
},
742+
}
743+
}
744+
745+
#[test]
746+
fn test_onion_message_intercepted_upgrade_from_0_2() {
747+
// Ensure that an `Event::OnionMessageIntercepted` serialized by LDK 0.2 (which uses
748+
// `peer_node_id: PublicKey` in TLV field 0) can be deserialized by the current version,
749+
// producing `NextMessageHop::NodeId`.
750+
let pubkey =
751+
PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[42; 32]).unwrap());
752+
753+
let event_0_2 = Event_0_2::OnionMessageIntercepted {
754+
peer_node_id: pubkey,
755+
message: dummy_onion_message_0_2(),
756+
};
757+
758+
let serialized = lightning_0_2::util::ser::Writeable::encode(&event_0_2);
759+
760+
let mut reader = Cursor::new(&serialized);
761+
let deserialized = <Event as MaybeReadable>::read(&mut reader).unwrap().unwrap();
762+
763+
match deserialized {
764+
Event::OnionMessageIntercepted { prev_hop, next_hop, message } => {
765+
// LDK 0.2 did not write a `prev_hop`, so it must default to `None`.
766+
assert_eq!(prev_hop, None);
767+
assert_eq!(next_hop, NextMessageHop::NodeId(pubkey));
768+
assert_eq!(message, dummy_onion_message());
769+
},
770+
_ => panic!("Expected OnionMessageIntercepted event"),
771+
}
772+
}
773+
774+
#[test]
775+
fn test_onion_message_intercepted_node_id_downgrade_to_0_2() {
776+
// Ensure that an `Event::OnionMessageIntercepted` with a `NodeId` next hop serialized by
777+
// the current version can be deserialized by LDK 0.2 (which expects `peer_node_id` in TLV
778+
// field 0 and ignores the newer `prev_hop` in TLV field 3).
779+
let pubkey =
780+
PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[42; 32]).unwrap());
781+
let prev_hop =
782+
PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[43; 32]).unwrap());
783+
784+
let event = Event::OnionMessageIntercepted {
785+
prev_hop: Some(prev_hop),
786+
next_hop: NextMessageHop::NodeId(pubkey),
787+
message: dummy_onion_message(),
788+
};
789+
790+
let serialized = event.encode();
791+
792+
let mut reader = Cursor::new(&serialized);
793+
let deserialized = <Event_0_2 as MaybeReadable_0_2>::read(&mut reader).unwrap().unwrap();
794+
795+
match deserialized {
796+
Event_0_2::OnionMessageIntercepted { peer_node_id, message } => {
797+
assert_eq!(peer_node_id, pubkey);
798+
assert_eq!(message, dummy_onion_message_0_2());
799+
},
800+
_ => panic!("Expected OnionMessageIntercepted event"),
801+
}
802+
}
803+
804+
#[test]
805+
fn test_onion_message_intercepted_scid_downgrade_to_0_2() {
806+
// Ensure that an `Event::OnionMessageIntercepted` with a `ShortChannelId` next hop
807+
// serialized by the current version cannot be deserialized by LDK 0.2, since the
808+
// `peer_node_id` field (0) is not written for SCID variants and LDK 0.2 requires it.
809+
let event = Event::OnionMessageIntercepted {
810+
prev_hop: None,
811+
next_hop: NextMessageHop::ShortChannelId(42),
812+
message: dummy_onion_message(),
813+
};
814+
815+
let serialized = event.encode();
816+
817+
// LDK 0.2 will try to read field 0 as required. Since it's absent, the read will fail.
818+
let mut reader = Cursor::new(&serialized);
819+
let result = <Event_0_2 as MaybeReadable_0_2>::read(&mut reader);
820+
assert!(result.is_err(), "LDK 0.2 should fail to decode a ShortChannelId variant");
821+
}

lightning/src/blinded_path/message.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,11 @@ pub enum NextMessageHop {
275275
ShortChannelId(u64),
276276
}
277277

278+
impl_ser_tlv_based_enum!(NextMessageHop,
279+
{0, NodeId} => (),
280+
{2, ShortChannelId} => (),
281+
);
282+
278283
/// An intermediate node, and possibly a short channel id leading to the next node.
279284
///
280285
/// Note:

lightning/src/events/mod.rs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub mod bump_transaction;
1818

1919
pub use bump_transaction::BumpTransactionEvent;
2020

21-
use crate::blinded_path::message::{BlindedMessagePath, OffersContext};
21+
use crate::blinded_path::message::{BlindedMessagePath, NextMessageHop, OffersContext};
2222
use crate::blinded_path::payment::{
2323
Bolt12OfferContext, Bolt12RefundContext, PaymentContext, PaymentContextRef,
2424
};
@@ -1836,9 +1836,13 @@ pub enum Event {
18361836
/// [`ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitments`]: crate::util::config::ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitments
18371837
BumpTransaction(BumpTransactionEvent),
18381838
/// We received an onion message that is intended to be forwarded to a peer
1839-
/// that is currently offline. This event will only be generated if the
1840-
/// `OnionMessenger` was initialized with
1841-
/// [`OnionMessenger::new_with_offline_peer_interception`], see its docs.
1839+
/// that is currently offline *or* that is intended to be forwarded along a channel with an
1840+
/// SCID unknown to us.
1841+
///
1842+
/// This event will only be generated if the `OnionMessenger` was initialized with
1843+
/// [`OnionMessenger::new_with_offline_peer_interception`], see its docs. The
1844+
/// [`NextMessageHop::ShortChannelId`] variant is only generated if `intercept_for_unknown_scids`
1845+
/// was set when constructing the `OnionMessenger`.
18421846
///
18431847
/// The offline peer should be awoken if possible on receipt of this event, such as via the LSPS5
18441848
/// protocol.
@@ -1852,9 +1856,21 @@ pub enum Event {
18521856
///
18531857
/// [`OnionMessenger::new_with_offline_peer_interception`]: crate::onion_message::messenger::OnionMessenger::new_with_offline_peer_interception
18541858
OnionMessageIntercepted {
1855-
/// The node id of the offline peer.
1856-
peer_node_id: PublicKey,
1857-
/// The onion message intended to be forwarded to `peer_node_id`.
1859+
/// The node id of the peer that sent the message, if known.
1860+
///
1861+
/// This is `None` when the message is sent with
1862+
/// [`MessageSendInstructions::ForwardedMessage`] (e.g., when calling
1863+
/// [`OffersMessageFlow::enqueue_invoice_request_to_forward`]) rather than forwarded
1864+
/// internally by the `OnionMessenger`, as well as for events serialized prior to LDK 0.3.
1865+
/// Otherwise it is the node we received the message from.
1866+
///
1867+
/// [`MessageSendInstructions::ForwardedMessage`]: crate::onion_message::messenger::MessageSendInstructions::ForwardedMessage
1868+
/// [`OffersMessageFlow::enqueue_invoice_request_to_forward`]: crate::offers::flow::OffersMessageFlow::enqueue_invoice_request_to_forward
1869+
prev_hop: Option<PublicKey>,
1870+
/// The next hop (offline peer or unknown SCID).
1871+
next_hop: NextMessageHop,
1872+
/// The onion message intended to be forwarded to the offline peer or via the unknown
1873+
/// channel once established.
18581874
message: msgs::OnionMessage,
18591875
},
18601876
/// Indicates that an onion message supporting peer has come online and any messages previously
@@ -2436,11 +2452,19 @@ impl Writeable for Event {
24362452
35u8.write(writer)?;
24372453
// Never write ConnectionNeeded events as buffered onion messages aren't serialized.
24382454
},
2439-
&Event::OnionMessageIntercepted { ref peer_node_id, ref message } => {
2455+
&Event::OnionMessageIntercepted { ref prev_hop, ref next_hop, ref message } => {
24402456
37u8.write(writer)?;
2457+
// 0 used to be peer_node_id in LDK v0.2 and prior; we keep writing it when the next
2458+
// hop is a node id for backwards compatibility.
2459+
let legacy_peer_node_id = match next_hop {
2460+
NextMessageHop::NodeId(node_id) => Some(node_id),
2461+
NextMessageHop::ShortChannelId(_) => None,
2462+
};
24412463
write_tlv_fields!(writer, {
2442-
(0, peer_node_id, required),
2464+
(0, legacy_peer_node_id, option),
2465+
(1, next_hop, required),
24432466
(2, message, required),
2467+
(3, prev_hop, option),
24442468
});
24452469
},
24462470
&Event::OnionMessagePeerConnected { ref peer_node_id } => {
@@ -3069,11 +3093,18 @@ impl MaybeReadable for Event {
30693093
37u8 => {
30703094
let mut f = || {
30713095
_init_and_read_len_prefixed_tlv_fields!(reader, {
3072-
(0, peer_node_id, required),
3096+
(0, peer_node_id, option),
3097+
(1, next_hop, option),
30733098
(2, message, required),
3099+
(3, prev_hop, option),
30743100
});
3101+
3102+
let next_hop = next_hop
3103+
.or(peer_node_id.map(NextMessageHop::NodeId))
3104+
.ok_or(msgs::DecodeError::InvalidValue)?;
30753105
Ok(Some(Event::OnionMessageIntercepted {
3076-
peer_node_id: peer_node_id.0.unwrap(),
3106+
prev_hop,
3107+
next_hop,
30773108
message: message.0.unwrap(),
30783109
}))
30793110
};

lightning/src/ln/functional_test_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4799,6 +4799,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(
47994799
&chan_mgrs[i],
48004800
IgnoringMessageHandler {},
48014801
IgnoringMessageHandler {},
4802+
true,
48024803
);
48034804
let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger);
48044805
let wallet_source = Arc::new(test_utils::TestWalletSource::new(

0 commit comments

Comments
 (0)