Merge pull request #2991 from optout21/txsigs-splicing
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Tue, 16 Apr 2024 20:27:54 +0000 (13:27 -0700)
committerGitHub <noreply@github.com>
Tue, 16 Apr 2024 20:27:54 +0000 (13:27 -0700)
Add tx_signatures.tlvs field (splicing-specific field in dual funding message)

21 files changed:
fuzz/src/onion_message.rs
fuzz/src/router.rs
lightning/src/blinded_path/message.rs
lightning/src/blinded_path/mod.rs
lightning/src/ln/channel.rs
lightning/src/ln/channelmanager.rs
lightning/src/ln/functional_test_utils.rs
lightning/src/ln/functional_tests.rs
lightning/src/ln/offers_tests.rs
lightning/src/ln/peer_handler.rs
lightning/src/offers/invoice.rs
lightning/src/offers/offer.rs
lightning/src/offers/refund.rs
lightning/src/offers/test_utils.rs
lightning/src/onion_message/functional_tests.rs
lightning/src/onion_message/messenger.rs
lightning/src/onion_message/packet.rs
lightning/src/routing/router.rs
lightning/src/routing/scoring.rs
lightning/src/util/indexed_map.rs
lightning/src/util/ser_macros.rs

index f2bae246fabeb09f6a2ab827c078b6046c2cd8e5..91fcb9bf2d406484ba1e364cdfc994971781f00e 100644 (file)
@@ -6,7 +6,7 @@ use bitcoin::secp256k1::ecdh::SharedSecret;
 use bitcoin::secp256k1::ecdsa::RecoverableSignature;
 use bitcoin::secp256k1::schnorr;
 
-use lightning::blinded_path::BlindedPath;
+use lightning::blinded_path::{BlindedPath, EmptyNodeIdLookUp};
 use lightning::ln::features::InitFeatures;
 use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler};
 use lightning::ln::script::ShutdownScript;
@@ -36,12 +36,13 @@ pub fn do_test<L: Logger>(data: &[u8], logger: &L) {
                        node_secret: secret,
                        counter: AtomicU64::new(0),
                };
+               let node_id_lookup = EmptyNodeIdLookUp {};
                let message_router = TestMessageRouter {};
                let offers_msg_handler = TestOffersMessageHandler {};
                let custom_msg_handler = TestCustomMessageHandler {};
                let onion_messenger = OnionMessenger::new(
-                       &keys_manager, &keys_manager, logger, &message_router, &offers_msg_handler,
-                       &custom_msg_handler
+                       &keys_manager, &keys_manager, logger, &node_id_lookup, &message_router,
+                       &offers_msg_handler, &custom_msg_handler
                );
 
                let peer_node_id = {
index ad4373c4793bc704f66d6cc095b83a5e82013cf7..afe028131c1de0a3a79eb30e975a9cc6e72097a9 100644 (file)
@@ -11,7 +11,7 @@ use bitcoin::blockdata::constants::ChainHash;
 use bitcoin::blockdata::script::Builder;
 use bitcoin::blockdata::transaction::TxOut;
 
-use lightning::blinded_path::{BlindedHop, BlindedPath};
+use lightning::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
 use lightning::chain::transaction::OutPoint;
 use lightning::ln::ChannelId;
 use lightning::ln::channelmanager::{self, ChannelDetails, ChannelCounterparty};
@@ -363,7 +363,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
                                                });
                                        }
                                        (payinfo, BlindedPath {
-                                               introduction_node_id: hop.src_node_id,
+                                               introduction_node: IntroductionNode::NodeId(hop.src_node_id),
                                                blinding_point: dummy_pk,
                                                blinded_hops,
                                        })
index bdcbd7726f71189175407bc3dd125aad2c1db199..df7f8e7ad6128ee90b1d6048c3cdef7a5875b28c 100644 (file)
@@ -3,7 +3,7 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
 #[allow(unused_imports)]
 use crate::prelude::*;
 
-use crate::blinded_path::{BlindedHop, BlindedPath};
+use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NodeIdLookUp};
 use crate::blinded_path::utils;
 use crate::io;
 use crate::io::Cursor;
@@ -19,8 +19,8 @@ use core::ops::Deref;
 /// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded
 /// route, they are encoded into [`BlindedHop::encrypted_payload`].
 pub(crate) struct ForwardTlvs {
-       /// The node id of the next hop in the onion message's path.
-       pub(crate) next_node_id: PublicKey,
+       /// The next hop in the onion message's path.
+       pub(crate) next_hop: NextHop,
        /// Senders to a blinded path use this value to concatenate the route they find to the
        /// introduction node with the blinded path.
        pub(crate) next_blinding_override: Option<PublicKey>,
@@ -34,11 +34,25 @@ pub(crate) struct ReceiveTlvs {
        pub(crate) path_id: Option<[u8; 32]>,
 }
 
+/// The next hop to forward the onion message along its path.
+#[derive(Debug)]
+pub enum NextHop {
+       /// The node id of the next hop.
+       NodeId(PublicKey),
+       /// The short channel id leading to the next hop.
+       ShortChannelId(u64),
+}
+
 impl Writeable for ForwardTlvs {
        fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+               let (next_node_id, short_channel_id) = match self.next_hop {
+                       NextHop::NodeId(pubkey) => (Some(pubkey), None),
+                       NextHop::ShortChannelId(scid) => (None, Some(scid)),
+               };
                // TODO: write padding
                encode_tlv_stream!(writer, {
-                       (4, self.next_node_id, required),
+                       (2, short_channel_id, option),
+                       (4, next_node_id, option),
                        (8, self.next_blinding_override, option)
                });
                Ok(())
@@ -61,9 +75,8 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
 ) -> Result<Vec<BlindedHop>, secp256k1::Error> {
        let blinded_tlvs = unblinded_path.iter()
                .skip(1) // The first node's TLVs contains the next node's pubkey
-               .map(|pk| {
-                       ControlTlvs::Forward(ForwardTlvs { next_node_id: *pk, next_blinding_override: None })
-               })
+               .map(|pk| ForwardTlvs { next_hop: NextHop::NodeId(*pk), next_blinding_override: None })
+               .map(|tlvs| ControlTlvs::Forward(tlvs))
                .chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { path_id: None })));
 
        utils::construct_blinded_hops(secp_ctx, unblinded_path.iter(), blinded_tlvs, session_priv)
@@ -71,18 +84,30 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
 
 // Advance the blinded onion message path by one hop, so make the second hop into the new
 // introduction node.
-pub(crate) fn advance_path_by_one<NS: Deref, T: secp256k1::Signing + secp256k1::Verification>(
-       path: &mut BlindedPath, node_signer: &NS, secp_ctx: &Secp256k1<T>
-) -> Result<(), ()> where NS::Target: NodeSigner {
+pub(crate) fn advance_path_by_one<NS: Deref, NL: Deref, T>(
+       path: &mut BlindedPath, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1<T>
+) -> Result<(), ()>
+where
+       NS::Target: NodeSigner,
+       NL::Target: NodeIdLookUp,
+       T: secp256k1::Signing + secp256k1::Verification,
+{
        let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &path.blinding_point, None)?;
        let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
        let encrypted_control_tlvs = path.blinded_hops.remove(0).encrypted_payload;
        let mut s = Cursor::new(&encrypted_control_tlvs);
        let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
        match ChaChaPolyReadAdapter::read(&mut reader, rho) {
-               Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs {
-                       mut next_node_id, next_blinding_override,
-               })}) => {
+               Ok(ChaChaPolyReadAdapter {
+                       readable: ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override })
+               }) => {
+                       let next_node_id = match next_hop {
+                               NextHop::NodeId(pubkey) => pubkey,
+                               NextHop::ShortChannelId(scid) => match node_id_lookup.next_node_id(scid) {
+                                       Some(pubkey) => pubkey,
+                                       None => return Err(()),
+                               },
+                       };
                        let mut new_blinding_point = match next_blinding_override {
                                Some(blinding_point) => blinding_point,
                                None => {
@@ -91,7 +116,7 @@ pub(crate) fn advance_path_by_one<NS: Deref, T: secp256k1::Signing + secp256k1::
                                }
                        };
                        mem::swap(&mut path.blinding_point, &mut new_blinding_point);
-                       mem::swap(&mut path.introduction_node_id, &mut next_node_id);
+                       path.introduction_node = IntroductionNode::NodeId(next_node_id);
                        Ok(())
                },
                _ => Err(())
index e70f310f5e1d8b00875ff58e069f432bd69526cf..07fa7b770249cae94e1ea18269b63805dc933f93 100644 (file)
@@ -17,6 +17,7 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
 
 use crate::ln::msgs::DecodeError;
 use crate::offers::invoice::BlindedPayInfo;
+use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph};
 use crate::sign::EntropySource;
 use crate::util::ser::{Readable, Writeable, Writer};
 
@@ -28,11 +29,11 @@ use crate::prelude::*;
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 pub struct BlindedPath {
        /// To send to a blinded path, the sender first finds a route to the unblinded
-       /// `introduction_node_id`, which can unblind its [`encrypted_payload`] to find out the onion
+       /// `introduction_node`, which can unblind its [`encrypted_payload`] to find out the onion
        /// message or payment's next hop and forward it along.
        ///
        /// [`encrypted_payload`]: BlindedHop::encrypted_payload
-       pub introduction_node_id: PublicKey,
+       pub introduction_node: IntroductionNode,
        /// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion
        /// message or payment.
        ///
@@ -42,6 +43,52 @@ pub struct BlindedPath {
        pub blinded_hops: Vec<BlindedHop>,
 }
 
+/// The unblinded node in a [`BlindedPath`].
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub enum IntroductionNode {
+       /// The node id of the introduction node.
+       NodeId(PublicKey),
+       /// The short channel id of the channel leading to the introduction node. The [`Direction`]
+       /// identifies which side of the channel is the introduction node.
+       DirectedShortChannelId(Direction, u64),
+}
+
+/// The side of a channel that is the [`IntroductionNode`] in a [`BlindedPath`]. [BOLT 7] defines
+/// which nodes is which in the [`ChannelAnnouncement`] message.
+///
+/// [BOLT 7]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_announcement-message
+/// [`ChannelAnnouncement`]: crate::ln::msgs::ChannelAnnouncement
+#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
+pub enum Direction {
+       /// The lesser node id when compared lexicographically in ascending order.
+       NodeOne,
+       /// The greater node id when compared lexicographically in ascending order.
+       NodeTwo,
+}
+
+/// An interface for looking up the node id of a channel counterparty for the purpose of forwarding
+/// an [`OnionMessage`].
+///
+/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
+pub trait NodeIdLookUp {
+       /// Returns the node id of the forwarding node's channel counterparty with `short_channel_id`.
+       ///
+       /// Here, the forwarding node is referring to the node of the [`OnionMessenger`] parameterized
+       /// by the [`NodeIdLookUp`] and the counterparty to one of that node's peers.
+       ///
+       /// [`OnionMessenger`]: crate::onion_message::messenger::OnionMessenger
+       fn next_node_id(&self, short_channel_id: u64) -> Option<PublicKey>;
+}
+
+/// A [`NodeIdLookUp`] that always returns `None`.
+pub struct EmptyNodeIdLookUp {}
+
+impl NodeIdLookUp for EmptyNodeIdLookUp {
+       fn next_node_id(&self, _short_channel_id: u64) -> Option<PublicKey> {
+               None
+       }
+}
+
 /// An encrypted payload and node id corresponding to a hop in a payment or onion message path, to
 /// be encoded in the sender's onion packet. These hops cannot be identified by outside observers
 /// and thus can be used to hide the identity of the recipient.
@@ -74,10 +121,10 @@ impl BlindedPath {
                if node_pks.is_empty() { return Err(()) }
                let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
                let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
-               let introduction_node_id = node_pks[0];
+               let introduction_node = IntroductionNode::NodeId(node_pks[0]);
 
                Ok(BlindedPath {
-                       introduction_node_id,
+                       introduction_node,
                        blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
                        blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
                })
@@ -111,6 +158,9 @@ impl BlindedPath {
                payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16,
                entropy_source: &ES, secp_ctx: &Secp256k1<T>
        ) -> Result<(BlindedPayInfo, Self), ()> {
+               let introduction_node = IntroductionNode::NodeId(
+                       intermediate_nodes.first().map_or(payee_node_id, |n| n.node_id)
+               );
                let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
                let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
 
@@ -118,18 +168,49 @@ impl BlindedPath {
                        intermediate_nodes, &payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta
                )?;
                Ok((blinded_payinfo, BlindedPath {
-                       introduction_node_id: intermediate_nodes.first().map_or(payee_node_id, |n| n.node_id),
+                       introduction_node,
                        blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
                        blinded_hops: payment::blinded_hops(
                                secp_ctx, intermediate_nodes, payee_node_id, payee_tlvs, &blinding_secret
                        ).map_err(|_| ())?,
                }))
        }
+
+       /// Returns the introduction [`NodeId`] of the blinded path, if it is publicly reachable (i.e.,
+       /// it is found in the network graph).
+       pub fn public_introduction_node_id<'a>(
+               &self, network_graph: &'a ReadOnlyNetworkGraph
+       ) -> Option<&'a NodeId> {
+               match &self.introduction_node {
+                       IntroductionNode::NodeId(pubkey) => {
+                               let node_id = NodeId::from_pubkey(pubkey);
+                               network_graph.nodes().get_key_value(&node_id).map(|(key, _)| key)
+                       },
+                       IntroductionNode::DirectedShortChannelId(direction, scid) => {
+                               network_graph
+                                       .channel(*scid)
+                                       .map(|c| match direction {
+                                               Direction::NodeOne => &c.node_one,
+                                               Direction::NodeTwo => &c.node_two,
+                                       })
+                       },
+               }
+       }
 }
 
 impl Writeable for BlindedPath {
        fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
-               self.introduction_node_id.write(w)?;
+               match &self.introduction_node {
+                       IntroductionNode::NodeId(pubkey) => pubkey.write(w)?,
+                       IntroductionNode::DirectedShortChannelId(direction, scid) => {
+                               match direction {
+                                       Direction::NodeOne => 0u8.write(w)?,
+                                       Direction::NodeTwo => 1u8.write(w)?,
+                               }
+                               scid.write(w)?;
+                       },
+               }
+
                self.blinding_point.write(w)?;
                (self.blinded_hops.len() as u8).write(w)?;
                for hop in &self.blinded_hops {
@@ -141,7 +222,17 @@ impl Writeable for BlindedPath {
 
 impl Readable for BlindedPath {
        fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
-               let introduction_node_id = Readable::read(r)?;
+               let mut first_byte: u8 = Readable::read(r)?;
+               let introduction_node = match first_byte {
+                       0 => IntroductionNode::DirectedShortChannelId(Direction::NodeOne, Readable::read(r)?),
+                       1 => IntroductionNode::DirectedShortChannelId(Direction::NodeTwo, Readable::read(r)?),
+                       2|3 => {
+                               use io::Read;
+                               let mut pubkey_read = core::slice::from_mut(&mut first_byte).chain(r.by_ref());
+                               IntroductionNode::NodeId(Readable::read(&mut pubkey_read)?)
+                       },
+                       _ => return Err(DecodeError::InvalidValue),
+               };
                let blinding_point = Readable::read(r)?;
                let num_hops: u8 = Readable::read(r)?;
                if num_hops == 0 { return Err(DecodeError::InvalidValue) }
@@ -150,7 +241,7 @@ impl Readable for BlindedPath {
                        blinded_hops.push(Readable::read(r)?);
                }
                Ok(BlindedPath {
-                       introduction_node_id,
+                       introduction_node,
                        blinding_point,
                        blinded_hops,
                })
@@ -162,3 +253,25 @@ impl_writeable!(BlindedHop, {
        encrypted_payload
 });
 
+impl Direction {
+       /// Returns the [`NodeId`] from the inputs corresponding to the direction.
+       pub fn select_node_id<'a>(&self, node_a: &'a NodeId, node_b: &'a NodeId) -> &'a NodeId {
+               match self {
+                       Direction::NodeOne => core::cmp::min(node_a, node_b),
+                       Direction::NodeTwo => core::cmp::max(node_a, node_b),
+               }
+       }
+
+       /// Returns the [`PublicKey`] from the inputs corresponding to the direction.
+       pub fn select_pubkey<'a>(&self, node_a: &'a PublicKey, node_b: &'a PublicKey) -> &'a PublicKey {
+               let (node_one, node_two) = if NodeId::from_pubkey(node_a) < NodeId::from_pubkey(node_b) {
+                       (node_a, node_b)
+               } else {
+                       (node_b, node_a)
+               };
+               match self {
+                       Direction::NodeOne => node_one,
+                       Direction::NodeTwo => node_two,
+               }
+       }
+}
index 2698abee2d7db271c0e8a0a2c11d7e77ca976930..58d35a1a95a4ddfc05b29d7fcfe4db5bdf6e89a7 100644 (file)
@@ -2730,7 +2730,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider  {
                        feerate_per_kw = cmp::max(feerate_per_kw, feerate);
                }
                let feerate_plus_quarter = feerate_per_kw.checked_mul(1250).map(|v| v / 1000);
-               cmp::max(2530, feerate_plus_quarter.unwrap_or(u32::max_value()))
+               cmp::max(feerate_per_kw + 2530, feerate_plus_quarter.unwrap_or(u32::max_value()))
        }
 
        /// Get forwarding information for the counterparty.
index 747312e5ea246f8062cd126522fc6834c57ffe5c..abbab5ff0a70d8f0d5b4b7bf58b3bf4702647566 100644 (file)
@@ -31,7 +31,7 @@ use bitcoin::secp256k1::{SecretKey,PublicKey};
 use bitcoin::secp256k1::Secp256k1;
 use bitcoin::{secp256k1, Sequence};
 
-use crate::blinded_path::BlindedPath;
+use crate::blinded_path::{BlindedPath, NodeIdLookUp};
 use crate::blinded_path::payment::{PaymentConstraints, ReceiveTlvs};
 use crate::chain;
 use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock};
@@ -10436,6 +10436,23 @@ where
        }
 }
 
+impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>
+NodeIdLookUp for ChannelManager<M, T, ES, NS, SP, F, R, L>
+where
+       M::Target: chain::Watch<<SP::Target as SignerProvider>::EcdsaSigner>,
+       T::Target: BroadcasterInterface,
+       ES::Target: EntropySource,
+       NS::Target: NodeSigner,
+       SP::Target: SignerProvider,
+       F::Target: FeeEstimator,
+       R::Target: Router,
+       L::Target: Logger,
+{
+       fn next_node_id(&self, short_channel_id: u64) -> Option<PublicKey> {
+               self.short_to_chan_info.read().unwrap().get(&short_channel_id).map(|(pubkey, _)| *pubkey)
+       }
+}
+
 /// Fetches the set of [`NodeFeatures`] flags that are provided by or required by
 /// [`ChannelManager`].
 pub(crate) fn provided_node_features(config: &UserConfig) -> NodeFeatures {
index 3a506b57fe2a05b572b7f4c34b2ecc1f617b4f38..ee4c027a10ec56c8ceb02beaf99bced52b7f71b4 100644 (file)
@@ -415,6 +415,7 @@ type TestOnionMessenger<'chan_man, 'node_cfg, 'chan_mon_cfg> = OnionMessenger<
        DedicatedEntropy,
        &'node_cfg test_utils::TestKeysInterface,
        &'chan_mon_cfg test_utils::TestLogger,
+       &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>,
        &'node_cfg test_utils::TestMessageRouter<'chan_mon_cfg>,
        &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>,
        IgnoringMessageHandler,
@@ -3199,8 +3200,8 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
        for i in 0..node_count {
                let dedicated_entropy = DedicatedEntropy(RandomBytes::new([i as u8; 32]));
                let onion_messenger = OnionMessenger::new(
-                       dedicated_entropy, cfgs[i].keys_manager, cfgs[i].logger, &cfgs[i].message_router,
-                       &chan_mgrs[i], IgnoringMessageHandler {},
+                       dedicated_entropy, cfgs[i].keys_manager, cfgs[i].logger, &chan_mgrs[i],
+                       &cfgs[i].message_router, &chan_mgrs[i], IgnoringMessageHandler {},
                );
                let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger);
                let wallet_source = Arc::new(test_utils::TestWalletSource::new(SecretKey::from_slice(&[i as u8 + 1; 32]).unwrap()));
index 5ea3e6372c0d1c497c0b8fcde11961cfdb4206b0..8dd3f1fc9124f9ed72b105b14dff7b0ee9e858af 100644 (file)
@@ -9950,7 +9950,10 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
        let dust_outbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_timeout_tx_weight(&channel_type_features) / 1000 + open_channel.common_fields.dust_limit_satoshis - 1) * 1000;
        let dust_outbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_outbound_htlc_on_holder_tx_msat;
 
-       let dust_inbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_success_tx_weight(&channel_type_features) / 1000 + open_channel.common_fields.dust_limit_satoshis - 1) * 1000;
+       // Substract 3 sats for multiplier and 2 sats for fixed limit to make sure we are 50% below the dust limit.
+       // This is to make sure we fully use the dust limit. If we don't, we could end up with `dust_ibd_htlc_on_holder_tx` being 1 
+       // while `max_dust_htlc_exposure_msat` is not equal to `dust_outbound_htlc_on_holder_tx_msat`.
+       let dust_inbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_success_tx_weight(&channel_type_features) / 1000 + open_channel.common_fields.dust_limit_satoshis - if multiplier_dust_limit { 3 } else { 2 }) * 1000; 
        let dust_inbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_inbound_htlc_on_holder_tx_msat;
 
        let dust_htlc_on_counterparty_tx: u64 = 4;
index e75bd2c70e1e9c3d6b68945fa0ea424a694d4f7d..4c8c1ecba038568d1fc690b8a18f1adfce0e6d56 100644 (file)
@@ -42,7 +42,7 @@
 
 use bitcoin::network::constants::Network;
 use core::time::Duration;
-use crate::blinded_path::BlindedPath;
+use crate::blinded_path::{BlindedPath, IntroductionNode};
 use crate::events::{Event, MessageSendEventsProvider, PaymentPurpose};
 use crate::ln::channelmanager::{PaymentId, RecentPaymentDetails, Retry, self};
 use crate::ln::functional_test_utils::*;
@@ -260,8 +260,8 @@ fn prefers_non_tor_nodes_in_blinded_paths() {
        assert_ne!(offer.signing_pubkey(), bob_id);
        assert!(!offer.paths().is_empty());
        for path in offer.paths() {
-               assert_ne!(path.introduction_node_id, bob_id);
-               assert_ne!(path.introduction_node_id, charlie_id);
+               assert_ne!(path.introduction_node, IntroductionNode::NodeId(bob_id));
+               assert_ne!(path.introduction_node, IntroductionNode::NodeId(charlie_id));
        }
 
        // Use a one-hop blinded path when Bob is announced and all his peers are Tor-only.
@@ -275,7 +275,7 @@ fn prefers_non_tor_nodes_in_blinded_paths() {
        assert_ne!(offer.signing_pubkey(), bob_id);
        assert!(!offer.paths().is_empty());
        for path in offer.paths() {
-               assert_eq!(path.introduction_node_id, bob_id);
+               assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
        }
 }
 
@@ -325,7 +325,7 @@ fn prefers_more_connected_nodes_in_blinded_paths() {
        assert_ne!(offer.signing_pubkey(), bob_id);
        assert!(!offer.paths().is_empty());
        for path in offer.paths() {
-               assert_eq!(path.introduction_node_id, nodes[4].node.get_our_node_id());
+               assert_eq!(path.introduction_node, IntroductionNode::NodeId(nodes[4].node.get_our_node_id()));
        }
 }
 
@@ -374,7 +374,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
        assert_ne!(offer.signing_pubkey(), alice_id);
        assert!(!offer.paths().is_empty());
        for path in offer.paths() {
-               assert_eq!(path.introduction_node_id, bob_id);
+               assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
        }
 
        let payment_id = PaymentId([1; 32]);
@@ -395,7 +395,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
        let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
        assert_eq!(invoice_request.amount_msats(), None);
        assert_ne!(invoice_request.payer_id(), david_id);
-       assert_eq!(reply_path.unwrap().introduction_node_id, charlie_id);
+       assert_eq!(reply_path.unwrap().introduction_node, IntroductionNode::NodeId(charlie_id));
 
        let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
        charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message);
@@ -408,7 +408,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
        assert_ne!(invoice.signing_pubkey(), alice_id);
        assert!(!invoice.payment_paths().is_empty());
        for (_, path) in invoice.payment_paths() {
-               assert_eq!(path.introduction_node_id, bob_id);
+               assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
        }
 
        route_bolt12_payment(david, &[charlie, bob, alice], &invoice);
@@ -469,7 +469,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() {
        assert_ne!(refund.payer_id(), david_id);
        assert!(!refund.paths().is_empty());
        for path in refund.paths() {
-               assert_eq!(path.introduction_node_id, charlie_id);
+               assert_eq!(path.introduction_node, IntroductionNode::NodeId(charlie_id));
        }
        expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
 
@@ -488,7 +488,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() {
        assert_ne!(invoice.signing_pubkey(), alice_id);
        assert!(!invoice.payment_paths().is_empty());
        for (_, path) in invoice.payment_paths() {
-               assert_eq!(path.introduction_node_id, bob_id);
+               assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
        }
 
        route_bolt12_payment(david, &[charlie, bob, alice], &invoice);
@@ -522,7 +522,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
        assert_ne!(offer.signing_pubkey(), alice_id);
        assert!(!offer.paths().is_empty());
        for path in offer.paths() {
-               assert_eq!(path.introduction_node_id, alice_id);
+               assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id));
        }
 
        let payment_id = PaymentId([1; 32]);
@@ -535,7 +535,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
        let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
        assert_eq!(invoice_request.amount_msats(), None);
        assert_ne!(invoice_request.payer_id(), bob_id);
-       assert_eq!(reply_path.unwrap().introduction_node_id, bob_id);
+       assert_eq!(reply_path.unwrap().introduction_node, IntroductionNode::NodeId(bob_id));
 
        let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
        bob.onion_messenger.handle_onion_message(&alice_id, &onion_message);
@@ -545,7 +545,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
        assert_ne!(invoice.signing_pubkey(), alice_id);
        assert!(!invoice.payment_paths().is_empty());
        for (_, path) in invoice.payment_paths() {
-               assert_eq!(path.introduction_node_id, alice_id);
+               assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id));
        }
 
        route_bolt12_payment(bob, &[alice], &invoice);
@@ -585,7 +585,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() {
        assert_ne!(refund.payer_id(), bob_id);
        assert!(!refund.paths().is_empty());
        for path in refund.paths() {
-               assert_eq!(path.introduction_node_id, bob_id);
+               assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id));
        }
        expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
 
@@ -599,7 +599,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() {
        assert_ne!(invoice.signing_pubkey(), alice_id);
        assert!(!invoice.payment_paths().is_empty());
        for (_, path) in invoice.payment_paths() {
-               assert_eq!(path.introduction_node_id, alice_id);
+               assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id));
        }
 
        route_bolt12_payment(bob, &[alice], &invoice);
index 663e99ccaf4029fa175fc40bf0e879a1e12d4e68..c7b54a19ddcfe3585177c303a4bd3c8cab03f510 100644 (file)
@@ -1478,7 +1478,6 @@ impl<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, OM: Deref, L: Deref, CM
                                                                let networks = self.message_handler.chan_handler.get_chain_hashes();
                                                                let resp = msgs::Init { features, networks, remote_network_address: filter_addresses(peer.their_socket_address.clone()) };
                                                                self.enqueue_message(peer, &resp);
-                                                               peer.awaiting_pong_timer_tick_intervals = 0;
                                                        },
                                                        NextNoiseStep::ActThree => {
                                                                let their_node_id = try_potential_handleerror!(peer,
@@ -1491,7 +1490,6 @@ impl<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, OM: Deref, L: Deref, CM
                                                                let networks = self.message_handler.chan_handler.get_chain_hashes();
                                                                let resp = msgs::Init { features, networks, remote_network_address: filter_addresses(peer.their_socket_address.clone()) };
                                                                self.enqueue_message(peer, &resp);
-                                                               peer.awaiting_pong_timer_tick_intervals = 0;
                                                        },
                                                        NextNoiseStep::NoiseComplete => {
                                                                if peer.pending_read_is_header {
@@ -1684,6 +1682,7 @@ impl<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, OM: Deref, L: Deref, CM
                                return Err(PeerHandleError { }.into());
                        }
 
+                       peer_lock.awaiting_pong_timer_tick_intervals = 0;
                        peer_lock.their_features = Some(msg.features);
                        return Ok(None);
                } else if peer_lock.their_features.is_none() {
@@ -2680,7 +2679,7 @@ mod tests {
        use crate::ln::ChannelId;
        use crate::ln::features::{InitFeatures, NodeFeatures};
        use crate::ln::peer_channel_encryptor::PeerChannelEncryptor;
-       use crate::ln::peer_handler::{CustomMessageHandler, PeerManager, MessageHandler, SocketDescriptor, IgnoringMessageHandler, filter_addresses};
+       use crate::ln::peer_handler::{CustomMessageHandler, PeerManager, MessageHandler, SocketDescriptor, IgnoringMessageHandler, filter_addresses, ErroringMessageHandler, MAX_BUFFER_DRAIN_TICK_INTERVALS_PER_PEER};
        use crate::ln::{msgs, wire};
        use crate::ln::msgs::{LightningError, SocketAddress};
        use crate::util::test_utils;
@@ -3222,6 +3221,105 @@ mod tests {
                assert!(peers[0].read_event(&mut fd_a, &b_data).is_err());
        }
 
+       #[test]
+       fn test_inbound_conn_handshake_complete_awaiting_pong() {
+               // Test that we do not disconnect an outbound peer after the noise handshake completes due
+               // to a pong timeout for a ping that was never sent if a timer tick fires after we send act
+               // two of the noise handshake along with our init message but before we receive their init
+               // message.
+               let logger = test_utils::TestLogger::new();
+               let node_signer_a = test_utils::TestNodeSigner::new(SecretKey::from_slice(&[42; 32]).unwrap());
+               let node_signer_b = test_utils::TestNodeSigner::new(SecretKey::from_slice(&[43; 32]).unwrap());
+               let peer_a = PeerManager::new(MessageHandler {
+                       chan_handler: ErroringMessageHandler::new(),
+                       route_handler: IgnoringMessageHandler {},
+                       onion_message_handler: IgnoringMessageHandler {},
+                       custom_message_handler: IgnoringMessageHandler {},
+               }, 0, &[0; 32], &logger, &node_signer_a);
+               let peer_b = PeerManager::new(MessageHandler {
+                       chan_handler: ErroringMessageHandler::new(),
+                       route_handler: IgnoringMessageHandler {},
+                       onion_message_handler: IgnoringMessageHandler {},
+                       custom_message_handler: IgnoringMessageHandler {},
+               }, 0, &[1; 32], &logger, &node_signer_b);
+
+               let a_id = node_signer_a.get_node_id(Recipient::Node).unwrap();
+               let mut fd_a = FileDescriptor {
+                       fd: 1, outbound_data: Arc::new(Mutex::new(Vec::new())),
+                       disconnect: Arc::new(AtomicBool::new(false)),
+               };
+               let mut fd_b = FileDescriptor {
+                       fd: 1, outbound_data: Arc::new(Mutex::new(Vec::new())),
+                       disconnect: Arc::new(AtomicBool::new(false)),
+               };
+
+               // Exchange messages with both peers until they both complete the init handshake.
+               let act_one = peer_b.new_outbound_connection(a_id, fd_b.clone(), None).unwrap();
+               peer_a.new_inbound_connection(fd_a.clone(), None).unwrap();
+
+               assert_eq!(peer_a.read_event(&mut fd_a, &act_one).unwrap(), false);
+               peer_a.process_events();
+
+               let act_two = fd_a.outbound_data.lock().unwrap().split_off(0);
+               assert_eq!(peer_b.read_event(&mut fd_b, &act_two).unwrap(), false);
+               peer_b.process_events();
+
+               // Calling this here triggers the race on inbound connections.
+               peer_b.timer_tick_occurred();
+
+               let act_three_with_init_b = fd_b.outbound_data.lock().unwrap().split_off(0);
+               assert!(!peer_a.peers.read().unwrap().get(&fd_a).unwrap().lock().unwrap().handshake_complete());
+               assert_eq!(peer_a.read_event(&mut fd_a, &act_three_with_init_b).unwrap(), false);
+               peer_a.process_events();
+               assert!(peer_a.peers.read().unwrap().get(&fd_a).unwrap().lock().unwrap().handshake_complete());
+
+               let init_a = fd_a.outbound_data.lock().unwrap().split_off(0);
+               assert!(!init_a.is_empty());
+
+               assert!(!peer_b.peers.read().unwrap().get(&fd_b).unwrap().lock().unwrap().handshake_complete());
+               assert_eq!(peer_b.read_event(&mut fd_b, &init_a).unwrap(), false);
+               peer_b.process_events();
+               assert!(peer_b.peers.read().unwrap().get(&fd_b).unwrap().lock().unwrap().handshake_complete());
+
+               // Make sure we're still connected.
+               assert_eq!(peer_b.peers.read().unwrap().len(), 1);
+
+               // B should send a ping on the first timer tick after `handshake_complete`.
+               assert!(fd_b.outbound_data.lock().unwrap().split_off(0).is_empty());
+               peer_b.timer_tick_occurred();
+               peer_b.process_events();
+               assert!(!fd_b.outbound_data.lock().unwrap().split_off(0).is_empty());
+
+               let mut send_warning = || {
+                       {
+                               let peers = peer_a.peers.read().unwrap();
+                               let mut peer_b = peers.get(&fd_a).unwrap().lock().unwrap();
+                               peer_a.enqueue_message(&mut peer_b, &msgs::WarningMessage {
+                                       channel_id: ChannelId([0; 32]),
+                                       data: "no disconnect plz".to_string(),
+                               });
+                       }
+                       peer_a.process_events();
+                       let msg = fd_a.outbound_data.lock().unwrap().split_off(0);
+                       assert!(!msg.is_empty());
+                       assert_eq!(peer_b.read_event(&mut fd_b, &msg).unwrap(), false);
+                       peer_b.process_events();
+               };
+
+               // Fire more ticks until we reach the pong timeout. We send any message except pong to
+               // pretend the connection is still alive.
+               send_warning();
+               for _ in 0..MAX_BUFFER_DRAIN_TICK_INTERVALS_PER_PEER {
+                       peer_b.timer_tick_occurred();
+                       send_warning();
+               }
+               assert_eq!(peer_b.peers.read().unwrap().len(), 1);
+
+               // One more tick should enforce the pong timeout.
+               peer_b.timer_tick_occurred();
+               assert_eq!(peer_b.peers.read().unwrap().len(), 0);
+       }
+
        #[test]
        fn test_filter_addresses(){
                // Tests the filter_addresses function.
index f2fb387942dc303e2c895e4e4917777a07aa6492..fbfcffe2ad805fdec8af1f4f24ae7dc7de916e63 100644 (file)
@@ -1455,7 +1455,7 @@ mod tests {
 
        use core::time::Duration;
 
-       use crate::blinded_path::{BlindedHop, BlindedPath};
+       use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
        use crate::sign::KeyMaterial;
        use crate::ln::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
        use crate::ln::inbound_payment::ExpandedKey;
@@ -1804,7 +1804,7 @@ mod tests {
                let secp_ctx = Secp256k1::new();
 
                let blinded_path = BlindedPath {
-                       introduction_node_id: pubkey(40),
+                       introduction_node: IntroductionNode::NodeId(pubkey(40)),
                        blinding_point: pubkey(41),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },
index f7b75138b5108bbecad7cae5faae4ede0d9749e5..d390552f866b0ac1c8169482e5fa17831c57cab9 100644 (file)
@@ -1078,7 +1078,7 @@ mod tests {
        use bitcoin::secp256k1::Secp256k1;
        use core::num::NonZeroU64;
        use core::time::Duration;
-       use crate::blinded_path::{BlindedHop, BlindedPath};
+       use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
        use crate::sign::KeyMaterial;
        use crate::ln::features::OfferFeatures;
        use crate::ln::inbound_payment::ExpandedKey;
@@ -1249,7 +1249,7 @@ mod tests {
                let secp_ctx = Secp256k1::new();
 
                let blinded_path = BlindedPath {
-                       introduction_node_id: pubkey(40),
+                       introduction_node: IntroductionNode::NodeId(pubkey(40)),
                        blinding_point: pubkey(41),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },
@@ -1395,7 +1395,7 @@ mod tests {
        fn builds_offer_with_paths() {
                let paths = vec![
                        BlindedPath {
-                               introduction_node_id: pubkey(40),
+                               introduction_node: IntroductionNode::NodeId(pubkey(40)),
                                blinding_point: pubkey(41),
                                blinded_hops: vec![
                                        BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
@@ -1403,7 +1403,7 @@ mod tests {
                                ],
                        },
                        BlindedPath {
-                               introduction_node_id: pubkey(40),
+                               introduction_node: IntroductionNode::NodeId(pubkey(40)),
                                blinding_point: pubkey(41),
                                blinded_hops: vec![
                                        BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
@@ -1585,7 +1585,7 @@ mod tests {
        fn parses_offer_with_paths() {
                let offer = OfferBuilder::new("foo".into(), pubkey(42))
                        .path(BlindedPath {
-                               introduction_node_id: pubkey(40),
+                               introduction_node: IntroductionNode::NodeId(pubkey(40)),
                                blinding_point: pubkey(41),
                                blinded_hops: vec![
                                        BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
@@ -1593,7 +1593,7 @@ mod tests {
                                ],
                        })
                        .path(BlindedPath {
-                               introduction_node_id: pubkey(40),
+                               introduction_node: IntroductionNode::NodeId(pubkey(40)),
                                blinding_point: pubkey(41),
                                blinded_hops: vec![
                                        BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
index 16014cd3c0b7ac6080a31b1c198b7bb669798219..03253fb6400bfe2c1261fcb2b00f13bc6eff1779 100644 (file)
@@ -907,7 +907,7 @@ mod tests {
 
        use core::time::Duration;
 
-       use crate::blinded_path::{BlindedHop, BlindedPath};
+       use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
        use crate::sign::KeyMaterial;
        use crate::ln::channelmanager::PaymentId;
        use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
@@ -1062,7 +1062,7 @@ mod tests {
                let payment_id = PaymentId([1; 32]);
 
                let blinded_path = BlindedPath {
-                       introduction_node_id: pubkey(40),
+                       introduction_node: IntroductionNode::NodeId(pubkey(40)),
                        blinding_point: pubkey(41),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
@@ -1151,7 +1151,7 @@ mod tests {
        fn builds_refund_with_paths() {
                let paths = vec![
                        BlindedPath {
-                               introduction_node_id: pubkey(40),
+                               introduction_node: IntroductionNode::NodeId(pubkey(40)),
                                blinding_point: pubkey(41),
                                blinded_hops: vec![
                                        BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
@@ -1159,7 +1159,7 @@ mod tests {
                                ],
                        },
                        BlindedPath {
-                               introduction_node_id: pubkey(40),
+                               introduction_node: IntroductionNode::NodeId(pubkey(40)),
                                blinding_point: pubkey(41),
                                blinded_hops: vec![
                                        BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
@@ -1368,7 +1368,7 @@ mod tests {
                let past_expiry = Duration::from_secs(0);
                let paths = vec![
                        BlindedPath {
-                               introduction_node_id: pubkey(40),
+                               introduction_node: IntroductionNode::NodeId(pubkey(40)),
                                blinding_point: pubkey(41),
                                blinded_hops: vec![
                                        BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
@@ -1376,7 +1376,7 @@ mod tests {
                                ],
                        },
                        BlindedPath {
-                               introduction_node_id: pubkey(40),
+                               introduction_node: IntroductionNode::NodeId(pubkey(40)),
                                blinding_point: pubkey(41),
                                blinded_hops: vec![
                                        BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
index b4329803016fadb395689bf696e30d78c2984e97..149ba15c3a2392d4caa98c5d63db02a1c988ab8b 100644 (file)
@@ -13,7 +13,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
 use bitcoin::secp256k1::schnorr::Signature;
 
 use core::time::Duration;
-use crate::blinded_path::{BlindedHop, BlindedPath};
+use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
 use crate::sign::EntropySource;
 use crate::ln::PaymentHash;
 use crate::ln::features::BlindedHopFeatures;
@@ -69,7 +69,7 @@ pub(super) fn privkey(byte: u8) -> SecretKey {
 pub(crate) fn payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> {
        let paths = vec![
                BlindedPath {
-                       introduction_node_id: pubkey(40),
+                       introduction_node: IntroductionNode::NodeId(pubkey(40)),
                        blinding_point: pubkey(41),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
@@ -77,7 +77,7 @@ pub(crate) fn payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> {
                        ],
                },
                BlindedPath {
-                       introduction_node_id: pubkey(40),
+                       introduction_node: IntroductionNode::NodeId(pubkey(40)),
                        blinding_point: pubkey(41),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
index 16f0babe3612e365accffd4c6a7ee110e65880ba..acf34a3a8c8a112067bd22cd905b328166398321 100644 (file)
@@ -9,7 +9,7 @@
 
 //! Onion message testing and test utilities live here.
 
-use crate::blinded_path::BlindedPath;
+use crate::blinded_path::{BlindedPath, EmptyNodeIdLookUp};
 use crate::events::{Event, EventsProvider};
 use crate::ln::features::{ChannelFeatures, InitFeatures};
 use crate::ln::msgs::{self, DecodeError, OnionMessageHandler};
@@ -42,6 +42,7 @@ struct MessengerNode {
                Arc<test_utils::TestKeysInterface>,
                Arc<test_utils::TestNodeSigner>,
                Arc<test_utils::TestLogger>,
+               Arc<EmptyNodeIdLookUp>,
                Arc<DefaultMessageRouter<
                        Arc<NetworkGraph<Arc<test_utils::TestLogger>>>,
                        Arc<test_utils::TestLogger>,
@@ -175,6 +176,7 @@ fn create_nodes_using_secrets(secrets: Vec<SecretKey>) -> Vec<MessengerNode> {
                let entropy_source = Arc::new(test_utils::TestKeysInterface::new(&seed, Network::Testnet));
                let node_signer = Arc::new(test_utils::TestNodeSigner::new(secret_key));
 
+               let node_id_lookup = Arc::new(EmptyNodeIdLookUp {});
                let message_router = Arc::new(
                        DefaultMessageRouter::new(network_graph.clone(), entropy_source.clone())
                );
@@ -185,7 +187,7 @@ fn create_nodes_using_secrets(secrets: Vec<SecretKey>) -> Vec<MessengerNode> {
                        node_id: node_signer.get_node_id(Recipient::Node).unwrap(),
                        entropy_source: entropy_source.clone(),
                        messenger: OnionMessenger::new(
-                               entropy_source, node_signer, logger.clone(), message_router,
+                               entropy_source, node_signer, logger.clone(), node_id_lookup, message_router,
                                offers_message_handler, custom_message_handler.clone()
                        ),
                        custom_message_handler,
index e213bcbb0e1dc50d9fda8983f164e82611db26d9..1d7a730fa3625126097fd6d25de150e5ba46c742 100644 (file)
@@ -15,15 +15,15 @@ use bitcoin::hashes::hmac::{Hmac, HmacEngine};
 use bitcoin::hashes::sha256::Hash as Sha256;
 use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey};
 
-use crate::blinded_path::BlindedPath;
-use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, ReceiveTlvs};
+use crate::blinded_path::{BlindedPath, IntroductionNode, NodeIdLookUp};
+use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, NextHop, ReceiveTlvs};
 use crate::blinded_path::utils;
 use crate::events::{Event, EventHandler, EventsProvider};
 use crate::sign::{EntropySource, NodeSigner, Recipient};
 use crate::ln::features::{InitFeatures, NodeFeatures};
 use crate::ln::msgs::{self, OnionMessage, OnionMessageHandler, SocketAddress};
 use crate::ln::onion_utils;
-use crate::routing::gossip::{NetworkGraph, NodeId};
+use crate::routing::gossip::{NetworkGraph, NodeId, ReadOnlyNetworkGraph};
 use super::packet::OnionMessageContents;
 use super::packet::ParsedOnionMessageContents;
 use super::offers::OffersMessageHandler;
@@ -70,7 +70,7 @@ pub(super) const MAX_TIMER_TICKS: usize = 2;
 /// # use bitcoin::hashes::_export::_core::time::Duration;
 /// # use bitcoin::hashes::hex::FromHex;
 /// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey, self};
-/// # use lightning::blinded_path::BlindedPath;
+/// # use lightning::blinded_path::{BlindedPath, EmptyNodeIdLookUp};
 /// # use lightning::sign::{EntropySource, KeysManager};
 /// # use lightning::ln::peer_handler::IgnoringMessageHandler;
 /// # use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath, OnionMessenger};
@@ -111,14 +111,15 @@ pub(super) const MAX_TIMER_TICKS: usize = 2;
 /// # let hop_node_id1 = PublicKey::from_secret_key(&secp_ctx, &node_secret);
 /// # let (hop_node_id3, hop_node_id4) = (hop_node_id1, hop_node_id1);
 /// # let destination_node_id = hop_node_id1;
+/// # let node_id_lookup = EmptyNodeIdLookUp {};
 /// # let message_router = Arc::new(FakeMessageRouter {});
 /// # let custom_message_handler = IgnoringMessageHandler {};
 /// # let offers_message_handler = IgnoringMessageHandler {};
 /// // Create the onion messenger. This must use the same `keys_manager` as is passed to your
 /// // ChannelManager.
 /// let onion_messenger = OnionMessenger::new(
-///     &keys_manager, &keys_manager, logger, message_router, &offers_message_handler,
-///     &custom_message_handler
+///     &keys_manager, &keys_manager, logger, &node_id_lookup, message_router,
+///     &offers_message_handler, &custom_message_handler
 /// );
 
 /// # #[derive(Debug)]
@@ -155,11 +156,12 @@ pub(super) const MAX_TIMER_TICKS: usize = 2;
 ///
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
 /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
-pub struct OnionMessenger<ES: Deref, NS: Deref, L: Deref, MR: Deref, OMH: Deref, CMH: Deref>
+pub struct OnionMessenger<ES: Deref, NS: Deref, L: Deref, NL: Deref, MR: Deref, OMH: Deref, CMH: Deref>
 where
        ES::Target: EntropySource,
        NS::Target: NodeSigner,
        L::Target: Logger,
+       NL::Target: NodeIdLookUp,
        MR::Target: MessageRouter,
        OMH::Target: OffersMessageHandler,
        CMH::Target: CustomOnionMessageHandler,
@@ -169,6 +171,7 @@ where
        logger: L,
        message_recipients: Mutex<HashMap<PublicKey, OnionMessageRecipient>>,
        secp_ctx: Secp256k1<secp256k1::All>,
+       node_id_lookup: NL,
        message_router: MR,
        offers_handler: OMH,
        custom_handler: CMH,
@@ -318,15 +321,21 @@ where
        ES::Target: EntropySource,
 {
        fn find_path(
-               &self, sender: PublicKey, peers: Vec<PublicKey>, destination: Destination
+               &self, sender: PublicKey, peers: Vec<PublicKey>, mut destination: Destination
        ) -> Result<OnionMessagePath, ()> {
-               let first_node = destination.first_node();
+               let network_graph = self.network_graph.deref().read_only();
+               destination.resolve(&network_graph);
+
+               let first_node = match destination.first_node() {
+                       Some(first_node) => first_node,
+                       None => return Err(()),
+               };
+
                if peers.contains(&first_node) || sender == first_node {
                        Ok(OnionMessagePath {
                                intermediate_nodes: vec![], destination, first_node_addresses: None
                        })
                } else {
-                       let network_graph = self.network_graph.deref().read_only();
                        let node_announcement = network_graph
                                .node(&NodeId::from_pubkey(&first_node))
                                .and_then(|node_info| node_info.announcement_info.as_ref())
@@ -416,11 +425,11 @@ pub struct OnionMessagePath {
 
 impl OnionMessagePath {
        /// Returns the first node in the path.
-       pub fn first_node(&self) -> PublicKey {
+       pub fn first_node(&self) -> Option<PublicKey> {
                self.intermediate_nodes
                        .first()
                        .copied()
-                       .unwrap_or_else(|| self.destination.first_node())
+                       .or_else(|| self.destination.first_node())
        }
 }
 
@@ -434,6 +443,22 @@ pub enum Destination {
 }
 
 impl Destination {
+       /// Attempts to resolve the [`IntroductionNode::DirectedShortChannelId`] of a
+       /// [`Destination::BlindedPath`] to a [`IntroductionNode::NodeId`], if applicable, using the
+       /// provided [`ReadOnlyNetworkGraph`].
+       pub fn resolve(&mut self, network_graph: &ReadOnlyNetworkGraph) {
+               if let Destination::BlindedPath(path) = self {
+                       if let IntroductionNode::DirectedShortChannelId(..) = path.introduction_node {
+                               if let Some(pubkey) = path
+                                       .public_introduction_node_id(network_graph)
+                                       .and_then(|node_id| node_id.as_pubkey().ok())
+                               {
+                                       path.introduction_node = IntroductionNode::NodeId(pubkey);
+                               }
+                       }
+               }
+       }
+
        pub(super) fn num_hops(&self) -> usize {
                match self {
                        Destination::Node(_) => 1,
@@ -441,10 +466,15 @@ impl Destination {
                }
        }
 
-       fn first_node(&self) -> PublicKey {
+       fn first_node(&self) -> Option<PublicKey> {
                match self {
-                       Destination::Node(node_id) => *node_id,
-                       Destination::BlindedPath(BlindedPath { introduction_node_id: node_id, .. }) => *node_id,
+                       Destination::Node(node_id) => Some(*node_id),
+                       Destination::BlindedPath(BlindedPath { introduction_node, .. }) => {
+                               match introduction_node {
+                                       IntroductionNode::NodeId(pubkey) => Some(*pubkey),
+                                       IntroductionNode::DirectedShortChannelId(..) => None,
+                               }
+                       },
                }
        }
 }
@@ -487,6 +517,10 @@ pub enum SendError {
        ///
        /// [`NodeSigner`]: crate::sign::NodeSigner
        GetNodeIdFailed,
+       /// The provided [`Destination`] has a blinded path with an unresolved introduction node. An
+       /// attempt to resolve it in the [`MessageRouter`] when finding an [`OnionMessagePath`] likely
+       /// failed.
+       UnresolvedIntroductionNode,
        /// We attempted to send to a blinded path where we are the introduction node, and failed to
        /// advance the blinded path to make the second hop the new introduction node. Either
        /// [`NodeSigner::ecdh`] failed, we failed to tweak the current blinding point to get the
@@ -538,23 +572,56 @@ pub trait CustomOnionMessageHandler {
 #[derive(Debug)]
 pub enum PeeledOnion<T: OnionMessageContents> {
        /// Forwarded onion, with the next node id and a new onion
-       Forward(PublicKey, OnionMessage),
+       Forward(NextHop, OnionMessage),
        /// Received onion message, with decrypted contents, path_id, and reply path
        Receive(ParsedOnionMessageContents<T>, Option<[u8; 32]>, Option<BlindedPath>)
 }
 
+
+/// Creates an [`OnionMessage`] with the given `contents` for sending to the destination of
+/// `path`, first calling [`Destination::resolve`] on `path.destination` with the given
+/// [`ReadOnlyNetworkGraph`].
+///
+/// Returns the node id of the peer to send the message to, the message itself, and any addresses
+/// needed to connect to the first node.
+pub fn create_onion_message_resolving_destination<
+       ES: Deref, NS: Deref, NL: Deref, T: OnionMessageContents
+>(
+       entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL,
+       network_graph: &ReadOnlyNetworkGraph, secp_ctx: &Secp256k1<secp256k1::All>,
+       mut path: OnionMessagePath, contents: T, reply_path: Option<BlindedPath>,
+) -> Result<(PublicKey, OnionMessage, Option<Vec<SocketAddress>>), SendError>
+where
+       ES::Target: EntropySource,
+       NS::Target: NodeSigner,
+       NL::Target: NodeIdLookUp,
+{
+       path.destination.resolve(network_graph);
+       create_onion_message(
+               entropy_source, node_signer, node_id_lookup, secp_ctx, path, contents, reply_path,
+       )
+}
+
 /// Creates an [`OnionMessage`] with the given `contents` for sending to the destination of
 /// `path`.
 ///
 /// Returns the node id of the peer to send the message to, the message itself, and any addresses
-/// need to connect to the first node.
-pub fn create_onion_message<ES: Deref, NS: Deref, T: OnionMessageContents>(
-       entropy_source: &ES, node_signer: &NS, secp_ctx: &Secp256k1<secp256k1::All>,
-       path: OnionMessagePath, contents: T, reply_path: Option<BlindedPath>,
+/// needed to connect to the first node.
+///
+/// Returns [`SendError::UnresolvedIntroductionNode`] if:
+/// - `destination` contains a blinded path with an [`IntroductionNode::DirectedShortChannelId`],
+/// - unless it can be resolved by [`NodeIdLookUp::next_node_id`].
+/// Use [`create_onion_message_resolving_destination`] instead to resolve the introduction node
+/// first with a [`ReadOnlyNetworkGraph`].
+pub fn create_onion_message<ES: Deref, NS: Deref, NL: Deref, T: OnionMessageContents>(
+       entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL,
+       secp_ctx: &Secp256k1<secp256k1::All>, path: OnionMessagePath, contents: T,
+       reply_path: Option<BlindedPath>,
 ) -> Result<(PublicKey, OnionMessage, Option<Vec<SocketAddress>>), SendError>
 where
        ES::Target: EntropySource,
        NS::Target: NodeSigner,
+       NL::Target: NodeIdLookUp,
 {
        let OnionMessagePath { intermediate_nodes, mut destination, first_node_addresses } = path;
        if let Destination::BlindedPath(BlindedPath { ref blinded_hops, .. }) = destination {
@@ -571,8 +638,17 @@ where
                if let Destination::BlindedPath(ref mut blinded_path) = destination {
                        let our_node_id = node_signer.get_node_id(Recipient::Node)
                                .map_err(|()| SendError::GetNodeIdFailed)?;
-                       if blinded_path.introduction_node_id == our_node_id {
-                               advance_path_by_one(blinded_path, node_signer, &secp_ctx)
+                       let introduction_node_id = match blinded_path.introduction_node {
+                               IntroductionNode::NodeId(pubkey) => pubkey,
+                               IntroductionNode::DirectedShortChannelId(direction, scid) => {
+                                       match node_id_lookup.next_node_id(scid) {
+                                               Some(next_node_id) => *direction.select_pubkey(&our_node_id, &next_node_id),
+                                               None => return Err(SendError::UnresolvedIntroductionNode),
+                                       }
+                               },
+                       };
+                       if introduction_node_id == our_node_id {
+                               advance_path_by_one(blinded_path, node_signer, node_id_lookup, &secp_ctx)
                                        .map_err(|()| SendError::BlindedPathAdvanceFailed)?;
                        }
                }
@@ -583,15 +659,21 @@ where
        let (first_node_id, blinding_point) = if let Some(first_node_id) = intermediate_nodes.first() {
                (*first_node_id, PublicKey::from_secret_key(&secp_ctx, &blinding_secret))
        } else {
-               match destination {
-                       Destination::Node(pk) => (pk, PublicKey::from_secret_key(&secp_ctx, &blinding_secret)),
-                       Destination::BlindedPath(BlindedPath { introduction_node_id, blinding_point, .. }) =>
-                               (introduction_node_id, blinding_point),
+               match &destination {
+                       Destination::Node(pk) => (*pk, PublicKey::from_secret_key(&secp_ctx, &blinding_secret)),
+                       Destination::BlindedPath(BlindedPath { introduction_node, blinding_point, .. }) => {
+                               match introduction_node {
+                                       IntroductionNode::NodeId(pubkey) => (*pubkey, *blinding_point),
+                                       IntroductionNode::DirectedShortChannelId(..) => {
+                                               return Err(SendError::UnresolvedIntroductionNode);
+                                       },
+                               }
+                       }
                }
        };
        let (packet_payloads, packet_keys) = packet_payloads_and_keys(
-               &secp_ctx, &intermediate_nodes, destination, contents, reply_path, &blinding_secret)
-               .map_err(|e| SendError::Secp256k1(e))?;
+               &secp_ctx, &intermediate_nodes, destination, contents, reply_path, &blinding_secret
+       )?;
 
        let prng_seed = entropy_source.get_secure_random_bytes();
        let onion_routing_packet = construct_onion_message_packet(
@@ -647,9 +729,9 @@ where
                        Ok(PeeledOnion::Receive(message, path_id, reply_path))
                },
                Ok((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
-                       next_node_id, next_blinding_override
+                       next_hop, next_blinding_override
                })), Some((next_hop_hmac, new_packet_bytes)))) => {
-                       // TODO: we need to check whether `next_node_id` is our node, in which case this is a dummy
+                       // TODO: we need to check whether `next_hop` is our node, in which case this is a dummy
                        // blinded hop and this onion message is destined for us. In this situation, we should keep
                        // unwrapping the onion layers to get to the final payload. Since we don't have the option
                        // of creating blinded paths with dummy hops currently, we should be ok to not handle this
@@ -685,7 +767,7 @@ where
                                onion_routing_packet: outgoing_packet,
                        };
 
-                       Ok(PeeledOnion::Forward(next_node_id, onion_message))
+                       Ok(PeeledOnion::Forward(next_hop, onion_message))
                },
                Err(e) => {
                        log_trace!(logger, "Errored decoding onion message packet: {:?}", e);
@@ -698,12 +780,13 @@ where
        }
 }
 
-impl<ES: Deref, NS: Deref, L: Deref, MR: Deref, OMH: Deref, CMH: Deref>
-OnionMessenger<ES, NS, L, MR, OMH, CMH>
+impl<ES: Deref, NS: Deref, L: Deref, NL: Deref, MR: Deref, OMH: Deref, CMH: Deref>
+OnionMessenger<ES, NS, L, NL, MR, OMH, CMH>
 where
        ES::Target: EntropySource,
        NS::Target: NodeSigner,
        L::Target: Logger,
+       NL::Target: NodeIdLookUp,
        MR::Target: MessageRouter,
        OMH::Target: OffersMessageHandler,
        CMH::Target: CustomOnionMessageHandler,
@@ -711,8 +794,8 @@ where
        /// Constructs a new `OnionMessenger` to send, forward, and delegate received onion messages to
        /// their respective handlers.
        pub fn new(
-               entropy_source: ES, node_signer: NS, logger: L, message_router: MR, offers_handler: OMH,
-               custom_handler: CMH
+               entropy_source: ES, node_signer: NS, logger: L, node_id_lookup: NL, message_router: MR,
+               offers_handler: OMH, custom_handler: CMH
        ) -> Self {
                let mut secp_ctx = Secp256k1::new();
                secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes());
@@ -722,6 +805,7 @@ where
                        message_recipients: Mutex::new(new_hash_map()),
                        secp_ctx,
                        logger,
+                       node_id_lookup,
                        message_router,
                        offers_handler,
                        custom_handler,
@@ -804,7 +888,8 @@ where
                log_trace!(self.logger, "Constructing onion message {}: {:?}", log_suffix, contents);
 
                let (first_node_id, onion_message, addresses) = create_onion_message(
-                       &self.entropy_source, &self.node_signer, &self.secp_ctx, path, contents, reply_path
+                       &self.entropy_source, &self.node_signer, &self.node_id_lookup, &self.secp_ctx, path,
+                       contents, reply_path,
                )?;
 
                let mut message_recipients = self.message_recipients.lock().unwrap();
@@ -900,12 +985,13 @@ fn outbound_buffer_full(peer_node_id: &PublicKey, buffer: &HashMap<PublicKey, On
        false
 }
 
-impl<ES: Deref, NS: Deref, L: Deref, MR: Deref, OMH: Deref, CMH: Deref> EventsProvider
-for OnionMessenger<ES, NS, L, MR, OMH, CMH>
+impl<ES: Deref, NS: Deref, L: Deref, NL: Deref, MR: Deref, OMH: Deref, CMH: Deref> EventsProvider
+for OnionMessenger<ES, NS, L, NL, MR, OMH, CMH>
 where
        ES::Target: EntropySource,
        NS::Target: NodeSigner,
        L::Target: Logger,
+       NL::Target: NodeIdLookUp,
        MR::Target: MessageRouter,
        OMH::Target: OffersMessageHandler,
        CMH::Target: CustomOnionMessageHandler,
@@ -921,12 +1007,13 @@ where
        }
 }
 
-impl<ES: Deref, NS: Deref, L: Deref, MR: Deref, OMH: Deref, CMH: Deref> OnionMessageHandler
-for OnionMessenger<ES, NS, L, MR, OMH, CMH>
+impl<ES: Deref, NS: Deref, L: Deref, NL: Deref, MR: Deref, OMH: Deref, CMH: Deref> OnionMessageHandler
+for OnionMessenger<ES, NS, L, NL, MR, OMH, CMH>
 where
        ES::Target: EntropySource,
        NS::Target: NodeSigner,
        L::Target: Logger,
+       NL::Target: NodeIdLookUp,
        MR::Target: MessageRouter,
        OMH::Target: OffersMessageHandler,
        CMH::Target: CustomOnionMessageHandler,
@@ -961,7 +1048,18 @@ where
                                        },
                                }
                        },
-                       Ok(PeeledOnion::Forward(next_node_id, onion_message)) => {
+                       Ok(PeeledOnion::Forward(next_hop, onion_message)) => {
+                               let next_node_id = match next_hop {
+                                       NextHop::NodeId(pubkey) => pubkey,
+                                       NextHop::ShortChannelId(scid) => match self.node_id_lookup.next_node_id(scid) {
+                                               Some(pubkey) => pubkey,
+                                               None => {
+                                                       log_trace!(self.logger, "Dropping forwarded onion messager: unable to resolve next hop using SCID {}", scid);
+                                                       return
+                                               },
+                                       },
+                               };
+
                                let mut message_recipients = self.message_recipients.lock().unwrap();
                                if outbound_buffer_full(&next_node_id, &message_recipients) {
                                        log_trace!(
@@ -1097,6 +1195,7 @@ pub type SimpleArcOnionMessenger<M, T, F, L> = OnionMessenger<
        Arc<KeysManager>,
        Arc<KeysManager>,
        Arc<L>,
+       Arc<SimpleArcChannelManager<M, T, F, L>>,
        Arc<DefaultMessageRouter<Arc<NetworkGraph<Arc<L>>>, Arc<L>, Arc<KeysManager>>>,
        Arc<SimpleArcChannelManager<M, T, F, L>>,
        IgnoringMessageHandler
@@ -1116,8 +1215,9 @@ pub type SimpleRefOnionMessenger<
        &'a KeysManager,
        &'a KeysManager,
        &'b L,
-       &'i DefaultMessageRouter<&'g NetworkGraph<&'b L>, &'b L, &'a KeysManager>,
-       &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L>,
+       &'i SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L>,
+       &'j DefaultMessageRouter<&'g NetworkGraph<&'b L>, &'b L, &'a KeysManager>,
+       &'i SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L>,
        IgnoringMessageHandler
 >;
 
@@ -1126,14 +1226,23 @@ pub type SimpleRefOnionMessenger<
 fn packet_payloads_and_keys<T: OnionMessageContents, S: secp256k1::Signing + secp256k1::Verification>(
        secp_ctx: &Secp256k1<S>, unblinded_path: &[PublicKey], destination: Destination, message: T,
        mut reply_path: Option<BlindedPath>, session_priv: &SecretKey
-) -> Result<(Vec<(Payload<T>, [u8; 32])>, Vec<onion_utils::OnionKeys>), secp256k1::Error> {
+) -> Result<(Vec<(Payload<T>, [u8; 32])>, Vec<onion_utils::OnionKeys>), SendError> {
        let num_hops = unblinded_path.len() + destination.num_hops();
        let mut payloads = Vec::with_capacity(num_hops);
        let mut onion_packet_keys = Vec::with_capacity(num_hops);
 
-       let (mut intro_node_id_blinding_pt, num_blinded_hops) = if let Destination::BlindedPath(BlindedPath {
-               introduction_node_id, blinding_point, blinded_hops }) = &destination {
-               (Some((*introduction_node_id, *blinding_point)), blinded_hops.len()) } else { (None, 0) };
+       let (mut intro_node_id_blinding_pt, num_blinded_hops) = match &destination {
+               Destination::Node(_) => (None, 0),
+               Destination::BlindedPath(BlindedPath { introduction_node, blinding_point, blinded_hops }) => {
+                       let introduction_node_id = match introduction_node {
+                               IntroductionNode::NodeId(pubkey) => pubkey,
+                               IntroductionNode::DirectedShortChannelId(..) => {
+                                       return Err(SendError::UnresolvedIntroductionNode);
+                               },
+                       };
+                       (Some((*introduction_node_id, *blinding_point)), blinded_hops.len())
+               },
+       };
        let num_unblinded_hops = num_hops - num_blinded_hops;
 
        let mut unblinded_path_idx = 0;
@@ -1146,7 +1255,7 @@ fn packet_payloads_and_keys<T: OnionMessageContents, S: secp256k1::Signing + sec
                                if let Some(ss) = prev_control_tlvs_ss.take() {
                                        payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(
                                                ForwardTlvs {
-                                                       next_node_id: unblinded_pk_opt.unwrap(),
+                                                       next_hop: NextHop::NodeId(unblinded_pk_opt.unwrap()),
                                                        next_blinding_override: None,
                                                }
                                        )), ss));
@@ -1156,7 +1265,7 @@ fn packet_payloads_and_keys<T: OnionMessageContents, S: secp256k1::Signing + sec
                        } else if let Some((intro_node_id, blinding_pt)) = intro_node_id_blinding_pt.take() {
                                if let Some(control_tlvs_ss) = prev_control_tlvs_ss.take() {
                                        payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
-                                               next_node_id: intro_node_id,
+                                               next_hop: NextHop::NodeId(intro_node_id),
                                                next_blinding_override: Some(blinding_pt),
                                        })), control_tlvs_ss));
                                }
@@ -1181,7 +1290,7 @@ fn packet_payloads_and_keys<T: OnionMessageContents, S: secp256k1::Signing + sec
                                mu,
                        });
                }
-       )?;
+       ).map_err(|e| SendError::Secp256k1(e))?;
 
        if let Some(control_tlvs) = final_control_tlvs {
                payloads.push((Payload::Receive {
index d9349fdadbfaba6c1f0d08c265c48bf14da49bb7..510f0ea025a0d615b0f54292d865602ac7e103a6 100644 (file)
@@ -13,7 +13,7 @@ use bitcoin::secp256k1::PublicKey;
 use bitcoin::secp256k1::ecdh::SharedSecret;
 
 use crate::blinded_path::BlindedPath;
-use crate::blinded_path::message::{ForwardTlvs, ReceiveTlvs};
+use crate::blinded_path::message::{ForwardTlvs, NextHop, ReceiveTlvs};
 use crate::blinded_path::utils::Padding;
 use crate::ln::msgs::DecodeError;
 use crate::ln::onion_utils;
@@ -284,20 +284,26 @@ impl Readable for ControlTlvs {
        fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
                _init_and_read_tlv_stream!(r, {
                        (1, _padding, option),
-                       (2, _short_channel_id, option),
+                       (2, short_channel_id, option),
                        (4, next_node_id, option),
                        (6, path_id, option),
                        (8, next_blinding_override, option),
                });
                let _padding: Option<Padding> = _padding;
-               let _short_channel_id: Option<u64> = _short_channel_id;
 
-               let valid_fwd_fmt  = next_node_id.is_some() && path_id.is_none();
-               let valid_recv_fmt = next_node_id.is_none() && next_blinding_override.is_none();
+               let next_hop = match (short_channel_id, next_node_id) {
+                       (Some(_), Some(_)) => return Err(DecodeError::InvalidValue),
+                       (Some(scid), None) => Some(NextHop::ShortChannelId(scid)),
+                       (None, Some(pubkey)) => Some(NextHop::NodeId(pubkey)),
+                       (None, None) => None,
+               };
+
+               let valid_fwd_fmt = next_hop.is_some() && path_id.is_none();
+               let valid_recv_fmt = next_hop.is_none() && next_blinding_override.is_none();
 
                let payload_fmt = if valid_fwd_fmt {
                        ControlTlvs::Forward(ForwardTlvs {
-                               next_node_id: next_node_id.unwrap(),
+                               next_hop: next_hop.unwrap(),
                                next_blinding_override,
                        })
                } else if valid_recv_fmt {
index e8276712ee89504e002b619aa70544a147467840..59ec3f6186236d07b13ec3ae7a74b3b74dce3b58 100644 (file)
@@ -11,7 +11,7 @@
 
 use bitcoin::secp256k1::{PublicKey, Secp256k1, self};
 
-use crate::blinded_path::{BlindedHop, BlindedPath};
+use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode};
 use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs};
 use crate::ln::PaymentHash;
 use crate::ln::channelmanager::{ChannelDetails, PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA};
@@ -1144,11 +1144,11 @@ pub struct FirstHopCandidate<'a> {
        ///
        /// [`find_route`] validates this prior to constructing a [`CandidateRouteHop`].
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub details: &'a ChannelDetails,
        /// The node id of the payer, which is also the source side of this candidate route hop.
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub payer_node_id: &'a NodeId,
 }
 
@@ -1158,7 +1158,7 @@ pub struct PublicHopCandidate<'a> {
        /// Information about the channel, including potentially its capacity and
        /// direction-specific information.
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub info: DirectedChannelInfo<'a>,
        /// The short channel ID of the channel, i.e. the identifier by which we refer to this
        /// channel.
@@ -1170,21 +1170,26 @@ pub struct PublicHopCandidate<'a> {
 pub struct PrivateHopCandidate<'a> {
        /// Information about the private hop communicated via BOLT 11.
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub hint: &'a RouteHintHop,
        /// Node id of the next hop in BOLT 11 route hint.
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub target_node_id: &'a NodeId
 }
 
 /// A [`CandidateRouteHop::Blinded`] entry.
 #[derive(Clone, Debug)]
 pub struct BlindedPathCandidate<'a> {
+       /// The node id of the introduction node, resolved from either the [`NetworkGraph`] or first
+       /// hops.
+       ///
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
+       pub source_node_id: &'a NodeId,
        /// Information about the blinded path including the fee, HTLC amount limits, and
        /// cryptographic material required to build an HTLC through the given path.
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub hint: &'a (BlindedPayInfo, BlindedPath),
        /// Index of the hint in the original list of blinded hints.
        ///
@@ -1196,12 +1201,17 @@ pub struct BlindedPathCandidate<'a> {
 /// A [`CandidateRouteHop::OneHopBlinded`] entry.
 #[derive(Clone, Debug)]
 pub struct OneHopBlindedPathCandidate<'a> {
+       /// The node id of the introduction node, resolved from either the [`NetworkGraph`] or first
+       /// hops.
+       ///
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
+       pub source_node_id: &'a NodeId,
        /// Information about the blinded path including the fee, HTLC amount limits, and
        /// cryptographic material required to build an HTLC terminating with the given path.
        ///
        /// Note that the [`BlindedPayInfo`] is ignored here.
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub hint: &'a (BlindedPayInfo, BlindedPath),
        /// Index of the hint in the original list of blinded hints.
        ///
@@ -1409,8 +1419,8 @@ impl<'a> CandidateRouteHop<'a> {
                        CandidateRouteHop::FirstHop(hop) => *hop.payer_node_id,
                        CandidateRouteHop::PublicHop(hop) => *hop.info.source(),
                        CandidateRouteHop::PrivateHop(hop) => hop.hint.src_node_id.into(),
-                       CandidateRouteHop::Blinded(hop) => hop.hint.1.introduction_node_id.into(),
-                       CandidateRouteHop::OneHopBlinded(hop) => hop.hint.1.introduction_node_id.into(),
+                       CandidateRouteHop::Blinded(hop) => *hop.source_node_id,
+                       CandidateRouteHop::OneHopBlinded(hop) => *hop.source_node_id,
                }
        }
        /// Returns the target node id of this hop, if known.
@@ -1725,8 +1735,20 @@ impl<'a> fmt::Display for LoggedCandidateHop<'a> {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                match self.0 {
                        CandidateRouteHop::Blinded(BlindedPathCandidate { hint, .. }) | CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, .. }) => {
-                               "blinded route hint with introduction node id ".fmt(f)?;
-                               hint.1.introduction_node_id.fmt(f)?;
+                               "blinded route hint with introduction node ".fmt(f)?;
+                               match &hint.1.introduction_node {
+                                       IntroductionNode::NodeId(pubkey) => write!(f, "id {}", pubkey)?,
+                                       IntroductionNode::DirectedShortChannelId(direction, scid) => {
+                                               match direction {
+                                                       Direction::NodeOne => {
+                                                               write!(f, "one on channel with SCID {}", scid)?;
+                                                       },
+                                                       Direction::NodeTwo => {
+                                                               write!(f, "two on channel with SCID {}", scid)?;
+                                                       },
+                                               }
+                                       }
+                               }
                                " and blinding point ".fmt(f)?;
                                hint.1.blinding_point.fmt(f)
                        },
@@ -1852,6 +1874,9 @@ where L::Target: Logger {
                return Err(LightningError{err: "Cannot send a payment of 0 msat".to_owned(), action: ErrorAction::IgnoreError});
        }
 
+       let introduction_node_id_cache = payment_params.payee.blinded_route_hints().iter()
+               .map(|(_, path)| path.public_introduction_node_id(network_graph))
+               .collect::<Vec<_>>();
        match &payment_params.payee {
                Payee::Clear { route_hints, node_id, .. } => {
                        for route in route_hints.iter() {
@@ -1863,17 +1888,19 @@ where L::Target: Logger {
                        }
                },
                Payee::Blinded { route_hints, .. } => {
-                       if route_hints.iter().all(|(_, path)| &path.introduction_node_id == our_node_pubkey) {
+                       if introduction_node_id_cache.iter().all(|introduction_node_id| *introduction_node_id == Some(&our_node_id)) {
                                return Err(LightningError{err: "Cannot generate a route to blinded paths if we are the introduction node to all of them".to_owned(), action: ErrorAction::IgnoreError});
                        }
-                       for (_, blinded_path) in route_hints.iter() {
+                       for ((_, blinded_path), introduction_node_id) in route_hints.iter().zip(introduction_node_id_cache.iter()) {
                                if blinded_path.blinded_hops.len() == 0 {
                                        return Err(LightningError{err: "0-hop blinded path provided".to_owned(), action: ErrorAction::IgnoreError});
-                               } else if &blinded_path.introduction_node_id == our_node_pubkey {
+                               } else if *introduction_node_id == Some(&our_node_id) {
                                        log_info!(logger, "Got blinded path with ourselves as the introduction node, ignoring");
                                } else if blinded_path.blinded_hops.len() == 1 &&
-                                       route_hints.iter().any( |(_, p)| p.blinded_hops.len() == 1
-                                               && p.introduction_node_id != blinded_path.introduction_node_id)
+                                       route_hints
+                                               .iter().zip(introduction_node_id_cache.iter())
+                                               .filter(|((_, p), _)| p.blinded_hops.len() == 1)
+                                               .any(|(_, p_introduction_node_id)| p_introduction_node_id != introduction_node_id)
                                {
                                        return Err(LightningError{err: format!("1-hop blinded paths must all have matching introduction node ids"), action: ErrorAction::IgnoreError});
                                }
@@ -2515,26 +2542,53 @@ where L::Target: Logger {
                // earlier than general path finding, they will be somewhat prioritized, although currently
                // it matters only if the fees are exactly the same.
                for (hint_idx, hint) in payment_params.payee.blinded_route_hints().iter().enumerate() {
-                       let intro_node_id = NodeId::from_pubkey(&hint.1.introduction_node_id);
-                       let have_intro_node_in_graph =
-                               // Only add the hops in this route to our candidate set if either
-                               // we have a direct channel to the first hop or the first hop is
-                               // in the regular network graph.
-                               first_hop_targets.get(&intro_node_id).is_some() ||
-                               network_nodes.get(&intro_node_id).is_some();
-                       if !have_intro_node_in_graph || our_node_id == intro_node_id { continue }
+                       // Only add the hops in this route to our candidate set if either
+                       // we have a direct channel to the first hop or the first hop is
+                       // in the regular network graph.
+                       let source_node_id = match introduction_node_id_cache[hint_idx] {
+                               Some(node_id) => node_id,
+                               None => match &hint.1.introduction_node {
+                                       IntroductionNode::NodeId(pubkey) => {
+                                               let node_id = NodeId::from_pubkey(&pubkey);
+                                               match first_hop_targets.get_key_value(&node_id).map(|(key, _)| key) {
+                                                       Some(node_id) => node_id,
+                                                       None => continue,
+                                               }
+                                       },
+                                       IntroductionNode::DirectedShortChannelId(direction, scid) => {
+                                               let first_hop = first_hop_targets.iter().find(|(_, channels)|
+                                                       channels
+                                                               .iter()
+                                                               .any(|details| Some(*scid) == details.get_outbound_payment_scid())
+                                               );
+                                               match first_hop {
+                                                       Some((counterparty_node_id, _)) => {
+                                                               direction.select_node_id(&our_node_id, counterparty_node_id)
+                                                       },
+                                                       None => continue,
+                                               }
+                                       },
+                               },
+                       };
+                       if our_node_id == *source_node_id { continue }
                        let candidate = if hint.1.blinded_hops.len() == 1 {
-                               CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, hint_idx })
-                       } else { CandidateRouteHop::Blinded(BlindedPathCandidate { hint, hint_idx }) };
+                               CandidateRouteHop::OneHopBlinded(
+                                       OneHopBlindedPathCandidate { source_node_id, hint, hint_idx }
+                               )
+                       } else {
+                               CandidateRouteHop::Blinded(BlindedPathCandidate { source_node_id, hint, hint_idx })
+                       };
                        let mut path_contribution_msat = path_value_msat;
                        if let Some(hop_used_msat) = add_entry!(&candidate,
                                0, path_contribution_msat, 0, 0_u64, 0, 0)
                        {
                                path_contribution_msat = hop_used_msat;
                        } else { continue }
-                       if let Some(first_channels) = first_hop_targets.get_mut(&NodeId::from_pubkey(&hint.1.introduction_node_id)) {
-                               sort_first_hop_channels(first_channels, &used_liquidities, recommended_value_msat,
-                                       our_node_pubkey);
+                       if let Some(first_channels) = first_hop_targets.get(source_node_id) {
+                               let mut first_channels = first_channels.clone();
+                               sort_first_hop_channels(
+                                       &mut first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey
+                               );
                                for details in first_channels {
                                        let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
                                                details, payer_node_id: &our_node_id,
@@ -2630,9 +2684,11 @@ where L::Target: Logger {
                                                .saturating_add(1);
 
                                        // Searching for a direct channel between last checked hop and first_hop_targets
-                                       if let Some(first_channels) = first_hop_targets.get_mut(target) {
-                                               sort_first_hop_channels(first_channels, &used_liquidities,
-                                                       recommended_value_msat, our_node_pubkey);
+                                       if let Some(first_channels) = first_hop_targets.get(target) {
+                                               let mut first_channels = first_channels.clone();
+                                               sort_first_hop_channels(
+                                                       &mut first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey
+                                               );
                                                for details in first_channels {
                                                        let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
                                                                details, payer_node_id: &our_node_id,
@@ -2677,9 +2733,11 @@ where L::Target: Logger {
                                                // Note that we *must* check if the last hop was added as `add_entry`
                                                // always assumes that the third argument is a node to which we have a
                                                // path.
-                                               if let Some(first_channels) = first_hop_targets.get_mut(&NodeId::from_pubkey(&hop.src_node_id)) {
-                                                       sort_first_hop_channels(first_channels, &used_liquidities,
-                                                               recommended_value_msat, our_node_pubkey);
+                                               if let Some(first_channels) = first_hop_targets.get(&NodeId::from_pubkey(&hop.src_node_id)) {
+                                                       let mut first_channels = first_channels.clone();
+                                                       sort_first_hop_channels(
+                                                               &mut first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey
+                                                       );
                                                        for details in first_channels {
                                                                let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
                                                                        details, payer_node_id: &our_node_id,
@@ -3223,7 +3281,7 @@ fn build_route_from_hops_internal<L: Deref>(
 
 #[cfg(test)]
 mod tests {
-       use crate::blinded_path::{BlindedHop, BlindedPath};
+       use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
        use crate::routing::gossip::{NetworkGraph, P2PGossipSync, NodeId, EffectiveCapacity};
        use crate::routing::utxo::UtxoResult;
        use crate::routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features,
@@ -5090,7 +5148,7 @@ mod tests {
                // MPP to a 1-hop blinded path for nodes[2]
                let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
                let blinded_path = BlindedPath {
-                       introduction_node_id: nodes[2],
+                       introduction_node: IntroductionNode::NodeId(nodes[2]),
                        blinding_point: ln_test_utils::pubkey(42),
                        blinded_hops: vec![BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }],
                };
@@ -5108,18 +5166,18 @@ mod tests {
 
                // MPP to 3 2-hop blinded paths
                let mut blinded_path_node_0 = blinded_path.clone();
-               blinded_path_node_0.introduction_node_id = nodes[0];
+               blinded_path_node_0.introduction_node = IntroductionNode::NodeId(nodes[0]);
                blinded_path_node_0.blinded_hops.push(blinded_path.blinded_hops[0].clone());
                let mut node_0_payinfo = blinded_payinfo.clone();
                node_0_payinfo.htlc_maximum_msat = 50_000;
 
                let mut blinded_path_node_7 = blinded_path_node_0.clone();
-               blinded_path_node_7.introduction_node_id = nodes[7];
+               blinded_path_node_7.introduction_node = IntroductionNode::NodeId(nodes[7]);
                let mut node_7_payinfo = blinded_payinfo.clone();
                node_7_payinfo.htlc_maximum_msat = 60_000;
 
                let mut blinded_path_node_1 = blinded_path_node_0.clone();
-               blinded_path_node_1.introduction_node_id = nodes[1];
+               blinded_path_node_1.introduction_node = IntroductionNode::NodeId(nodes[1]);
                let mut node_1_payinfo = blinded_payinfo.clone();
                node_1_payinfo.htlc_maximum_msat = 180_000;
 
@@ -5301,10 +5359,15 @@ mod tests {
                                if let Some(bt) = &path.blinded_tail {
                                        assert_eq!(path.hops.len() + if bt.hops.len() == 1 { 0 } else { 1 }, 2);
                                        if bt.hops.len() > 1 {
-                                               assert_eq!(path.hops.last().unwrap().pubkey,
+                                               let network_graph = network_graph.read_only();
+                                               assert_eq!(
+                                                       NodeId::from_pubkey(&path.hops.last().unwrap().pubkey),
                                                        payment_params.payee.blinded_route_hints().iter()
                                                                .find(|(p, _)| p.htlc_maximum_msat == path.final_value_msat())
-                                                               .map(|(_, p)| p.introduction_node_id).unwrap());
+                                                               .and_then(|(_, p)| p.public_introduction_node_id(&network_graph))
+                                                               .copied()
+                                                               .unwrap()
+                                               );
                                        } else {
                                                assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
                                        }
@@ -7200,7 +7263,7 @@ mod tests {
 
                // Make sure this works for blinded route hints.
                let blinded_path = BlindedPath {
-                       introduction_node_id: intermed_node_id,
+                       introduction_node: IntroductionNode::NodeId(intermed_node_id),
                        blinding_point: ln_test_utils::pubkey(42),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: ln_test_utils::pubkey(42), encrypted_payload: vec![] },
@@ -7234,7 +7297,7 @@ mod tests {
        #[test]
        fn blinded_route_ser() {
                let blinded_path_1 = BlindedPath {
-                       introduction_node_id: ln_test_utils::pubkey(42),
+                       introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(42)),
                        blinding_point: ln_test_utils::pubkey(43),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: ln_test_utils::pubkey(44), encrypted_payload: Vec::new() },
@@ -7242,7 +7305,7 @@ mod tests {
                        ],
                };
                let blinded_path_2 = BlindedPath {
-                       introduction_node_id: ln_test_utils::pubkey(46),
+                       introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(46)),
                        blinding_point: ln_test_utils::pubkey(47),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: ln_test_utils::pubkey(48), encrypted_payload: Vec::new() },
@@ -7301,7 +7364,7 @@ mod tests {
                // account for the blinded tail's final amount_msat.
                let mut inflight_htlcs = InFlightHtlcs::new();
                let blinded_path = BlindedPath {
-                       introduction_node_id: ln_test_utils::pubkey(43),
+                       introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(43)),
                        blinding_point: ln_test_utils::pubkey(48),
                        blinded_hops: vec![BlindedHop { blinded_node_id: ln_test_utils::pubkey(49), encrypted_payload: Vec::new() }],
                };
@@ -7316,7 +7379,7 @@ mod tests {
                                maybe_announced_channel: false,
                        },
                        RouteHop {
-                               pubkey: blinded_path.introduction_node_id,
+                               pubkey: ln_test_utils::pubkey(43),
                                node_features: NodeFeatures::empty(),
                                short_channel_id: 43,
                                channel_features: ChannelFeatures::empty(),
@@ -7340,7 +7403,7 @@ mod tests {
        fn blinded_path_cltv_shadow_offset() {
                // Make sure we add a shadow offset when sending to blinded paths.
                let blinded_path = BlindedPath {
-                       introduction_node_id: ln_test_utils::pubkey(43),
+                       introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(43)),
                        blinding_point: ln_test_utils::pubkey(44),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: ln_test_utils::pubkey(45), encrypted_payload: Vec::new() },
@@ -7358,7 +7421,7 @@ mod tests {
                                maybe_announced_channel: false,
                        },
                        RouteHop {
-                               pubkey: blinded_path.introduction_node_id,
+                               pubkey: ln_test_utils::pubkey(43),
                                node_features: NodeFeatures::empty(),
                                short_channel_id: 43,
                                channel_features: ChannelFeatures::empty(),
@@ -7400,7 +7463,7 @@ mod tests {
                let random_seed_bytes = keys_manager.get_secure_random_bytes();
 
                let mut blinded_path = BlindedPath {
-                       introduction_node_id: nodes[2],
+                       introduction_node: IntroductionNode::NodeId(nodes[2]),
                        blinding_point: ln_test_utils::pubkey(42),
                        blinded_hops: Vec::with_capacity(num_blinded_hops),
                };
@@ -7432,7 +7495,10 @@ mod tests {
                assert_eq!(tail.final_value_msat, 1001);
 
                let final_hop = route.paths[0].hops.last().unwrap();
-               assert_eq!(final_hop.pubkey, blinded_path.introduction_node_id);
+               assert_eq!(
+                       NodeId::from_pubkey(&final_hop.pubkey),
+                       *blinded_path.public_introduction_node_id(&network_graph).unwrap()
+               );
                if tail.hops.len() > 1 {
                        assert_eq!(final_hop.fee_msat,
                                blinded_payinfo.fee_base_msat as u64 + blinded_payinfo.fee_proportional_millionths as u64 * tail.final_value_msat / 1000000);
@@ -7455,7 +7521,7 @@ mod tests {
                let random_seed_bytes = keys_manager.get_secure_random_bytes();
 
                let mut invalid_blinded_path = BlindedPath {
-                       introduction_node_id: nodes[2],
+                       introduction_node: IntroductionNode::NodeId(nodes[2]),
                        blinding_point: ln_test_utils::pubkey(42),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: ln_test_utils::pubkey(43), encrypted_payload: vec![0; 43] },
@@ -7471,7 +7537,7 @@ mod tests {
                };
 
                let mut invalid_blinded_path_2 = invalid_blinded_path.clone();
-               invalid_blinded_path_2.introduction_node_id = ln_test_utils::pubkey(45);
+               invalid_blinded_path_2.introduction_node = IntroductionNode::NodeId(ln_test_utils::pubkey(45));
                let payment_params = PaymentParameters::blinded(vec![
                        (blinded_payinfo.clone(), invalid_blinded_path.clone()),
                        (blinded_payinfo.clone(), invalid_blinded_path_2)]);
@@ -7485,7 +7551,7 @@ mod tests {
                        _ => panic!("Expected error")
                }
 
-               invalid_blinded_path.introduction_node_id = our_id;
+               invalid_blinded_path.introduction_node = IntroductionNode::NodeId(our_id);
                let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), invalid_blinded_path.clone())]);
                let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001);
                match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer,
@@ -7497,7 +7563,7 @@ mod tests {
                        _ => panic!("Expected error")
                }
 
-               invalid_blinded_path.introduction_node_id = ln_test_utils::pubkey(46);
+               invalid_blinded_path.introduction_node = IntroductionNode::NodeId(ln_test_utils::pubkey(46));
                invalid_blinded_path.blinded_hops.clear();
                let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo, invalid_blinded_path)]);
                let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001);
@@ -7526,7 +7592,7 @@ mod tests {
 
                let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
                let blinded_path_1 = BlindedPath {
-                       introduction_node_id: nodes[2],
+                       introduction_node: IntroductionNode::NodeId(nodes[2]),
                        blinding_point: ln_test_utils::pubkey(42),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
@@ -7623,7 +7689,7 @@ mod tests {
                        get_channel_details(Some(1), nodes[1], InitFeatures::from_le_bytes(vec![0b11]), 10_000_000)];
 
                let blinded_path = BlindedPath {
-                       introduction_node_id: nodes[1],
+                       introduction_node: IntroductionNode::NodeId(nodes[1]),
                        blinding_point: ln_test_utils::pubkey(42),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
@@ -7692,7 +7758,7 @@ mod tests {
                                18446744073709551615)];
 
                let blinded_path = BlindedPath {
-                       introduction_node_id: nodes[1],
+                       introduction_node: IntroductionNode::NodeId(nodes[1]),
                        blinding_point: ln_test_utils::pubkey(42),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
@@ -7748,7 +7814,7 @@ mod tests {
                let amt_msat = 21_7020_5185_1423_0019;
 
                let blinded_path = BlindedPath {
-                       introduction_node_id: our_id,
+                       introduction_node: IntroductionNode::NodeId(our_id),
                        blinding_point: ln_test_utils::pubkey(42),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
@@ -7767,7 +7833,7 @@ mod tests {
                        (blinded_payinfo.clone(), blinded_path.clone()),
                        (blinded_payinfo.clone(), blinded_path.clone()),
                ];
-               blinded_hints[1].1.introduction_node_id = nodes[6];
+               blinded_hints[1].1.introduction_node = IntroductionNode::NodeId(nodes[6]);
 
                let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
                let payment_params = PaymentParameters::blinded(blinded_hints.clone())
@@ -7800,7 +7866,7 @@ mod tests {
                let amt_msat = 21_7020_5185_1423_0019;
 
                let blinded_path = BlindedPath {
-                       introduction_node_id: our_id,
+                       introduction_node: IntroductionNode::NodeId(our_id),
                        blinding_point: ln_test_utils::pubkey(42),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
@@ -7824,7 +7890,7 @@ mod tests {
                blinded_hints[1].0.htlc_minimum_msat = 21_7020_5185_1423_0019;
                blinded_hints[1].0.htlc_maximum_msat = 1844_6744_0737_0955_1615;
 
-               blinded_hints[2].1.introduction_node_id = nodes[6];
+               blinded_hints[2].1.introduction_node = IntroductionNode::NodeId(nodes[6]);
 
                let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
                let payment_params = PaymentParameters::blinded(blinded_hints.clone())
@@ -7871,7 +7937,7 @@ mod tests {
                let htlc_min = 2_5165_8240;
                let payment_params = if blinded_payee {
                        let blinded_path = BlindedPath {
-                               introduction_node_id: nodes[0],
+                               introduction_node: IntroductionNode::NodeId(nodes[0]),
                                blinding_point: ln_test_utils::pubkey(42),
                                blinded_hops: vec![
                                        BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
@@ -7951,7 +8017,7 @@ mod tests {
                let htlc_mins = [1_4392, 19_7401, 1027, 6_5535];
                let payment_params = if blinded_payee {
                        let blinded_path = BlindedPath {
-                               introduction_node_id: nodes[0],
+                               introduction_node: IntroductionNode::NodeId(nodes[0]),
                                blinding_point: ln_test_utils::pubkey(42),
                                blinded_hops: vec![
                                        BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
@@ -8052,7 +8118,7 @@ mod tests {
                                cltv_expiry_delta: 10,
                                features: BlindedHopFeatures::empty(),
                        }, BlindedPath {
-                               introduction_node_id: nodes[0],
+                               introduction_node: IntroductionNode::NodeId(nodes[0]),
                                blinding_point: ln_test_utils::pubkey(42),
                                blinded_hops: vec![
                                        BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
@@ -8102,7 +8168,7 @@ mod tests {
                let htlc_mins = [49_0000, 1125_0000];
                let payment_params = {
                        let blinded_path = BlindedPath {
-                               introduction_node_id: nodes[0],
+                               introduction_node: IntroductionNode::NodeId(nodes[0]),
                                blinding_point: ln_test_utils::pubkey(42),
                                blinded_hops: vec![
                                        BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
index 4850479b8992905cc20e6d1e219dab6251081b28..4cb9144d3394ed94896b861e5ac847994776f122 100644 (file)
@@ -2152,7 +2152,7 @@ impl Readable for ChannelLiquidity {
 #[cfg(test)]
 mod tests {
        use super::{ChannelLiquidity, HistoricalBucketRangeTracker, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters, ProbabilisticScorer};
-       use crate::blinded_path::{BlindedHop, BlindedPath};
+       use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode};
        use crate::util::config::UserConfig;
 
        use crate::ln::channelmanager;
@@ -3567,7 +3567,7 @@ mod tests {
                let mut path = payment_path_for_amount(768);
                let recipient_hop = path.hops.pop().unwrap();
                let blinded_path = BlindedPath {
-                       introduction_node_id: path.hops.last().as_ref().unwrap().pubkey,
+                       introduction_node: IntroductionNode::NodeId(path.hops.last().as_ref().unwrap().pubkey),
                        blinding_point: test_utils::pubkey(42),
                        blinded_hops: vec![
                                BlindedHop { blinded_node_id: test_utils::pubkey(44), encrypted_payload: Vec::new() }
index 4f694bd2b6ec723e5dcaed614db36bdad0ea2aeb..97788ffe68acbd5f90d67bd501710c3b5942ce51 100644 (file)
@@ -56,6 +56,11 @@ impl<K: Clone + Hash + Ord, V> IndexedMap<K, V> {
                self.map.get_mut(key)
        }
 
+       /// Fetches the key-value pair corresponding to the supplied key, if one exists.
+       pub fn get_key_value(&self, key: &K) -> Option<(&K, &V)> {
+               self.map.get_key_value(key)
+       }
+
        #[inline]
        /// Returns true if an element with the given `key` exists in the map.
        pub fn contains_key(&self, key: &K) -> bool {
index df030d0b01eb4452ccf8e7b391e8c738bb9af453..740b7c12561ce8466d1a2e30f56faacc797961ac 100644 (file)
@@ -1148,7 +1148,7 @@ mod tests {
 
        use crate::io::{self, Cursor};
        use crate::ln::msgs::DecodeError;
-       use crate::util::ser::{Writeable, HighZeroBytesDroppedBigSize, VecWriter};
+       use crate::util::ser::{MaybeReadable, Readable, Writeable, HighZeroBytesDroppedBigSize, VecWriter};
        use bitcoin::hashes::hex::FromHex;
        use bitcoin::secp256k1::PublicKey;
 
@@ -1258,6 +1258,131 @@ mod tests {
                } else { panic!(); }
        }
 
+       /// A "V1" enum with only one variant
+       enum InnerEnumV1 {
+               StructVariantA {
+                       field: u32,
+               },
+       }
+
+       impl_writeable_tlv_based_enum_upgradable!(InnerEnumV1,
+               (0, StructVariantA) => {
+                       (0, field, required),
+               },
+       );
+
+       struct OuterStructOptionalEnumV1 {
+               inner_enum: Option<InnerEnumV1>,
+               other_field: u32,
+       }
+
+       impl_writeable_tlv_based!(OuterStructOptionalEnumV1, {
+               (0, inner_enum, upgradable_option),
+               (2, other_field, required),
+       });
+
+       /// An upgraded version of [`InnerEnumV1`] that added a second variant
+       enum InnerEnumV2 {
+               StructVariantA {
+                       field: u32,
+               },
+               StructVariantB {
+                       field2: u64,
+               }
+       }
+
+       impl_writeable_tlv_based_enum_upgradable!(InnerEnumV2,
+               (0, StructVariantA) => {
+                       (0, field, required),
+               },
+               (1, StructVariantB) => {
+                       (0, field2, required),
+               },
+       );
+
+       struct OuterStructOptionalEnumV2 {
+               inner_enum: Option<InnerEnumV2>,
+               other_field: u32,
+       }
+
+       impl_writeable_tlv_based!(OuterStructOptionalEnumV2, {
+               (0, inner_enum, upgradable_option),
+               (2, other_field, required),
+       });
+
+       #[test]
+       fn upgradable_enum_option() {
+               // Test downgrading from `OuterStructOptionalEnumV2` to `OuterStructOptionalEnumV1` and
+               // ensure we still read the `other_field` just fine.
+               let serialized_bytes = OuterStructOptionalEnumV2 {
+                       inner_enum: Some(InnerEnumV2::StructVariantB { field2: 64 }),
+                       other_field: 0x1bad1dea,
+               }.encode();
+               let mut s = Cursor::new(serialized_bytes);
+
+               let outer_struct: OuterStructOptionalEnumV1 = Readable::read(&mut s).unwrap();
+               assert!(outer_struct.inner_enum.is_none());
+               assert_eq!(outer_struct.other_field, 0x1bad1dea);
+       }
+
+       /// A struct that is read with an [`InnerEnumV1`] but is written with an [`InnerEnumV2`].
+       struct OuterStructRequiredEnum {
+               #[allow(unused)]
+               inner_enum: InnerEnumV1,
+       }
+
+       impl MaybeReadable for OuterStructRequiredEnum {
+               fn read<R: io::Read>(reader: &mut R) -> Result<Option<Self>, DecodeError> {
+                       let mut inner_enum = crate::util::ser::UpgradableRequired(None);
+                       read_tlv_fields!(reader, {
+                               (0, inner_enum, upgradable_required),
+                       });
+                       Ok(Some(Self {
+                               inner_enum: inner_enum.0.unwrap(),
+                       }))
+               }
+       }
+
+       impl Writeable for OuterStructRequiredEnum {
+               fn write<W: crate::util::ser::Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+                       write_tlv_fields!(writer, {
+                               (0, InnerEnumV2::StructVariantB { field2: 0xdeadbeef }, required),
+                       });
+                       Ok(())
+               }
+       }
+
+       struct OuterOuterStruct {
+               outer_struct: Option<OuterStructRequiredEnum>,
+               other_field: u32,
+       }
+
+       impl_writeable_tlv_based!(OuterOuterStruct, {
+               (0, outer_struct, upgradable_option),
+               (2, other_field, required),
+       });
+
+
+       #[test]
+       fn upgradable_enum_required() {
+               // Test downgrading from an `OuterOuterStruct` (i.e. test downgrading an
+               // `upgradable_required` `InnerEnumV2` to an `InnerEnumV1`).
+               //
+               // Note that `OuterStructRequiredEnum` has a split write/read implementation that writes an
+               // `InnerEnumV2::StructVariantB` irrespective of the value of `inner_enum`.
+
+               let dummy_inner_enum = InnerEnumV1::StructVariantA { field: 42 };
+               let serialized_bytes = OuterOuterStruct {
+                       outer_struct: Some(OuterStructRequiredEnum { inner_enum: dummy_inner_enum }),
+                       other_field: 0x1bad1dea,
+               }.encode();
+               let mut s = Cursor::new(serialized_bytes);
+
+               let outer_outer_struct: OuterOuterStruct = Readable::read(&mut s).unwrap();
+               assert!(outer_outer_struct.outer_struct.is_none());
+               assert_eq!(outer_outer_struct.other_field, 0x1bad1dea);
+       }
+
        // BOLT TLV test cases
        fn tlv_reader_n1(s: &[u8]) -> Result<(Option<HighZeroBytesDroppedBigSize<u64>>, Option<u64>, Option<(PublicKey, u64, u64)>, Option<u16>), DecodeError> {
                let mut s = Cursor::new(s);