diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 1f3f5a1fa38..bdbb4be4541 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -30,7 +30,7 @@ use core::mem; use core::ops::Deref; /// An intermediate node, and possibly a short channel id leading to the next node. -#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct ForwardNode { /// This node's pubkey. pub node_id: PublicKey, @@ -106,6 +106,8 @@ pub(super) fn blinded_hops( // Advance the blinded onion message path by one hop, so make the second hop into the new // introduction node. +// +// Will only modify `path` when returning `Ok`. pub(crate) fn advance_path_by_one( path: &mut BlindedPath, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1 ) -> Result<(), ()> @@ -116,8 +118,8 @@ where { let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &path.blinding_point, None)?; let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes()); - let encrypted_control_tlvs = path.blinded_hops.remove(0).encrypted_payload; - let mut s = Cursor::new(&encrypted_control_tlvs); + let encrypted_control_tlvs = &path.blinded_hops.get(0).ok_or(())?.encrypted_payload; + let mut s = Cursor::new(encrypted_control_tlvs); let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64); match ChaChaPolyReadAdapter::read(&mut reader, rho) { Ok(ChaChaPolyReadAdapter { @@ -139,6 +141,7 @@ where }; mem::swap(&mut path.blinding_point, &mut new_blinding_point); path.introduction_node = IntroductionNode::NodeId(next_node_id); + path.blinded_hops.remove(0); Ok(()) }, _ => Err(()) diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 3b71f77c6d5..7df7d1c63ed 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -13,17 +13,24 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; -use crate::blinded_path::BlindedHop; +use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NodeIdLookUp}; use crate::blinded_path::utils; +use crate::crypto::streams::ChaChaPolyReadAdapter; use crate::io; +use crate::io::Cursor; use crate::ln::types::PaymentSecret; use crate::ln::channel_state::CounterpartyForwardingInfo; use crate::ln::features::BlindedHopFeatures; use crate::ln::msgs::DecodeError; +use crate::ln::onion_utils; use crate::offers::invoice::BlindedPayInfo; use crate::offers::invoice_request::InvoiceRequestFields; use crate::offers::offer::OfferId; -use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, Writeable, Writer}; +use crate::sign::{NodeSigner, Recipient}; +use crate::util::ser::{FixedLengthReader, LengthReadableArgs, HighZeroBytesDroppedBigSize, Readable, Writeable, Writer}; + +use core::mem; +use core::ops::Deref; #[allow(unused_imports)] use crate::prelude::*; @@ -274,6 +281,43 @@ pub(super) fn blinded_hops( utils::construct_blinded_hops(secp_ctx, pks, tlvs, session_priv) } +// Advance the blinded onion payment path by one hop, so make the second hop into the new +// introduction node. +// +// Will only modify `path` when returning `Ok`. +pub(crate) fn advance_path_by_one( + path: &mut BlindedPath, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1 +) -> Result<(), ()> +where + NS::Target: NodeSigner, + NL::Target: NodeIdLookUp, + T: secp256k1::Signing + secp256k1::Verification, +{ + let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &path.blinding_point, None)?; + let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes()); + let encrypted_control_tlvs = &path.blinded_hops.get(0).ok_or(())?.encrypted_payload; + let mut s = Cursor::new(encrypted_control_tlvs); + let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64); + match ChaChaPolyReadAdapter::read(&mut reader, rho) { + Ok(ChaChaPolyReadAdapter { + readable: BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, .. }) + }) => { + let next_node_id = match node_id_lookup.next_node_id(short_channel_id) { + Some(node_id) => node_id, + None => return Err(()), + }; + let mut new_blinding_point = onion_utils::next_hop_pubkey( + secp_ctx, path.blinding_point, control_tlvs_ss.as_ref() + ).map_err(|_| ())?; + mem::swap(&mut path.blinding_point, &mut new_blinding_point); + path.introduction_node = IntroductionNode::NodeId(next_node_id); + path.blinded_hops.remove(0); + Ok(()) + }, + _ => Err(()) + } +} + /// `None` if underflow occurs. pub(crate) fn amt_to_forward_msat(inbound_amt_msat: u64, payment_relay: &PaymentRelay) -> Option { let inbound_amt = inbound_amt_msat as u128; diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 508d13a1582..9e573a705f7 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4031,8 +4031,8 @@ where self.pending_outbound_payments .send_payment_for_bolt12_invoice( invoice, payment_id, &self.router, self.list_usable_channels(), - || self.compute_inflight_htlcs(), &self.entropy_source, &self.node_signer, - best_block_height, &self.logger, &self.pending_events, + || self.compute_inflight_htlcs(), &self.entropy_source, &self.node_signer, &self, + &self.secp_ctx, best_block_height, &self.logger, &self.pending_events, |args| self.send_payment_along_path(args) ) } diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index eca43afee8a..52375f723c7 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -950,9 +950,12 @@ fn pays_bolt12_invoice_asynchronously() { ); } -/// Fails creating an offer when a blinded path cannot be created without exposing the node's id. +/// Checks that an offer can be created using an unannounced node as a blinded path's introduction +/// node. This is only preferred if there are no other options which may indicated either the offer +/// is intended for the unannounced node or that the node is actually announced (e.g., an LSP) but +/// the recipient doesn't have a network graph. #[test] -fn fails_creating_offer_without_blinded_paths() { +fn creates_offer_with_blinded_path_using_unannounced_introduction_node() { let chanmon_cfgs = create_chanmon_cfgs(2); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); @@ -960,15 +963,63 @@ fn fails_creating_offer_without_blinded_paths() { create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); - match nodes[0].node.create_offer_builder(None) { - Ok(_) => panic!("Expected error"), - Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + let alice = &nodes[0]; + let alice_id = alice.node.get_our_node_id(); + let bob = &nodes[1]; + let bob_id = bob.node.get_our_node_id(); + + let offer = alice.node + .create_offer_builder(None).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + assert_ne!(offer.signing_pubkey(), Some(alice_id)); + assert!(!offer.paths().is_empty()); + for path in offer.paths() { + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); } + + let payment_id = PaymentId([1; 32]); + bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); + expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); + + let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + alice.onion_messenger.handle_onion_message(&bob_id, &onion_message); + + let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); + let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext { + offer_id: offer.id(), + invoice_request: InvoiceRequestFields { + payer_id: invoice_request.payer_id(), + quantity: None, + payer_note_truncated: None, + }, + }); + assert_ne!(invoice_request.payer_id(), bob_id); + assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(alice_id)); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let invoice = extract_invoice(bob, &onion_message); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for (_, path) in invoice.payment_paths() { + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); + } + + route_bolt12_payment(bob, &[alice], &invoice); + expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(bob, &[alice], payment_context); + expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); } -/// Fails creating a refund when a blinded path cannot be created without exposing the node's id. +/// Checks that a refund can be created using an unannounced node as a blinded path's introduction +/// node. This is only preferred if there are no other options which may indicated either the refund +/// is intended for the unannounced node or that the node is actually announced (e.g., an LSP) but +/// the sender doesn't have a network graph. #[test] -fn fails_creating_refund_without_blinded_paths() { +fn creates_refund_with_blinded_path_using_unannounced_introduction_node() { let chanmon_cfgs = create_chanmon_cfgs(2); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); @@ -976,17 +1027,35 @@ fn fails_creating_refund_without_blinded_paths() { create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + let alice = &nodes[0]; + let alice_id = alice.node.get_our_node_id(); + let bob = &nodes[1]; + let bob_id = bob.node.get_our_node_id(); + let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); - - match nodes[0].node.create_refund_builder( - 10_000, absolute_expiry, payment_id, Retry::Attempts(0), None - ) { - Ok(_) => panic!("Expected error"), - Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + let refund = bob.node + .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) + .unwrap() + .build().unwrap(); + assert_ne!(refund.payer_id(), bob_id); + assert!(!refund.paths().is_empty()); + for path in refund.paths() { + assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id)); } + expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); - assert!(nodes[0].node.list_recent_payments().is_empty()); + let expected_invoice = alice.node.request_refund_payment(&refund).unwrap(); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + + let invoice = extract_invoice(bob, &onion_message); + assert_eq!(invoice, expected_invoice); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for (_, path) in invoice.payment_paths() { + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); + } } /// Fails creating or paying an offer when a blinded path cannot be created because no peers are @@ -1165,8 +1234,7 @@ fn fails_sending_invoice_with_unsupported_chain_for_refund() { } } -/// Fails creating an invoice request when a blinded reply path cannot be created without exposing -/// the node's id. +/// Fails creating an invoice request when a blinded reply path cannot be created. #[test] fn fails_creating_invoice_request_without_blinded_reply_path() { let chanmon_cfgs = create_chanmon_cfgs(6); @@ -1183,7 +1251,7 @@ fn fails_creating_invoice_request_without_blinded_reply_path() { let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); - disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, charlie, &nodes[4], &nodes[5]]); let offer = alice.node .create_offer_builder(None).unwrap() diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 0b8819aa0c6..1915a4c5a14 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -13,7 +13,8 @@ use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, Secp256k1, SecretKey}; -use crate::sign::{EntropySource, NodeSigner, Recipient}; +use crate::blinded_path::{IntroductionNode, NodeIdLookUp}; +use crate::blinded_path::payment::advance_path_by_one; use crate::events::{self, PaymentFailureReason}; use crate::ln::types::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channel_state::ChannelDetails; @@ -22,6 +23,7 @@ use crate::ln::onion_utils; use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason}; use crate::offers::invoice::Bolt12Invoice; use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router}; +use crate::sign::{EntropySource, NodeSigner, Recipient}; use crate::util::errors::APIError; use crate::util::logger::Logger; use crate::util::time::Time; @@ -775,10 +777,13 @@ impl OutboundPayments { } } - pub(super) fn send_payment_for_bolt12_invoice( + pub(super) fn send_payment_for_bolt12_invoice< + R: Deref, ES: Deref, NS: Deref, NL: Deref, IH, SP, L: Deref + >( &self, invoice: &Bolt12Invoice, payment_id: PaymentId, router: &R, first_hops: Vec, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS, - best_block_height: u32, logger: &L, + node_id_lookup: &NL, secp_ctx: &Secp256k1, best_block_height: u32, + logger: &L, pending_events: &Mutex)>>, send_payment_along_path: SP, ) -> Result<(), Bolt12PaymentError> @@ -786,6 +791,7 @@ impl OutboundPayments { R::Target: Router, ES::Target: EntropySource, NS::Target: NodeSigner, + NL::Target: NodeIdLookUp, L::Target: Logger, IH: Fn() -> InFlightHtlcs, SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, @@ -807,9 +813,30 @@ impl OutboundPayments { hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice), }; - let pay_params = PaymentParameters::from_bolt12_invoice(&invoice); + let mut payment_params = PaymentParameters::from_bolt12_invoice(&invoice); + + // Advance any blinded path where the introduction node is our node. + if let Ok(our_node_id) = node_signer.get_node_id(Recipient::Node) { + for (_, path) in payment_params.payee.blinded_route_hints_mut().iter_mut() { + let introduction_node_id = match path.introduction_node { + IntroductionNode::NodeId(pubkey) => pubkey, + IntroductionNode::DirectedShortChannelId(direction, scid) => { + match node_id_lookup.next_node_id(scid) { + Some(next_node_id) => *direction.select_pubkey(&our_node_id, &next_node_id), + None => continue, + } + }, + }; + if introduction_node_id == our_node_id { + let _ = advance_path_by_one(path, node_signer, node_id_lookup, secp_ctx); + } + } + } + let amount_msat = invoice.amount_msats(); - let mut route_params = RouteParameters::from_payment_params_and_value(pay_params, amount_msat); + let mut route_params = RouteParameters::from_payment_params_and_value( + payment_params, amount_msat + ); if let Some(max_fee_msat) = max_total_routing_fee_msat { route_params.max_total_routing_fee_msat = Some(max_fee_msat); } @@ -1856,6 +1883,7 @@ mod tests { use core::time::Duration; + use crate::blinded_path::EmptyNodeIdLookUp; use crate::events::{Event, PathFailure, PaymentFailureReason}; use crate::ln::types::PaymentHash; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; @@ -2199,6 +2227,7 @@ mod tests { let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); + let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); @@ -2227,7 +2256,8 @@ mod tests { assert_eq!( outbound_payments.send_payment_for_bolt12_invoice( &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, - &&keys_manager, 0, &&logger, &pending_events, |_| panic!() + &&keys_manager, &EmptyNodeIdLookUp {}, &secp_ctx, 0, &&logger, &pending_events, + |_| panic!() ), Ok(()), ); @@ -2250,6 +2280,7 @@ mod tests { let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); + let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); @@ -2286,7 +2317,8 @@ mod tests { assert_eq!( outbound_payments.send_payment_for_bolt12_invoice( &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, - &&keys_manager, 0, &&logger, &pending_events, |_| panic!() + &&keys_manager, &EmptyNodeIdLookUp {}, &secp_ctx, 0, &&logger, &pending_events, + |_| panic!() ), Ok(()), ); @@ -2309,6 +2341,7 @@ mod tests { let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); + let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); @@ -2358,7 +2391,8 @@ mod tests { assert_eq!( outbound_payments.send_payment_for_bolt12_invoice( &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, - &&keys_manager, 0, &&logger, &pending_events, |_| panic!() + &&keys_manager, &EmptyNodeIdLookUp {}, &secp_ctx, 0, &&logger, &pending_events, + |_| panic!() ), Err(Bolt12PaymentError::UnexpectedInvoice), ); @@ -2375,7 +2409,8 @@ mod tests { assert_eq!( outbound_payments.send_payment_for_bolt12_invoice( &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, - &&keys_manager, 0, &&logger, &pending_events, |_| Ok(()) + &&keys_manager, &EmptyNodeIdLookUp {}, &secp_ctx, 0, &&logger, &pending_events, + |_| Ok(()) ), Ok(()), ); @@ -2385,7 +2420,8 @@ mod tests { assert_eq!( outbound_payments.send_payment_for_bolt12_invoice( &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, - &&keys_manager, 0, &&logger, &pending_events, |_| panic!() + &&keys_manager, &EmptyNodeIdLookUp {}, &secp_ctx, 0, &&logger, &pending_events, + |_| panic!() ), Err(Bolt12PaymentError::DuplicateInvoice), ); diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index 08be1b2c502..ed601c047c2 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -492,8 +492,9 @@ fn async_response_with_reply_path_fails() { let path_id = Some([2; 32]); let reply_path = BlindedPath::new_for_message(&[], bob.node_id, &*bob.entropy_source, &secp_ctx).unwrap(); - // Alice tries to asynchronously respond to Bob, but fails because the nodes are unannounced. - // Therefore, the reply_path cannot be used for the response. + // Alice tries to asynchronously respond to Bob, but fails because the nodes are unannounced and + // disconnected. Thus, a reply path could no be created for the response. + disconnect_peers(alice, bob); let responder = Responder::new(reply_path, path_id); alice.custom_message_handler.expect_message_and_response(message.clone()); let response_instruction = alice.custom_message_handler.handle_custom_message(message, Some(responder)); diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 0fb72c52d27..859c3f2b5b9 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -489,7 +489,7 @@ where } fn create_blinded_paths_from_iter< - I: Iterator, + I: ExactSizeIterator, T: secp256k1::Signing + secp256k1::Verification >( &self, recipient: PublicKey, peers: I, secp_ctx: &Secp256k1, compact_paths: bool @@ -505,13 +505,20 @@ where let is_recipient_announced = network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)); + let has_one_peer = peers.len() == 1; let mut peer_info = peers - // Limit to peers with announced channels + // Limit to peers with announced channels unless the recipient is unannounced. .filter_map(|peer| network_graph .node(&NodeId::from_pubkey(&peer.node_id)) - .filter(|info| info.channels.len() >= MIN_PEER_CHANNELS) + .filter(|info| + !is_recipient_announced || info.channels.len() >= MIN_PEER_CHANNELS + ) .map(|info| (peer, info.is_tor_only(), info.channels.len())) + // Allow messages directly with the only peer when unannounced. + .or_else(|| (!is_recipient_announced && has_one_peer) + .then(|| (peer, false, 0)) + ) ) // Exclude Tor-only nodes when the recipient is announced. .filter(|(_, is_tor_only, _)| !(*is_tor_only && is_recipient_announced)) diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 9a88d913896..28aaba0513f 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -100,16 +100,31 @@ impl> + Clone, L: Deref, ES: Deref, S: Deref, // recipient's node_id. const MIN_PEER_CHANNELS: usize = 3; + let has_one_peer = first_hops + .first() + .map(|details| details.counterparty.node_id) + .map(|node_id| first_hops + .iter() + .skip(1) + .all(|details| details.counterparty.node_id == node_id) + ) + .unwrap_or(false); + let network_graph = self.network_graph.deref().read_only(); + let is_recipient_announced = + network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)); + let paths = first_hops.into_iter() .filter(|details| details.counterparty.features.supports_route_blinding()) .filter(|details| amount_msats <= details.inbound_capacity_msat) .filter(|details| amount_msats >= details.inbound_htlc_minimum_msat.unwrap_or(0)) .filter(|details| amount_msats <= details.inbound_htlc_maximum_msat.unwrap_or(u64::MAX)) + // Limit to peers with announced channels unless the recipient is unannounced. .filter(|details| network_graph .node(&NodeId::from_pubkey(&details.counterparty.node_id)) - .map(|node_info| node_info.channels.len() >= MIN_PEER_CHANNELS) - .unwrap_or(false) + .map(|node| !is_recipient_announced || node.channels.len() >= MIN_PEER_CHANNELS) + // Allow payments directly with the only peer when unannounced. + .unwrap_or(!is_recipient_announced && has_one_peer) ) .filter_map(|details| { let short_channel_id = match details.get_inbound_payment_scid() { @@ -1031,6 +1046,13 @@ impl Payee { } } + pub(crate) fn blinded_route_hints_mut(&mut self) -> &mut [(BlindedPayInfo, BlindedPath)] { + match self { + Self::Blinded { route_hints, .. } => &mut route_hints[..], + Self::Clear { .. } => &mut [] + } + } + fn unblinded_route_hints(&self) -> &[RouteHint] { match self { Self::Blinded { .. } => &[],