mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 15:02:20 +01:00
Merge pull request #3132 from jkczyz/2024-06-bolt12-unannounced
Blinded paths with unannounced introduction nodes
This commit is contained in:
commit
f45a840c47
8 changed files with 222 additions and 41 deletions
|
@ -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<T: secp256k1::Signing + secp256k1::Verification>(
|
|||
|
||||
// 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<NS: Deref, NL: Deref, T>(
|
||||
path: &mut BlindedPath, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1<T>
|
||||
) -> 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(())
|
||||
|
|
|
@ -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<T: secp256k1::Signing + secp256k1::Verification>(
|
|||
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<NS: Deref, NL: Deref, T>(
|
||||
path: &mut BlindedPath, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1<T>
|
||||
) -> 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<u64> {
|
||||
let inbound_amt = inbound_amt_msat as u128;
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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<R: Deref, ES: Deref, NS: Deref, IH, SP, L: Deref>(
|
||||
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<ChannelDetails>, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
|
||||
best_block_height: u32, logger: &L,
|
||||
node_id_lookup: &NL, secp_ctx: &Secp256k1<secp256k1::All>, best_block_height: u32,
|
||||
logger: &L,
|
||||
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
|
||||
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),
|
||||
);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -489,7 +489,7 @@ where
|
|||
}
|
||||
|
||||
fn create_blinded_paths_from_iter<
|
||||
I: Iterator<Item = ForwardNode>,
|
||||
I: ExactSizeIterator<Item = ForwardNode>,
|
||||
T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, recipient: PublicKey, peers: I, secp_ctx: &Secp256k1<T>, 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))
|
||||
|
|
|
@ -100,16 +100,31 @@ impl<G: Deref<Target = NetworkGraph<L>> + 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 { .. } => &[],
|
||||
|
|
Loading…
Add table
Reference in a new issue