mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-03-15 15:39:09 +01:00
Merge pull request #2781 from jkczyz/2023-09-multihop-paths
Multi-hop `BlindedPath` creation interface
This commit is contained in:
commit
b9797ebdd9
11 changed files with 459 additions and 126 deletions
|
@ -30,6 +30,8 @@ use bitcoin::hashes::sha256::Hash as Sha256;
|
|||
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
|
||||
use bitcoin::hash_types::{BlockHash, WPubkeyHash};
|
||||
|
||||
use lightning::blinded_path::BlindedPath;
|
||||
use lightning::blinded_path::payment::ReceiveTlvs;
|
||||
use lightning::chain;
|
||||
use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, chainmonitor, channelmonitor, Confirm, Watch};
|
||||
use lightning::chain::channelmonitor::{ChannelMonitor, MonitorEvent};
|
||||
|
@ -44,8 +46,9 @@ use lightning::ln::channel::FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
|
|||
use lightning::ln::msgs::{self, CommitmentUpdate, ChannelMessageHandler, DecodeError, UpdateAddHTLC, Init};
|
||||
use lightning::ln::script::ShutdownScript;
|
||||
use lightning::ln::functional_test_utils::*;
|
||||
use lightning::offers::invoice::UnsignedBolt12Invoice;
|
||||
use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
|
||||
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
|
||||
use lightning::onion_message::{Destination, MessageRouter, OnionMessagePath};
|
||||
use lightning::util::test_channel_signer::{TestChannelSigner, EnforcementState};
|
||||
use lightning::util::errors::APIError;
|
||||
use lightning::util::logger::Logger;
|
||||
|
@ -56,7 +59,7 @@ use lightning::routing::router::{InFlightHtlcs, Path, Route, RouteHop, RoutePara
|
|||
use crate::utils::test_logger::{self, Output};
|
||||
use crate::utils::test_persister::TestPersister;
|
||||
|
||||
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1};
|
||||
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1, self};
|
||||
use bitcoin::secp256k1::ecdh::SharedSecret;
|
||||
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
|
||||
use bitcoin::secp256k1::schnorr;
|
||||
|
@ -99,6 +102,32 @@ impl Router for FuzzRouter {
|
|||
action: msgs::ErrorAction::IgnoreError
|
||||
})
|
||||
}
|
||||
|
||||
fn create_blinded_payment_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, _recipient: PublicKey, _first_hops: Vec<ChannelDetails>, _tlvs: ReceiveTlvs,
|
||||
_amount_msats: u64, _entropy_source: &ES, _secp_ctx: &Secp256k1<T>
|
||||
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageRouter for FuzzRouter {
|
||||
fn find_path(
|
||||
&self, _sender: PublicKey, _peers: Vec<PublicKey>, _destination: Destination
|
||||
) -> Result<OnionMessagePath, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn create_blinded_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _entropy_source: &ES,
|
||||
_secp_ctx: &Secp256k1<T>
|
||||
) -> Result<Vec<BlindedPath>, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestBroadcaster {}
|
||||
|
|
|
@ -28,6 +28,8 @@ use bitcoin::hashes::sha256::Hash as Sha256;
|
|||
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
|
||||
use bitcoin::hash_types::{Txid, BlockHash, WPubkeyHash};
|
||||
|
||||
use lightning::blinded_path::BlindedPath;
|
||||
use lightning::blinded_path::payment::ReceiveTlvs;
|
||||
use lightning::chain;
|
||||
use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen};
|
||||
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
|
||||
|
@ -41,8 +43,9 @@ use lightning::ln::peer_handler::{MessageHandler,PeerManager,SocketDescriptor,Ig
|
|||
use lightning::ln::msgs::{self, DecodeError};
|
||||
use lightning::ln::script::ShutdownScript;
|
||||
use lightning::ln::functional_test_utils::*;
|
||||
use lightning::offers::invoice::UnsignedBolt12Invoice;
|
||||
use lightning::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
|
||||
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
|
||||
use lightning::onion_message::{Destination, MessageRouter, OnionMessagePath};
|
||||
use lightning::routing::gossip::{P2PGossipSync, NetworkGraph};
|
||||
use lightning::routing::utxo::UtxoLookup;
|
||||
use lightning::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteParameters, Router};
|
||||
|
@ -55,7 +58,7 @@ use lightning::util::ser::{ReadableArgs, Writeable};
|
|||
use crate::utils::test_logger;
|
||||
use crate::utils::test_persister::TestPersister;
|
||||
|
||||
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1};
|
||||
use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1, self};
|
||||
use bitcoin::secp256k1::ecdh::SharedSecret;
|
||||
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
|
||||
use bitcoin::secp256k1::schnorr;
|
||||
|
@ -142,6 +145,32 @@ impl Router for FuzzRouter {
|
|||
action: msgs::ErrorAction::IgnoreError
|
||||
})
|
||||
}
|
||||
|
||||
fn create_blinded_payment_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, _recipient: PublicKey, _first_hops: Vec<ChannelDetails>, _tlvs: ReceiveTlvs,
|
||||
_amount_msats: u64, _entropy_source: &ES, _secp_ctx: &Secp256k1<T>
|
||||
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageRouter for FuzzRouter {
|
||||
fn find_path(
|
||||
&self, _sender: PublicKey, _peers: Vec<PublicKey>, _destination: Destination
|
||||
) -> Result<OnionMessagePath, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn create_blinded_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _entropy_source: &ES,
|
||||
_secp_ctx: &Secp256k1<T>
|
||||
) -> Result<Vec<BlindedPath>, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
struct TestBroadcaster {
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
// Imports that need to be added manually
|
||||
use bitcoin::bech32::u5;
|
||||
use bitcoin::blockdata::script::ScriptBuf;
|
||||
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
|
||||
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, self};
|
||||
use bitcoin::secp256k1::ecdh::SharedSecret;
|
||||
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
|
||||
use bitcoin::secp256k1::schnorr;
|
||||
|
||||
use lightning::sign::{Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider};
|
||||
use lightning::blinded_path::BlindedPath;
|
||||
use lightning::ln::features::InitFeatures;
|
||||
use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler};
|
||||
use lightning::ln::script::ShutdownScript;
|
||||
use lightning::offers::invoice::UnsignedBolt12Invoice;
|
||||
use lightning::offers::invoice_request::UnsignedInvoiceRequest;
|
||||
use lightning::sign::{Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider};
|
||||
use lightning::util::test_channel_signer::TestChannelSigner;
|
||||
use lightning::util::logger::Logger;
|
||||
use lightning::util::ser::{Readable, Writeable, Writer};
|
||||
|
@ -82,6 +83,15 @@ impl MessageRouter for TestMessageRouter {
|
|||
first_node_addresses: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_blinded_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _entropy_source: &ES,
|
||||
_secp_ctx: &Secp256k1<T>
|
||||
) -> Result<Vec<BlindedPath>, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
struct TestOffersMessageHandler {}
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::blinded_path::BlindedHop;
|
|||
use crate::blinded_path::utils;
|
||||
use crate::io;
|
||||
use crate::ln::PaymentSecret;
|
||||
use crate::ln::channelmanager::CounterpartyForwardingInfo;
|
||||
use crate::ln::features::BlindedHopFeatures;
|
||||
use crate::ln::msgs::DecodeError;
|
||||
use crate::offers::invoice::BlindedPayInfo;
|
||||
|
@ -96,6 +97,15 @@ pub struct PaymentConstraints {
|
|||
pub htlc_minimum_msat: u64,
|
||||
}
|
||||
|
||||
impl From<CounterpartyForwardingInfo> for PaymentRelay {
|
||||
fn from(info: CounterpartyForwardingInfo) -> Self {
|
||||
let CounterpartyForwardingInfo {
|
||||
fee_base_msat, fee_proportional_millionths, cltv_expiry_delta
|
||||
} = info;
|
||||
Self { cltv_expiry_delta, fee_proportional_millionths, fee_base_msat }
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for ForwardTlvs {
|
||||
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
|
||||
encode_tlv_stream!(w, {
|
||||
|
|
|
@ -65,7 +65,7 @@ use crate::offers::merkle::SignError;
|
|||
use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder};
|
||||
use crate::offers::parse::Bolt12SemanticError;
|
||||
use crate::offers::refund::{Refund, RefundBuilder};
|
||||
use crate::onion_message::{Destination, OffersMessage, OffersMessageHandler, PendingOnionMessage, new_pending_onion_message};
|
||||
use crate::onion_message::{Destination, MessageRouter, OffersMessage, OffersMessageHandler, PendingOnionMessage, new_pending_onion_message};
|
||||
use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider};
|
||||
use crate::sign::ecdsa::WriteableEcdsaChannelSigner;
|
||||
use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate};
|
||||
|
@ -7483,32 +7483,43 @@ where
|
|||
///
|
||||
/// # Privacy
|
||||
///
|
||||
/// Uses a one-hop [`BlindedPath`] for the offer with [`ChannelManager::get_our_node_id`] as the
|
||||
/// introduction node and a derived signing pubkey for recipient privacy. As such, currently,
|
||||
/// the node must be announced. Otherwise, there is no way to find a path to the introduction
|
||||
/// node in order to send the [`InvoiceRequest`].
|
||||
/// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the offer.
|
||||
/// However, if one is not found, uses a one-hop [`BlindedPath`] with
|
||||
/// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
|
||||
/// the node must be announced, otherwise, there is no way to find a path to the introduction in
|
||||
/// order to send the [`InvoiceRequest`].
|
||||
///
|
||||
/// Also, uses a derived signing pubkey in the offer for recipient privacy.
|
||||
///
|
||||
/// # Limitations
|
||||
///
|
||||
/// Requires a direct connection to the introduction node in the responding [`InvoiceRequest`]'s
|
||||
/// reply path.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Errors if the parameterized [`Router`] is unable to create a blinded path for the offer.
|
||||
///
|
||||
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
|
||||
///
|
||||
/// [`Offer`]: crate::offers::offer::Offer
|
||||
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
|
||||
pub fn create_offer_builder(
|
||||
&self, description: String
|
||||
) -> OfferBuilder<DerivedMetadata, secp256k1::All> {
|
||||
) -> Result<OfferBuilder<DerivedMetadata, secp256k1::All>, Bolt12SemanticError> {
|
||||
let node_id = self.get_our_node_id();
|
||||
let expanded_key = &self.inbound_payment_key;
|
||||
let entropy = &*self.entropy_source;
|
||||
let secp_ctx = &self.secp_ctx;
|
||||
let path = self.create_one_hop_blinded_path();
|
||||
|
||||
OfferBuilder::deriving_signing_pubkey(description, node_id, expanded_key, entropy, secp_ctx)
|
||||
let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
|
||||
let builder = OfferBuilder::deriving_signing_pubkey(
|
||||
description, node_id, expanded_key, entropy, secp_ctx
|
||||
)
|
||||
.chain_hash(self.chain_hash)
|
||||
.path(path)
|
||||
.path(path);
|
||||
|
||||
Ok(builder)
|
||||
}
|
||||
|
||||
/// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the
|
||||
|
@ -7533,10 +7544,13 @@ where
|
|||
///
|
||||
/// # Privacy
|
||||
///
|
||||
/// Uses a one-hop [`BlindedPath`] for the refund with [`ChannelManager::get_our_node_id`] as
|
||||
/// the introduction node and a derived payer id for payer privacy. As such, currently, the
|
||||
/// node must be announced. Otherwise, there is no way to find a path to the introduction node
|
||||
/// in order to send the [`Bolt12Invoice`].
|
||||
/// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the refund.
|
||||
/// However, if one is not found, uses a one-hop [`BlindedPath`] with
|
||||
/// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
|
||||
/// the node must be announced, otherwise, there is no way to find a path to the introduction in
|
||||
/// order to send the [`Bolt12Invoice`].
|
||||
///
|
||||
/// Also, uses a derived payer id in the refund for payer privacy.
|
||||
///
|
||||
/// # Limitations
|
||||
///
|
||||
|
@ -7545,14 +7559,17 @@ where
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Errors if a duplicate `payment_id` is provided given the caveats in the aforementioned link
|
||||
/// or if `amount_msats` is invalid.
|
||||
/// Errors if:
|
||||
/// - a duplicate `payment_id` is provided given the caveats in the aforementioned link,
|
||||
/// - `amount_msats` is invalid, or
|
||||
/// - the parameterized [`Router`] is unable to create a blinded path for the refund.
|
||||
///
|
||||
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
|
||||
///
|
||||
/// [`Refund`]: crate::offers::refund::Refund
|
||||
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
|
||||
/// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths
|
||||
/// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments
|
||||
pub fn create_refund_builder(
|
||||
&self, description: String, amount_msats: u64, absolute_expiry: Duration,
|
||||
payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>
|
||||
|
@ -7561,8 +7578,8 @@ where
|
|||
let expanded_key = &self.inbound_payment_key;
|
||||
let entropy = &*self.entropy_source;
|
||||
let secp_ctx = &self.secp_ctx;
|
||||
let path = self.create_one_hop_blinded_path();
|
||||
|
||||
let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
|
||||
let builder = RefundBuilder::deriving_payer_id(
|
||||
description, node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
|
||||
)?
|
||||
|
@ -7620,8 +7637,11 @@ where
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Errors if a duplicate `payment_id` is provided given the caveats in the aforementioned link
|
||||
/// or if the provided parameters are invalid for the offer.
|
||||
/// Errors if:
|
||||
/// - a duplicate `payment_id` is provided given the caveats in the aforementioned link,
|
||||
/// - the provided parameters are invalid for the offer,
|
||||
/// - the parameterized [`Router`] is unable to create a blinded reply path for the invoice
|
||||
/// request.
|
||||
///
|
||||
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
|
||||
/// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity
|
||||
|
@ -7654,9 +7674,8 @@ where
|
|||
None => builder,
|
||||
Some(payer_note) => builder.payer_note(payer_note),
|
||||
};
|
||||
|
||||
let invoice_request = builder.build_and_sign()?;
|
||||
let reply_path = self.create_one_hop_blinded_path();
|
||||
let reply_path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
|
||||
|
||||
let expiration = StaleExpiration::TimerTicks(1);
|
||||
self.pending_outbound_payments
|
||||
|
@ -7705,6 +7724,11 @@ where
|
|||
/// node meeting the aforementioned criteria, but there's no guarantee that they will be
|
||||
/// received and no retries will be made.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Errors if the parameterized [`Router`] is unable to create a blinded payment path or reply
|
||||
/// path for the invoice.
|
||||
///
|
||||
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
|
||||
pub fn request_refund_payment(&self, refund: &Refund) -> Result<(), Bolt12SemanticError> {
|
||||
let expanded_key = &self.inbound_payment_key;
|
||||
|
@ -7716,9 +7740,9 @@ where
|
|||
|
||||
match self.create_inbound_payment(Some(amount_msats), relative_expiry, None) {
|
||||
Ok((payment_hash, payment_secret)) => {
|
||||
let payment_paths = vec![
|
||||
self.create_one_hop_blinded_payment_path(payment_secret),
|
||||
];
|
||||
let payment_paths = self.create_blinded_payment_paths(amount_msats, payment_secret)
|
||||
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
|
||||
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
let builder = refund.respond_using_derived_keys(
|
||||
payment_paths, payment_hash, expanded_key, entropy
|
||||
|
@ -7732,7 +7756,8 @@ where
|
|||
payment_paths, payment_hash, created_at, expanded_key, entropy
|
||||
)?;
|
||||
let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?;
|
||||
let reply_path = self.create_one_hop_blinded_path();
|
||||
let reply_path = self.create_blinded_path()
|
||||
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
|
||||
|
||||
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
|
||||
if refund.paths().is_empty() {
|
||||
|
@ -7859,24 +7884,37 @@ where
|
|||
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
|
||||
}
|
||||
|
||||
/// Creates a one-hop blinded path with [`ChannelManager::get_our_node_id`] as the introduction
|
||||
/// node.
|
||||
fn create_one_hop_blinded_path(&self) -> BlindedPath {
|
||||
/// Creates a blinded path by delegating to [`MessageRouter::create_blinded_paths`].
|
||||
///
|
||||
/// Errors if the `MessageRouter` errors or returns an empty `Vec`.
|
||||
fn create_blinded_path(&self) -> Result<BlindedPath, ()> {
|
||||
let recipient = self.get_our_node_id();
|
||||
let entropy_source = self.entropy_source.deref();
|
||||
let secp_ctx = &self.secp_ctx;
|
||||
BlindedPath::one_hop_for_message(self.get_our_node_id(), entropy_source, secp_ctx).unwrap()
|
||||
|
||||
let peers = self.per_peer_state.read().unwrap()
|
||||
.iter()
|
||||
.filter(|(_, peer)| peer.lock().unwrap().latest_features.supports_onion_messages())
|
||||
.map(|(node_id, _)| *node_id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.router
|
||||
.create_blinded_paths(recipient, peers, entropy_source, secp_ctx)
|
||||
.and_then(|paths| paths.into_iter().next().ok_or(()))
|
||||
}
|
||||
|
||||
/// Creates a one-hop blinded path with [`ChannelManager::get_our_node_id`] as the introduction
|
||||
/// node.
|
||||
fn create_one_hop_blinded_payment_path(
|
||||
&self, payment_secret: PaymentSecret
|
||||
) -> (BlindedPayInfo, BlindedPath) {
|
||||
/// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to
|
||||
/// [`Router::create_blinded_payment_paths`].
|
||||
fn create_blinded_payment_paths(
|
||||
&self, amount_msats: u64, payment_secret: PaymentSecret
|
||||
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
|
||||
let entropy_source = self.entropy_source.deref();
|
||||
let secp_ctx = &self.secp_ctx;
|
||||
|
||||
let first_hops = self.list_usable_channels();
|
||||
let payee_node_id = self.get_our_node_id();
|
||||
let max_cltv_expiry = self.best_block.read().unwrap().height() + LATENCY_GRACE_PERIOD_BLOCKS;
|
||||
let max_cltv_expiry = self.best_block.read().unwrap().height() + CLTV_FAR_FAR_AWAY
|
||||
+ LATENCY_GRACE_PERIOD_BLOCKS;
|
||||
let payee_tlvs = ReceiveTlvs {
|
||||
payment_secret,
|
||||
payment_constraints: PaymentConstraints {
|
||||
|
@ -7884,10 +7922,9 @@ where
|
|||
htlc_minimum_msat: 1,
|
||||
},
|
||||
};
|
||||
// TODO: Err for overflow?
|
||||
BlindedPath::one_hop_for_payment(
|
||||
payee_node_id, payee_tlvs, entropy_source, secp_ctx
|
||||
).unwrap()
|
||||
self.router.create_blinded_payment_paths(
|
||||
payee_node_id, first_hops, payee_tlvs, amount_msats, entropy_source, secp_ctx
|
||||
)
|
||||
}
|
||||
|
||||
/// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids
|
||||
|
@ -9127,7 +9164,7 @@ where
|
|||
let amount_msats = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
|
||||
&invoice_request
|
||||
) {
|
||||
Ok(amount_msats) => Some(amount_msats),
|
||||
Ok(amount_msats) => amount_msats,
|
||||
Err(error) => return Some(OffersMessage::InvoiceError(error.into())),
|
||||
};
|
||||
let invoice_request = match invoice_request.verify(expanded_key, secp_ctx) {
|
||||
|
@ -9137,64 +9174,69 @@ where
|
|||
return Some(OffersMessage::InvoiceError(error.into()));
|
||||
},
|
||||
};
|
||||
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
|
||||
|
||||
match self.create_inbound_payment(amount_msats, relative_expiry, None) {
|
||||
Ok((payment_hash, payment_secret)) if invoice_request.keys.is_some() => {
|
||||
let payment_paths = vec![
|
||||
self.create_one_hop_blinded_payment_path(payment_secret),
|
||||
];
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
let builder = invoice_request.respond_using_derived_keys(
|
||||
payment_paths, payment_hash
|
||||
);
|
||||
#[cfg(feature = "no-std")]
|
||||
let created_at = Duration::from_secs(
|
||||
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
|
||||
);
|
||||
#[cfg(feature = "no-std")]
|
||||
let builder = invoice_request.respond_using_derived_keys_no_std(
|
||||
payment_paths, payment_hash, created_at
|
||||
);
|
||||
match builder.and_then(|b| b.allow_mpp().build_and_sign(secp_ctx)) {
|
||||
Ok(invoice) => Some(OffersMessage::Invoice(invoice)),
|
||||
Err(error) => Some(OffersMessage::InvoiceError(error.into())),
|
||||
}
|
||||
},
|
||||
Ok((payment_hash, payment_secret)) => {
|
||||
let payment_paths = vec![
|
||||
self.create_one_hop_blinded_payment_path(payment_secret),
|
||||
];
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
let builder = invoice_request.respond_with(payment_paths, payment_hash);
|
||||
#[cfg(feature = "no-std")]
|
||||
let created_at = Duration::from_secs(
|
||||
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
|
||||
);
|
||||
#[cfg(feature = "no-std")]
|
||||
let builder = invoice_request.respond_with_no_std(
|
||||
payment_paths, payment_hash, created_at
|
||||
);
|
||||
let response = builder.and_then(|builder| builder.allow_mpp().build())
|
||||
.map_err(|e| OffersMessage::InvoiceError(e.into()))
|
||||
.and_then(|invoice|
|
||||
match invoice.sign(|invoice| self.node_signer.sign_bolt12_invoice(invoice)) {
|
||||
Ok(invoice) => Ok(OffersMessage::Invoice(invoice)),
|
||||
Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError(
|
||||
InvoiceError::from_string("Failed signing invoice".to_string())
|
||||
)),
|
||||
Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError(
|
||||
InvoiceError::from_string("Failed invoice signature verification".to_string())
|
||||
)),
|
||||
});
|
||||
match response {
|
||||
Ok(invoice) => Some(invoice),
|
||||
Err(error) => Some(error),
|
||||
}
|
||||
},
|
||||
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
|
||||
let (payment_hash, payment_secret) = match self.create_inbound_payment(
|
||||
Some(amount_msats), relative_expiry, None
|
||||
) {
|
||||
Ok((payment_hash, payment_secret)) => (payment_hash, payment_secret),
|
||||
Err(()) => {
|
||||
Some(OffersMessage::InvoiceError(Bolt12SemanticError::InvalidAmount.into()))
|
||||
let error = Bolt12SemanticError::InvalidAmount;
|
||||
return Some(OffersMessage::InvoiceError(error.into()));
|
||||
},
|
||||
};
|
||||
|
||||
let payment_paths = match self.create_blinded_payment_paths(
|
||||
amount_msats, payment_secret
|
||||
) {
|
||||
Ok(payment_paths) => payment_paths,
|
||||
Err(()) => {
|
||||
let error = Bolt12SemanticError::MissingPaths;
|
||||
return Some(OffersMessage::InvoiceError(error.into()));
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(feature = "no-std")]
|
||||
let created_at = Duration::from_secs(
|
||||
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
|
||||
);
|
||||
|
||||
if invoice_request.keys.is_some() {
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
let builder = invoice_request.respond_using_derived_keys(
|
||||
payment_paths, payment_hash
|
||||
);
|
||||
#[cfg(feature = "no-std")]
|
||||
let builder = invoice_request.respond_using_derived_keys_no_std(
|
||||
payment_paths, payment_hash, created_at
|
||||
);
|
||||
match builder.and_then(|b| b.allow_mpp().build_and_sign(secp_ctx)) {
|
||||
Ok(invoice) => Some(OffersMessage::Invoice(invoice)),
|
||||
Err(error) => Some(OffersMessage::InvoiceError(error.into())),
|
||||
}
|
||||
} else {
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
let builder = invoice_request.respond_with(payment_paths, payment_hash);
|
||||
#[cfg(feature = "no-std")]
|
||||
let builder = invoice_request.respond_with_no_std(
|
||||
payment_paths, payment_hash, created_at
|
||||
);
|
||||
let response = builder.and_then(|builder| builder.allow_mpp().build())
|
||||
.map_err(|e| OffersMessage::InvoiceError(e.into()))
|
||||
.and_then(|invoice|
|
||||
match invoice.sign(|invoice| self.node_signer.sign_bolt12_invoice(invoice)) {
|
||||
Ok(invoice) => Ok(OffersMessage::Invoice(invoice)),
|
||||
Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError(
|
||||
InvoiceError::from_string("Failed signing invoice".to_string())
|
||||
)),
|
||||
Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError(
|
||||
InvoiceError::from_string("Failed invoice signature verification".to_string())
|
||||
)),
|
||||
});
|
||||
match response {
|
||||
Ok(invoice) => Some(invoice),
|
||||
Err(error) => Some(error),
|
||||
}
|
||||
}
|
||||
},
|
||||
OffersMessage::Invoice(invoice) => {
|
||||
|
|
|
@ -41,6 +41,12 @@
|
|||
//! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md#basic-multi-part-payments) for more information).
|
||||
//! - `Wumbo` - requires/supports that a node create large channels. Called `option_support_large_channel` in the spec.
|
||||
//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel-message) for more information).
|
||||
//! - `AnchorsZeroFeeHtlcTx` - requires/supports that commitment transactions include anchor outputs
|
||||
//! and HTLC transactions are pre-signed with zero fee (see
|
||||
//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more
|
||||
//! information).
|
||||
//! - `RouteBlinding` - requires/supports that a node can relay payments over blinded paths
|
||||
//! (see [BOLT-4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md#route-blinding) for more information).
|
||||
//! - `ShutdownAnySegwit` - requires/supports that future segwit versions are allowed in `shutdown`
|
||||
//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information).
|
||||
//! - `OnionMessages` - requires/supports forwarding onion messages
|
||||
|
@ -60,10 +66,6 @@
|
|||
//! for more info).
|
||||
//! - `Keysend` - send funds to a node without an invoice
|
||||
//! (see the [`Keysend` feature assignment proposal](https://github.com/lightning/bolts/issues/605#issuecomment-606679798) for more information).
|
||||
//! - `AnchorsZeroFeeHtlcTx` - requires/supports that commitment transactions include anchor outputs
|
||||
//! and HTLC transactions are pre-signed with zero fee (see
|
||||
//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more
|
||||
//! information).
|
||||
//!
|
||||
//! LDK knows about the following features, but does not support them:
|
||||
//! - `AnchorsNonzeroFeeHtlcTx` - the initial version of anchor outputs, which was later found to be
|
||||
|
@ -143,7 +145,7 @@ mod sealed {
|
|||
// Byte 2
|
||||
BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx,
|
||||
// Byte 3
|
||||
ShutdownAnySegwit | Taproot,
|
||||
RouteBlinding | ShutdownAnySegwit | Taproot,
|
||||
// Byte 4
|
||||
OnionMessages,
|
||||
// Byte 5
|
||||
|
@ -159,7 +161,7 @@ mod sealed {
|
|||
// Byte 2
|
||||
BasicMPP | Wumbo | AnchorsNonzeroFeeHtlcTx | AnchorsZeroFeeHtlcTx,
|
||||
// Byte 3
|
||||
ShutdownAnySegwit | Taproot,
|
||||
RouteBlinding | ShutdownAnySegwit | Taproot,
|
||||
// Byte 4
|
||||
OnionMessages,
|
||||
// Byte 5
|
||||
|
@ -391,6 +393,9 @@ mod sealed {
|
|||
define_feature!(23, AnchorsZeroFeeHtlcTx, [InitContext, NodeContext, ChannelTypeContext],
|
||||
"Feature flags for `option_anchors_zero_fee_htlc_tx`.", set_anchors_zero_fee_htlc_tx_optional,
|
||||
set_anchors_zero_fee_htlc_tx_required, supports_anchors_zero_fee_htlc_tx, requires_anchors_zero_fee_htlc_tx);
|
||||
define_feature!(25, RouteBlinding, [InitContext, NodeContext],
|
||||
"Feature flags for `option_route_blinding`.", set_route_blinding_optional,
|
||||
set_route_blinding_required, supports_route_blinding, requires_route_blinding);
|
||||
define_feature!(27, ShutdownAnySegwit, [InitContext, NodeContext],
|
||||
"Feature flags for `opt_shutdown_anysegwit`.", set_shutdown_any_segwit_optional,
|
||||
set_shutdown_any_segwit_required, supports_shutdown_anysegwit, requires_shutdown_anysegwit);
|
||||
|
@ -1053,6 +1058,7 @@ mod tests {
|
|||
init_features.set_basic_mpp_optional();
|
||||
init_features.set_wumbo_optional();
|
||||
init_features.set_anchors_zero_fee_htlc_tx_optional();
|
||||
init_features.set_route_blinding_optional();
|
||||
init_features.set_shutdown_any_segwit_optional();
|
||||
init_features.set_onion_messages_optional();
|
||||
init_features.set_channel_type_optional();
|
||||
|
@ -1068,8 +1074,8 @@ mod tests {
|
|||
// Check that the flags are as expected:
|
||||
// - option_data_loss_protect (req)
|
||||
// - var_onion_optin (req) | static_remote_key (req) | payment_secret(req)
|
||||
// - basic_mpp | wumbo | anchors_zero_fee_htlc_tx
|
||||
// - opt_shutdown_anysegwit
|
||||
// - basic_mpp | wumbo | option_anchors_zero_fee_htlc_tx
|
||||
// - option_route_blinding | opt_shutdown_anysegwit
|
||||
// - onion_messages
|
||||
// - option_channel_type | option_scid_alias
|
||||
// - option_zeroconf
|
||||
|
@ -1077,7 +1083,7 @@ mod tests {
|
|||
assert_eq!(node_features.flags[0], 0b00000001);
|
||||
assert_eq!(node_features.flags[1], 0b01010001);
|
||||
assert_eq!(node_features.flags[2], 0b10001010);
|
||||
assert_eq!(node_features.flags[3], 0b00001000);
|
||||
assert_eq!(node_features.flags[3], 0b00001010);
|
||||
assert_eq!(node_features.flags[4], 0b10000000);
|
||||
assert_eq!(node_features.flags[5], 0b10100000);
|
||||
assert_eq!(node_features.flags[6], 0b00001000);
|
||||
|
|
|
@ -13,14 +13,14 @@ use crate::blinded_path::BlindedPath;
|
|||
use crate::events::{Event, EventsProvider};
|
||||
use crate::ln::features::InitFeatures;
|
||||
use crate::ln::msgs::{self, DecodeError, OnionMessageHandler, SocketAddress};
|
||||
use crate::sign::{NodeSigner, Recipient};
|
||||
use crate::sign::{EntropySource, NodeSigner, Recipient};
|
||||
use crate::util::ser::{FixedLengthReader, LengthReadable, Writeable, Writer};
|
||||
use crate::util::test_utils;
|
||||
use super::{CustomOnionMessageHandler, Destination, MessageRouter, OffersMessage, OffersMessageHandler, OnionMessageContents, OnionMessagePath, OnionMessenger, PendingOnionMessage, SendError};
|
||||
|
||||
use bitcoin::network::constants::Network;
|
||||
use bitcoin::hashes::hex::FromHex;
|
||||
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
|
||||
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey, self};
|
||||
|
||||
use crate::io;
|
||||
use crate::io_extras::read_to_end;
|
||||
|
@ -55,6 +55,15 @@ impl MessageRouter for TestMessageRouter {
|
|||
Some(vec![SocketAddress::TcpIpV4 { addr: [127, 0, 0, 1], port: 1000 }]),
|
||||
})
|
||||
}
|
||||
|
||||
fn create_blinded_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _entropy_source: &ES,
|
||||
_secp_ctx: &Secp256k1<T>
|
||||
) -> Result<Vec<BlindedPath>, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
struct TestOffersMessageHandler {}
|
||||
|
|
|
@ -64,9 +64,9 @@ pub(super) const MAX_TIMER_TICKS: usize = 2;
|
|||
/// # extern crate bitcoin;
|
||||
/// # use bitcoin::hashes::_export::_core::time::Duration;
|
||||
/// # use bitcoin::hashes::hex::FromHex;
|
||||
/// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
|
||||
/// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey, self};
|
||||
/// # use lightning::blinded_path::BlindedPath;
|
||||
/// # use lightning::sign::KeysManager;
|
||||
/// # use lightning::sign::{EntropySource, KeysManager};
|
||||
/// # use lightning::ln::peer_handler::IgnoringMessageHandler;
|
||||
/// # use lightning::onion_message::{OnionMessageContents, Destination, MessageRouter, OnionMessagePath, OnionMessenger};
|
||||
/// # use lightning::util::logger::{Logger, Record};
|
||||
|
@ -90,6 +90,11 @@ pub(super) const MAX_TIMER_TICKS: usize = 2;
|
|||
/// # first_node_addresses: None,
|
||||
/// # })
|
||||
/// # }
|
||||
/// # fn create_blinded_paths<ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification>(
|
||||
/// # &self, _recipient: PublicKey, _peers: Vec<PublicKey>, _entropy_source: &ES, _secp_ctx: &Secp256k1<T>
|
||||
/// # ) -> Result<Vec<BlindedPath>, ()> {
|
||||
/// # unreachable!()
|
||||
/// # }
|
||||
/// # }
|
||||
/// # let seed = [42u8; 32];
|
||||
/// # let time = Duration::from_secs(123456);
|
||||
|
@ -270,6 +275,15 @@ pub trait MessageRouter {
|
|||
fn find_path(
|
||||
&self, sender: PublicKey, peers: Vec<PublicKey>, destination: Destination
|
||||
) -> Result<OnionMessagePath, ()>;
|
||||
|
||||
/// Creates [`BlindedPath`]s to the `recipient` node. The nodes in `peers` are assumed to be
|
||||
/// direct peers with the `recipient`.
|
||||
fn create_blinded_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, recipient: PublicKey, peers: Vec<PublicKey>, entropy_source: &ES,
|
||||
secp_ctx: &Secp256k1<T>
|
||||
) -> Result<Vec<BlindedPath>, ()>;
|
||||
}
|
||||
|
||||
/// A [`MessageRouter`] that can only route to a directly connected [`Destination`].
|
||||
|
@ -321,6 +335,47 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_blinded_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, recipient: PublicKey, peers: Vec<PublicKey>, entropy_source: &ES,
|
||||
secp_ctx: &Secp256k1<T>
|
||||
) -> Result<Vec<BlindedPath>, ()> {
|
||||
// Limit the number of blinded paths that are computed.
|
||||
const MAX_PATHS: usize = 3;
|
||||
|
||||
// Ensure peers have at least three channels so that it is more difficult to infer the
|
||||
// recipient's node_id.
|
||||
const MIN_PEER_CHANNELS: usize = 3;
|
||||
|
||||
let network_graph = self.network_graph.deref().read_only();
|
||||
let paths = peers.iter()
|
||||
// Limit to peers with announced channels
|
||||
.filter(|pubkey|
|
||||
network_graph
|
||||
.node(&NodeId::from_pubkey(pubkey))
|
||||
.map(|info| &info.channels[..])
|
||||
.map(|channels| channels.len() >= MIN_PEER_CHANNELS)
|
||||
.unwrap_or(false)
|
||||
)
|
||||
.map(|pubkey| vec![*pubkey, recipient])
|
||||
.map(|node_pks| BlindedPath::new_for_message(&node_pks, entropy_source, secp_ctx))
|
||||
.take(MAX_PATHS)
|
||||
.collect::<Result<Vec<_>, _>>();
|
||||
|
||||
match paths {
|
||||
Ok(paths) if !paths.is_empty() => Ok(paths),
|
||||
_ => {
|
||||
if network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)) {
|
||||
BlindedPath::one_hop_for_message(recipient, entropy_source, secp_ctx)
|
||||
.map(|path| vec![path])
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A path for sending an [`OnionMessage`].
|
||||
|
|
|
@ -9,18 +9,21 @@
|
|||
|
||||
//! The router finds paths within a [`NetworkGraph`] for a payment.
|
||||
|
||||
use bitcoin::secp256k1::PublicKey;
|
||||
use bitcoin::secp256k1::{PublicKey, Secp256k1, self};
|
||||
use bitcoin::hashes::Hash;
|
||||
use bitcoin::hashes::sha256::Hash as Sha256;
|
||||
|
||||
use crate::blinded_path::{BlindedHop, BlindedPath};
|
||||
use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs};
|
||||
use crate::ln::PaymentHash;
|
||||
use crate::ln::channelmanager::{ChannelDetails, PaymentId};
|
||||
use crate::ln::features::{Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
|
||||
use crate::ln::features::{BlindedHopFeatures, Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
|
||||
use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
|
||||
use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice};
|
||||
use crate::onion_message::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath};
|
||||
use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees};
|
||||
use crate::routing::scoring::{ChannelUsage, LockableScore, ScoreLookUp};
|
||||
use crate::sign::EntropySource;
|
||||
use crate::util::ser::{Writeable, Readable, ReadableArgs, Writer};
|
||||
use crate::util::logger::{Level, Logger};
|
||||
use crate::util::chacha20::ChaCha20;
|
||||
|
@ -33,7 +36,7 @@ use core::{cmp, fmt};
|
|||
use core::ops::Deref;
|
||||
|
||||
/// A [`Router`] implemented using [`find_route`].
|
||||
pub struct DefaultRouter<G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> where
|
||||
pub struct DefaultRouter<G: Deref<Target = NetworkGraph<L>> + Clone, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> where
|
||||
L::Target: Logger,
|
||||
S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>,
|
||||
{
|
||||
|
@ -41,21 +44,23 @@ pub struct DefaultRouter<G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref,
|
|||
logger: L,
|
||||
random_seed_bytes: Mutex<[u8; 32]>,
|
||||
scorer: S,
|
||||
score_params: SP
|
||||
score_params: SP,
|
||||
message_router: DefaultMessageRouter<G, L>,
|
||||
}
|
||||
|
||||
impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> DefaultRouter<G, L, S, SP, Sc> where
|
||||
impl<G: Deref<Target = NetworkGraph<L>> + Clone, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> DefaultRouter<G, L, S, SP, Sc> where
|
||||
L::Target: Logger,
|
||||
S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>,
|
||||
{
|
||||
/// Creates a new router.
|
||||
pub fn new(network_graph: G, logger: L, random_seed_bytes: [u8; 32], scorer: S, score_params: SP) -> Self {
|
||||
let random_seed_bytes = Mutex::new(random_seed_bytes);
|
||||
Self { network_graph, logger, random_seed_bytes, scorer, score_params }
|
||||
let message_router = DefaultMessageRouter::new(network_graph.clone());
|
||||
Self { network_graph, logger, random_seed_bytes, scorer, score_params, message_router }
|
||||
}
|
||||
}
|
||||
|
||||
impl< G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> Router for DefaultRouter<G, L, S, SP, Sc> where
|
||||
impl<G: Deref<Target = NetworkGraph<L>> + Clone, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> Router for DefaultRouter<G, L, S, SP, Sc> where
|
||||
L::Target: Logger,
|
||||
S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>,
|
||||
{
|
||||
|
@ -78,10 +83,109 @@ impl< G: Deref<Target = NetworkGraph<L>>, L: Deref, S: Deref, SP: Sized, Sc: Sco
|
|||
&random_seed_bytes
|
||||
)
|
||||
}
|
||||
|
||||
fn create_blinded_payment_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, recipient: PublicKey, first_hops: Vec<ChannelDetails>, tlvs: ReceiveTlvs,
|
||||
amount_msats: u64, entropy_source: &ES, secp_ctx: &Secp256k1<T>
|
||||
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
|
||||
// Limit the number of blinded paths that are computed.
|
||||
const MAX_PAYMENT_PATHS: usize = 3;
|
||||
|
||||
// Ensure peers have at least three channels so that it is more difficult to infer the
|
||||
// recipient's node_id.
|
||||
const MIN_PEER_CHANNELS: usize = 3;
|
||||
|
||||
let network_graph = self.network_graph.deref().read_only();
|
||||
let paths = first_hops.into_iter()
|
||||
.filter(|details| details.counterparty.features.supports_route_blinding())
|
||||
.filter(|details| amount_msats <= details.inbound_capacity_msat)
|
||||
.filter(|details| amount_msats >= details.inbound_htlc_minimum_msat.unwrap_or(0))
|
||||
.filter(|details| amount_msats <= details.inbound_htlc_maximum_msat.unwrap_or(0))
|
||||
.filter(|details| network_graph
|
||||
.node(&NodeId::from_pubkey(&details.counterparty.node_id))
|
||||
.map(|node_info| node_info.channels.len() >= MIN_PEER_CHANNELS)
|
||||
.unwrap_or(false)
|
||||
)
|
||||
.filter_map(|details| {
|
||||
let short_channel_id = match details.get_inbound_payment_scid() {
|
||||
Some(short_channel_id) => short_channel_id,
|
||||
None => return None,
|
||||
};
|
||||
let payment_relay: PaymentRelay = match details.counterparty.forwarding_info {
|
||||
Some(forwarding_info) => forwarding_info.into(),
|
||||
None => return None,
|
||||
};
|
||||
|
||||
// Avoid exposing esoteric CLTV expiry deltas
|
||||
let cltv_expiry_delta = match payment_relay.cltv_expiry_delta {
|
||||
0..=40 => 40u32,
|
||||
41..=80 => 80u32,
|
||||
81..=144 => 144u32,
|
||||
145..=216 => 216u32,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let payment_constraints = PaymentConstraints {
|
||||
max_cltv_expiry: tlvs.payment_constraints.max_cltv_expiry + cltv_expiry_delta,
|
||||
htlc_minimum_msat: details.inbound_htlc_minimum_msat.unwrap_or(0),
|
||||
};
|
||||
Some(ForwardNode {
|
||||
tlvs: ForwardTlvs {
|
||||
short_channel_id,
|
||||
payment_relay,
|
||||
payment_constraints,
|
||||
features: BlindedHopFeatures::empty(),
|
||||
},
|
||||
node_id: details.counterparty.node_id,
|
||||
htlc_maximum_msat: details.inbound_htlc_maximum_msat.unwrap_or(0),
|
||||
})
|
||||
})
|
||||
.map(|forward_node| {
|
||||
BlindedPath::new_for_payment(
|
||||
&[forward_node], recipient, tlvs.clone(), u64::MAX, entropy_source, secp_ctx
|
||||
)
|
||||
})
|
||||
.take(MAX_PAYMENT_PATHS)
|
||||
.collect::<Result<Vec<_>, _>>();
|
||||
|
||||
match paths {
|
||||
Ok(paths) if !paths.is_empty() => Ok(paths),
|
||||
_ => {
|
||||
if network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)) {
|
||||
BlindedPath::one_hop_for_payment(recipient, tlvs, entropy_source, secp_ctx)
|
||||
.map(|path| vec![path])
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl< G: Deref<Target = NetworkGraph<L>> + Clone, L: Deref, S: Deref, SP: Sized, Sc: ScoreLookUp<ScoreParams = SP>> MessageRouter for DefaultRouter<G, L, S, SP, Sc> where
|
||||
L::Target: Logger,
|
||||
S::Target: for <'a> LockableScore<'a, ScoreLookUp = Sc>,
|
||||
{
|
||||
fn find_path(
|
||||
&self, sender: PublicKey, peers: Vec<PublicKey>, destination: Destination
|
||||
) -> Result<OnionMessagePath, ()> {
|
||||
self.message_router.find_path(sender, peers, destination)
|
||||
}
|
||||
|
||||
fn create_blinded_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, recipient: PublicKey, peers: Vec<PublicKey>, entropy_source: &ES,
|
||||
secp_ctx: &Secp256k1<T>
|
||||
) -> Result<Vec<BlindedPath>, ()> {
|
||||
self.message_router.create_blinded_paths(recipient, peers, entropy_source, secp_ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait defining behavior for routing a payment.
|
||||
pub trait Router {
|
||||
pub trait Router: MessageRouter {
|
||||
/// Finds a [`Route`] for a payment between the given `payer` and a payee.
|
||||
///
|
||||
/// The `payee` and the payment's value are given in [`RouteParameters::payment_params`]
|
||||
|
@ -105,6 +209,16 @@ pub trait Router {
|
|||
) -> Result<Route, LightningError> {
|
||||
self.find_route(payer, route_params, first_hops, inflight_htlcs)
|
||||
}
|
||||
|
||||
/// Creates [`BlindedPath`]s for payment to the `recipient` node. The channels in `first_hops`
|
||||
/// are assumed to be with the `recipient`'s peers. The payment secret and any constraints are
|
||||
/// given in `tlvs`.
|
||||
fn create_blinded_payment_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, recipient: PublicKey, first_hops: Vec<ChannelDetails>, tlvs: ReceiveTlvs,
|
||||
amount_msats: u64, entropy_source: &ES, secp_ctx: &Secp256k1<T>
|
||||
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()>;
|
||||
}
|
||||
|
||||
/// [`ScoreLookUp`] implementation that factors in in-flight HTLC liquidity.
|
||||
|
|
|
@ -3442,7 +3442,7 @@ mod tests {
|
|||
Some(([0; 32], [0; 32])));
|
||||
assert_eq!(scorer.historical_estimated_payment_success_probability(42, &target, 1, ¶ms), None);
|
||||
|
||||
let mut usage = ChannelUsage {
|
||||
let usage = ChannelUsage {
|
||||
amount_msat: 100,
|
||||
inflight_htlc_msat: 1024,
|
||||
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 },
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
// You may not use this file except in accordance with one or both of these
|
||||
// licenses.
|
||||
|
||||
use crate::blinded_path::BlindedPath;
|
||||
use crate::blinded_path::payment::ReceiveTlvs;
|
||||
use crate::chain;
|
||||
use crate::chain::WatchedOutput;
|
||||
use crate::chain::chaininterface;
|
||||
|
@ -22,14 +24,15 @@ use crate::sign;
|
|||
use crate::events;
|
||||
use crate::events::bump_transaction::{WalletSource, Utxo};
|
||||
use crate::ln::ChannelId;
|
||||
use crate::ln::channelmanager;
|
||||
use crate::ln::channelmanager::{ChannelDetails, self};
|
||||
use crate::ln::chan_utils::CommitmentTransaction;
|
||||
use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
|
||||
use crate::ln::{msgs, wire};
|
||||
use crate::ln::msgs::LightningError;
|
||||
use crate::ln::script::ShutdownScript;
|
||||
use crate::offers::invoice::UnsignedBolt12Invoice;
|
||||
use crate::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
|
||||
use crate::offers::invoice_request::UnsignedInvoiceRequest;
|
||||
use crate::onion_message::{Destination, MessageRouter, OnionMessagePath};
|
||||
use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId, RoutingFees};
|
||||
use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult};
|
||||
use crate::routing::router::{find_route, InFlightHtlcs, Path, Route, RouteParameters, RouteHintHop, Router, ScorerAccountingForInFlightHtlcs};
|
||||
|
@ -51,7 +54,7 @@ use bitcoin::network::constants::Network;
|
|||
use bitcoin::hash_types::{BlockHash, Txid};
|
||||
use bitcoin::sighash::{SighashCache, EcdsaSighashType};
|
||||
|
||||
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
|
||||
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, self};
|
||||
use bitcoin::secp256k1::ecdh::SharedSecret;
|
||||
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
|
||||
use bitcoin::secp256k1::schnorr;
|
||||
|
@ -119,7 +122,7 @@ impl<'a> TestRouter<'a> {
|
|||
|
||||
impl<'a> Router for TestRouter<'a> {
|
||||
fn find_route(
|
||||
&self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&channelmanager::ChannelDetails]>,
|
||||
&self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&ChannelDetails]>,
|
||||
inflight_htlcs: InFlightHtlcs
|
||||
) -> Result<Route, msgs::LightningError> {
|
||||
if let Some((find_route_query, find_route_res)) = self.next_routes.lock().unwrap().pop_front() {
|
||||
|
@ -189,6 +192,32 @@ impl<'a> Router for TestRouter<'a> {
|
|||
&[42; 32]
|
||||
)
|
||||
}
|
||||
|
||||
fn create_blinded_payment_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, _recipient: PublicKey, _first_hops: Vec<ChannelDetails>, _tlvs: ReceiveTlvs,
|
||||
_amount_msats: u64, _entropy_source: &ES, _secp_ctx: &Secp256k1<T>
|
||||
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageRouter for TestRouter<'a> {
|
||||
fn find_path(
|
||||
&self, _sender: PublicKey, _peers: Vec<PublicKey>, _destination: Destination
|
||||
) -> Result<OnionMessagePath, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn create_blinded_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _entropy_source: &ES,
|
||||
_secp_ctx: &Secp256k1<T>
|
||||
) -> Result<Vec<BlindedPath>, ()> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for TestRouter<'a> {
|
||||
|
|
Loading…
Add table
Reference in a new issue