Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions fuzz/src/onion_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl MessageRouter for TestMessageRouter {
fn find_path(
&self, _sender: PublicKey, _peers: Vec<PublicKey>, destination: Destination,
) -> Result<OnionMessagePath, ()> {
Ok(OnionMessagePath { intermediate_nodes: vec![], destination, first_node_addresses: None })
Ok(OnionMessagePath { intermediate_nodes: vec![], destination, first_node_addresses: vec![] })
}

fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
Expand Down Expand Up @@ -430,7 +430,7 @@ mod tests {
super::do_test(&<Vec<u8>>::from_hex(two_unblinded_hops_om).unwrap(), &logger);
{
let log_entries = logger.lines.lock().unwrap();
assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202".to_string())), Some(&1));
assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202 when forwarding peeled onion message from 020000000000000000000000000000000000000000000000000000000000000002".to_string())), Some(&1));
}

let two_unblinded_two_blinded_om = "\
Expand Down Expand Up @@ -471,7 +471,7 @@ mod tests {
super::do_test(&<Vec<u8>>::from_hex(two_unblinded_two_blinded_om).unwrap(), &logger);
{
let log_entries = logger.lines.lock().unwrap();
assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202".to_string())), Some(&1));
assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202 when forwarding peeled onion message from 020000000000000000000000000000000000000000000000000000000000000002".to_string())), Some(&1));
}

let three_blinded_om = "\
Expand Down Expand Up @@ -512,7 +512,7 @@ mod tests {
super::do_test(&<Vec<u8>>::from_hex(three_blinded_om).unwrap(), &logger);
{
let log_entries = logger.lines.lock().unwrap();
assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202".to_string())), Some(&1));
assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Forwarding an onion message to peer 020202020202020202020202020202020202020202020202020202020202020202 when forwarding peeled onion message from 020000000000000000000000000000000000000000000000000000000000000002".to_string())), Some(&1));
}
}
}
2 changes: 1 addition & 1 deletion lightning-dns-resolver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ mod test {
) -> Result<OnionMessagePath, ()> {
Ok(OnionMessagePath {
destination,
first_node_addresses: None,
first_node_addresses: Vec::new(),
intermediate_nodes: Vec::new(),
})
}
Expand Down
15 changes: 14 additions & 1 deletion lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,9 @@ pub enum Event {
ConnectionNeeded {
/// The node id for the node needing a connection.
node_id: PublicKey,
/// Sockets for connecting to the node.
/// Sockets for connecting to the node, if available. We don't require these addresses to be
/// present in case the node id corresponds to a known peer that is offline and can be awoken,
/// such as via the LSPS5 protocol.
addresses: Vec<msgs::SocketAddress>,
},
/// Indicates a [`Bolt12Invoice`] in response to an [`InvoiceRequest`] or a [`Refund`] was
Expand Down Expand Up @@ -1617,6 +1619,9 @@ pub enum Event {
/// `OnionMessenger` was initialized with
/// [`OnionMessenger::new_with_offline_peer_interception`], see its docs.
///
/// The offline peer should be awoken if possible on receipt of this event, such as via the LSPS5
/// protocol.
///
/// # Failure Behavior and Persistence
/// This event will eventually be replayed after failures-to-handle (i.e., the event handler
/// returning `Err(ReplayEvent ())`), but won't be persisted across restarts.
Expand Down Expand Up @@ -1661,6 +1666,14 @@ pub enum Event {
/// recipient is online to provide a new invoice. This path should be persisted and
/// later provided to [`ChannelManager::respond_to_static_invoice_request`].
///
/// This path's [`BlindedMessagePath::introduction_node`] MUST be set to our node or one of our
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be documented on the client end of the protocol not the server end?

Copy link
Contributor Author

@valentinewallace valentinewallace Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally agree, though the only other possible place I see is on the ServeStaticInvoice onion message field where no one will see it. Maybe that's fine? There's nothing clients can do in practice to satisfy this requirement right now anyway since LDK generates the path without their intervention, so I guess I'll move it.

Edit: basically duplicated the docs in both places so they are more likely to be surfaced

/// peers. This is because, for DoS protection, invoice requests forwarded over this path are
/// treated by our node like any other onion message forward and will not generate
/// [`Event::ConnectionNeeded`] if the first hop in the path is not our peer.
///
/// If the next-hop peer in the path is offline, if configured to do so we will generate an
/// [`Event::OnionMessageIntercepted`] for the invoice request.
///
/// [`ChannelManager::respond_to_static_invoice_request`]: crate::ln::channelmanager::ChannelManager::respond_to_static_invoice_request
invoice_request_path: BlindedMessagePath,
/// Useful for the recipient to replace a specific invoice stored by us as the static invoice
Expand Down
15 changes: 15 additions & 0 deletions lightning/src/ln/async_payments_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2887,6 +2887,21 @@ fn async_payment_e2e() {
.into_iter()
.find_map(|ev| {
if let Event::OnionMessageIntercepted { message, .. } = ev {
// At least one of the intercepted onion messages will be an invoice request that the
// invoice server is attempting to forward to the recipient, ignore that as we're testing
// the static invoice flow
let peeled_onion = recipient.onion_messenger.peel_onion_message(&message).unwrap();
if matches!(
peeled_onion,
PeeledOnion::Offers(OffersMessage::InvoiceRequest { .. }, _, _)
) {
return None;
}

assert!(matches!(
peeled_onion,
PeeledOnion::AsyncPayments(AsyncPaymentsMessage::HeldHtlcAvailable(_), _, _)
));
Some(message)
} else {
None
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/offers/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1169,9 +1169,9 @@ where
) {
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
let message = OffersMessage::InvoiceRequest(invoice_request);
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
let instructions = MessageSendInstructions::ForwardedMessage {
destination: Destination::BlindedPath(destination),
reply_path: reply_path.into_blinded_path(),
reply_path: Some(reply_path.into_blinded_path()),
};
pending_offers_messages.push((message, instructions));
}
Expand Down
5 changes: 5 additions & 0 deletions lightning/src/onion_message/async_payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ pub struct ServeStaticInvoice {
/// [`Bolt12Invoice`] if the recipient is online at the time. Use this path to forward the
/// [`InvoiceRequest`] to the async recipient.
///
/// This path's [`BlindedMessagePath::introduction_node`] MUST be set to the static invoice server
/// node or one of its peers. This is because, for DoS protection, invoice requests forwarded over
/// this path are treated by the server node like any other onion message forward and the server
/// will not directly connect to the introduction node if they are not already peers.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub forward_invoice_request_path: BlindedMessagePath,
Expand Down
33 changes: 28 additions & 5 deletions lightning/src/onion_message/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ fn two_unblinded_hops() {
let path = OnionMessagePath {
intermediate_nodes: vec![nodes[1].node_id],
destination: Destination::Node(nodes[2].node_id),
first_node_addresses: None,
first_node_addresses: Vec::new(),
};

nodes[0].messenger.send_onion_message_using_path(path, test_msg, None).unwrap();
Expand Down Expand Up @@ -494,7 +494,7 @@ fn two_unblinded_two_blinded() {
let path = OnionMessagePath {
intermediate_nodes: vec![nodes[1].node_id, nodes[2].node_id],
destination: Destination::BlindedPath(blinded_path),
first_node_addresses: None,
first_node_addresses: Vec::new(),
};

nodes[0].messenger.send_onion_message_using_path(path, test_msg, None).unwrap();
Expand Down Expand Up @@ -660,7 +660,7 @@ fn too_big_packet_error() {
let path = OnionMessagePath {
intermediate_nodes: hops,
destination: Destination::Node(hop_node_id),
first_node_addresses: None,
first_node_addresses: Vec::new(),
};
let err = nodes[0].messenger.send_onion_message_using_path(path, test_msg, None).unwrap_err();
assert_eq!(err, SendError::TooBigPacket);
Expand Down Expand Up @@ -822,7 +822,7 @@ fn reply_path() {
let path = OnionMessagePath {
intermediate_nodes: vec![nodes[1].node_id, nodes[2].node_id],
destination: Destination::Node(nodes[3].node_id),
first_node_addresses: None,
first_node_addresses: Vec::new(),
};
let intermediate_nodes = [
MessageForwardNode { node_id: nodes[2].node_id, short_channel_id: None },
Expand Down Expand Up @@ -959,7 +959,7 @@ fn many_hops() {
let path = OnionMessagePath {
intermediate_nodes,
destination: Destination::Node(nodes[num_nodes - 1].node_id),
first_node_addresses: None,
first_node_addresses: Vec::new(),
};
nodes[0].messenger.send_onion_message_using_path(path, test_msg, None).unwrap();
nodes[num_nodes - 1].custom_message_handler.expect_message(TestCustomMessage::Pong);
Expand Down Expand Up @@ -1012,6 +1012,29 @@ fn requests_peer_connection_for_buffered_messages() {
connect_peers(&nodes[0], &nodes[1]);
assert!(nodes[0].messenger.next_onion_message_for_peer(nodes[1].node_id).is_some());
assert!(nodes[0].messenger.next_onion_message_for_peer(nodes[1].node_id).is_none());

// Buffer an onion message for a disconnected node who is not in the network graph.
disconnect_peers(&nodes[0], &nodes[2]);

let message = TestCustomMessage::Ping;
let destination = Destination::Node(nodes[2].node_id);
let instructions = MessageSendInstructions::WithoutReplyPath { destination };
nodes[0].messenger.send_onion_message(message.clone(), instructions.clone()).unwrap();

// Check that a ConnectionNeeded event for the peer is provided
let events = release_events(&nodes[0]);
assert_eq!(events.len(), 1);
match &events[0] {
Event::ConnectionNeeded { node_id, addresses } => {
assert_eq!(*node_id, nodes[2].node_id);
assert!(addresses.is_empty());
},
e => panic!("Unexpected event: {:?}", e),
}

// Release the buffered onion message when reconnected
connect_peers(&nodes[0], &nodes[2]);
assert!(nodes[0].messenger.next_onion_message_for_peer(nodes[2].node_id).is_some());
}

#[test]
Expand Down
Loading
Loading