Add create_blinded_payment_paths to Router
authorJeffrey Czyz <jkczyz@gmail.com>
Fri, 8 Dec 2023 18:03:06 +0000 (12:03 -0600)
committerJeffrey Czyz <jkczyz@gmail.com>
Fri, 15 Dec 2023 21:40:09 +0000 (15:40 -0600)
The Router trait is used to find a Route for paying a node. Expand the
interface with a create_blinded_payment paths method for creating such
paths to a recipient node.

Provide an implementation for DefaultRouter that creates two-hop
blinded paths where the recipient's peers serve as the introduction
nodes.

fuzz/src/chanmon_consistency.rs
fuzz/src/full_stack.rs
lightning/src/routing/router.rs
lightning/src/util/test_utils.rs

index 2c4552d437496fd90258cfb6948bc69f0cc3f5c2..7a32434b86fff16dfcd58e76e9495095c6c4098f 100644 (file)
@@ -31,6 +31,7 @@ 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};
@@ -45,7 +46,7 @@ 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};
@@ -101,6 +102,15 @@ 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 {
index 9eb39b119af8db3cb55a1cf1d69d67fd2c648195..a2ce98cf4d22c9bebaf40ff895b98f88d7b8da2e 100644 (file)
@@ -29,6 +29,7 @@ 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};
@@ -42,7 +43,7 @@ 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};
@@ -144,6 +145,15 @@ 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 {
index 42eeb6cb136ee6a1747ece85b3f8fd929572f0c0..08c57266cb52e7101a94ba25fac77bcd73c75db0 100644 (file)
@@ -14,9 +14,10 @@ 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};
@@ -82,6 +83,81 @@ impl<G: Deref<Target = NetworkGraph<L>> + Clone, L: Deref, S: Deref, SP: Sized,
                        &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),
+                       _ => {
+                               BlindedPath::one_hop_for_payment(recipient, tlvs, entropy_source, secp_ctx)
+                                       .map(|path| vec![path])
+                       },
+               }
+       }
 }
 
 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
@@ -129,6 +205,16 @@ pub trait Router: MessageRouter {
        ) -> 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.
index 3954088fc16046e6697a5c6064a12168e837cbcb..ba56edf3584f017ab0aa623e19aad6d2ec07f2e2 100644 (file)
@@ -8,6 +8,7 @@
 // licenses.
 
 use crate::blinded_path::BlindedPath;
+use crate::blinded_path::payment::ReceiveTlvs;
 use crate::chain;
 use crate::chain::WatchedOutput;
 use crate::chain::chaininterface;
@@ -23,13 +24,13 @@ 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};
@@ -121,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() {
@@ -191,6 +192,15 @@ 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> {