diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index f654908771f..7a32434b86f 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -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, _tlvs: ReceiveTlvs, + _amount_msats: u64, _entropy_source: &ES, _secp_ctx: &Secp256k1 + ) -> Result, ()> { + unreachable!() + } +} + +impl MessageRouter for FuzzRouter { + fn find_path( + &self, _sender: PublicKey, _peers: Vec, _destination: Destination + ) -> Result { + unreachable!() + } + + fn create_blinded_paths< + ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification + >( + &self, _recipient: PublicKey, _peers: Vec, _entropy_source: &ES, + _secp_ctx: &Secp256k1 + ) -> Result, ()> { + unreachable!() + } } pub struct TestBroadcaster {} diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 725f83af984..a2ce98cf4d2 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -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, _tlvs: ReceiveTlvs, + _amount_msats: u64, _entropy_source: &ES, _secp_ctx: &Secp256k1 + ) -> Result, ()> { + unreachable!() + } +} + +impl MessageRouter for FuzzRouter { + fn find_path( + &self, _sender: PublicKey, _peers: Vec, _destination: Destination + ) -> Result { + unreachable!() + } + + fn create_blinded_paths< + ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification + >( + &self, _recipient: PublicKey, _peers: Vec, _entropy_source: &ES, + _secp_ctx: &Secp256k1 + ) -> Result, ()> { + unreachable!() + } } struct TestBroadcaster { diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index 1051083d36b..d2d60cfcf64 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -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, _entropy_source: &ES, + _secp_ctx: &Secp256k1 + ) -> Result, ()> { + unreachable!() + } } struct TestOffersMessageHandler {} diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 7b604fbdcb1..f4df1e379d9 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -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 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(&self, w: &mut W) -> Result<(), io::Error> { encode_tlv_stream!(w, { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 9536a9366e1..560a7e58eda 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -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 { + ) -> Result, 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 @@ -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 { + 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::>(); + + 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, ()> { 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::::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(()) => { + 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(()) => { - Some(OffersMessage::InvoiceError(Bolt12SemanticError::InvalidAmount.into())) + 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) => { diff --git a/lightning/src/ln/features.rs b/lightning/src/ln/features.rs index d10c3a71927..df5c0abf25f 100644 --- a/lightning/src/ln/features.rs +++ b/lightning/src/ln/features.rs @@ -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); diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index 9323be5fff2..b0031a6c3f4 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -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, _entropy_source: &ES, + _secp_ctx: &Secp256k1 + ) -> Result, ()> { + unreachable!() + } } struct TestOffersMessageHandler {} diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 8a44eb2a5be..7743270e257 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -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( +/// # &self, _recipient: PublicKey, _peers: Vec, _entropy_source: &ES, _secp_ctx: &Secp256k1 +/// # ) -> Result, ()> { +/// # 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, destination: Destination ) -> Result; + + /// 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, entropy_source: &ES, + secp_ctx: &Secp256k1 + ) -> Result, ()>; } /// 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, entropy_source: &ES, + secp_ctx: &Secp256k1 + ) -> Result, ()> { + // 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::, _>>(); + + 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`]. diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 7bd1cebc532..86c42841027 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -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>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> where +pub struct DefaultRouter> + Clone, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> where L::Target: Logger, S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>, { @@ -41,21 +44,23 @@ pub struct DefaultRouter>, L: Deref, S: Deref, logger: L, random_seed_bytes: Mutex<[u8; 32]>, scorer: S, - score_params: SP + score_params: SP, + message_router: DefaultMessageRouter, } -impl>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> DefaultRouter where +impl> + Clone, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> DefaultRouter 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>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> Router for DefaultRouter where +impl> + Clone, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> Router for DefaultRouter where L::Target: Logger, S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>, { @@ -78,10 +83,109 @@ impl< G: Deref>, 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, tlvs: ReceiveTlvs, + amount_msats: u64, entropy_source: &ES, secp_ctx: &Secp256k1 + ) -> Result, ()> { + // 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::, _>>(); + + 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> + Clone, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp> MessageRouter for DefaultRouter where + L::Target: Logger, + S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>, +{ + fn find_path( + &self, sender: PublicKey, peers: Vec, destination: Destination + ) -> Result { + 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, entropy_source: &ES, + secp_ctx: &Secp256k1 + ) -> Result, ()> { + 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 { 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, tlvs: ReceiveTlvs, + amount_msats: u64, entropy_source: &ES, secp_ctx: &Secp256k1 + ) -> Result, ()>; } /// [`ScoreLookUp`] implementation that factors in in-flight HTLC liquidity. diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index 12ffdecec42..6ace8681ac0 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -3442,7 +3442,7 @@ mod tests { Some(([0; 32], [0; 32]))); assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms), 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 }, diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 805806dc346..ba56edf3584 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -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 { 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, _tlvs: ReceiveTlvs, + _amount_msats: u64, _entropy_source: &ES, _secp_ctx: &Secp256k1 + ) -> Result, ()> { + unreachable!() + } +} + +impl<'a> MessageRouter for TestRouter<'a> { + fn find_path( + &self, _sender: PublicKey, _peers: Vec, _destination: Destination + ) -> Result { + unreachable!() + } + + fn create_blinded_paths< + ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification + >( + &self, _recipient: PublicKey, _peers: Vec, _entropy_source: &ES, + _secp_ctx: &Secp256k1 + ) -> Result, ()> { + unreachable!() + } } impl<'a> Drop for TestRouter<'a> {