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;
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 = {
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};
});
}
(payinfo, BlindedPath {
- introduction_node_id: hop.src_node_id,
+ introduction_node: IntroductionNode::NodeId(hop.src_node_id),
blinding_point: dummy_pk,
blinded_hops,
})
#[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;
/// 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>,
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(())
) -> 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)
// 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 => {
}
};
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(())
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};
#[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.
///
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.
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(|_| ())?,
})
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");
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 {
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) }
blinded_hops.push(Readable::read(r)?);
}
Ok(BlindedPath {
- introduction_node_id,
+ introduction_node,
blinding_point,
blinded_hops,
})
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,
+ }
+ }
+}
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.
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};
}
}
+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 {
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,
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()));
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;
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::*;
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.
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));
}
}
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()));
}
}
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]);
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);
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);
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);
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);
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]);
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);
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);
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);
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);
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,
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 {
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() {
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;
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.
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;
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] },
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;
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] },
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] },
],
},
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] },
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] },
],
})
.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] },
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};
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] },
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] },
],
},
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] },
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] },
],
},
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] },
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;
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] },
],
},
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] },
//! 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};
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>,
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())
);
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,
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;
/// # 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};
/// # 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)]
///
/// [`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,
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,
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())
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())
}
}
}
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,
}
}
- 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,
+ }
+ },
}
}
}
///
/// [`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
#[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 {
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)?;
}
}
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(
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
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);
}
}
-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,
/// 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());
message_recipients: Mutex::new(new_hash_map()),
secp_ctx,
logger,
+ node_id_lookup,
message_router,
offers_handler,
custom_handler,
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();
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,
}
}
-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,
},
}
},
- 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!(
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
&'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
>;
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;
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));
} 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));
}
mu,
});
}
- )?;
+ ).map_err(|e| SendError::Secp256k1(e))?;
if let Some(control_tlvs) = final_control_tlvs {
payloads.push((Payload::Receive {
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;
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 {
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};
///
/// [`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,
}
/// 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.
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.
///
/// 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.
///
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.
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)
},
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() {
}
},
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});
}
// 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,
.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,
// 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,
#[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,
// 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() }],
};
// 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;
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]);
}
// 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![] },
#[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() },
],
};
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() },
// 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() }],
};
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(),
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() },
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(),
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),
};
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);
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] },
};
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)]);
_ => 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,
_ => 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);
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() },
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() },
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() },
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() },
(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())
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() },
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())
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() },
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() },
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() },
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() },
#[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;
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() }
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 {
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;
} 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);