Merge pull request #2781 from jkczyz/2023-09-multihop-paths

Multi-hop `BlindedPath` creation interface
This commit is contained in:
Matt Corallo 2023-12-15 22:30:43 +00:00 committed by GitHub
commit b9797ebdd9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 459 additions and 126 deletions

View file

@ -30,6 +30,8 @@ use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hash_types::{BlockHash, WPubkeyHash};
use lightning::blinded_path::BlindedPath;
use lightning::blinded_path::payment::ReceiveTlvs;
use lightning::chain;
use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, chainmonitor, channelmonitor, Confirm, Watch};
use lightning::chain::channelmonitor::{ChannelMonitor, MonitorEvent};
@ -44,8 +46,9 @@ use lightning::ln::channel::FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
use lightning::ln::msgs::{self, CommitmentUpdate, ChannelMessageHandler, DecodeError, UpdateAddHTLC, Init};
use lightning::ln::script::ShutdownScript;
use lightning::ln::functional_test_utils::*;
use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::onion_message::{Destination, MessageRouter, OnionMessagePath};
use lightning::util::test_channel_signer::{TestChannelSigner, EnforcementState};
use lightning::util::errors::APIError;
use lightning::util::logger::Logger;
@ -56,7 +59,7 @@ use lightning::routing::router::{InFlightHtlcs, Path, Route, RouteHop, RoutePara
use crate::utils::test_logger::{self, Output};
use crate::utils::test_persister::TestPersister;
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1};
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1, self};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
use bitcoin::secp256k1::schnorr;
@ -99,6 +102,32 @@ impl Router for FuzzRouter {
action: msgs::ErrorAction::IgnoreError
})
}
fn create_blinded_payment_paths<
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
>(
&self, _recipient: PublicKey, _first_hops: Vec<ChannelDetails>, _tlvs: ReceiveTlvs,
_amount_msats: u64, _entropy_source: &ES, _secp_ctx: &Secp256k1<T>
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
unreachable!()
}
}
impl MessageRouter for FuzzRouter {
fn find_path(
&self, _sender: PublicKey, _peers: Vec<PublicKey>, _destination: Destination
) -> Result<OnionMessagePath, ()> {
unreachable!()
}
fn create_blinded_paths<
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
>(
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _entropy_source: &ES,
_secp_ctx: &Secp256k1<T>
) -> Result<Vec<BlindedPath>, ()> {
unreachable!()
}
}
pub struct TestBroadcaster {}

View file

@ -28,6 +28,8 @@ use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hash_types::{Txid, BlockHash, WPubkeyHash};
use lightning::blinded_path::BlindedPath;
use lightning::blinded_path::payment::ReceiveTlvs;
use lightning::chain;
use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen};
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
@ -41,8 +43,9 @@ use lightning::ln::peer_handler::{MessageHandler,PeerManager,SocketDescriptor,Ig
use lightning::ln::msgs::{self, DecodeError};
use lightning::ln::script::ShutdownScript;
use lightning::ln::functional_test_utils::*;
use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::onion_message::{Destination, MessageRouter, OnionMessagePath};
use lightning::routing::gossip::{P2PGossipSync, NetworkGraph};
use lightning::routing::utxo::UtxoLookup;
use lightning::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteParameters, Router};
@ -55,7 +58,7 @@ use lightning::util::ser::{ReadableArgs, Writeable};
use crate::utils::test_logger;
use crate::utils::test_persister::TestPersister;
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1};
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1, self};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
use bitcoin::secp256k1::schnorr;
@ -142,6 +145,32 @@ impl Router for FuzzRouter {
action: msgs::ErrorAction::IgnoreError
})
}
fn create_blinded_payment_paths<
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
>(
&self, _recipient: PublicKey, _first_hops: Vec<ChannelDetails>, _tlvs: ReceiveTlvs,
_amount_msats: u64, _entropy_source: &ES, _secp_ctx: &Secp256k1<T>
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
unreachable!()
}
}
impl MessageRouter for FuzzRouter {
fn find_path(
&self, _sender: PublicKey, _peers: Vec<PublicKey>, _destination: Destination
) -> Result<OnionMessagePath, ()> {
unreachable!()
}
fn create_blinded_paths<
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
>(
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _entropy_source: &ES,
_secp_ctx: &Secp256k1<T>
) -> Result<Vec<BlindedPath>, ()> {
unreachable!()
}
}
struct TestBroadcaster {

View file

@ -1,17 +1,18 @@
// Imports that need to be added manually
use bitcoin::bech32::u5;
use bitcoin::blockdata::script::ScriptBuf;
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, self};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
use bitcoin::secp256k1::schnorr;
use lightning::sign::{Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider};
use lightning::blinded_path::BlindedPath;
use lightning::ln::features::InitFeatures;
use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler};
use lightning::ln::script::ShutdownScript;
use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
use lightning::sign::{Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider};
use lightning::util::test_channel_signer::TestChannelSigner;
use lightning::util::logger::Logger;
use lightning::util::ser::{Readable, Writeable, Writer};
@ -82,6 +83,15 @@ impl MessageRouter for TestMessageRouter {
first_node_addresses: None,
})
}
fn create_blinded_paths<
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
>(
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _entropy_source: &ES,
_secp_ctx: &Secp256k1<T>
) -> Result<Vec<BlindedPath>, ()> {
unreachable!()
}
}
struct TestOffersMessageHandler {}

View file

@ -8,6 +8,7 @@ use crate::blinded_path::BlindedHop;
use crate::blinded_path::utils;
use crate::io;
use crate::ln::PaymentSecret;
use crate::ln::channelmanager::CounterpartyForwardingInfo;
use crate::ln::features::BlindedHopFeatures;
use crate::ln::msgs::DecodeError;
use crate::offers::invoice::BlindedPayInfo;
@ -96,6 +97,15 @@ pub struct PaymentConstraints {
pub htlc_minimum_msat: u64,
}
impl From<CounterpartyForwardingInfo> for PaymentRelay {
fn from(info: CounterpartyForwardingInfo) -> Self {
let CounterpartyForwardingInfo {
fee_base_msat, fee_proportional_millionths, cltv_expiry_delta
} = info;
Self { cltv_expiry_delta, fee_proportional_millionths, fee_base_msat }
}
}
impl Writeable for ForwardTlvs {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
encode_tlv_stream!(w, {

View file

@ -65,7 +65,7 @@ use crate::offers::merkle::SignError;
use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder};
use crate::offers::parse::Bolt12SemanticError;
use crate::offers::refund::{Refund, RefundBuilder};
use crate::onion_message::{Destination, OffersMessage, OffersMessageHandler, PendingOnionMessage, new_pending_onion_message};
use crate::onion_message::{Destination, MessageRouter, OffersMessage, OffersMessageHandler, PendingOnionMessage, new_pending_onion_message};
use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider};
use crate::sign::ecdsa::WriteableEcdsaChannelSigner;
use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate};
@ -7483,32 +7483,43 @@ where
///
/// # Privacy
///
/// Uses a one-hop [`BlindedPath`] for the offer with [`ChannelManager::get_our_node_id`] as the
/// introduction node and a derived signing pubkey for recipient privacy. As such, currently,
/// the node must be announced. Otherwise, there is no way to find a path to the introduction
/// node in order to send the [`InvoiceRequest`].
/// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the offer.
/// However, if one is not found, uses a one-hop [`BlindedPath`] with
/// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
/// the node must be announced, otherwise, there is no way to find a path to the introduction in
/// order to send the [`InvoiceRequest`].
///
/// Also, uses a derived signing pubkey in the offer for recipient privacy.
///
/// # Limitations
///
/// Requires a direct connection to the introduction node in the responding [`InvoiceRequest`]'s
/// reply path.
///
/// # Errors
///
/// Errors if the parameterized [`Router`] is unable to create a blinded path for the offer.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
/// [`Offer`]: crate::offers::offer::Offer
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
pub fn create_offer_builder(
&self, description: String
) -> OfferBuilder<DerivedMetadata, secp256k1::All> {
) -> Result<OfferBuilder<DerivedMetadata, secp256k1::All>, Bolt12SemanticError> {
let node_id = self.get_our_node_id();
let expanded_key = &self.inbound_payment_key;
let entropy = &*self.entropy_source;
let secp_ctx = &self.secp_ctx;
let path = self.create_one_hop_blinded_path();
OfferBuilder::deriving_signing_pubkey(description, node_id, expanded_key, entropy, secp_ctx)
let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
let builder = OfferBuilder::deriving_signing_pubkey(
description, node_id, expanded_key, entropy, secp_ctx
)
.chain_hash(self.chain_hash)
.path(path)
.path(path);
Ok(builder)
}
/// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the
@ -7533,10 +7544,13 @@ where
///
/// # Privacy
///
/// Uses a one-hop [`BlindedPath`] for the refund with [`ChannelManager::get_our_node_id`] as
/// the introduction node and a derived payer id for payer privacy. As such, currently, the
/// node must be announced. Otherwise, there is no way to find a path to the introduction node
/// in order to send the [`Bolt12Invoice`].
/// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the refund.
/// However, if one is not found, uses a one-hop [`BlindedPath`] with
/// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
/// the node must be announced, otherwise, there is no way to find a path to the introduction in
/// order to send the [`Bolt12Invoice`].
///
/// Also, uses a derived payer id in the refund for payer privacy.
///
/// # Limitations
///
@ -7545,14 +7559,17 @@ where
///
/// # Errors
///
/// Errors if a duplicate `payment_id` is provided given the caveats in the aforementioned link
/// or if `amount_msats` is invalid.
/// Errors if:
/// - a duplicate `payment_id` is provided given the caveats in the aforementioned link,
/// - `amount_msats` is invalid, or
/// - the parameterized [`Router`] is unable to create a blinded path for the refund.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
/// [`Refund`]: crate::offers::refund::Refund
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
/// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths
/// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments
pub fn create_refund_builder(
&self, description: String, amount_msats: u64, absolute_expiry: Duration,
payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>
@ -7561,8 +7578,8 @@ where
let expanded_key = &self.inbound_payment_key;
let entropy = &*self.entropy_source;
let secp_ctx = &self.secp_ctx;
let path = self.create_one_hop_blinded_path();
let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
let builder = RefundBuilder::deriving_payer_id(
description, node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
)?
@ -7620,8 +7637,11 @@ where
///
/// # Errors
///
/// Errors if a duplicate `payment_id` is provided given the caveats in the aforementioned link
/// or if the provided parameters are invalid for the offer.
/// Errors if:
/// - a duplicate `payment_id` is provided given the caveats in the aforementioned link,
/// - the provided parameters are invalid for the offer,
/// - the parameterized [`Router`] is unable to create a blinded reply path for the invoice
/// request.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity
@ -7654,9 +7674,8 @@ where
None => builder,
Some(payer_note) => builder.payer_note(payer_note),
};
let invoice_request = builder.build_and_sign()?;
let reply_path = self.create_one_hop_blinded_path();
let reply_path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
let expiration = StaleExpiration::TimerTicks(1);
self.pending_outbound_payments
@ -7705,6 +7724,11 @@ where
/// node meeting the aforementioned criteria, but there's no guarantee that they will be
/// received and no retries will be made.
///
/// # Errors
///
/// Errors if the parameterized [`Router`] is unable to create a blinded payment path or reply
/// path for the invoice.
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn request_refund_payment(&self, refund: &Refund) -> Result<(), Bolt12SemanticError> {
let expanded_key = &self.inbound_payment_key;
@ -7716,9 +7740,9 @@ where
match self.create_inbound_payment(Some(amount_msats), relative_expiry, None) {
Ok((payment_hash, payment_secret)) => {
let payment_paths = vec![
self.create_one_hop_blinded_payment_path(payment_secret),
];
let payment_paths = self.create_blinded_payment_paths(amount_msats, payment_secret)
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
#[cfg(not(feature = "no-std"))]
let builder = refund.respond_using_derived_keys(
payment_paths, payment_hash, expanded_key, entropy
@ -7732,7 +7756,8 @@ where
payment_paths, payment_hash, created_at, expanded_key, entropy
)?;
let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?;
let reply_path = self.create_one_hop_blinded_path();
let reply_path = self.create_blinded_path()
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
if refund.paths().is_empty() {
@ -7859,24 +7884,37 @@ where
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
}
/// Creates a one-hop blinded path with [`ChannelManager::get_our_node_id`] as the introduction
/// node.
fn create_one_hop_blinded_path(&self) -> BlindedPath {
/// Creates a blinded path by delegating to [`MessageRouter::create_blinded_paths`].
///
/// Errors if the `MessageRouter` errors or returns an empty `Vec`.
fn create_blinded_path(&self) -> Result<BlindedPath, ()> {
let recipient = self.get_our_node_id();
let entropy_source = self.entropy_source.deref();
let secp_ctx = &self.secp_ctx;
BlindedPath::one_hop_for_message(self.get_our_node_id(), entropy_source, secp_ctx).unwrap()
let peers = self.per_peer_state.read().unwrap()
.iter()
.filter(|(_, peer)| peer.lock().unwrap().latest_features.supports_onion_messages())
.map(|(node_id, _)| *node_id)
.collect::<Vec<_>>();
self.router
.create_blinded_paths(recipient, peers, entropy_source, secp_ctx)
.and_then(|paths| paths.into_iter().next().ok_or(()))
}
/// Creates a one-hop blinded path with [`ChannelManager::get_our_node_id`] as the introduction
/// node.
fn create_one_hop_blinded_payment_path(
&self, payment_secret: PaymentSecret
) -> (BlindedPayInfo, BlindedPath) {
/// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to
/// [`Router::create_blinded_payment_paths`].
fn create_blinded_payment_paths(
&self, amount_msats: u64, payment_secret: PaymentSecret
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
let entropy_source = self.entropy_source.deref();
let secp_ctx = &self.secp_ctx;
let first_hops = self.list_usable_channels();
let payee_node_id = self.get_our_node_id();
let max_cltv_expiry = self.best_block.read().unwrap().height() + LATENCY_GRACE_PERIOD_BLOCKS;
let max_cltv_expiry = self.best_block.read().unwrap().height() + CLTV_FAR_FAR_AWAY
+ LATENCY_GRACE_PERIOD_BLOCKS;
let payee_tlvs = ReceiveTlvs {
payment_secret,
payment_constraints: PaymentConstraints {
@ -7884,10 +7922,9 @@ where
htlc_minimum_msat: 1,
},
};
// TODO: Err for overflow?
BlindedPath::one_hop_for_payment(
payee_node_id, payee_tlvs, entropy_source, secp_ctx
).unwrap()
self.router.create_blinded_payment_paths(
payee_node_id, first_hops, payee_tlvs, amount_msats, entropy_source, secp_ctx
)
}
/// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids
@ -9127,7 +9164,7 @@ where
let amount_msats = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
&invoice_request
) {
Ok(amount_msats) => Some(amount_msats),
Ok(amount_msats) => amount_msats,
Err(error) => return Some(OffersMessage::InvoiceError(error.into())),
};
let invoice_request = match invoice_request.verify(expanded_key, secp_ctx) {
@ -9137,64 +9174,69 @@ where
return Some(OffersMessage::InvoiceError(error.into()));
},
};
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
match self.create_inbound_payment(amount_msats, relative_expiry, None) {
Ok((payment_hash, payment_secret)) if invoice_request.keys.is_some() => {
let payment_paths = vec![
self.create_one_hop_blinded_payment_path(payment_secret),
];
#[cfg(not(feature = "no-std"))]
let builder = invoice_request.respond_using_derived_keys(
payment_paths, payment_hash
);
#[cfg(feature = "no-std")]
let created_at = Duration::from_secs(
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
);
#[cfg(feature = "no-std")]
let builder = invoice_request.respond_using_derived_keys_no_std(
payment_paths, payment_hash, created_at
);
match builder.and_then(|b| b.allow_mpp().build_and_sign(secp_ctx)) {
Ok(invoice) => Some(OffersMessage::Invoice(invoice)),
Err(error) => Some(OffersMessage::InvoiceError(error.into())),
}
},
Ok((payment_hash, payment_secret)) => {
let payment_paths = vec![
self.create_one_hop_blinded_payment_path(payment_secret),
];
#[cfg(not(feature = "no-std"))]
let builder = invoice_request.respond_with(payment_paths, payment_hash);
#[cfg(feature = "no-std")]
let created_at = Duration::from_secs(
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
);
#[cfg(feature = "no-std")]
let builder = invoice_request.respond_with_no_std(
payment_paths, payment_hash, created_at
);
let response = builder.and_then(|builder| builder.allow_mpp().build())
.map_err(|e| OffersMessage::InvoiceError(e.into()))
.and_then(|invoice|
match invoice.sign(|invoice| self.node_signer.sign_bolt12_invoice(invoice)) {
Ok(invoice) => Ok(OffersMessage::Invoice(invoice)),
Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError(
InvoiceError::from_string("Failed signing invoice".to_string())
)),
Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError(
InvoiceError::from_string("Failed invoice signature verification".to_string())
)),
});
match response {
Ok(invoice) => Some(invoice),
Err(error) => Some(error),
}
},
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
let (payment_hash, payment_secret) = match self.create_inbound_payment(
Some(amount_msats), relative_expiry, None
) {
Ok((payment_hash, payment_secret)) => (payment_hash, payment_secret),
Err(()) => {
Some(OffersMessage::InvoiceError(Bolt12SemanticError::InvalidAmount.into()))
let error = Bolt12SemanticError::InvalidAmount;
return Some(OffersMessage::InvoiceError(error.into()));
},
};
let payment_paths = match self.create_blinded_payment_paths(
amount_msats, payment_secret
) {
Ok(payment_paths) => payment_paths,
Err(()) => {
let error = Bolt12SemanticError::MissingPaths;
return Some(OffersMessage::InvoiceError(error.into()));
},
};
#[cfg(feature = "no-std")]
let created_at = Duration::from_secs(
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
);
if invoice_request.keys.is_some() {
#[cfg(not(feature = "no-std"))]
let builder = invoice_request.respond_using_derived_keys(
payment_paths, payment_hash
);
#[cfg(feature = "no-std")]
let builder = invoice_request.respond_using_derived_keys_no_std(
payment_paths, payment_hash, created_at
);
match builder.and_then(|b| b.allow_mpp().build_and_sign(secp_ctx)) {
Ok(invoice) => Some(OffersMessage::Invoice(invoice)),
Err(error) => Some(OffersMessage::InvoiceError(error.into())),
}
} else {
#[cfg(not(feature = "no-std"))]
let builder = invoice_request.respond_with(payment_paths, payment_hash);
#[cfg(feature = "no-std")]
let builder = invoice_request.respond_with_no_std(
payment_paths, payment_hash, created_at
);
let response = builder.and_then(|builder| builder.allow_mpp().build())
.map_err(|e| OffersMessage::InvoiceError(e.into()))
.and_then(|invoice|
match invoice.sign(|invoice| self.node_signer.sign_bolt12_invoice(invoice)) {
Ok(invoice) => Ok(OffersMessage::Invoice(invoice)),
Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError(
InvoiceError::from_string("Failed signing invoice".to_string())
)),
Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError(
InvoiceError::from_string("Failed invoice signature verification".to_string())
)),
});
match response {
Ok(invoice) => Some(invoice),
Err(error) => Some(error),
}
}
},
OffersMessage::Invoice(invoice) => {

View file

@ -41,6 +41,12 @@
//! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md#basic-multi-part-payments) for more information).
//! - `Wumbo` - requires/supports that a node create large channels. Called `option_support_large_channel` in the spec.
//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel-message) for more information).
//! - `AnchorsZeroFeeHtlcTx` - requires/supports that commitment transactions include anchor outputs
//! and HTLC transactions are pre-signed with zero fee (see
//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more
//! information).
//! - `RouteBlinding` - requires/supports that a node can relay payments over blinded paths
//! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md#route-blinding) for more information).
//! - `ShutdownAnySegwit` - requires/supports that future segwit versions are allowed in `shutdown`
//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information).
//! - `OnionMessages` - requires/supports forwarding onion messages
@ -60,10 +66,6 @@
//! for more info).
//! - `Keysend` - send funds to a node without an invoice
//! (see the [`Keysend` feature assignment proposal](https://github.com/lightning/bolts/issues/605#issuecomment-606679798) for more information).
//! - `AnchorsZeroFeeHtlcTx` - requires/supports that commitment transactions include anchor outputs
//! and HTLC transactions are pre-signed with zero fee (see
//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more
//! information).
//!
//! LDK knows about the following features, but does not support them:
//! - `AnchorsNonzeroFeeHtlcTx` - the initial version of anchor outputs, which was later found to be
@ -143,7 +145,7 @@ mod sealed {
// Byte 2
BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx,
// Byte 3
ShutdownAnySegwit | Taproot,
RouteBlinding | ShutdownAnySegwit | Taproot,
// Byte 4
OnionMessages,
// Byte 5
@ -159,7 +161,7 @@ mod sealed {
// Byte 2
BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx,
// Byte 3
ShutdownAnySegwit | Taproot,
RouteBlinding | ShutdownAnySegwit | Taproot,
// Byte 4
OnionMessages,
// Byte 5
@ -391,6 +393,9 @@ mod sealed {
define_feature!(23, AnchorsZeroFeeHtlcTx, [InitContext, NodeContext, ChannelTypeContext],
"Feature flags for `option_anchors_zero_fee_htlc_tx`.", set_anchors_zero_fee_htlc_tx_optional,
set_anchors_zero_fee_htlc_tx_required, supports_anchors_zero_fee_htlc_tx, requires_anchors_zero_fee_htlc_tx);
define_feature!(25, RouteBlinding, [InitContext, NodeContext],
"Feature flags for `option_route_blinding`.", set_route_blinding_optional,
set_route_blinding_required, supports_route_blinding, requires_route_blinding);
define_feature!(27, ShutdownAnySegwit, [InitContext, NodeContext],
"Feature flags for `opt_shutdown_anysegwit`.", set_shutdown_any_segwit_optional,
set_shutdown_any_segwit_required, supports_shutdown_anysegwit, requires_shutdown_anysegwit);
@ -1053,6 +1058,7 @@ mod tests {
init_features.set_basic_mpp_optional();
init_features.set_wumbo_optional();
init_features.set_anchors_zero_fee_htlc_tx_optional();
init_features.set_route_blinding_optional();
init_features.set_shutdown_any_segwit_optional();
init_features.set_onion_messages_optional();
init_features.set_channel_type_optional();
@ -1068,8 +1074,8 @@ mod tests {
// Check that the flags are as expected:
// - option_data_loss_protect (req)
// - var_onion_optin (req) | static_remote_key (req) | payment_secret(req)
// - basic_mpp | wumbo | anchors_zero_fee_htlc_tx
// - opt_shutdown_anysegwit
// - basic_mpp | wumbo | option_anchors_zero_fee_htlc_tx
// - option_route_blinding | opt_shutdown_anysegwit
// - onion_messages
// - option_channel_type | option_scid_alias
// - option_zeroconf
@ -1077,7 +1083,7 @@ mod tests {
assert_eq!(node_features.flags[0], 0b00000001);
assert_eq!(node_features.flags[1], 0b01010001);
assert_eq!(node_features.flags[2], 0b10001010);
assert_eq!(node_features.flags[3], 0b00001000);
assert_eq!(node_features.flags[3], 0b00001010);
assert_eq!(node_features.flags[4], 0b10000000);
assert_eq!(node_features.flags[5], 0b10100000);
assert_eq!(node_features.flags[6], 0b00001000);

View file

@ -13,14 +13,14 @@ use crate::blinded_path::BlindedPath;
use crate::events::{Event, EventsProvider};
use crate::ln::features::InitFeatures;
use crate::ln::msgs::{self, DecodeError, OnionMessageHandler, SocketAddress};
use crate::sign::{NodeSigner, Recipient};
use crate::sign::{EntropySource, NodeSigner, Recipient};
use crate::util::ser::{FixedLengthReader, LengthReadable, Writeable, Writer};
use crate::util::test_utils;
use super::{CustomOnionMessageHandler, Destination, MessageRouter, OffersMessage, OffersMessageHandler, OnionMessageContents, OnionMessagePath, OnionMessenger, PendingOnionMessage, SendError};
use bitcoin::network::constants::Network;
use bitcoin::hashes::hex::FromHex;
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey, self};
use crate::io;
use crate::io_extras::read_to_end;
@ -55,6 +55,15 @@ impl MessageRouter for TestMessageRouter {
Some(vec![SocketAddress::TcpIpV4 { addr: [127, 0, 0, 1], port: 1000 }]),
})
}
fn create_blinded_paths<
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
>(
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _entropy_source: &ES,
_secp_ctx: &Secp256k1<T>
) -> Result<Vec<BlindedPath>, ()> {
unreachable!()
}
}
struct TestOffersMessageHandler {}

View file

@ -64,9 +64,9 @@ pub(super) const MAX_TIMER_TICKS: usize = 2;
/// # extern crate bitcoin;
/// # use bitcoin::hashes::_export::_core::time::Duration;
/// # use bitcoin::hashes::hex::FromHex;
/// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
/// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey, self};
/// # use lightning::blinded_path::BlindedPath;
/// # use lightning::sign::KeysManager;
/// # use lightning::sign::{EntropySource, KeysManager};
/// # use lightning::ln::peer_handler::IgnoringMessageHandler;
/// # use lightning::onion_message::{OnionMessageContents, Destination, MessageRouter, OnionMessagePath, OnionMessenger};
/// # use lightning::util::logger::{Logger, Record};
@ -90,6 +90,11 @@ pub(super) const MAX_TIMER_TICKS: usize = 2;
/// # first_node_addresses: None,
/// # })
/// # }
/// # fn create_blinded_paths<ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification>(
/// # &self, _recipient: PublicKey, _peers: Vec<PublicKey>, _entropy_source: &ES, _secp_ctx: &Secp256k1<T>
/// # ) -> Result<Vec<BlindedPath>, ()> {
/// # unreachable!()
/// # }
/// # }
/// # let seed = [42u8; 32];
/// # let time = Duration::from_secs(123456);
@ -270,6 +275,15 @@ pub trait MessageRouter {
fn find_path(
&self, sender: PublicKey, peers: Vec<PublicKey>, destination: Destination
) -> Result<OnionMessagePath, ()>;
/// Creates [`BlindedPath`]s to the `recipient` node. The nodes in `peers` are assumed to be
/// direct peers with the `recipient`.
fn create_blinded_paths<
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
>(
&self, recipient: PublicKey, peers: Vec<PublicKey>, entropy_source: &ES,
secp_ctx: &Secp256k1<T>
) -> Result<Vec<BlindedPath>, ()>;
}
/// A [`MessageRouter`] that can only route to a directly connected [`Destination`].
@ -321,6 +335,47 @@ where
}
}
}
fn create_blinded_paths<
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
>(
&self, recipient: PublicKey, peers: Vec<PublicKey>, entropy_source: &ES,
secp_ctx: &Secp256k1<T>
) -> Result<Vec<BlindedPath>, ()> {
// Limit the number of blinded paths that are computed.
const MAX_PATHS: usize = 3;
// Ensure peers have at least three channels so that it is more difficult to infer the
// recipient's node_id.
const MIN_PEER_CHANNELS: usize = 3;
let network_graph = self.network_graph.deref().read_only();
let paths = peers.iter()
// Limit to peers with announced channels
.filter(|pubkey|
network_graph
.node(&NodeId::from_pubkey(pubkey))
.map(|info| &info.channels[..])
.map(|channels| channels.len() >= MIN_PEER_CHANNELS)
.unwrap_or(false)
)
.map(|pubkey| vec![*pubkey, recipient])
.map(|node_pks| BlindedPath::new_for_message(&node_pks, entropy_source, secp_ctx))
.take(MAX_PATHS)
.collect::<Result<Vec<_>, _>>();
match paths {
Ok(paths) if !paths.is_empty() => Ok(paths),
_ => {
if network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)) {
BlindedPath::one_hop_for_message(recipient, entropy_source, secp_ctx)
.map(|path| vec![path])
} else {
Err(())
}
},
}
}
}
/// A path for sending an [`OnionMessage`].

View file

@ -9,18 +9,21 @@
//! The router finds paths within a [`NetworkGraph`] for a payment.
use bitcoin::secp256k1::PublicKey;
use bitcoin::secp256k1::{PublicKey, Secp256k1, self};
use bitcoin::hashes::Hash;
use bitcoin::hashes::sha256::Hash as Sha256;
use crate::blinded_path::{BlindedHop, BlindedPath};
use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs};
use crate::ln::PaymentHash;
use crate::ln::channelmanager::{ChannelDetails, PaymentId};
use crate::ln::features::{Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
use crate::ln::features::{BlindedHopFeatures, Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice};
use crate::onion_message::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath};
use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees};
use crate::routing::scoring::{ChannelUsage, LockableScore, ScoreLookUp};
use crate::sign::EntropySource;
use crate::util::ser::{Writeable, Readable, ReadableArgs, Writer};
use crate::util::logger::{Level, Logger};
use crate::util::chacha20::ChaCha20;
@ -33,7 +36,7 @@ use core::{cmp, fmt};
use core::ops::Deref;
/// A [`Router`] implemented using [`find_route`].
pub struct DefaultRouter<G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> where
pub struct DefaultRouter<G: Deref<Target = NetworkGraph<L>> + Clone, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> where
L::Target: Logger,
S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>,
{
@ -41,21 +44,23 @@ pub struct DefaultRouter<G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref,
logger: L,
random_seed_bytes: Mutex<[u8; 32]>,
scorer: S,
score_params: SP
score_params: SP,
message_router: DefaultMessageRouter<G, L>,
}
impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> DefaultRouter<G, L, S, SP, Sc> where
impl<G: Deref<Target = NetworkGraph<L>> + Clone, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> DefaultRouter<G, L, S, SP, Sc> where
L::Target: Logger,
S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>,
{
/// Creates a new router.
pub fn new(network_graph: G, logger: L, random_seed_bytes: [u8; 32], scorer: S, score_params: SP) -> Self {
let random_seed_bytes = Mutex::new(random_seed_bytes);
Self { network_graph, logger, random_seed_bytes, scorer, score_params }
let message_router = DefaultMessageRouter::new(network_graph.clone());
Self { network_graph, logger, random_seed_bytes, scorer, score_params, message_router }
}
}
impl< G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> Router for DefaultRouter<G, L, S, SP, Sc> where
impl<G: Deref<Target = NetworkGraph<L>> + Clone, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> Router for DefaultRouter<G, L, S, SP, Sc> where
L::Target: Logger,
S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>,
{
@ -78,10 +83,109 @@ impl< G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: Sco
&random_seed_bytes
)
}
fn create_blinded_payment_paths<
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
>(
&self, recipient: PublicKey, first_hops: Vec<ChannelDetails>, tlvs: ReceiveTlvs,
amount_msats: u64, entropy_source: &ES, secp_ctx: &Secp256k1<T>
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
// Limit the number of blinded paths that are computed.
const MAX_PAYMENT_PATHS: usize = 3;
// Ensure peers have at least three channels so that it is more difficult to infer the
// recipient's node_id.
const MIN_PEER_CHANNELS: usize = 3;
let network_graph = self.network_graph.deref().read_only();
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(0))
.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)
)
.filter_map(|details| {
let short_channel_id = match details.get_inbound_payment_scid() {
Some(short_channel_id) => short_channel_id,
None => return None,
};
let payment_relay: PaymentRelay = match details.counterparty.forwarding_info {
Some(forwarding_info) => forwarding_info.into(),
None => return None,
};
// Avoid exposing esoteric CLTV expiry deltas
let cltv_expiry_delta = match payment_relay.cltv_expiry_delta {
0..=40 => 40u32,
41..=80 => 80u32,
81..=144 => 144u32,
145..=216 => 216u32,
_ => return None,
};
let payment_constraints = PaymentConstraints {
max_cltv_expiry: tlvs.payment_constraints.max_cltv_expiry + cltv_expiry_delta,
htlc_minimum_msat: details.inbound_htlc_minimum_msat.unwrap_or(0),
};
Some(ForwardNode {
tlvs: ForwardTlvs {
short_channel_id,
payment_relay,
payment_constraints,
features: BlindedHopFeatures::empty(),
},
node_id: details.counterparty.node_id,
htlc_maximum_msat: details.inbound_htlc_maximum_msat.unwrap_or(0),
})
})
.map(|forward_node| {
BlindedPath::new_for_payment(
&[forward_node], recipient, tlvs.clone(), u64::MAX, entropy_source, secp_ctx
)
})
.take(MAX_PAYMENT_PATHS)
.collect::<Result<Vec<_>, _>>();
match paths {
Ok(paths) if !paths.is_empty() => Ok(paths),
_ => {
if network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)) {
BlindedPath::one_hop_for_payment(recipient, tlvs, entropy_source, secp_ctx)
.map(|path| vec![path])
} else {
Err(())
}
},
}
}
}
impl< G: Deref<Target = NetworkGraph<L>> + Clone, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> MessageRouter for DefaultRouter<G, L, S, SP, Sc> where
L::Target: Logger,
S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>,
{
fn find_path(
&self, sender: PublicKey, peers: Vec<PublicKey>, destination: Destination
) -> Result<OnionMessagePath, ()> {
self.message_router.find_path(sender, peers, destination)
}
fn create_blinded_paths<
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
>(
&self, recipient: PublicKey, peers: Vec<PublicKey>, entropy_source: &ES,
secp_ctx: &Secp256k1<T>
) -> Result<Vec<BlindedPath>, ()> {
self.message_router.create_blinded_paths(recipient, peers, entropy_source, secp_ctx)
}
}
/// A trait defining behavior for routing a payment.
pub trait Router {
pub trait Router: MessageRouter {
/// Finds a [`Route`] for a payment between the given `payer` and a payee.
///
/// The `payee` and the payment's value are given in [`RouteParameters::payment_params`]
@ -105,6 +209,16 @@ pub trait Router {
) -> Result<Route, LightningError> {
self.find_route(payer, route_params, first_hops, inflight_htlcs)
}
/// Creates [`BlindedPath`]s for payment to the `recipient` node. The channels in `first_hops`
/// are assumed to be with the `recipient`'s peers. The payment secret and any constraints are
/// given in `tlvs`.
fn create_blinded_payment_paths<
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
>(
&self, recipient: PublicKey, first_hops: Vec<ChannelDetails>, tlvs: ReceiveTlvs,
amount_msats: u64, entropy_source: &ES, secp_ctx: &Secp256k1<T>
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()>;
}
/// [`ScoreLookUp`] implementation that factors in in-flight HTLC liquidity.

View file

@ -3442,7 +3442,7 @@ mod tests {
Some(([0; 32], [0; 32])));
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1, &params), None);
let mut usage = ChannelUsage {
let usage = ChannelUsage {
amount_msat: 100,
inflight_htlc_msat: 1024,
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 },

View file

@ -7,6 +7,8 @@
// You may not use this file except in accordance with one or both of these
// licenses.
use crate::blinded_path::BlindedPath;
use crate::blinded_path::payment::ReceiveTlvs;
use crate::chain;
use crate::chain::WatchedOutput;
use crate::chain::chaininterface;
@ -22,14 +24,15 @@ use crate::sign;
use crate::events;
use crate::events::bump_transaction::{WalletSource, Utxo};
use crate::ln::ChannelId;
use crate::ln::channelmanager;
use crate::ln::channelmanager::{ChannelDetails, self};
use crate::ln::chan_utils::CommitmentTransaction;
use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
use crate::ln::{msgs, wire};
use crate::ln::msgs::LightningError;
use crate::ln::script::ShutdownScript;
use crate::offers::invoice::UnsignedBolt12Invoice;
use crate::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
use crate::offers::invoice_request::UnsignedInvoiceRequest;
use crate::onion_message::{Destination, MessageRouter, OnionMessagePath};
use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId, RoutingFees};
use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult};
use crate::routing::router::{find_route, InFlightHtlcs, Path, Route, RouteParameters, RouteHintHop, Router, ScorerAccountingForInFlightHtlcs};
@ -51,7 +54,7 @@ use bitcoin::network::constants::Network;
use bitcoin::hash_types::{BlockHash, Txid};
use bitcoin::sighash::{SighashCache, EcdsaSighashType};
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, self};
use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
use bitcoin::secp256k1::schnorr;
@ -119,7 +122,7 @@ impl<'a> TestRouter<'a> {
impl<'a> Router for TestRouter<'a> {
fn find_route(
&self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&channelmanager::ChannelDetails]>,
&self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&ChannelDetails]>,
inflight_htlcs: InFlightHtlcs
) -> Result<Route, msgs::LightningError> {
if let Some((find_route_query, find_route_res)) = self.next_routes.lock().unwrap().pop_front() {
@ -189,6 +192,32 @@ impl<'a> Router for TestRouter<'a> {
&[42; 32]
)
}
fn create_blinded_payment_paths<
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
>(
&self, _recipient: PublicKey, _first_hops: Vec<ChannelDetails>, _tlvs: ReceiveTlvs,
_amount_msats: u64, _entropy_source: &ES, _secp_ctx: &Secp256k1<T>
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
unreachable!()
}
}
impl<'a> MessageRouter for TestRouter<'a> {
fn find_path(
&self, _sender: PublicKey, _peers: Vec<PublicKey>, _destination: Destination
) -> Result<OnionMessagePath, ()> {
unreachable!()
}
fn create_blinded_paths<
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
>(
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _entropy_source: &ES,
_secp_ctx: &Secp256k1<T>
) -> Result<Vec<BlindedPath>, ()> {
unreachable!()
}
}
impl<'a> Drop for TestRouter<'a> {