Skip to content

Commit bc37b7d

Browse files
Merge pull request #3 from Synesso/bcoin-7796-onion-message-origin
Backport lightningdevkit#4682: report sending peer (prev_hop) in OnionMessageIntercepted
2 parents bee3c3c + 97b3fc0 commit bc37b7d

5 files changed

Lines changed: 263 additions & 30 deletions

File tree

lightning/src/blinded_path/message.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,11 @@ pub enum NextMessageHop {
258258
ShortChannelId(u64),
259259
}
260260

261+
impl_writeable_tlv_based_enum!(NextMessageHop,
262+
{0, NodeId} => (),
263+
{2, ShortChannelId} => (),
264+
);
265+
261266
/// An intermediate node, and possibly a short channel id leading to the next node.
262267
///
263268
/// 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
};
@@ -1691,9 +1691,13 @@ pub enum Event {
16911691
/// [`ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitments`]: crate::util::config::ChannelHandshakeConfig::negotiate_anchor_zero_fee_commitments
16921692
BumpTransaction(BumpTransactionEvent),
16931693
/// We received an onion message that is intended to be forwarded to a peer
1694-
/// that is currently offline. This event will only be generated if the
1695-
/// `OnionMessenger` was initialized with
1696-
/// [`OnionMessenger::new_with_offline_peer_interception`], see its docs.
1694+
/// that is currently offline *or* that is intended to be forwarded along a channel with an
1695+
/// SCID unknown to us.
1696+
///
1697+
/// This event will only be generated if the `OnionMessenger` was initialized with
1698+
/// [`OnionMessenger::new_with_offline_peer_interception`], see its docs. The
1699+
/// [`NextMessageHop::ShortChannelId`] variant is only generated if `intercept_for_unknown_scids`
1700+
/// was set when constructing the `OnionMessenger`.
16971701
///
16981702
/// The offline peer should be awoken if possible on receipt of this event, such as via the LSPS5
16991703
/// protocol.
@@ -1707,9 +1711,21 @@ pub enum Event {
17071711
///
17081712
/// [`OnionMessenger::new_with_offline_peer_interception`]: crate::onion_message::messenger::OnionMessenger::new_with_offline_peer_interception
17091713
OnionMessageIntercepted {
1710-
/// The node id of the offline peer.
1711-
peer_node_id: PublicKey,
1712-
/// The onion message intended to be forwarded to `peer_node_id`.
1714+
/// The node id of the peer that sent the message, if known.
1715+
///
1716+
/// This is `None` when the message is sent with
1717+
/// [`MessageSendInstructions::ForwardedMessage`] (e.g., when calling
1718+
/// [`OffersMessageFlow::enqueue_invoice_request_to_forward`]) rather than forwarded
1719+
/// internally by the `OnionMessenger`, as well as for events serialized prior to LDK 0.3.
1720+
/// Otherwise it is the node we received the message from.
1721+
///
1722+
/// [`MessageSendInstructions::ForwardedMessage`]: crate::onion_message::messenger::MessageSendInstructions::ForwardedMessage
1723+
/// [`OffersMessageFlow::enqueue_invoice_request_to_forward`]: crate::offers::flow::OffersMessageFlow::enqueue_invoice_request_to_forward
1724+
prev_hop: Option<PublicKey>,
1725+
/// The next hop (offline peer or unknown SCID).
1726+
next_hop: NextMessageHop,
1727+
/// The onion message intended to be forwarded to the offline peer or via the unknown
1728+
/// channel once established.
17131729
message: msgs::OnionMessage,
17141730
},
17151731
/// Indicates that an onion message supporting peer has come online and any messages previously
@@ -2259,11 +2275,19 @@ impl Writeable for Event {
22592275
35u8.write(writer)?;
22602276
// Never write ConnectionNeeded events as buffered onion messages aren't serialized.
22612277
},
2262-
&Event::OnionMessageIntercepted { ref peer_node_id, ref message } => {
2278+
&Event::OnionMessageIntercepted { ref prev_hop, ref next_hop, ref message } => {
22632279
37u8.write(writer)?;
2280+
// 0 used to be peer_node_id in LDK v0.2 and prior; we keep writing it when the next
2281+
// hop is a node id for backwards compatibility.
2282+
let legacy_peer_node_id = match next_hop {
2283+
NextMessageHop::NodeId(node_id) => Some(node_id),
2284+
NextMessageHop::ShortChannelId(_) => None,
2285+
};
22642286
write_tlv_fields!(writer, {
2265-
(0, peer_node_id, required),
2287+
(0, legacy_peer_node_id, option),
2288+
(1, next_hop, required),
22662289
(2, message, required),
2290+
(3, prev_hop, option),
22672291
});
22682292
},
22692293
&Event::OnionMessagePeerConnected { ref peer_node_id } => {
@@ -2869,11 +2893,18 @@ impl MaybeReadable for Event {
28692893
37u8 => {
28702894
let mut f = || {
28712895
_init_and_read_len_prefixed_tlv_fields!(reader, {
2872-
(0, peer_node_id, required),
2896+
(0, peer_node_id, option),
2897+
(1, next_hop, option),
28732898
(2, message, required),
2899+
(3, prev_hop, option),
28742900
});
2901+
2902+
let next_hop = next_hop
2903+
.or(peer_node_id.map(NextMessageHop::NodeId))
2904+
.ok_or(msgs::DecodeError::InvalidValue)?;
28752905
Ok(Some(Event::OnionMessageIntercepted {
2876-
peer_node_id: peer_node_id.0.unwrap(),
2906+
prev_hop,
2907+
next_hop,
28772908
message: message.0.unwrap(),
28782909
}))
28792910
};

lightning/src/ln/functional_test_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4653,6 +4653,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(
46534653
&chan_mgrs[i],
46544654
IgnoringMessageHandler {},
46554655
IgnoringMessageHandler {},
4656+
true,
46564657
);
46574658
let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger);
46584659
let wallet_source = Arc::new(test_utils::TestWalletSource::new(

lightning/src/onion_message/functional_tests.rs

Lines changed: 174 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ use super::offers::{OffersMessage, OffersMessageHandler};
2424
use super::packet::{OnionMessageContents, Packet};
2525
use crate::blinded_path::message::{
2626
AsyncPaymentsContext, BlindedMessagePath, DNSResolverContext, MessageContext,
27-
MessageForwardNode, OffersContext, MESSAGE_PADDING_ROUND_OFF,
27+
MessageForwardNode, NextMessageHop, OffersContext, MESSAGE_PADDING_ROUND_OFF,
2828
};
2929
use crate::blinded_path::utils::is_padded;
30-
use crate::blinded_path::EmptyNodeIdLookUp;
30+
use crate::blinded_path::NodeIdLookUp;
3131
use crate::events::{Event, EventsProvider};
3232
use crate::ln::msgs::{self, BaseMessageHandler, DecodeError, OnionMessageHandler};
3333
use crate::routing::gossip::{NetworkGraph, P2PGossipSync};
@@ -60,17 +60,40 @@ struct MessengerNode {
6060
Arc<TestKeysInterface>,
6161
Arc<TestNodeSigner>,
6262
Arc<TestLogger>,
63-
Arc<EmptyNodeIdLookUp>,
63+
Arc<TestNodeIdLookUp>,
6464
Arc<MessageRouter>,
6565
Arc<TestOffersMessageHandler>,
6666
Arc<TestAsyncPaymentsMessageHandler>,
6767
Arc<TestDNSResolverMessageHandler>,
6868
Arc<TestCustomMessageHandler>,
6969
>,
70+
node_id_lookup: Arc<TestNodeIdLookUp>,
7071
custom_message_handler: Arc<TestCustomMessageHandler>,
7172
gossip_sync: Arc<P2PGossipSync<Arc<NetGraph>, Arc<TestChainSource>, Arc<TestLogger>>>,
7273
}
7374

75+
/// A [`NodeIdLookUp`] that resolves SCIDs to node ids from an insertable map (empty by default,
76+
/// so it behaves like an empty lookup unless a mapping is added).
77+
struct TestNodeIdLookUp {
78+
scid_to_node_id: Mutex<HashMap<u64, PublicKey>>,
79+
}
80+
81+
impl TestNodeIdLookUp {
82+
fn new() -> Self {
83+
Self { scid_to_node_id: Mutex::new(new_hash_map()) }
84+
}
85+
86+
fn add_mapping(&self, scid: u64, node_id: PublicKey) {
87+
self.scid_to_node_id.lock().unwrap().insert(scid, node_id);
88+
}
89+
}
90+
91+
impl NodeIdLookUp for TestNodeIdLookUp {
92+
fn next_node_id(&self, short_channel_id: u64) -> Option<PublicKey> {
93+
self.scid_to_node_id.lock().unwrap().get(&short_channel_id).copied()
94+
}
95+
}
96+
7497
impl Drop for MessengerNode {
7598
fn drop(&mut self) {
7699
if std::thread::panicking() {
@@ -275,10 +298,15 @@ fn create_nodes(num_messengers: u8) -> Vec<MessengerNode> {
275298
struct MessengerCfg {
276299
secret_override: Option<SecretKey>,
277300
intercept_offline_peer_oms: bool,
301+
intercept_unknown_scid_oms: bool,
278302
}
279303
impl MessengerCfg {
280304
fn new() -> Self {
281-
Self { secret_override: None, intercept_offline_peer_oms: false }
305+
Self {
306+
secret_override: None,
307+
intercept_offline_peer_oms: false,
308+
intercept_unknown_scid_oms: false,
309+
}
282310
}
283311
fn with_node_secret(mut self, secret: SecretKey) -> Self {
284312
self.secret_override = Some(secret);
@@ -288,6 +316,10 @@ impl MessengerCfg {
288316
self.intercept_offline_peer_oms = true;
289317
self
290318
}
319+
fn with_unknown_scid_interception(mut self) -> Self {
320+
self.intercept_unknown_scid_oms = true;
321+
self
322+
}
291323
}
292324

293325
fn create_nodes_using_cfgs(cfgs: Vec<MessengerCfg>) -> Vec<MessengerNode> {
@@ -304,31 +336,32 @@ fn create_nodes_using_cfgs(cfgs: Vec<MessengerCfg>) -> Vec<MessengerNode> {
304336
let entropy_source = Arc::new(TestKeysInterface::new(&seed, Network::Testnet));
305337
let node_signer = Arc::new(TestNodeSigner::new(secret_key));
306338

307-
let node_id_lookup = Arc::new(EmptyNodeIdLookUp {});
339+
let node_id_lookup = Arc::new(TestNodeIdLookUp::new());
308340
let message_router =
309341
DefaultMessageRouter::new(Arc::clone(&network_graph), Arc::clone(&entropy_source));
310342
let offers_message_handler = Arc::new(TestOffersMessageHandler {});
311343
let async_payments_message_handler = Arc::new(TestAsyncPaymentsMessageHandler {});
312344
let dns_resolver_message_handler = Arc::new(TestDNSResolverMessageHandler {});
313345
let custom_message_handler = Arc::new(TestCustomMessageHandler::new());
314-
let messenger = if cfg.intercept_offline_peer_oms {
346+
let messenger = if cfg.intercept_offline_peer_oms || cfg.intercept_unknown_scid_oms {
315347
OnionMessenger::new_with_offline_peer_interception(
316348
Arc::clone(&entropy_source),
317349
Arc::clone(&node_signer),
318350
logger,
319-
node_id_lookup,
351+
Arc::clone(&node_id_lookup),
320352
Arc::new(message_router),
321353
offers_message_handler,
322354
async_payments_message_handler,
323355
dns_resolver_message_handler,
324356
Arc::clone(&custom_message_handler),
357+
cfg.intercept_unknown_scid_oms,
325358
)
326359
} else {
327360
OnionMessenger::new(
328361
Arc::clone(&entropy_source),
329362
Arc::clone(&node_signer),
330363
logger,
331-
node_id_lookup,
364+
Arc::clone(&node_id_lookup),
332365
Arc::new(message_router),
333366
offers_message_handler,
334367
async_payments_message_handler,
@@ -341,6 +374,7 @@ fn create_nodes_using_cfgs(cfgs: Vec<MessengerCfg>) -> Vec<MessengerNode> {
341374
node_id: node_signer.get_node_id(Recipient::Node).unwrap(),
342375
entropy_source,
343376
messenger,
377+
node_id_lookup,
344378
custom_message_handler,
345379
gossip_sync: Arc::clone(&gossip_sync),
346380
});
@@ -1133,9 +1167,14 @@ fn intercept_offline_peer_oms() {
11331167
let mut events = release_events(&nodes[1]);
11341168
assert_eq!(events.len(), 1);
11351169
let onion_message = match events.remove(0) {
1136-
Event::OnionMessageIntercepted { peer_node_id, message } => {
1137-
assert_eq!(peer_node_id, final_node_vec[0].node_id);
1138-
message
1170+
Event::OnionMessageIntercepted { prev_hop, next_hop, message } => {
1171+
assert_eq!(prev_hop, Some(nodes[0].node_id));
1172+
if let NextMessageHop::NodeId(peer_node_id) = next_hop {
1173+
assert_eq!(peer_node_id, final_node_vec[0].node_id);
1174+
message
1175+
} else {
1176+
panic!();
1177+
}
11391178
},
11401179
_ => panic!(),
11411180
};
@@ -1162,6 +1201,130 @@ fn intercept_offline_peer_oms() {
11621201
pass_along_path(&vec![nodes.remove(1), final_node_vec.remove(0)]);
11631202
}
11641203

1204+
#[test]
1205+
fn intercept_unknown_scid_oms() {
1206+
// Ensure that if OnionMessenger is initialized with
1207+
// new_with_offline_peer_interception and `intercept_for_unknown_scids` set, we will
1208+
// intercept OMs that use an unknown SCID as the next hop, generate the right events, and
1209+
// forward OMs when they are re-injected by the user.
1210+
let node_cfgs = vec![
1211+
MessengerCfg::new(),
1212+
MessengerCfg::new().with_unknown_scid_interception(),
1213+
MessengerCfg::new(),
1214+
];
1215+
let mut nodes = create_nodes_using_cfgs(node_cfgs);
1216+
1217+
let peer_conn_evs = release_events(&nodes[1]);
1218+
assert_eq!(peer_conn_evs.len(), 2);
1219+
for (i, ev) in peer_conn_evs.iter().enumerate() {
1220+
match ev {
1221+
Event::OnionMessagePeerConnected { peer_node_id } => {
1222+
let node_idx = if i == 0 { 0 } else { 2 };
1223+
assert_eq!(peer_node_id, &nodes[node_idx].node_id);
1224+
},
1225+
_ => panic!(),
1226+
}
1227+
}
1228+
1229+
// Use a SCID-based intermediate hop to trigger the unknown SCID interception path. Since no
1230+
// mapping was added to the `TestNodeIdLookUp`, the SCID cannot be resolved, so the
1231+
// OnionMessenger will generate an `OnionMessageIntercepted` event with a `ShortChannelId`
1232+
// next hop.
1233+
let scid = 42;
1234+
let message = TestCustomMessage::Pong;
1235+
let intermediate_nodes =
1236+
[MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: Some(scid) }];
1237+
let blinded_path = BlindedMessagePath::new(
1238+
&intermediate_nodes,
1239+
nodes[2].node_id,
1240+
nodes[2].messenger.node_signer.get_receive_auth_key(),
1241+
MessageContext::Custom(Vec::new()),
1242+
false,
1243+
&*nodes[2].entropy_source,
1244+
&Secp256k1::new(),
1245+
);
1246+
let destination = Destination::BlindedPath(blinded_path);
1247+
let instructions = MessageSendInstructions::WithoutReplyPath { destination };
1248+
1249+
nodes[0].messenger.send_onion_message(message, instructions).unwrap();
1250+
let mut final_node_vec = nodes.split_off(2);
1251+
pass_along_path(&nodes);
1252+
1253+
// We expect an `OnionMessageIntercepted` event with a `ShortChannelId` next hop since the
1254+
// SCID is not resolvable (no mapping was added to the `TestNodeIdLookUp`).
1255+
let mut events = release_events(&nodes[1]);
1256+
assert_eq!(events.len(), 1);
1257+
let onion_message = match events.remove(0) {
1258+
Event::OnionMessageIntercepted { prev_hop, next_hop, message } => {
1259+
assert_eq!(prev_hop, Some(nodes[0].node_id));
1260+
if let NextMessageHop::ShortChannelId(intercepted_scid) = next_hop {
1261+
assert_eq!(intercepted_scid, scid);
1262+
message
1263+
} else {
1264+
panic!("Expected ShortChannelId next hop, got NodeId");
1265+
}
1266+
},
1267+
_ => panic!(),
1268+
};
1269+
1270+
// The user resolves the SCID externally and forwards the intercepted message to the
1271+
// correct peer.
1272+
nodes[1].messenger.forward_onion_message(onion_message, &final_node_vec[0].node_id).unwrap();
1273+
final_node_vec[0].custom_message_handler.expect_message(TestCustomMessage::Pong);
1274+
pass_along_path(&vec![nodes.remove(1), final_node_vec.remove(0)]);
1275+
}
1276+
1277+
#[test]
1278+
fn intercept_resolved_scid_offline_peer_oms() {
1279+
// Ensure that when a forwarded OM's next hop is a SCID that resolves to a known but offline
1280+
// peer, the offline-peer interception path reports the resolved node id rather than the SCID,
1281+
// even though `intercept_for_unknown_scids` is disabled.
1282+
let node_cfgs = vec![
1283+
MessengerCfg::new(),
1284+
MessengerCfg::new().with_offline_peer_interception(),
1285+
MessengerCfg::new(),
1286+
];
1287+
let mut nodes = create_nodes_using_cfgs(node_cfgs);
1288+
1289+
// Clear the initial `OnionMessagePeerConnected` events.
1290+
let _ = release_events(&nodes[1]);
1291+
1292+
// Resolve the SCID to nodes[2] and disconnect it so it appears as a known-but-offline peer.
1293+
let scid = 42;
1294+
nodes[1].node_id_lookup.add_mapping(scid, nodes[2].node_id);
1295+
disconnect_peers(&nodes[1], &nodes[2]);
1296+
1297+
let message = TestCustomMessage::Pong;
1298+
let intermediate_nodes =
1299+
[MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: Some(scid) }];
1300+
let blinded_path = BlindedMessagePath::new(
1301+
&intermediate_nodes,
1302+
nodes[2].node_id,
1303+
nodes[2].messenger.node_signer.get_receive_auth_key(),
1304+
MessageContext::Custom(Vec::new()),
1305+
false,
1306+
&*nodes[2].entropy_source,
1307+
&Secp256k1::new(),
1308+
);
1309+
let destination = Destination::BlindedPath(blinded_path);
1310+
let instructions = MessageSendInstructions::WithoutReplyPath { destination };
1311+
1312+
nodes[0].messenger.send_onion_message(message, instructions).unwrap();
1313+
let final_node_vec = nodes.split_off(2);
1314+
pass_along_path(&nodes);
1315+
1316+
// The next hop resolved to a known (but offline) peer, so the event must carry its node id
1317+
// rather than the SCID variant (which `intercept_for_unknown_scids` would have produced).
1318+
let mut events = release_events(&nodes[1]);
1319+
assert_eq!(events.len(), 1);
1320+
match events.remove(0) {
1321+
Event::OnionMessageIntercepted { next_hop, .. } => {
1322+
assert_eq!(next_hop, NextMessageHop::NodeId(final_node_vec[0].node_id));
1323+
},
1324+
_ => panic!(),
1325+
}
1326+
}
1327+
11651328
#[test]
11661329
fn spec_test_vector() {
11671330
let node_cfgs = [

0 commit comments

Comments
 (0)