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::{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::messenger::{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::crypto::chacha20::ChaCha20;
use crate::io;
use crate::prelude::*;
use crate::sync::Mutex;
use alloc::collections::BinaryHeap;
use core::{cmp, fmt};
use core::ops::Deref;
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>,
{
network_graph: G,
logger: L,
random_seed_bytes: Mutex<[u8; 32]>,
scorer: S,
score_params: SP,
message_router: DefaultMessageRouter<G, L>,
}
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>,
{
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);
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>> + 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>,
{
fn find_route(
&self,
payer: &PublicKey,
params: &RouteParameters,
first_hops: Option<&[&ChannelDetails]>,
inflight_htlcs: InFlightHtlcs
) -> Result<Route, LightningError> {
let random_seed_bytes = {
let mut locked_random_seed_bytes = self.random_seed_bytes.lock().unwrap();
*locked_random_seed_bytes = Sha256::hash(&*locked_random_seed_bytes).to_byte_array();
*locked_random_seed_bytes
};
find_route(
payer, params, &self.network_graph, first_hops, &*self.logger,
&ScorerAccountingForInFlightHtlcs::new(self.scorer.read_lock(), &inflight_htlcs),
&self.score_params,
&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)>, ()> {
const MAX_PAYMENT_PATHS: usize = 3;
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(u64::MAX))
.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) => match forwarding_info.try_into() {
Ok(payment_relay) => payment_relay,
Err(()) => return None,
},
None => return None,
};
let cltv_expiry_delta = payment_relay.cltv_expiry_delta as u32;
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(u64::MAX),
})
})
.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)
}
}
pub trait Router: MessageRouter {
fn find_route(
&self, payer: &PublicKey, route_params: &RouteParameters,
first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: InFlightHtlcs
) -> Result<Route, LightningError>;
fn find_route_with_id(
&self, payer: &PublicKey, route_params: &RouteParameters,
first_hops: Option<&[&ChannelDetails]>, inflight_htlcs: InFlightHtlcs,
_payment_hash: PaymentHash, _payment_id: PaymentId
) -> Result<Route, LightningError> {
self.find_route(payer, route_params, first_hops, inflight_htlcs)
}
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)>, ()>;
}
pub struct ScorerAccountingForInFlightHtlcs<'a, S: Deref> where S::Target: ScoreLookUp {
scorer: S,
inflight_htlcs: &'a InFlightHtlcs,
}
impl<'a, S: Deref> ScorerAccountingForInFlightHtlcs<'a, S> where S::Target: ScoreLookUp {
pub fn new(scorer: S, inflight_htlcs: &'a InFlightHtlcs) -> Self {
ScorerAccountingForInFlightHtlcs {
scorer,
inflight_htlcs
}
}
}
impl<'a, S: Deref> ScoreLookUp for ScorerAccountingForInFlightHtlcs<'a, S> where S::Target: ScoreLookUp {
type ScoreParams = <S::Target as ScoreLookUp>::ScoreParams;
fn channel_penalty_msat(&self, candidate: &CandidateRouteHop, usage: ChannelUsage, score_params: &Self::ScoreParams) -> u64 {
let target = match candidate.target() {
Some(target) => target,
None => return self.scorer.channel_penalty_msat(candidate, usage, score_params),
};
let short_channel_id = match candidate.short_channel_id() {
Some(short_channel_id) => short_channel_id,
None => return self.scorer.channel_penalty_msat(candidate, usage, score_params),
};
let source = candidate.source();
if let Some(used_liquidity) = self.inflight_htlcs.used_liquidity_msat(
&source, &target, short_channel_id
) {
let usage = ChannelUsage {
inflight_htlc_msat: usage.inflight_htlc_msat.saturating_add(used_liquidity),
..usage
};
self.scorer.channel_penalty_msat(candidate, usage, score_params)
} else {
self.scorer.channel_penalty_msat(candidate, usage, score_params)
}
}
}
#[derive(Clone)]
pub struct InFlightHtlcs(
HashMap<(u64, bool), u64>
);
impl InFlightHtlcs {
pub fn new() -> Self { InFlightHtlcs(HashMap::new()) }
pub fn process_path(&mut self, path: &Path, payer_node_id: PublicKey) {
if path.hops.is_empty() { return };
let mut cumulative_msat = 0;
if let Some(tail) = &path.blinded_tail {
cumulative_msat += tail.final_value_msat;
}
let reversed_hops_with_payer = path.hops.iter().rev().skip(1)
.map(|hop| hop.pubkey)
.chain(core::iter::once(payer_node_id));
for (next_hop, prev_hop) in path.hops.iter().rev().zip(reversed_hops_with_payer) {
cumulative_msat += next_hop.fee_msat;
self.0
.entry((next_hop.short_channel_id, NodeId::from_pubkey(&prev_hop) < NodeId::from_pubkey(&next_hop.pubkey)))
.and_modify(|used_liquidity_msat| *used_liquidity_msat += cumulative_msat)
.or_insert(cumulative_msat);
}
}
pub fn add_inflight_htlc(&mut self, source: &NodeId, target: &NodeId, channel_scid: u64, used_msat: u64){
self.0
.entry((channel_scid, source < target))
.and_modify(|used_liquidity_msat| *used_liquidity_msat += used_msat)
.or_insert(used_msat);
}
pub fn used_liquidity_msat(&self, source: &NodeId, target: &NodeId, channel_scid: u64) -> Option<u64> {
self.0.get(&(channel_scid, source < target)).map(|v| *v)
}
}
impl Writeable for InFlightHtlcs {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> { self.0.write(writer) }
}
impl Readable for InFlightHtlcs {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let infight_map: HashMap<(u64, bool), u64> = Readable::read(reader)?;
Ok(Self(infight_map))
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct RouteHop {
pub pubkey: PublicKey,
pub node_features: NodeFeatures,
pub short_channel_id: u64,
pub channel_features: ChannelFeatures,
pub fee_msat: u64,
pub cltv_expiry_delta: u32,
pub maybe_announced_channel: bool,
}
impl_writeable_tlv_based!(RouteHop, {
(0, pubkey, required),
(1, maybe_announced_channel, (default_value, true)),
(2, node_features, required),
(4, short_channel_id, required),
(6, channel_features, required),
(8, fee_msat, required),
(10, cltv_expiry_delta, required),
});
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BlindedTail {
pub hops: Vec<BlindedHop>,
pub blinding_point: PublicKey,
pub excess_final_cltv_expiry_delta: u32,
pub final_value_msat: u64,
}
impl_writeable_tlv_based!(BlindedTail, {
(0, hops, required_vec),
(2, blinding_point, required),
(4, excess_final_cltv_expiry_delta, required),
(6, final_value_msat, required),
});
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Path {
pub hops: Vec<RouteHop>,
pub blinded_tail: Option<BlindedTail>,
}
impl Path {
pub fn fee_msat(&self) -> u64 {
match &self.blinded_tail {
Some(_) => self.hops.iter().map(|hop| hop.fee_msat).sum::<u64>(),
None => {
self.hops.split_last().map_or(0,
|(_, path_prefix)| path_prefix.iter().map(|hop| hop.fee_msat).sum())
}
}
}
pub fn final_value_msat(&self) -> u64 {
match &self.blinded_tail {
Some(blinded_tail) => blinded_tail.final_value_msat,
None => self.hops.last().map_or(0, |hop| hop.fee_msat)
}
}
pub fn final_cltv_expiry_delta(&self) -> Option<u32> {
match &self.blinded_tail {
Some(_) => None,
None => self.hops.last().map(|hop| hop.cltv_expiry_delta)
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Route {
pub paths: Vec<Path>,
pub route_params: Option<RouteParameters>,
}
impl Route {
pub fn get_total_fees(&self) -> u64 {
let overpaid_value_msat = self.route_params.as_ref()
.map_or(0, |p| self.get_total_amount().saturating_sub(p.final_value_msat));
overpaid_value_msat + self.paths.iter().map(|path| path.fee_msat()).sum::<u64>()
}
pub fn get_total_amount(&self) -> u64 {
self.paths.iter().map(|path| path.final_value_msat()).sum()
}
}
impl fmt::Display for Route {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
log_route!(self).fmt(f)
}
}
const SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 1;
impl Writeable for Route {
fn write<W: crate::util::ser::Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION);
(self.paths.len() as u64).write(writer)?;
let mut blinded_tails = Vec::new();
for (idx, path) in self.paths.iter().enumerate() {
(path.hops.len() as u8).write(writer)?;
for hop in path.hops.iter() {
hop.write(writer)?;
}
if let Some(blinded_tail) = &path.blinded_tail {
if blinded_tails.is_empty() {
blinded_tails = Vec::with_capacity(path.hops.len());
for _ in 0..idx {
blinded_tails.push(None);
}
}
blinded_tails.push(Some(blinded_tail));
} else if !blinded_tails.is_empty() { blinded_tails.push(None); }
}
write_tlv_fields!(writer, {
(1, self.route_params.as_ref().map(|p| &p.payment_params), option),
(2, blinded_tails, optional_vec),
(3, self.route_params.as_ref().map(|p| p.final_value_msat), option),
(5, self.route_params.as_ref().and_then(|p| p.max_total_routing_fee_msat), option),
});
Ok(())
}
}
impl Readable for Route {
fn read<R: io::Read>(reader: &mut R) -> Result<Route, DecodeError> {
let _ver = read_ver_prefix!(reader, SERIALIZATION_VERSION);
let path_count: u64 = Readable::read(reader)?;
if path_count == 0 { return Err(DecodeError::InvalidValue); }
let mut paths = Vec::with_capacity(cmp::min(path_count, 128) as usize);
let mut min_final_cltv_expiry_delta = u32::max_value();
for _ in 0..path_count {
let hop_count: u8 = Readable::read(reader)?;
let mut hops: Vec<RouteHop> = Vec::with_capacity(hop_count as usize);
for _ in 0..hop_count {
hops.push(Readable::read(reader)?);
}
if hops.is_empty() { return Err(DecodeError::InvalidValue); }
min_final_cltv_expiry_delta =
cmp::min(min_final_cltv_expiry_delta, hops.last().unwrap().cltv_expiry_delta);
paths.push(Path { hops, blinded_tail: None });
}
_init_and_read_len_prefixed_tlv_fields!(reader, {
(1, payment_params, (option: ReadableArgs, min_final_cltv_expiry_delta)),
(2, blinded_tails, optional_vec),
(3, final_value_msat, option),
(5, max_total_routing_fee_msat, option)
});
let blinded_tails = blinded_tails.unwrap_or(Vec::new());
if blinded_tails.len() != 0 {
if blinded_tails.len() != paths.len() { return Err(DecodeError::InvalidValue) }
for (path, blinded_tail_opt) in paths.iter_mut().zip(blinded_tails.into_iter()) {
path.blinded_tail = blinded_tail_opt;
}
}
let route_params = match (payment_params, final_value_msat) {
(Some(payment_params), Some(final_value_msat)) => {
Some(RouteParameters { payment_params, final_value_msat, max_total_routing_fee_msat })
}
_ => None,
};
Ok(Route { paths, route_params })
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct RouteParameters {
pub payment_params: PaymentParameters,
pub final_value_msat: u64,
pub max_total_routing_fee_msat: Option<u64>,
}
impl RouteParameters {
pub fn from_payment_params_and_value(payment_params: PaymentParameters, final_value_msat: u64) -> Self {
Self { payment_params, final_value_msat, max_total_routing_fee_msat: Some(final_value_msat / 100 + 50_000) }
}
}
impl Writeable for RouteParameters {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
write_tlv_fields!(writer, {
(0, self.payment_params, required),
(1, self.max_total_routing_fee_msat, option),
(2, self.final_value_msat, required),
(4, self.payment_params.payee.final_cltv_expiry_delta(), option),
});
Ok(())
}
}
impl Readable for RouteParameters {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
_init_and_read_len_prefixed_tlv_fields!(reader, {
(0, payment_params, (required: ReadableArgs, 0)),
(1, max_total_routing_fee_msat, option),
(2, final_value_msat, required),
(4, final_cltv_delta, option),
});
let mut payment_params: PaymentParameters = payment_params.0.unwrap();
if let Payee::Clear { ref mut final_cltv_expiry_delta, .. } = payment_params.payee {
if final_cltv_expiry_delta == &0 {
*final_cltv_expiry_delta = final_cltv_delta.ok_or(DecodeError::InvalidValue)?;
}
}
Ok(Self {
payment_params,
final_value_msat: final_value_msat.0.unwrap(),
max_total_routing_fee_msat,
})
}
}
pub const DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA: u32 = 1008;
pub const DEFAULT_MAX_PATH_COUNT: u8 = 10;
const DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF: u8 = 2;
const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40;
const MAX_PATH_LENGTH_ESTIMATE: u8 = 19;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct PaymentParameters {
pub payee: Payee,
pub expiry_time: Option<u64>,
pub max_total_cltv_expiry_delta: u32,
pub max_path_count: u8,
pub max_channel_saturation_power_of_half: u8,
pub previously_failed_channels: Vec<u64>,
pub previously_failed_blinded_path_idxs: Vec<u64>,
}
impl Writeable for PaymentParameters {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
let mut clear_hints = &vec![];
let mut blinded_hints = &vec![];
match &self.payee {
Payee::Clear { route_hints, .. } => clear_hints = route_hints,
Payee::Blinded { route_hints, .. } => blinded_hints = route_hints,
}
write_tlv_fields!(writer, {
(0, self.payee.node_id(), option),
(1, self.max_total_cltv_expiry_delta, required),
(2, self.payee.features(), option),
(3, self.max_path_count, required),
(4, *clear_hints, required_vec),
(5, self.max_channel_saturation_power_of_half, required),
(6, self.expiry_time, option),
(7, self.previously_failed_channels, required_vec),
(8, *blinded_hints, optional_vec),
(9, self.payee.final_cltv_expiry_delta(), option),
(11, self.previously_failed_blinded_path_idxs, required_vec),
});
Ok(())
}
}
impl ReadableArgs<u32> for PaymentParameters {
fn read<R: io::Read>(reader: &mut R, default_final_cltv_expiry_delta: u32) -> Result<Self, DecodeError> {
_init_and_read_len_prefixed_tlv_fields!(reader, {
(0, payee_pubkey, option),
(1, max_total_cltv_expiry_delta, (default_value, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA)),
(2, features, (option: ReadableArgs, payee_pubkey.is_some())),
(3, max_path_count, (default_value, DEFAULT_MAX_PATH_COUNT)),
(4, clear_route_hints, required_vec),
(5, max_channel_saturation_power_of_half, (default_value, DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF)),
(6, expiry_time, option),
(7, previously_failed_channels, optional_vec),
(8, blinded_route_hints, optional_vec),
(9, final_cltv_expiry_delta, (default_value, default_final_cltv_expiry_delta)),
(11, previously_failed_blinded_path_idxs, optional_vec),
});
let blinded_route_hints = blinded_route_hints.unwrap_or(vec![]);
let payee = if blinded_route_hints.len() != 0 {
if clear_route_hints.len() != 0 || payee_pubkey.is_some() { return Err(DecodeError::InvalidValue) }
Payee::Blinded {
route_hints: blinded_route_hints,
features: features.and_then(|f: Features| f.bolt12()),
}
} else {
Payee::Clear {
route_hints: clear_route_hints,
node_id: payee_pubkey.ok_or(DecodeError::InvalidValue)?,
features: features.and_then(|f| f.bolt11()),
final_cltv_expiry_delta: final_cltv_expiry_delta.0.unwrap(),
}
};
Ok(Self {
max_total_cltv_expiry_delta: _init_tlv_based_struct_field!(max_total_cltv_expiry_delta, (default_value, unused)),
max_path_count: _init_tlv_based_struct_field!(max_path_count, (default_value, unused)),
payee,
max_channel_saturation_power_of_half: _init_tlv_based_struct_field!(max_channel_saturation_power_of_half, (default_value, unused)),
expiry_time,
previously_failed_channels: previously_failed_channels.unwrap_or(Vec::new()),
previously_failed_blinded_path_idxs: previously_failed_blinded_path_idxs.unwrap_or(Vec::new()),
})
}
}
impl PaymentParameters {
pub fn from_node_id(payee_pubkey: PublicKey, final_cltv_expiry_delta: u32) -> Self {
Self {
payee: Payee::Clear { node_id: payee_pubkey, route_hints: vec![], features: None, final_cltv_expiry_delta },
expiry_time: None,
max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
max_path_count: DEFAULT_MAX_PATH_COUNT,
max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF,
previously_failed_channels: Vec::new(),
previously_failed_blinded_path_idxs: Vec::new(),
}
}
pub fn for_keysend(payee_pubkey: PublicKey, final_cltv_expiry_delta: u32, allow_mpp: bool) -> Self {
Self::from_node_id(payee_pubkey, final_cltv_expiry_delta)
.with_bolt11_features(Bolt11InvoiceFeatures::for_keysend(allow_mpp))
.expect("PaymentParameters::from_node_id should always initialize the payee as unblinded")
}
pub fn from_bolt12_invoice(invoice: &Bolt12Invoice) -> Self {
Self::blinded(invoice.payment_paths().to_vec())
.with_bolt12_features(invoice.invoice_features().clone()).unwrap()
.with_expiry_time(invoice.created_at().as_secs().saturating_add(invoice.relative_expiry().as_secs()))
}
pub fn blinded(blinded_route_hints: Vec<(BlindedPayInfo, BlindedPath)>) -> Self {
Self {
payee: Payee::Blinded { route_hints: blinded_route_hints, features: None },
expiry_time: None,
max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
max_path_count: DEFAULT_MAX_PATH_COUNT,
max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF,
previously_failed_channels: Vec::new(),
previously_failed_blinded_path_idxs: Vec::new(),
}
}
pub fn with_bolt12_features(self, features: Bolt12InvoiceFeatures) -> Result<Self, ()> {
match self.payee {
Payee::Clear { .. } => Err(()),
Payee::Blinded { route_hints, .. } =>
Ok(Self { payee: Payee::Blinded { route_hints, features: Some(features) }, ..self })
}
}
pub fn with_bolt11_features(self, features: Bolt11InvoiceFeatures) -> Result<Self, ()> {
match self.payee {
Payee::Blinded { .. } => Err(()),
Payee::Clear { route_hints, node_id, final_cltv_expiry_delta, .. } =>
Ok(Self {
payee: Payee::Clear {
route_hints, node_id, features: Some(features), final_cltv_expiry_delta
}, ..self
})
}
}
pub fn with_route_hints(self, route_hints: Vec<RouteHint>) -> Result<Self, ()> {
match self.payee {
Payee::Blinded { .. } => Err(()),
Payee::Clear { node_id, features, final_cltv_expiry_delta, .. } =>
Ok(Self {
payee: Payee::Clear {
route_hints, node_id, features, final_cltv_expiry_delta,
}, ..self
})
}
}
pub fn with_expiry_time(self, expiry_time: u64) -> Self {
Self { expiry_time: Some(expiry_time), ..self }
}
pub fn with_max_total_cltv_expiry_delta(self, max_total_cltv_expiry_delta: u32) -> Self {
Self { max_total_cltv_expiry_delta, ..self }
}
pub fn with_max_path_count(self, max_path_count: u8) -> Self {
Self { max_path_count, ..self }
}
pub fn with_max_channel_saturation_power_of_half(self, max_channel_saturation_power_of_half: u8) -> Self {
Self { max_channel_saturation_power_of_half, ..self }
}
pub(crate) fn insert_previously_failed_blinded_path(&mut self, failed_blinded_tail: &BlindedTail) {
let mut found_blinded_tail = false;
for (idx, (_, path)) in self.payee.blinded_route_hints().iter().enumerate() {
if failed_blinded_tail.hops == path.blinded_hops &&
failed_blinded_tail.blinding_point == path.blinding_point
{
self.previously_failed_blinded_path_idxs.push(idx as u64);
found_blinded_tail = true;
}
}
debug_assert!(found_blinded_tail);
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum Payee {
Blinded {
route_hints: Vec<(BlindedPayInfo, BlindedPath)>,
features: Option<Bolt12InvoiceFeatures>,
},
Clear {
node_id: PublicKey,
route_hints: Vec<RouteHint>,
features: Option<Bolt11InvoiceFeatures>,
final_cltv_expiry_delta: u32,
},
}
impl Payee {
fn node_id(&self) -> Option<PublicKey> {
match self {
Self::Clear { node_id, .. } => Some(*node_id),
_ => None,
}
}
fn node_features(&self) -> Option<NodeFeatures> {
match self {
Self::Clear { features, .. } => features.as_ref().map(|f| f.to_context()),
Self::Blinded { features, .. } => features.as_ref().map(|f| f.to_context()),
}
}
fn supports_basic_mpp(&self) -> bool {
match self {
Self::Clear { features, .. } => features.as_ref().map_or(false, |f| f.supports_basic_mpp()),
Self::Blinded { features, .. } => features.as_ref().map_or(false, |f| f.supports_basic_mpp()),
}
}
fn features(&self) -> Option<FeaturesRef> {
match self {
Self::Clear { features, .. } => features.as_ref().map(|f| FeaturesRef::Bolt11(f)),
Self::Blinded { features, .. } => features.as_ref().map(|f| FeaturesRef::Bolt12(f)),
}
}
fn final_cltv_expiry_delta(&self) -> Option<u32> {
match self {
Self::Clear { final_cltv_expiry_delta, .. } => Some(*final_cltv_expiry_delta),
_ => None,
}
}
fn blinded_route_hints(&self) -> &[(BlindedPayInfo, BlindedPath)] {
match self {
Self::Blinded { route_hints, .. } => &route_hints[..],
Self::Clear { .. } => &[]
}
}
fn unblinded_route_hints(&self) -> &[RouteHint] {
match self {
Self::Blinded { .. } => &[],
Self::Clear { route_hints, .. } => &route_hints[..]
}
}
}
enum FeaturesRef<'a> {
Bolt11(&'a Bolt11InvoiceFeatures),
Bolt12(&'a Bolt12InvoiceFeatures),
}
enum Features {
Bolt11(Bolt11InvoiceFeatures),
Bolt12(Bolt12InvoiceFeatures),
}
impl Features {
fn bolt12(self) -> Option<Bolt12InvoiceFeatures> {
match self {
Self::Bolt12(f) => Some(f),
_ => None,
}
}
fn bolt11(self) -> Option<Bolt11InvoiceFeatures> {
match self {
Self::Bolt11(f) => Some(f),
_ => None,
}
}
}
impl<'a> Writeable for FeaturesRef<'a> {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
match self {
Self::Bolt11(f) => Ok(f.write(w)?),
Self::Bolt12(f) => Ok(f.write(w)?),
}
}
}
impl ReadableArgs<bool> for Features {
fn read<R: io::Read>(reader: &mut R, bolt11: bool) -> Result<Self, DecodeError> {
if bolt11 { return Ok(Self::Bolt11(Readable::read(reader)?)) }
Ok(Self::Bolt12(Readable::read(reader)?))
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct RouteHint(pub Vec<RouteHintHop>);
impl Writeable for RouteHint {
fn write<W: crate::util::ser::Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
(self.0.len() as u64).write(writer)?;
for hop in self.0.iter() {
hop.write(writer)?;
}
Ok(())
}
}
impl Readable for RouteHint {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let hop_count: u64 = Readable::read(reader)?;
let mut hops = Vec::with_capacity(cmp::min(hop_count, 16) as usize);
for _ in 0..hop_count {
hops.push(Readable::read(reader)?);
}
Ok(Self(hops))
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct RouteHintHop {
pub src_node_id: PublicKey,
pub short_channel_id: u64,
pub fees: RoutingFees,
pub cltv_expiry_delta: u16,
pub htlc_minimum_msat: Option<u64>,
pub htlc_maximum_msat: Option<u64>,
}
impl_writeable_tlv_based!(RouteHintHop, {
(0, src_node_id, required),
(1, htlc_minimum_msat, option),
(2, short_channel_id, required),
(3, htlc_maximum_msat, option),
(4, fees, required),
(6, cltv_expiry_delta, required),
});
#[derive(Eq, PartialEq)]
#[repr(align(64))] struct RouteGraphNode {
node_id: NodeId,
score: u64,
value_contribution_msat: u64,
total_cltv_delta: u32,
path_length_to_node: u8,
}
impl cmp::Ord for RouteGraphNode {
fn cmp(&self, other: &RouteGraphNode) -> cmp::Ordering {
other.score.cmp(&self.score).then_with(|| other.node_id.cmp(&self.node_id))
}
}
impl cmp::PartialOrd for RouteGraphNode {
fn partial_cmp(&self, other: &RouteGraphNode) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
#[cfg(any(ldk_bench, not(any(test, fuzzing))))]
const _GRAPH_NODE_SMALL: usize = 64 - core::mem::size_of::<RouteGraphNode>();
#[cfg(any(ldk_bench, not(any(test, fuzzing))))]
const _GRAPH_NODE_FIXED_SIZE: usize = core::mem::size_of::<RouteGraphNode>() - 64;
#[derive(Clone, Debug)]
pub struct FirstHopCandidate<'a> {
pub details: &'a ChannelDetails,
pub payer_node_id: &'a NodeId,
}
#[derive(Clone, Debug)]
pub struct PublicHopCandidate<'a> {
pub info: DirectedChannelInfo<'a>,
pub short_channel_id: u64,
}
#[derive(Clone, Debug)]
pub struct PrivateHopCandidate<'a> {
pub hint: &'a RouteHintHop,
pub target_node_id: &'a NodeId
}
#[derive(Clone, Debug)]
pub struct BlindedPathCandidate<'a> {
pub hint: &'a (BlindedPayInfo, BlindedPath),
hint_idx: usize,
}
#[derive(Clone, Debug)]
pub struct OneHopBlindedPathCandidate<'a> {
pub hint: &'a (BlindedPayInfo, BlindedPath),
hint_idx: usize,
}
#[derive(Clone, Debug)]
pub enum CandidateRouteHop<'a> {
FirstHop(FirstHopCandidate<'a>),
PublicHop(PublicHopCandidate<'a>),
PrivateHop(PrivateHopCandidate<'a>),
Blinded(BlindedPathCandidate<'a>),
OneHopBlinded(OneHopBlindedPathCandidate<'a>),
}
impl<'a> CandidateRouteHop<'a> {
#[inline]
fn short_channel_id(&self) -> Option<u64> {
match self {
CandidateRouteHop::FirstHop(hop) => hop.details.get_outbound_payment_scid(),
CandidateRouteHop::PublicHop(hop) => Some(hop.short_channel_id),
CandidateRouteHop::PrivateHop(hop) => Some(hop.hint.short_channel_id),
CandidateRouteHop::Blinded(_) => None,
CandidateRouteHop::OneHopBlinded(_) => None,
}
}
#[inline]
pub fn globally_unique_short_channel_id(&self) -> Option<u64> {
match self {
CandidateRouteHop::FirstHop(hop) => if hop.details.is_public { hop.details.short_channel_id } else { None },
CandidateRouteHop::PublicHop(hop) => Some(hop.short_channel_id),
CandidateRouteHop::PrivateHop(_) => None,
CandidateRouteHop::Blinded(_) => None,
CandidateRouteHop::OneHopBlinded(_) => None,
}
}
fn features(&self) -> ChannelFeatures {
match self {
CandidateRouteHop::FirstHop(hop) => hop.details.counterparty.features.to_context(),
CandidateRouteHop::PublicHop(hop) => hop.info.channel().features.clone(),
CandidateRouteHop::PrivateHop(_) => ChannelFeatures::empty(),
CandidateRouteHop::Blinded(_) => ChannelFeatures::empty(),
CandidateRouteHop::OneHopBlinded(_) => ChannelFeatures::empty(),
}
}
#[inline]
pub fn cltv_expiry_delta(&self) -> u32 {
match self {
CandidateRouteHop::FirstHop(_) => 0,
CandidateRouteHop::PublicHop(hop) => hop.info.direction().cltv_expiry_delta as u32,
CandidateRouteHop::PrivateHop(hop) => hop.hint.cltv_expiry_delta as u32,
CandidateRouteHop::Blinded(hop) => hop.hint.0.cltv_expiry_delta as u32,
CandidateRouteHop::OneHopBlinded(_) => 0,
}
}
#[inline]
pub fn htlc_minimum_msat(&self) -> u64 {
match self {
CandidateRouteHop::FirstHop(hop) => hop.details.next_outbound_htlc_minimum_msat,
CandidateRouteHop::PublicHop(hop) => hop.info.direction().htlc_minimum_msat,
CandidateRouteHop::PrivateHop(hop) => hop.hint.htlc_minimum_msat.unwrap_or(0),
CandidateRouteHop::Blinded(hop) => hop.hint.0.htlc_minimum_msat,
CandidateRouteHop::OneHopBlinded { .. } => 0,
}
}
#[inline]
pub fn fees(&self) -> RoutingFees {
match self {
CandidateRouteHop::FirstHop(_) => RoutingFees {
base_msat: 0, proportional_millionths: 0,
},
CandidateRouteHop::PublicHop(hop) => hop.info.direction().fees,
CandidateRouteHop::PrivateHop(hop) => hop.hint.fees,
CandidateRouteHop::Blinded(hop) => {
RoutingFees {
base_msat: hop.hint.0.fee_base_msat,
proportional_millionths: hop.hint.0.fee_proportional_millionths
}
},
CandidateRouteHop::OneHopBlinded(_) =>
RoutingFees { base_msat: 0, proportional_millionths: 0 },
}
}
fn effective_capacity(&self) -> EffectiveCapacity {
match self {
CandidateRouteHop::FirstHop(hop) => EffectiveCapacity::ExactLiquidity {
liquidity_msat: hop.details.next_outbound_htlc_limit_msat,
},
CandidateRouteHop::PublicHop(hop) => hop.info.effective_capacity(),
CandidateRouteHop::PrivateHop(PrivateHopCandidate { hint: RouteHintHop { htlc_maximum_msat: Some(max), .. }, .. }) =>
EffectiveCapacity::HintMaxHTLC { amount_msat: *max },
CandidateRouteHop::PrivateHop(PrivateHopCandidate { hint: RouteHintHop { htlc_maximum_msat: None, .. }, .. }) =>
EffectiveCapacity::Infinite,
CandidateRouteHop::Blinded(hop) =>
EffectiveCapacity::HintMaxHTLC { amount_msat: hop.hint.0.htlc_maximum_msat },
CandidateRouteHop::OneHopBlinded(_) => EffectiveCapacity::Infinite,
}
}
#[inline]
fn id(&self) -> CandidateHopId {
match self {
CandidateRouteHop::Blinded(hop) => CandidateHopId::Blinded(hop.hint_idx),
CandidateRouteHop::OneHopBlinded(hop) => CandidateHopId::Blinded(hop.hint_idx),
_ => CandidateHopId::Clear((self.short_channel_id().unwrap(), self.source() < self.target().unwrap())),
}
}
fn blinded_path(&self) -> Option<&'a BlindedPath> {
match self {
CandidateRouteHop::Blinded(BlindedPathCandidate { hint, .. }) | CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, .. }) => {
Some(&hint.1)
},
_ => None,
}
}
fn blinded_hint_idx(&self) -> Option<usize> {
match self {
Self::Blinded(BlindedPathCandidate { hint_idx, .. }) |
Self::OneHopBlinded(OneHopBlindedPathCandidate { hint_idx, .. }) => {
Some(*hint_idx)
},
_ => None,
}
}
#[inline]
pub fn source(&self) -> NodeId {
match self {
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(),
}
}
#[inline]
pub fn target(&self) -> Option<NodeId> {
match self {
CandidateRouteHop::FirstHop(hop) => Some(hop.details.counterparty.node_id.into()),
CandidateRouteHop::PublicHop(hop) => Some(*hop.info.target()),
CandidateRouteHop::PrivateHop(hop) => Some(*hop.target_node_id),
CandidateRouteHop::Blinded(_) => None,
CandidateRouteHop::OneHopBlinded(_) => None,
}
}
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialOrd, PartialEq)]
enum CandidateHopId {
Clear((u64, bool)),
Blinded(usize),
}
#[inline]
fn max_htlc_from_capacity(capacity: EffectiveCapacity, max_channel_saturation_power_of_half: u8) -> u64 {
let saturation_shift: u32 = max_channel_saturation_power_of_half as u32;
match capacity {
EffectiveCapacity::ExactLiquidity { liquidity_msat } => liquidity_msat,
EffectiveCapacity::Infinite => u64::max_value(),
EffectiveCapacity::Unknown => EffectiveCapacity::Unknown.as_msat(),
EffectiveCapacity::AdvertisedMaxHTLC { amount_msat } =>
amount_msat.checked_shr(saturation_shift).unwrap_or(0),
EffectiveCapacity::HintMaxHTLC { amount_msat } => amount_msat,
EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat } =>
cmp::min(capacity_msat.checked_shr(saturation_shift).unwrap_or(0), htlc_maximum_msat),
}
}
fn iter_equal<I1: Iterator, I2: Iterator>(mut iter_a: I1, mut iter_b: I2)
-> bool where I1::Item: PartialEq<I2::Item> {
loop {
let a = iter_a.next();
let b = iter_b.next();
if a.is_none() && b.is_none() { return true; }
if a.is_none() || b.is_none() { return false; }
if a.unwrap().ne(&b.unwrap()) { return false; }
}
}
#[derive(Clone)]
#[repr(C)] struct PathBuildingHop<'a> {
candidate: CandidateRouteHop<'a>,
was_processed: bool,
total_fee_msat: u64,
path_htlc_minimum_msat: u64,
path_penalty_msat: u64,
fee_msat: u64,
next_hops_fee_msat: u64,
hop_use_fee_msat: u64,
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
value_contribution_msat: u64,
}
#[cfg(ldk_bench)]
const _NODE_MAP_SIZE_TWO_CACHE_LINES: usize = 128 - core::mem::size_of::<(NodeId, PathBuildingHop)>();
#[cfg(ldk_bench)]
const _NODE_MAP_SIZE_EXACTLY_CACHE_LINES: usize = core::mem::size_of::<(NodeId, PathBuildingHop)>() - 128;
impl<'a> core::fmt::Debug for PathBuildingHop<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
let mut debug_struct = f.debug_struct("PathBuildingHop");
debug_struct
.field("node_id", &self.candidate.target())
.field("short_channel_id", &self.candidate.short_channel_id())
.field("total_fee_msat", &self.total_fee_msat)
.field("next_hops_fee_msat", &self.next_hops_fee_msat)
.field("hop_use_fee_msat", &self.hop_use_fee_msat)
.field("total_fee_msat - (next_hops_fee_msat + hop_use_fee_msat)", &(&self.total_fee_msat - (&self.next_hops_fee_msat + &self.hop_use_fee_msat)))
.field("path_penalty_msat", &self.path_penalty_msat)
.field("path_htlc_minimum_msat", &self.path_htlc_minimum_msat)
.field("cltv_expiry_delta", &self.candidate.cltv_expiry_delta());
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
let debug_struct = debug_struct
.field("value_contribution_msat", &self.value_contribution_msat);
debug_struct.finish()
}
}
#[derive(Clone)]
struct PaymentPath<'a> {
hops: Vec<(PathBuildingHop<'a>, NodeFeatures)>,
}
impl<'a> PaymentPath<'a> {
fn get_value_msat(&self) -> u64 {
self.hops.last().unwrap().0.fee_msat
}
fn get_path_penalty_msat(&self) -> u64 {
self.hops.first().map(|h| h.0.path_penalty_msat).unwrap_or(u64::max_value())
}
fn get_total_fee_paid_msat(&self) -> u64 {
if self.hops.len() < 1 {
return 0;
}
let mut result = 0;
for (i, (hop, _)) in self.hops.iter().enumerate() {
if i != self.hops.len() - 1 {
result += hop.fee_msat;
}
}
return result;
}
fn get_cost_msat(&self) -> u64 {
self.get_total_fee_paid_msat().saturating_add(self.get_path_penalty_msat())
}
fn update_value_and_recompute_fees(&mut self, value_msat: u64) -> u64 {
let mut extra_contribution_msat = 0;
let mut total_fee_paid_msat = 0 as u64;
for i in (0..self.hops.len()).rev() {
let last_hop = i == self.hops.len() - 1;
let mut cur_hop_fees_msat = 0;
if !last_hop {
cur_hop_fees_msat = self.hops.get(i + 1).unwrap().0.hop_use_fee_msat;
}
let cur_hop = &mut self.hops.get_mut(i).unwrap().0;
cur_hop.next_hops_fee_msat = total_fee_paid_msat;
cur_hop.path_penalty_msat += extra_contribution_msat;
let mut cur_hop_transferred_amount_msat = total_fee_paid_msat + value_msat;
if let Some(extra_fees_msat) = cur_hop.candidate.htlc_minimum_msat().checked_sub(cur_hop_transferred_amount_msat) {
cur_hop_transferred_amount_msat += extra_fees_msat;
if last_hop {
extra_contribution_msat = extra_fees_msat;
} else {
total_fee_paid_msat += extra_fees_msat;
cur_hop_fees_msat += extra_fees_msat;
}
}
if last_hop {
cur_hop.fee_msat = cur_hop_transferred_amount_msat;
} else {
cur_hop.fee_msat = cur_hop_fees_msat;
}
if i != 0 {
if let Some(new_fee) = compute_fees(cur_hop_transferred_amount_msat, cur_hop.candidate.fees()) {
cur_hop.hop_use_fee_msat = new_fee;
total_fee_paid_msat += new_fee;
} else {
unreachable!();
}
}
}
value_msat + extra_contribution_msat
}
}
#[inline(always)]
fn compute_fees(amount_msat: u64, channel_fees: RoutingFees) -> Option<u64> {
amount_msat.checked_mul(channel_fees.proportional_millionths as u64)
.and_then(|part| (channel_fees.base_msat as u64).checked_add(part / 1_000_000))
}
#[inline(always)]
fn compute_fees_saturating(amount_msat: u64, channel_fees: RoutingFees) -> u64 {
amount_msat.checked_mul(channel_fees.proportional_millionths as u64)
.map(|prop| prop / 1_000_000).unwrap_or(u64::max_value())
.saturating_add(channel_fees.base_msat as u64)
}
fn default_node_features() -> NodeFeatures {
let mut features = NodeFeatures::empty();
features.set_variable_length_onion_optional();
features
}
struct LoggedPayeePubkey(Option<PublicKey>);
impl fmt::Display for LoggedPayeePubkey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
Some(pk) => {
"payee node id ".fmt(f)?;
pk.fmt(f)
},
None => {
"blinded payee".fmt(f)
},
}
}
}
struct LoggedCandidateHop<'a>(&'a CandidateRouteHop<'a>);
impl<'a> fmt::Display for LoggedCandidateHop<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
CandidateRouteHop::Blinded(BlindedPathCandidate { hint, .. }) | CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, .. }) => {
"blinded route hint with introduction node id ".fmt(f)?;
hint.1.introduction_node_id.fmt(f)?;
" and blinding point ".fmt(f)?;
hint.1.blinding_point.fmt(f)
},
CandidateRouteHop::FirstHop(_) => {
"first hop with SCID ".fmt(f)?;
self.0.short_channel_id().unwrap().fmt(f)
},
CandidateRouteHop::PrivateHop(_) => {
"route hint with SCID ".fmt(f)?;
self.0.short_channel_id().unwrap().fmt(f)
},
_ => {
"SCID ".fmt(f)?;
self.0.short_channel_id().unwrap().fmt(f)
},
}
}
}
#[inline]
fn sort_first_hop_channels(
channels: &mut Vec<&ChannelDetails>, used_liquidities: &HashMap<CandidateHopId, u64>,
recommended_value_msat: u64, our_node_pubkey: &PublicKey
) {
channels.sort_unstable_by(|chan_a, chan_b| {
let chan_a_outbound_limit_msat = chan_a.next_outbound_htlc_limit_msat
.saturating_sub(*used_liquidities.get(&CandidateHopId::Clear((chan_a.get_outbound_payment_scid().unwrap(),
our_node_pubkey < &chan_a.counterparty.node_id))).unwrap_or(&0));
let chan_b_outbound_limit_msat = chan_b.next_outbound_htlc_limit_msat
.saturating_sub(*used_liquidities.get(&CandidateHopId::Clear((chan_b.get_outbound_payment_scid().unwrap(),
our_node_pubkey < &chan_b.counterparty.node_id))).unwrap_or(&0));
if chan_b_outbound_limit_msat < recommended_value_msat || chan_a_outbound_limit_msat < recommended_value_msat {
chan_b_outbound_limit_msat.cmp(&chan_a_outbound_limit_msat)
} else {
chan_a_outbound_limit_msat.cmp(&chan_b_outbound_limit_msat)
}
});
}
pub fn find_route<L: Deref, GL: Deref, S: ScoreLookUp>(
our_node_pubkey: &PublicKey, route_params: &RouteParameters,
network_graph: &NetworkGraph<GL>, first_hops: Option<&[&ChannelDetails]>, logger: L,
scorer: &S, score_params: &S::ScoreParams, random_seed_bytes: &[u8; 32]
) -> Result<Route, LightningError>
where L::Target: Logger, GL::Target: Logger {
let graph_lock = network_graph.read_only();
let mut route = get_route(our_node_pubkey, &route_params, &graph_lock, first_hops, logger,
scorer, score_params, random_seed_bytes)?;
add_random_cltv_offset(&mut route, &route_params.payment_params, &graph_lock, random_seed_bytes);
Ok(route)
}
pub(crate) fn get_route<L: Deref, S: ScoreLookUp>(
our_node_pubkey: &PublicKey, route_params: &RouteParameters, network_graph: &ReadOnlyNetworkGraph,
first_hops: Option<&[&ChannelDetails]>, logger: L, scorer: &S, score_params: &S::ScoreParams,
_random_seed_bytes: &[u8; 32]
) -> Result<Route, LightningError>
where L::Target: Logger {
let payment_params = &route_params.payment_params;
let final_value_msat = route_params.final_value_msat;
let payee_node_id_opt = payment_params.payee.node_id().map(|pk| NodeId::from_pubkey(&pk));
const DUMMY_BLINDED_PAYEE_ID: [u8; 33] = [2; 33];
let maybe_dummy_payee_pk = payment_params.payee.node_id().unwrap_or_else(|| PublicKey::from_slice(&DUMMY_BLINDED_PAYEE_ID).unwrap());
let maybe_dummy_payee_node_id = NodeId::from_pubkey(&maybe_dummy_payee_pk);
let our_node_id = NodeId::from_pubkey(&our_node_pubkey);
if payee_node_id_opt.map_or(false, |payee| payee == our_node_id) {
return Err(LightningError{err: "Cannot generate a route to ourselves".to_owned(), action: ErrorAction::IgnoreError});
}
if our_node_id == maybe_dummy_payee_node_id {
return Err(LightningError{err: "Invalid origin node id provided, use a different one".to_owned(), action: ErrorAction::IgnoreError});
}
if final_value_msat > MAX_VALUE_MSAT {
return Err(LightningError{err: "Cannot generate a route of more value than all existing satoshis".to_owned(), action: ErrorAction::IgnoreError});
}
if final_value_msat == 0 {
return Err(LightningError{err: "Cannot send a payment of 0 msat".to_owned(), action: ErrorAction::IgnoreError});
}
match &payment_params.payee {
Payee::Clear { route_hints, node_id, .. } => {
for route in route_hints.iter() {
for hop in &route.0 {
if hop.src_node_id == *node_id {
return Err(LightningError{err: "Route hint cannot have the payee as the source.".to_owned(), action: ErrorAction::IgnoreError});
}
}
}
},
Payee::Blinded { route_hints, .. } => {
if route_hints.iter().all(|(_, path)| &path.introduction_node_id == our_node_pubkey) {
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() {
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 {
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)
{
return Err(LightningError{err: format!("1-hop blinded paths must all have matching introduction node ids"), action: ErrorAction::IgnoreError});
}
}
}
}
let final_cltv_expiry_delta = payment_params.payee.final_cltv_expiry_delta().unwrap_or(0);
if payment_params.max_total_cltv_expiry_delta <= final_cltv_expiry_delta {
return Err(LightningError{err: "Can't find a route where the maximum total CLTV expiry delta is below the final CLTV expiry.".to_owned(), action: ErrorAction::IgnoreError});
}
let network_channels = network_graph.channels();
let network_nodes = network_graph.nodes();
if payment_params.max_path_count == 0 {
return Err(LightningError{err: "Can't find a route with no paths allowed.".to_owned(), action: ErrorAction::IgnoreError});
}
let allow_mpp = if payment_params.max_path_count == 1 {
false
} else if payment_params.payee.supports_basic_mpp() {
true
} else if let Some(payee) = payee_node_id_opt {
network_nodes.get(&payee).map_or(false, |node| node.announcement_info.as_ref().map_or(false,
|info| info.features.supports_basic_mpp()))
} else { false };
let max_total_routing_fee_msat = route_params.max_total_routing_fee_msat.unwrap_or(u64::max_value());
log_trace!(logger, "Searching for a route from payer {} to {} {} MPP and {} first hops {}overriding the network graph with a fee limit of {} msat",
our_node_pubkey, LoggedPayeePubkey(payment_params.payee.node_id()),
if allow_mpp { "with" } else { "without" },
first_hops.map(|hops| hops.len()).unwrap_or(0), if first_hops.is_some() { "" } else { "not " },
max_total_routing_fee_msat);
let mut first_hop_targets: HashMap<_, Vec<&ChannelDetails>> =
HashMap::with_capacity(if first_hops.is_some() { first_hops.as_ref().unwrap().len() } else { 0 });
if let Some(hops) = first_hops {
for chan in hops {
if chan.get_outbound_payment_scid().is_none() {
panic!("first_hops should be filled in with usable channels, not pending ones");
}
if chan.counterparty.node_id == *our_node_pubkey {
return Err(LightningError{err: "First hop cannot have our_node_pubkey as a destination.".to_owned(), action: ErrorAction::IgnoreError});
}
first_hop_targets
.entry(NodeId::from_pubkey(&chan.counterparty.node_id))
.or_insert(Vec::new())
.push(chan);
}
if first_hop_targets.is_empty() {
return Err(LightningError{err: "Cannot route when there are no outbound routes away from us".to_owned(), action: ErrorAction::IgnoreError});
}
}
let mut private_hop_key_cache = HashMap::with_capacity(
payment_params.payee.unblinded_route_hints().iter().map(|path| path.0.len()).sum()
);
private_hop_key_cache.insert(maybe_dummy_payee_pk, NodeId::from_pubkey(&maybe_dummy_payee_pk));
for route in payment_params.payee.unblinded_route_hints().iter() {
for hop in route.0.iter() {
private_hop_key_cache.insert(hop.src_node_id, NodeId::from_pubkey(&hop.src_node_id));
}
}
let mut targets: BinaryHeap<RouteGraphNode> = BinaryHeap::new();
let mut dist: HashMap<NodeId, PathBuildingHop> = HashMap::with_capacity(network_nodes.len());
let mut hit_minimum_limit;
const ROUTE_CAPACITY_PROVISION_FACTOR: u64 = 3;
let recommended_value_msat = final_value_msat * ROUTE_CAPACITY_PROVISION_FACTOR as u64;
let mut path_value_msat = final_value_msat;
let minimal_value_contribution_msat: u64 = if allow_mpp {
(final_value_msat + (payment_params.max_path_count as u64 - 1)) / payment_params.max_path_count as u64
} else {
final_value_msat
};
let mut channel_saturation_pow_half = payment_params.max_channel_saturation_power_of_half;
let mut used_liquidities: HashMap<CandidateHopId, u64> =
HashMap::with_capacity(network_nodes.len());
let mut already_collected_value_msat = 0;
for (_, channels) in first_hop_targets.iter_mut() {
sort_first_hop_channels(channels, &used_liquidities, recommended_value_msat,
our_node_pubkey);
}
log_trace!(logger, "Building path from {} to payer {} for value {} msat.",
LoggedPayeePubkey(payment_params.payee.node_id()), our_node_pubkey, final_value_msat);
let mut num_ignored_value_contribution: u32 = 0;
let mut num_ignored_path_length_limit: u32 = 0;
let mut num_ignored_cltv_delta_limit: u32 = 0;
let mut num_ignored_previously_failed: u32 = 0;
let mut num_ignored_total_fee_limit: u32 = 0;
let mut num_ignored_avoid_overpayment: u32 = 0;
let mut num_ignored_htlc_minimum_msat_limit: u32 = 0;
macro_rules! add_entry {
( $candidate: expr, $next_hops_fee_msat: expr,
$next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr,
$next_hops_path_penalty_msat: expr, $next_hops_cltv_delta: expr, $next_hops_path_length: expr ) => { {
let mut hop_contribution_amt_msat = None;
let src_node_id = $candidate.source();
if Some(src_node_id) != $candidate.target() {
let scid_opt = $candidate.short_channel_id();
let effective_capacity = $candidate.effective_capacity();
let htlc_maximum_msat = max_htlc_from_capacity(effective_capacity, channel_saturation_pow_half);
if let Some(mut available_value_contribution_msat) = htlc_maximum_msat.checked_sub($next_hops_fee_msat) {
let used_liquidity_msat = used_liquidities
.get(&$candidate.id())
.map_or(0, |used_liquidity_msat| {
available_value_contribution_msat = available_value_contribution_msat
.saturating_sub(*used_liquidity_msat);
*used_liquidity_msat
});
let contributes_sufficient_value = available_value_contribution_msat >= minimal_value_contribution_msat;
let path_length_to_node = $next_hops_path_length + 1;
let exceeds_max_path_length = path_length_to_node > MAX_PATH_LENGTH_ESTIMATE;
let max_total_cltv_expiry_delta = (payment_params.max_total_cltv_expiry_delta - final_cltv_expiry_delta)
.checked_sub(2*MEDIAN_HOP_CLTV_EXPIRY_DELTA)
.unwrap_or(payment_params.max_total_cltv_expiry_delta - final_cltv_expiry_delta);
let hop_total_cltv_delta = ($next_hops_cltv_delta as u32)
.saturating_add($candidate.cltv_expiry_delta());
let exceeds_cltv_delta_limit = hop_total_cltv_delta > max_total_cltv_expiry_delta;
let value_contribution_msat = cmp::min(available_value_contribution_msat, $next_hops_value_contribution);
let amount_to_transfer_over_msat: u64 = match value_contribution_msat.checked_add($next_hops_fee_msat) {
Some(result) => result,
None => unreachable!(),
};
#[allow(unused_comparisons)] let over_path_minimum_msat = amount_to_transfer_over_msat >= $candidate.htlc_minimum_msat() &&
amount_to_transfer_over_msat >= $next_hops_path_htlc_minimum_msat;
#[allow(unused_comparisons)] let may_overpay_to_meet_path_minimum_msat =
((amount_to_transfer_over_msat < $candidate.htlc_minimum_msat() &&
recommended_value_msat >= $candidate.htlc_minimum_msat()) ||
(amount_to_transfer_over_msat < $next_hops_path_htlc_minimum_msat &&
recommended_value_msat >= $next_hops_path_htlc_minimum_msat));
let payment_failed_on_this_channel = match scid_opt {
Some(scid) => payment_params.previously_failed_channels.contains(&scid),
None => match $candidate.blinded_hint_idx() {
Some(idx) => {
payment_params.previously_failed_blinded_path_idxs.contains(&(idx as u64))
},
None => false,
},
};
let (should_log_candidate, first_hop_details) = match $candidate {
CandidateRouteHop::FirstHop(hop) => (true, Some(hop.details)),
CandidateRouteHop::PrivateHop(_) => (true, None),
CandidateRouteHop::Blinded(_) => (true, None),
CandidateRouteHop::OneHopBlinded(_) => (true, None),
_ => (false, None),
};
if !contributes_sufficient_value {
if should_log_candidate {
log_trace!(logger, "Ignoring {} due to insufficient value contribution.", LoggedCandidateHop(&$candidate));
if let Some(details) = first_hop_details {
log_trace!(logger,
"First hop candidate next_outbound_htlc_limit_msat: {}",
details.next_outbound_htlc_limit_msat,
);
}
}
num_ignored_value_contribution += 1;
} else if exceeds_max_path_length {
if should_log_candidate {
log_trace!(logger, "Ignoring {} due to exceeding maximum path length limit.", LoggedCandidateHop(&$candidate));
}
num_ignored_path_length_limit += 1;
} else if exceeds_cltv_delta_limit {
if should_log_candidate {
log_trace!(logger, "Ignoring {} due to exceeding CLTV delta limit.", LoggedCandidateHop(&$candidate));
if let Some(_) = first_hop_details {
log_trace!(logger,
"First hop candidate cltv_expiry_delta: {}. Limit: {}",
hop_total_cltv_delta,
max_total_cltv_expiry_delta,
);
}
}
num_ignored_cltv_delta_limit += 1;
} else if payment_failed_on_this_channel {
if should_log_candidate {
log_trace!(logger, "Ignoring {} due to a failed previous payment attempt.", LoggedCandidateHop(&$candidate));
}
num_ignored_previously_failed += 1;
} else if may_overpay_to_meet_path_minimum_msat {
if should_log_candidate {
log_trace!(logger,
"Ignoring {} to avoid overpaying to meet htlc_minimum_msat limit.",
LoggedCandidateHop(&$candidate));
if let Some(details) = first_hop_details {
log_trace!(logger,
"First hop candidate next_outbound_htlc_minimum_msat: {}",
details.next_outbound_htlc_minimum_msat,
);
}
}
num_ignored_avoid_overpayment += 1;
hit_minimum_limit = true;
} else if over_path_minimum_msat {
let curr_min = cmp::max(
$next_hops_path_htlc_minimum_msat, $candidate.htlc_minimum_msat()
);
let path_htlc_minimum_msat = compute_fees_saturating(curr_min, $candidate.fees())
.saturating_add(curr_min);
let hm_entry = dist.entry(src_node_id);
let old_entry = hm_entry.or_insert_with(|| {
PathBuildingHop {
candidate: $candidate.clone(),
fee_msat: 0,
next_hops_fee_msat: u64::max_value(),
hop_use_fee_msat: u64::max_value(),
total_fee_msat: u64::max_value(),
path_htlc_minimum_msat,
path_penalty_msat: u64::max_value(),
was_processed: false,
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
value_contribution_msat,
}
});
#[allow(unused_mut)] let mut should_process = !old_entry.was_processed;
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
{
if !should_process { should_process = true; }
}
if should_process {
let mut hop_use_fee_msat = 0;
let mut total_fee_msat: u64 = $next_hops_fee_msat;
if src_node_id != our_node_id {
hop_use_fee_msat = compute_fees_saturating(amount_to_transfer_over_msat, $candidate.fees());
total_fee_msat = total_fee_msat.saturating_add(hop_use_fee_msat);
}
if total_fee_msat > max_total_routing_fee_msat {
if should_log_candidate {
log_trace!(logger, "Ignoring {} due to exceeding max total routing fee limit.", LoggedCandidateHop(&$candidate));
if let Some(_) = first_hop_details {
log_trace!(logger,
"First hop candidate routing fee: {}. Limit: {}",
total_fee_msat,
max_total_routing_fee_msat,
);
}
}
num_ignored_total_fee_limit += 1;
} else {
let channel_usage = ChannelUsage {
amount_msat: amount_to_transfer_over_msat,
inflight_htlc_msat: used_liquidity_msat,
effective_capacity,
};
let channel_penalty_msat =
scorer.channel_penalty_msat($candidate,
channel_usage,
score_params);
let path_penalty_msat = $next_hops_path_penalty_msat
.saturating_add(channel_penalty_msat);
let old_cost = cmp::max(old_entry.total_fee_msat, old_entry.path_htlc_minimum_msat)
.saturating_add(old_entry.path_penalty_msat);
let new_cost = cmp::max(total_fee_msat, path_htlc_minimum_msat)
.saturating_add(path_penalty_msat);
if !old_entry.was_processed && new_cost < old_cost {
let new_graph_node = RouteGraphNode {
node_id: src_node_id,
score: cmp::max(total_fee_msat, path_htlc_minimum_msat).saturating_add(path_penalty_msat),
total_cltv_delta: hop_total_cltv_delta,
value_contribution_msat,
path_length_to_node,
};
targets.push(new_graph_node);
old_entry.next_hops_fee_msat = $next_hops_fee_msat;
old_entry.hop_use_fee_msat = hop_use_fee_msat;
old_entry.total_fee_msat = total_fee_msat;
old_entry.candidate = $candidate.clone();
old_entry.fee_msat = 0; old_entry.path_htlc_minimum_msat = path_htlc_minimum_msat;
old_entry.path_penalty_msat = path_penalty_msat;
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
{
old_entry.value_contribution_msat = value_contribution_msat;
}
hop_contribution_amt_msat = Some(value_contribution_msat);
} else if old_entry.was_processed && new_cost < old_cost {
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
{
debug_assert!(path_htlc_minimum_msat < old_entry.path_htlc_minimum_msat);
debug_assert!(
value_contribution_msat + path_penalty_msat <
old_entry.value_contribution_msat + old_entry.path_penalty_msat
);
}
}
}
}
} else {
if should_log_candidate {
log_trace!(logger,
"Ignoring {} due to its htlc_minimum_msat limit.",
LoggedCandidateHop(&$candidate));
if let Some(details) = first_hop_details {
log_trace!(logger,
"First hop candidate next_outbound_htlc_minimum_msat: {}",
details.next_outbound_htlc_minimum_msat,
);
}
}
num_ignored_htlc_minimum_msat_limit += 1;
}
}
}
hop_contribution_amt_msat
} }
}
let default_node_features = default_node_features();
macro_rules! add_entries_to_cheapest_to_target_node {
( $node: expr, $node_id: expr, $next_hops_value_contribution: expr,
$next_hops_cltv_delta: expr, $next_hops_path_length: expr ) => {
let fee_to_target_msat;
let next_hops_path_htlc_minimum_msat;
let next_hops_path_penalty_msat;
let skip_node = if let Some(elem) = dist.get_mut(&$node_id) {
let was_processed = elem.was_processed;
elem.was_processed = true;
fee_to_target_msat = elem.total_fee_msat;
next_hops_path_htlc_minimum_msat = elem.path_htlc_minimum_msat;
next_hops_path_penalty_msat = elem.path_penalty_msat;
was_processed
} else {
debug_assert_eq!($node_id, maybe_dummy_payee_node_id);
fee_to_target_msat = 0;
next_hops_path_htlc_minimum_msat = 0;
next_hops_path_penalty_msat = 0;
false
};
if !skip_node {
if let Some(first_channels) = first_hop_targets.get(&$node_id) {
for details in first_channels {
let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
details, payer_node_id: &our_node_id,
});
add_entry!(&candidate, fee_to_target_msat,
$next_hops_value_contribution,
next_hops_path_htlc_minimum_msat, next_hops_path_penalty_msat,
$next_hops_cltv_delta, $next_hops_path_length);
}
}
let features = if let Some(node_info) = $node.announcement_info.as_ref() {
&node_info.features
} else {
&default_node_features
};
if !features.requires_unknown_bits() {
for chan_id in $node.channels.iter() {
let chan = network_channels.get(chan_id).unwrap();
if !chan.features.requires_unknown_bits() {
if let Some((directed_channel, source)) = chan.as_directed_to(&$node_id) {
if first_hops.is_none() || *source != our_node_id {
if directed_channel.direction().enabled {
let candidate = CandidateRouteHop::PublicHop(PublicHopCandidate {
info: directed_channel,
short_channel_id: *chan_id,
});
add_entry!(&candidate,
fee_to_target_msat,
$next_hops_value_contribution,
next_hops_path_htlc_minimum_msat,
next_hops_path_penalty_msat,
$next_hops_cltv_delta, $next_hops_path_length);
}
}
}
}
}
}
}
};
}
let mut payment_paths = Vec::<PaymentPath>::new();
'paths_collection: loop {
targets.clear();
dist.clear();
hit_minimum_limit = false;
payee_node_id_opt.map(|payee| first_hop_targets.get(&payee).map(|first_channels| {
for details in first_channels {
let candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
details, payer_node_id: &our_node_id,
});
let added = add_entry!(&candidate, 0, path_value_msat,
0, 0u64, 0, 0).is_some();
log_trace!(logger, "{} direct route to payee via {}",
if added { "Added" } else { "Skipped" }, LoggedCandidateHop(&candidate));
}
}));
payee_node_id_opt.map(|payee| match network_nodes.get(&payee) {
None => {},
Some(node) => {
add_entries_to_cheapest_to_target_node!(node, payee, path_value_msat, 0, 0);
},
});
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 =
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 }
let candidate = if hint.1.blinded_hops.len() == 1 {
CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, hint_idx })
} else { CandidateRouteHop::Blinded(BlindedPathCandidate { 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);
for details in first_channels {
let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
details, payer_node_id: &our_node_id,
});
let blinded_path_fee = match compute_fees(path_contribution_msat, candidate.fees()) {
Some(fee) => fee,
None => continue
};
let path_min = candidate.htlc_minimum_msat().saturating_add(
compute_fees_saturating(candidate.htlc_minimum_msat(), candidate.fees()));
add_entry!(&first_hop_candidate, blinded_path_fee,
path_contribution_msat, path_min, 0_u64, candidate.cltv_expiry_delta(),
candidate.blinded_path().map_or(1, |bp| bp.blinded_hops.len() as u8));
}
}
}
for route in payment_params.payee.unblinded_route_hints().iter()
.filter(|route| !route.0.is_empty())
{
let first_hop_src_id = NodeId::from_pubkey(&route.0.first().unwrap().src_node_id);
let first_hop_src_is_reachable =
our_node_id == first_hop_src_id ||
first_hop_targets.get(&first_hop_src_id).is_some() ||
network_nodes.get(&first_hop_src_id).is_some();
if first_hop_src_is_reachable {
let hop_iter = route.0.iter().rev();
let prev_hop_iter = core::iter::once(&maybe_dummy_payee_pk).chain(
route.0.iter().skip(1).rev().map(|hop| &hop.src_node_id));
let mut hop_used = true;
let mut aggregate_next_hops_fee_msat: u64 = 0;
let mut aggregate_next_hops_path_htlc_minimum_msat: u64 = 0;
let mut aggregate_next_hops_path_penalty_msat: u64 = 0;
let mut aggregate_next_hops_cltv_delta: u32 = 0;
let mut aggregate_next_hops_path_length: u8 = 0;
let mut aggregate_path_contribution_msat = path_value_msat;
for (idx, (hop, prev_hop_id)) in hop_iter.zip(prev_hop_iter).enumerate() {
let target = private_hop_key_cache.get(&prev_hop_id).unwrap();
if let Some(first_channels) = first_hop_targets.get(&target) {
if first_channels.iter().any(|d| d.outbound_scid_alias == Some(hop.short_channel_id)) {
log_trace!(logger, "Ignoring route hint with SCID {} (and any previous) due to it being a direct channel of ours.",
hop.short_channel_id);
break;
}
}
let candidate = network_channels
.get(&hop.short_channel_id)
.and_then(|channel| channel.as_directed_to(&target))
.map(|(info, _)| CandidateRouteHop::PublicHop(PublicHopCandidate {
info,
short_channel_id: hop.short_channel_id,
}))
.unwrap_or_else(|| CandidateRouteHop::PrivateHop(PrivateHopCandidate { hint: hop, target_node_id: target }));
if let Some(hop_used_msat) = add_entry!(&candidate,
aggregate_next_hops_fee_msat, aggregate_path_contribution_msat,
aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat,
aggregate_next_hops_cltv_delta, aggregate_next_hops_path_length)
{
aggregate_path_contribution_msat = hop_used_msat;
} else {
hop_used = false;
}
let used_liquidity_msat = used_liquidities
.get(&candidate.id()).copied()
.unwrap_or(0);
let channel_usage = ChannelUsage {
amount_msat: final_value_msat + aggregate_next_hops_fee_msat,
inflight_htlc_msat: used_liquidity_msat,
effective_capacity: candidate.effective_capacity(),
};
let channel_penalty_msat = scorer.channel_penalty_msat(
&candidate, channel_usage, score_params
);
aggregate_next_hops_path_penalty_msat = aggregate_next_hops_path_penalty_msat
.saturating_add(channel_penalty_msat);
aggregate_next_hops_cltv_delta = aggregate_next_hops_cltv_delta
.saturating_add(hop.cltv_expiry_delta as u32);
aggregate_next_hops_path_length = aggregate_next_hops_path_length
.saturating_add(1);
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);
for details in first_channels {
let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
details, payer_node_id: &our_node_id,
});
add_entry!(&first_hop_candidate,
aggregate_next_hops_fee_msat, aggregate_path_contribution_msat,
aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat,
aggregate_next_hops_cltv_delta, aggregate_next_hops_path_length);
}
}
if !hop_used {
break;
}
let hops_fee = compute_fees(aggregate_next_hops_fee_msat + final_value_msat, hop.fees)
.map_or(None, |inc| inc.checked_add(aggregate_next_hops_fee_msat));
aggregate_next_hops_fee_msat = if let Some(val) = hops_fee { val } else { break; };
aggregate_next_hops_path_htlc_minimum_msat = {
let curr_htlc_min = cmp::max(
candidate.htlc_minimum_msat(), aggregate_next_hops_path_htlc_minimum_msat
);
let curr_htlc_min_fee = if let Some(val) = compute_fees(curr_htlc_min, hop.fees) { val } else { break };
if let Some(min) = curr_htlc_min.checked_add(curr_htlc_min_fee) { min } else { break }
};
if idx == route.0.len() - 1 {
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);
for details in first_channels {
let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate {
details, payer_node_id: &our_node_id,
});
add_entry!(&first_hop_candidate,
aggregate_next_hops_fee_msat,
aggregate_path_contribution_msat,
aggregate_next_hops_path_htlc_minimum_msat,
aggregate_next_hops_path_penalty_msat,
aggregate_next_hops_cltv_delta,
aggregate_next_hops_path_length);
}
}
}
}
}
}
log_trace!(logger, "Starting main path collection loop with {} nodes pre-filled from first/last hops.", targets.len());
let mut found_new_path = false;
'path_construction: while let Some(RouteGraphNode { node_id, total_cltv_delta, mut value_contribution_msat, path_length_to_node, .. }) = targets.pop() {
if node_id == our_node_id {
let mut new_entry = dist.remove(&our_node_id).unwrap();
let mut ordered_hops: Vec<(PathBuildingHop, NodeFeatures)> = vec!((new_entry.clone(), default_node_features.clone()));
'path_walk: loop {
let mut features_set = false;
let target = ordered_hops.last().unwrap().0.candidate.target().unwrap_or(maybe_dummy_payee_node_id);
if let Some(first_channels) = first_hop_targets.get(&target) {
for details in first_channels {
if let CandidateRouteHop::FirstHop(FirstHopCandidate { details: last_hop_details, .. })
= ordered_hops.last().unwrap().0.candidate
{
if details.get_outbound_payment_scid() == last_hop_details.get_outbound_payment_scid() {
ordered_hops.last_mut().unwrap().1 = details.counterparty.features.to_context();
features_set = true;
break;
}
}
}
}
if !features_set {
if let Some(node) = network_nodes.get(&target) {
if let Some(node_info) = node.announcement_info.as_ref() {
ordered_hops.last_mut().unwrap().1 = node_info.features.clone();
} else {
ordered_hops.last_mut().unwrap().1 = default_node_features.clone();
}
} else {
}
}
if target == maybe_dummy_payee_node_id {
break 'path_walk;
}
new_entry = match dist.remove(&target) {
Some(payment_hop) => payment_hop,
None => unreachable!(),
};
ordered_hops.last_mut().unwrap().0.fee_msat = new_entry.hop_use_fee_msat;
ordered_hops.push((new_entry.clone(), default_node_features.clone()));
}
ordered_hops.last_mut().unwrap().0.fee_msat = value_contribution_msat;
ordered_hops.last_mut().unwrap().0.hop_use_fee_msat = 0;
log_trace!(logger, "Found a path back to us from the target with {} hops contributing up to {} msat: \n {:#?}",
ordered_hops.len(), value_contribution_msat, ordered_hops.iter().map(|h| &(h.0)).collect::<Vec<&PathBuildingHop>>());
let mut payment_path = PaymentPath {hops: ordered_hops};
debug_assert_eq!(payment_path.get_value_msat(), value_contribution_msat);
let desired_value_contribution = cmp::min(value_contribution_msat, final_value_msat);
value_contribution_msat = payment_path.update_value_and_recompute_fees(desired_value_contribution);
let mut prevented_redundant_path_selection = false;
for (hop, _) in payment_path.hops.iter() {
let spent_on_hop_msat = value_contribution_msat + hop.next_hops_fee_msat;
let used_liquidity_msat = used_liquidities
.entry(hop.candidate.id())
.and_modify(|used_liquidity_msat| *used_liquidity_msat += spent_on_hop_msat)
.or_insert(spent_on_hop_msat);
let hop_capacity = hop.candidate.effective_capacity();
let hop_max_msat = max_htlc_from_capacity(hop_capacity, channel_saturation_pow_half);
if *used_liquidity_msat == hop_max_msat {
prevented_redundant_path_selection = true;
}
debug_assert!(*used_liquidity_msat <= hop_max_msat);
}
if !prevented_redundant_path_selection {
let victim_candidate = &payment_path.hops[(payment_path.hops.len()) / 2].0.candidate;
let exhausted = u64::max_value();
log_trace!(logger,
"Disabling route candidate {} for future path building iterations to avoid duplicates.",
LoggedCandidateHop(victim_candidate));
if let Some(scid) = victim_candidate.short_channel_id() {
*used_liquidities.entry(CandidateHopId::Clear((scid, false))).or_default() = exhausted;
*used_liquidities.entry(CandidateHopId::Clear((scid, true))).or_default() = exhausted;
}
}
already_collected_value_msat += value_contribution_msat;
payment_paths.push(payment_path);
found_new_path = true;
break 'path_construction;
}
if node_id == maybe_dummy_payee_node_id { continue 'path_construction; }
match network_nodes.get(&node_id) {
None => {},
Some(node) => {
add_entries_to_cheapest_to_target_node!(node, node_id,
value_contribution_msat,
total_cltv_delta, path_length_to_node);
},
}
}
if !allow_mpp {
if !found_new_path && channel_saturation_pow_half != 0 {
channel_saturation_pow_half = 0;
continue 'paths_collection;
}
break 'paths_collection;
}
if !found_new_path && channel_saturation_pow_half != 0 {
channel_saturation_pow_half = 0;
} else if !found_new_path && hit_minimum_limit && already_collected_value_msat < final_value_msat && path_value_msat != recommended_value_msat {
log_trace!(logger, "Failed to collect enough value, but running again to collect extra paths with a potentially higher limit.");
path_value_msat = recommended_value_msat;
} else if already_collected_value_msat >= recommended_value_msat || !found_new_path {
log_trace!(logger, "Have now collected {} msat (seeking {} msat) in paths. Last path loop {} a new path.",
already_collected_value_msat, recommended_value_msat, if found_new_path { "found" } else { "did not find" });
break 'paths_collection;
} else if found_new_path && already_collected_value_msat == final_value_msat && payment_paths.len() == 1 {
if !hit_minimum_limit {
log_trace!(logger, "Collected exactly our payment amount on the first pass, without hitting an htlc_minimum_msat limit, exiting.");
break 'paths_collection;
}
log_trace!(logger, "Collected our payment amount on the first pass, but running again to collect extra paths with a potentially higher value to meet htlc_minimum_msat limit.");
path_value_msat = recommended_value_msat;
}
}
let num_ignored_total = num_ignored_value_contribution + num_ignored_path_length_limit +
num_ignored_cltv_delta_limit + num_ignored_previously_failed +
num_ignored_avoid_overpayment + num_ignored_htlc_minimum_msat_limit +
num_ignored_total_fee_limit;
if num_ignored_total > 0 {
log_trace!(logger,
"Ignored {} candidate hops due to insufficient value contribution, {} due to path length limit, {} due to CLTV delta limit, {} due to previous payment failure, {} due to htlc_minimum_msat limit, {} to avoid overpaying, {} due to maximum total fee limit. Total: {} ignored candidates.",
num_ignored_value_contribution, num_ignored_path_length_limit,
num_ignored_cltv_delta_limit, num_ignored_previously_failed,
num_ignored_htlc_minimum_msat_limit, num_ignored_avoid_overpayment,
num_ignored_total_fee_limit, num_ignored_total);
}
if payment_paths.len() == 0 {
return Err(LightningError{err: "Failed to find a path to the given destination".to_owned(), action: ErrorAction::IgnoreError});
}
if already_collected_value_msat < final_value_msat {
return Err(LightningError{err: "Failed to find a sufficient route to the given destination".to_owned(), action: ErrorAction::IgnoreError});
}
let mut selected_route = payment_paths;
debug_assert_eq!(selected_route.iter().map(|p| p.get_value_msat()).sum::<u64>(), already_collected_value_msat);
let mut overpaid_value_msat = already_collected_value_msat - final_value_msat;
selected_route.sort_unstable_by(|a, b|
(((b.get_cost_msat() as u128) << 64) / (b.get_value_msat() as u128))
.cmp(&(((a.get_cost_msat() as u128) << 64) / (a.get_value_msat() as u128)))
);
let mut paths_left = selected_route.len();
selected_route.retain(|path| {
if paths_left == 1 {
return true
}
let path_value_msat = path.get_value_msat();
if path_value_msat <= overpaid_value_msat {
overpaid_value_msat -= path_value_msat;
paths_left -= 1;
return false;
}
true
});
debug_assert!(selected_route.len() > 0);
if overpaid_value_msat != 0 {
selected_route.sort_unstable_by(|a, b| {
let a_f = a.hops.iter().map(|hop| hop.0.candidate.fees().proportional_millionths as u64).sum::<u64>();
let b_f = b.hops.iter().map(|hop| hop.0.candidate.fees().proportional_millionths as u64).sum::<u64>();
a_f.cmp(&b_f).then_with(|| b.get_cost_msat().cmp(&a.get_cost_msat()))
});
let expensive_payment_path = selected_route.first_mut().unwrap();
let expensive_path_new_value_msat = expensive_payment_path.get_value_msat() - overpaid_value_msat;
expensive_payment_path.update_value_and_recompute_fees(expensive_path_new_value_msat);
}
selected_route.sort_unstable_by_key(|path| {
let mut key = [CandidateHopId::Clear((42, true)) ; MAX_PATH_LENGTH_ESTIMATE as usize];
debug_assert!(path.hops.len() <= key.len());
for (scid, key) in path.hops.iter() .map(|h| h.0.candidate.id()).zip(key.iter_mut()) {
*key = scid;
}
key
});
for idx in 0..(selected_route.len() - 1) {
if idx + 1 >= selected_route.len() { break; }
if iter_equal(selected_route[idx ].hops.iter().map(|h| (h.0.candidate.id(), h.0.candidate.target())),
selected_route[idx + 1].hops.iter().map(|h| (h.0.candidate.id(), h.0.candidate.target()))) {
let new_value = selected_route[idx].get_value_msat() + selected_route[idx + 1].get_value_msat();
selected_route[idx].update_value_and_recompute_fees(new_value);
selected_route.remove(idx + 1);
}
}
let mut paths = Vec::new();
for payment_path in selected_route {
let mut hops = Vec::with_capacity(payment_path.hops.len());
for (hop, node_features) in payment_path.hops.iter()
.filter(|(h, _)| h.candidate.short_channel_id().is_some())
{
let target = hop.candidate.target().expect("target is defined when short_channel_id is defined");
let maybe_announced_channel = if let CandidateRouteHop::PublicHop(_) = hop.candidate {
true
} else if let CandidateRouteHop::FirstHop(first_hop) = &hop.candidate {
first_hop.details.is_public
} else {
network_graph.node(&target).map_or(false, |hop_node|
hop_node.channels.iter().any(|scid| network_graph.channel(*scid)
.map_or(false, |c| c.as_directed_from(&hop.candidate.source()).is_some()))
)
};
hops.push(RouteHop {
pubkey: PublicKey::from_slice(target.as_slice()).map_err(|_| LightningError{err: format!("Public key {:?} is invalid", &target), action: ErrorAction::IgnoreAndLog(Level::Trace)})?,
node_features: node_features.clone(),
short_channel_id: hop.candidate.short_channel_id().unwrap(),
channel_features: hop.candidate.features(),
fee_msat: hop.fee_msat,
cltv_expiry_delta: hop.candidate.cltv_expiry_delta(),
maybe_announced_channel,
});
}
let mut final_cltv_delta = final_cltv_expiry_delta;
let blinded_tail = payment_path.hops.last().and_then(|(h, _)| {
if let Some(blinded_path) = h.candidate.blinded_path() {
final_cltv_delta = h.candidate.cltv_expiry_delta();
Some(BlindedTail {
hops: blinded_path.blinded_hops.clone(),
blinding_point: blinded_path.blinding_point,
excess_final_cltv_expiry_delta: 0,
final_value_msat: h.fee_msat,
})
} else { None }
});
hops.iter_mut().rev().fold(final_cltv_delta, |prev_cltv_expiry_delta, hop| {
core::mem::replace(&mut hop.cltv_expiry_delta, prev_cltv_expiry_delta)
});
paths.push(Path { hops, blinded_tail });
}
debug_assert!(paths.len() <= payment_params.max_path_count.into());
if let Some(node_features) = payment_params.payee.node_features() {
for path in paths.iter_mut() {
path.hops.last_mut().unwrap().node_features = node_features.clone();
}
}
let route = Route { paths, route_params: Some(route_params.clone()) };
if let Some(max_total_routing_fee_msat) = route_params.max_total_routing_fee_msat {
if route.get_total_fees() > max_total_routing_fee_msat {
return Err(LightningError{err: format!("Failed to find route that adheres to the maximum total fee limit of {}msat",
max_total_routing_fee_msat), action: ErrorAction::IgnoreError});
}
}
log_info!(logger, "Got route: {}", log_route!(route));
Ok(route)
}
fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
network_graph: &ReadOnlyNetworkGraph, random_seed_bytes: &[u8; 32]
) {
let network_channels = network_graph.channels();
let network_nodes = network_graph.nodes();
for path in route.paths.iter_mut() {
let mut shadow_ctlv_expiry_delta_offset: u32 = 0;
let mut nodes_to_avoid: [NodeId; 3] = [NodeId::from_pubkey(&path.hops.last().unwrap().pubkey),
NodeId::from_pubkey(&path.hops.get(path.hops.len().saturating_sub(2)).unwrap().pubkey),
NodeId::from_pubkey(&path.hops.get(path.hops.len().saturating_sub(3)).unwrap().pubkey)];
let mut cur_hop: Option<NodeId> = None;
let mut path_nonce = [0u8; 12];
if let Some(starting_hop) = path.hops.iter().rev()
.find(|h| network_nodes.contains_key(&NodeId::from_pubkey(&h.pubkey))) {
cur_hop = Some(NodeId::from_pubkey(&starting_hop.pubkey));
path_nonce.copy_from_slice(&cur_hop.unwrap().as_slice()[..12]);
}
let mut prng = ChaCha20::new(random_seed_bytes, &path_nonce);
let mut random_path_bytes = [0u8; ::core::mem::size_of::<usize>()];
prng.process_in_place(&mut random_path_bytes);
let random_walk_length = usize::from_be_bytes(random_path_bytes).wrapping_rem(3).wrapping_add(1);
for random_hop in 0..random_walk_length {
let mut random_hop_offset = MEDIAN_HOP_CLTV_EXPIRY_DELTA;
if let Some(cur_node_id) = cur_hop {
if let Some(cur_node) = network_nodes.get(&cur_node_id) {
prng.process_in_place(&mut random_path_bytes);
if let Some(random_channel) = usize::from_be_bytes(random_path_bytes)
.checked_rem(cur_node.channels.len())
.and_then(|index| cur_node.channels.get(index))
.and_then(|id| network_channels.get(id)) {
random_channel.as_directed_from(&cur_node_id).map(|(dir_info, next_id)| {
if !nodes_to_avoid.iter().any(|x| x == next_id) {
nodes_to_avoid[random_hop] = *next_id;
random_hop_offset = dir_info.direction().cltv_expiry_delta.into();
cur_hop = Some(*next_id);
}
});
}
}
}
shadow_ctlv_expiry_delta_offset = shadow_ctlv_expiry_delta_offset
.checked_add(random_hop_offset)
.unwrap_or(shadow_ctlv_expiry_delta_offset);
}
const MAX_SHADOW_CLTV_EXPIRY_DELTA_OFFSET: u32 = 3*144;
shadow_ctlv_expiry_delta_offset = cmp::min(shadow_ctlv_expiry_delta_offset, MAX_SHADOW_CLTV_EXPIRY_DELTA_OFFSET);
let path_total_cltv_expiry_delta: u32 = path.hops.iter().map(|h| h.cltv_expiry_delta).sum();
let mut max_path_offset = payment_params.max_total_cltv_expiry_delta - path_total_cltv_expiry_delta;
max_path_offset = cmp::max(
max_path_offset - (max_path_offset % MEDIAN_HOP_CLTV_EXPIRY_DELTA),
max_path_offset % MEDIAN_HOP_CLTV_EXPIRY_DELTA);
shadow_ctlv_expiry_delta_offset = cmp::min(shadow_ctlv_expiry_delta_offset, max_path_offset);
if let Some(tail) = path.blinded_tail.as_mut() {
tail.excess_final_cltv_expiry_delta = tail.excess_final_cltv_expiry_delta
.checked_add(shadow_ctlv_expiry_delta_offset).unwrap_or(tail.excess_final_cltv_expiry_delta);
}
if let Some(last_hop) = path.hops.last_mut() {
last_hop.cltv_expiry_delta = last_hop.cltv_expiry_delta
.checked_add(shadow_ctlv_expiry_delta_offset).unwrap_or(last_hop.cltv_expiry_delta);
}
}
}
pub fn build_route_from_hops<L: Deref, GL: Deref>(
our_node_pubkey: &PublicKey, hops: &[PublicKey], route_params: &RouteParameters,
network_graph: &NetworkGraph<GL>, logger: L, random_seed_bytes: &[u8; 32]
) -> Result<Route, LightningError>
where L::Target: Logger, GL::Target: Logger {
let graph_lock = network_graph.read_only();
let mut route = build_route_from_hops_internal(our_node_pubkey, hops, &route_params,
&graph_lock, logger, random_seed_bytes)?;
add_random_cltv_offset(&mut route, &route_params.payment_params, &graph_lock, random_seed_bytes);
Ok(route)
}
fn build_route_from_hops_internal<L: Deref>(
our_node_pubkey: &PublicKey, hops: &[PublicKey], route_params: &RouteParameters,
network_graph: &ReadOnlyNetworkGraph, logger: L, random_seed_bytes: &[u8; 32],
) -> Result<Route, LightningError> where L::Target: Logger {
struct HopScorer {
our_node_id: NodeId,
hop_ids: [Option<NodeId>; MAX_PATH_LENGTH_ESTIMATE as usize],
}
impl ScoreLookUp for HopScorer {
type ScoreParams = ();
fn channel_penalty_msat(&self, candidate: &CandidateRouteHop,
_usage: ChannelUsage, _score_params: &Self::ScoreParams) -> u64
{
let mut cur_id = self.our_node_id;
for i in 0..self.hop_ids.len() {
if let Some(next_id) = self.hop_ids[i] {
if cur_id == candidate.source() && Some(next_id) == candidate.target() {
return 0;
}
cur_id = next_id;
} else {
break;
}
}
u64::max_value()
}
}
impl<'a> Writeable for HopScorer {
#[inline]
fn write<W: Writer>(&self, _w: &mut W) -> Result<(), io::Error> {
unreachable!();
}
}
if hops.len() > MAX_PATH_LENGTH_ESTIMATE.into() {
return Err(LightningError{err: "Cannot build a route exceeding the maximum path length.".to_owned(), action: ErrorAction::IgnoreError});
}
let our_node_id = NodeId::from_pubkey(our_node_pubkey);
let mut hop_ids = [None; MAX_PATH_LENGTH_ESTIMATE as usize];
for i in 0..hops.len() {
hop_ids[i] = Some(NodeId::from_pubkey(&hops[i]));
}
let scorer = HopScorer { our_node_id, hop_ids };
get_route(our_node_pubkey, route_params, network_graph, None, logger, &scorer, &Default::default(), random_seed_bytes)
}
#[cfg(test)]
mod tests {
use crate::blinded_path::{BlindedHop, BlindedPath};
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,
BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees,
DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE, RouteParameters, CandidateRouteHop, PublicHopCandidate};
use crate::routing::scoring::{ChannelUsage, FixedPenaltyScorer, ScoreLookUp, ProbabilisticScorer, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters};
use crate::routing::test_utils::{add_channel, add_or_update_node, build_graph, build_line_graph, id_to_feature_flags, get_nodes, update_channel};
use crate::chain::transaction::OutPoint;
use crate::sign::EntropySource;
use crate::ln::ChannelId;
use crate::ln::features::{BlindedHopFeatures, ChannelFeatures, InitFeatures, NodeFeatures};
use crate::ln::msgs::{ErrorAction, LightningError, UnsignedChannelUpdate, MAX_VALUE_MSAT};
use crate::ln::channelmanager;
use crate::offers::invoice::BlindedPayInfo;
use crate::util::config::UserConfig;
use crate::util::test_utils as ln_test_utils;
use crate::crypto::chacha20::ChaCha20;
use crate::util::ser::{Readable, Writeable};
#[cfg(c_bindings)]
use crate::util::ser::Writer;
use bitcoin::hashes::Hash;
use bitcoin::network::constants::Network;
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::blockdata::script::Builder;
use bitcoin::blockdata::opcodes;
use bitcoin::blockdata::transaction::TxOut;
use bitcoin::hashes::hex::FromHex;
use bitcoin::secp256k1::{PublicKey,SecretKey};
use bitcoin::secp256k1::Secp256k1;
use crate::io::Cursor;
use crate::prelude::*;
use crate::sync::Arc;
use core::convert::TryInto;
fn get_channel_details(short_channel_id: Option<u64>, node_id: PublicKey,
features: InitFeatures, outbound_capacity_msat: u64) -> channelmanager::ChannelDetails {
channelmanager::ChannelDetails {
channel_id: ChannelId::new_zero(),
counterparty: channelmanager::ChannelCounterparty {
features,
node_id,
unspendable_punishment_reserve: 0,
forwarding_info: None,
outbound_htlc_minimum_msat: None,
outbound_htlc_maximum_msat: None,
},
funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
channel_type: None,
short_channel_id,
outbound_scid_alias: None,
inbound_scid_alias: None,
channel_value_satoshis: 0,
user_channel_id: 0,
balance_msat: 0,
outbound_capacity_msat,
next_outbound_htlc_limit_msat: outbound_capacity_msat,
next_outbound_htlc_minimum_msat: 0,
inbound_capacity_msat: 42,
unspendable_punishment_reserve: None,
confirmations_required: None,
confirmations: None,
force_close_spend_delay: None,
is_outbound: true, is_channel_ready: true,
is_usable: true, is_public: true,
inbound_htlc_minimum_msat: None,
inbound_htlc_maximum_msat: None,
config: None,
feerate_sat_per_1000_weight: None,
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
}
}
#[test]
fn simple_route_test() {
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[2], 42);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 0);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
&route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes) {
assert_eq!(err, "Cannot send a payment of 0 msat");
} else { panic!(); }
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
assert_eq!(route.paths[0].hops[0].fee_msat, 100);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
assert_eq!(route.paths[0].hops[1].fee_msat, 100);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
}
#[test]
fn invalid_first_hop_test() {
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[2], 42);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let our_chans = vec![get_channel_details(Some(2), our_id, InitFeatures::from_le_bytes(vec![0b11]), 100000)];
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
&route_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()),
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "First hop cannot have our_node_pubkey as a destination.");
} else { panic!(); }
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
}
#[test]
fn htlc_minimum_test() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[2], 42);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 2, cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 2, cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 2, cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 2, cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2, cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 3,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 200_000_000,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 3,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 199_999_999,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 199_999_999);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
&route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!(); }
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 4,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
}
#[test]
fn htlc_minimum_overpay_test() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let config = UserConfig::default();
let payment_params = PaymentParameters::from_node_id(nodes[2], 42)
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap();
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 35_000,
htlc_maximum_msat: 40_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 3,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 35_000,
htlc_maximum_msat: 40_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 3,
flags: 2, cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
let mut route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 60_000);
route_params.max_total_routing_fee_msat = Some(15_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
let overpaid_fees = route.paths[0].hops[0].fee_msat + route.paths[1].hops[0].fee_msat;
assert_eq!(overpaid_fees, 15_000);
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 4,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 65_000,
htlc_maximum_msat: 80_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 3,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 4,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 100_000,
excess_data: Vec::new()
});
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops[0].short_channel_id, 12);
let fees = route.paths[0].hops[0].fee_msat;
assert_eq!(fees, 5_000);
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 50_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
let fees = route.paths[0].hops[0].fee_msat;
assert_eq!(fees, 5_000);
}
#[test]
fn htlc_minimum_recipient_overpay_test() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let config = UserConfig::default();
let payment_params = PaymentParameters::from_node_id(nodes[2], 42).with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap();
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 3,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 0,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 15_000,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
let mut route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 5_000);
route_params.max_total_routing_fee_msat = Some(9_999);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
&route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find route that adheres to the maximum total fee limit of 9999msat");
} else { panic!(); }
let mut route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 5_000);
route_params.max_total_routing_fee_msat = Some(10_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.get_total_fees(), 10_000);
}
#[test]
fn disable_channels_test() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[2], 42);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 2, cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 2, cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
&route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!(); }
let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(),
InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
let route = get_route(&our_id, &route_params, &network_graph.read_only(),
Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 42);
assert_eq!(route.paths[0].hops[0].fee_msat, 200);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &vec![0b11]); assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &Vec::<u8>::new()); assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
assert_eq!(route.paths[0].hops[1].fee_msat, 100);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(13));
}
#[test]
fn disable_node_test() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[2], 42);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let mut unknown_features = NodeFeatures::empty();
unknown_features.set_unknown_feature_required();
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[0], unknown_features.clone(), 1);
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[1], unknown_features.clone(), 1);
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[7], unknown_features.clone(), 1);
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
&route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!(); }
let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(),
InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
let route = get_route(&our_id, &route_params, &network_graph.read_only(),
Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 42);
assert_eq!(route.paths[0].hops[0].fee_msat, 200);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &vec![0b11]); assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &Vec::<u8>::new()); assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
assert_eq!(route.paths[0].hops[1].fee_msat, 100);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(13));
}
#[test]
fn our_chans_test() {
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[0], 42);
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 3);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
assert_eq!(route.paths[0].hops[0].fee_msat, 200);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
assert_eq!(route.paths[0].hops[1].fee_msat, 100);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (3 << 4) | 2);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[2].pubkey, nodes[0]);
assert_eq!(route.paths[0].hops[2].short_channel_id, 3);
assert_eq!(route.paths[0].hops[2].fee_msat, 100);
assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(1));
assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(3));
let payment_params = PaymentParameters::from_node_id(nodes[2], 42);
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(),
InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
let route = get_route(&our_id, &route_params, &network_graph.read_only(),
Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 42);
assert_eq!(route.paths[0].hops[0].fee_msat, 200);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &vec![0b11]);
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &Vec::<u8>::new()); assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
assert_eq!(route.paths[0].hops[1].fee_msat, 100);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(13));
}
fn last_hops(nodes: &Vec<PublicKey>) -> Vec<RouteHint> {
let zero_fees = RoutingFees {
base_msat: 0,
proportional_millionths: 0,
};
vec![RouteHint(vec![RouteHintHop {
src_node_id: nodes[3],
short_channel_id: 8,
fees: zero_fees,
cltv_expiry_delta: (8 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}
]), RouteHint(vec![RouteHintHop {
src_node_id: nodes[4],
short_channel_id: 9,
fees: RoutingFees {
base_msat: 1001,
proportional_millionths: 0,
},
cltv_expiry_delta: (9 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}]), RouteHint(vec![RouteHintHop {
src_node_id: nodes[5],
short_channel_id: 10,
fees: zero_fees,
cltv_expiry_delta: (10 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}])]
}
fn last_hops_multi_private_channels(nodes: &Vec<PublicKey>) -> Vec<RouteHint> {
let zero_fees = RoutingFees {
base_msat: 0,
proportional_millionths: 0,
};
vec![RouteHint(vec![RouteHintHop {
src_node_id: nodes[2],
short_channel_id: 5,
fees: RoutingFees {
base_msat: 100,
proportional_millionths: 0,
},
cltv_expiry_delta: (5 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}, RouteHintHop {
src_node_id: nodes[3],
short_channel_id: 8,
fees: zero_fees,
cltv_expiry_delta: (8 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}
]), RouteHint(vec![RouteHintHop {
src_node_id: nodes[4],
short_channel_id: 9,
fees: RoutingFees {
base_msat: 1001,
proportional_millionths: 0,
},
cltv_expiry_delta: (9 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}]), RouteHint(vec![RouteHintHop {
src_node_id: nodes[5],
short_channel_id: 10,
fees: zero_fees,
cltv_expiry_delta: (10 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}])]
}
#[test]
fn partial_route_hint_test() {
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let invalid_last_hop = RouteHint(vec![RouteHintHop {
src_node_id: nodes[6],
short_channel_id: 8,
fees: RoutingFees {
base_msat: 1000,
proportional_millionths: 0,
},
cltv_expiry_delta: (8 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}]);
let mut invalid_last_hops = last_hops_multi_private_channels(&nodes);
invalid_last_hops.push(invalid_last_hop);
{
let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
.with_route_hints(invalid_last_hops).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
&route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes) {
assert_eq!(err, "Route hint cannot have the payee as the source.");
} else { panic!(); }
}
let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
.with_route_hints(last_hops_multi_private_channels(&nodes)).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 5);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
assert_eq!(route.paths[0].hops[0].fee_msat, 100);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
assert_eq!(route.paths[0].hops[1].fee_msat, 0);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (6 << 4) | 1);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[2].pubkey, nodes[4]);
assert_eq!(route.paths[0].hops[2].short_channel_id, 6);
assert_eq!(route.paths[0].hops[2].fee_msat, 0);
assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, (11 << 4) | 1);
assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(5));
assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(6));
assert_eq!(route.paths[0].hops[3].pubkey, nodes[3]);
assert_eq!(route.paths[0].hops[3].short_channel_id, 11);
assert_eq!(route.paths[0].hops[3].fee_msat, 0);
assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, (8 << 4) | 1);
assert_eq!(route.paths[0].hops[3].node_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &id_to_feature_flags(11));
assert_eq!(route.paths[0].hops[4].pubkey, nodes[6]);
assert_eq!(route.paths[0].hops[4].short_channel_id, 8);
assert_eq!(route.paths[0].hops[4].fee_msat, 100);
assert_eq!(route.paths[0].hops[4].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[4].node_features.le_flags(), default_node_features().le_flags()); assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::<u8>::new()); }
fn empty_last_hop(nodes: &Vec<PublicKey>) -> Vec<RouteHint> {
let zero_fees = RoutingFees {
base_msat: 0,
proportional_millionths: 0,
};
vec![RouteHint(vec![RouteHintHop {
src_node_id: nodes[3],
short_channel_id: 8,
fees: zero_fees,
cltv_expiry_delta: (8 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}]), RouteHint(vec![
]), RouteHint(vec![RouteHintHop {
src_node_id: nodes[5],
short_channel_id: 10,
fees: zero_fees,
cltv_expiry_delta: (10 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}])]
}
#[test]
fn ignores_empty_last_hops_test() {
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(empty_last_hop(&nodes)).unwrap();
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 5);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
assert_eq!(route.paths[0].hops[0].fee_msat, 100);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
assert_eq!(route.paths[0].hops[1].fee_msat, 0);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (6 << 4) | 1);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[2].pubkey, nodes[4]);
assert_eq!(route.paths[0].hops[2].short_channel_id, 6);
assert_eq!(route.paths[0].hops[2].fee_msat, 0);
assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, (11 << 4) | 1);
assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(5));
assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(6));
assert_eq!(route.paths[0].hops[3].pubkey, nodes[3]);
assert_eq!(route.paths[0].hops[3].short_channel_id, 11);
assert_eq!(route.paths[0].hops[3].fee_msat, 0);
assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, (8 << 4) | 1);
assert_eq!(route.paths[0].hops[3].node_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &id_to_feature_flags(11));
assert_eq!(route.paths[0].hops[4].pubkey, nodes[6]);
assert_eq!(route.paths[0].hops[4].short_channel_id, 8);
assert_eq!(route.paths[0].hops[4].fee_msat, 100);
assert_eq!(route.paths[0].hops[4].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[4].node_features.le_flags(), default_node_features().le_flags()); assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::<u8>::new()); }
fn multi_hop_last_hops_hint(hint_hops: [PublicKey; 2]) -> Vec<RouteHint> {
let zero_fees = RoutingFees {
base_msat: 0,
proportional_millionths: 0,
};
vec![RouteHint(vec![RouteHintHop {
src_node_id: hint_hops[0],
short_channel_id: 0xff00,
fees: RoutingFees {
base_msat: 100,
proportional_millionths: 0,
},
cltv_expiry_delta: (5 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}, RouteHintHop {
src_node_id: hint_hops[1],
short_channel_id: 0xff01,
fees: zero_fees,
cltv_expiry_delta: (8 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}])]
}
#[test]
fn multi_hint_last_hops_test() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let last_hops = multi_hop_last_hops_hint([nodes[2], nodes[3]]);
let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops.clone()).unwrap();
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 2, cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2, cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 4);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
assert_eq!(route.paths[0].hops[0].fee_msat, 200);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, 65);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
assert_eq!(route.paths[0].hops[1].fee_msat, 100);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 81);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[2].pubkey, nodes[3]);
assert_eq!(route.paths[0].hops[2].short_channel_id, last_hops[0].0[0].short_channel_id);
assert_eq!(route.paths[0].hops[2].fee_msat, 0);
assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, 129);
assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &Vec::<u8>::new()); assert_eq!(route.paths[0].hops[3].pubkey, nodes[6]);
assert_eq!(route.paths[0].hops[3].short_channel_id, last_hops[0].0[1].short_channel_id);
assert_eq!(route.paths[0].hops[3].fee_msat, 100);
assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[3].node_features.le_flags(), default_node_features().le_flags()); assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::<u8>::new()); }
#[test]
fn private_multi_hint_last_hops_test() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let non_announced_privkey = SecretKey::from_slice(&<Vec<u8>>::from_hex(&format!("{:02x}", 0xf0).repeat(32)).unwrap()[..]).unwrap();
let non_announced_pubkey = PublicKey::from_secret_key(&secp_ctx, &non_announced_privkey);
let last_hops = multi_hop_last_hops_hint([nodes[2], non_announced_pubkey]);
let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops.clone()).unwrap();
let scorer = ln_test_utils::TestScorer::new();
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 2, cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2, cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &[42u8; 32]).unwrap();
assert_eq!(route.paths[0].hops.len(), 4);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
assert_eq!(route.paths[0].hops[0].fee_msat, 200);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, 65);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
assert_eq!(route.paths[0].hops[1].fee_msat, 100);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 81);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[2].pubkey, non_announced_pubkey);
assert_eq!(route.paths[0].hops[2].short_channel_id, last_hops[0].0[0].short_channel_id);
assert_eq!(route.paths[0].hops[2].fee_msat, 0);
assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, 129);
assert_eq!(route.paths[0].hops[2].node_features.le_flags(), default_node_features().le_flags()); assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &Vec::<u8>::new()); assert_eq!(route.paths[0].hops[3].pubkey, nodes[6]);
assert_eq!(route.paths[0].hops[3].short_channel_id, last_hops[0].0[1].short_channel_id);
assert_eq!(route.paths[0].hops[3].fee_msat, 100);
assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[3].node_features.le_flags(), default_node_features().le_flags()); assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::<u8>::new()); }
fn last_hops_with_public_channel(nodes: &Vec<PublicKey>) -> Vec<RouteHint> {
let zero_fees = RoutingFees {
base_msat: 0,
proportional_millionths: 0,
};
vec![RouteHint(vec![RouteHintHop {
src_node_id: nodes[4],
short_channel_id: 11,
fees: zero_fees,
cltv_expiry_delta: (11 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}, RouteHintHop {
src_node_id: nodes[3],
short_channel_id: 8,
fees: zero_fees,
cltv_expiry_delta: (8 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}]), RouteHint(vec![RouteHintHop {
src_node_id: nodes[4],
short_channel_id: 9,
fees: RoutingFees {
base_msat: 1001,
proportional_millionths: 0,
},
cltv_expiry_delta: (9 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}]), RouteHint(vec![RouteHintHop {
src_node_id: nodes[5],
short_channel_id: 10,
fees: zero_fees,
cltv_expiry_delta: (10 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}])]
}
#[test]
fn last_hops_with_public_channel_test() {
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops_with_public_channel(&nodes)).unwrap();
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 5);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
assert_eq!(route.paths[0].hops[0].fee_msat, 100);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
assert_eq!(route.paths[0].hops[1].fee_msat, 0);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (6 << 4) | 1);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[2].pubkey, nodes[4]);
assert_eq!(route.paths[0].hops[2].short_channel_id, 6);
assert_eq!(route.paths[0].hops[2].fee_msat, 0);
assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, (11 << 4) | 1);
assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(5));
assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(6));
assert_eq!(route.paths[0].hops[3].pubkey, nodes[3]);
assert_eq!(route.paths[0].hops[3].short_channel_id, 11);
assert_eq!(route.paths[0].hops[3].fee_msat, 0);
assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, (8 << 4) | 1);
assert_eq!(route.paths[0].hops[3].node_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &id_to_feature_flags(11));
assert_eq!(route.paths[0].hops[4].pubkey, nodes[6]);
assert_eq!(route.paths[0].hops[4].short_channel_id, 8);
assert_eq!(route.paths[0].hops[4].fee_msat, 100);
assert_eq!(route.paths[0].hops[4].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[4].node_features.le_flags(), default_node_features().le_flags()); assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::<u8>::new()); }
#[test]
fn our_chans_last_hop_connect_test() {
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let our_chans = vec![get_channel_details(Some(42), nodes[3].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
let mut last_hops = last_hops(&nodes);
let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
.with_route_hints(last_hops.clone()).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let route = get_route(&our_id, &route_params, &network_graph.read_only(),
Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[3]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 42);
assert_eq!(route.paths[0].hops[0].fee_msat, 0);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (8 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &vec![0b11]);
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &Vec::<u8>::new()); assert_eq!(route.paths[0].hops[1].pubkey, nodes[6]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 8);
assert_eq!(route.paths[0].hops[1].fee_msat, 100);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), default_node_features().le_flags()); assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &Vec::<u8>::new()); last_hops[0].0[0].fees.base_msat = 1000;
let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
.with_route_hints(last_hops).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 100);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 4);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
assert_eq!(route.paths[0].hops[0].fee_msat, 200); assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
assert_eq!(route.paths[0].hops[1].fee_msat, 100);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (7 << 4) | 1);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[2].pubkey, nodes[5]);
assert_eq!(route.paths[0].hops[2].short_channel_id, 7);
assert_eq!(route.paths[0].hops[2].fee_msat, 0);
assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, (10 << 4) | 1);
assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(6));
assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(7));
assert_eq!(route.paths[0].hops[3].pubkey, nodes[6]);
assert_eq!(route.paths[0].hops[3].short_channel_id, 10);
assert_eq!(route.paths[0].hops[3].fee_msat, 100);
assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[3].node_features.le_flags(), default_node_features().le_flags()); assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::<u8>::new()); let route_params = RouteParameters::from_payment_params_and_value(payment_params, 2000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 5);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 2);
assert_eq!(route.paths[0].hops[0].fee_msat, 3000);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (4 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(2));
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 4);
assert_eq!(route.paths[0].hops[1].fee_msat, 0);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (6 << 4) | 1);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[2].pubkey, nodes[4]);
assert_eq!(route.paths[0].hops[2].short_channel_id, 6);
assert_eq!(route.paths[0].hops[2].fee_msat, 0);
assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, (11 << 4) | 1);
assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(5));
assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(6));
assert_eq!(route.paths[0].hops[3].pubkey, nodes[3]);
assert_eq!(route.paths[0].hops[3].short_channel_id, 11);
assert_eq!(route.paths[0].hops[3].fee_msat, 1000);
assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, (8 << 4) | 1);
assert_eq!(route.paths[0].hops[3].node_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &id_to_feature_flags(11));
assert_eq!(route.paths[0].hops[4].pubkey, nodes[6]);
assert_eq!(route.paths[0].hops[4].short_channel_id, 8);
assert_eq!(route.paths[0].hops[4].fee_msat, 2000);
assert_eq!(route.paths[0].hops[4].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[4].node_features.le_flags(), default_node_features().le_flags()); assert_eq!(route.paths[0].hops[4].channel_features.le_flags(), &Vec::<u8>::new()); }
fn do_unannounced_path_test(last_hop_htlc_max: Option<u64>, last_hop_fee_prop: u32, outbound_capacity_msat: u64, route_val: u64) -> Result<Route, LightningError> {
let source_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&<Vec<u8>>::from_hex(&format!("{:02}", 41).repeat(32)).unwrap()[..]).unwrap());
let middle_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&<Vec<u8>>::from_hex(&format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap());
let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&<Vec<u8>>::from_hex(&format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap());
let last_hops = RouteHint(vec![RouteHintHop {
src_node_id: middle_node_id,
short_channel_id: 8,
fees: RoutingFees {
base_msat: 1000,
proportional_millionths: last_hop_fee_prop,
},
cltv_expiry_delta: (8 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: last_hop_htlc_max,
}]);
let payment_params = PaymentParameters::from_node_id(target_node_id, 42).with_route_hints(vec![last_hops]).unwrap();
let our_chans = vec![get_channel_details(Some(42), middle_node_id, InitFeatures::from_le_bytes(vec![0b11]), outbound_capacity_msat)];
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let logger = ln_test_utils::TestLogger::new();
let network_graph = NetworkGraph::new(Network::Testnet, &logger);
let route_params = RouteParameters::from_payment_params_and_value(payment_params, route_val);
let route = get_route(&source_node_id, &route_params, &network_graph.read_only(),
Some(&our_chans.iter().collect::<Vec<_>>()), &logger, &scorer, &Default::default(),
&random_seed_bytes);
route
}
#[test]
fn unannounced_path_test() {
let route = do_unannounced_path_test(None, 1, 2000000, 1000000).unwrap();
let middle_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&<Vec<u8>>::from_hex(&format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap());
let target_node_id = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&<Vec<u8>>::from_hex(&format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap());
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, middle_node_id);
assert_eq!(route.paths[0].hops[0].short_channel_id, 42);
assert_eq!(route.paths[0].hops[0].fee_msat, 1001);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (8 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &[0b11]);
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &[0; 0]); assert_eq!(route.paths[0].hops[1].pubkey, target_node_id);
assert_eq!(route.paths[0].hops[1].short_channel_id, 8);
assert_eq!(route.paths[0].hops[1].fee_msat, 1000000);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), default_node_features().le_flags()); assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &[0; 0]); }
#[test]
fn overflow_unannounced_path_test_liquidity_underflow() {
assert!(do_unannounced_path_test(Some(21_000_000_0000_0000_000), 0, 21_000_000_0000_0000_000, 21_000_000_0000_0000_000).is_err());
}
#[test]
fn overflow_unannounced_path_test_feerate_overflow() {
assert!(do_unannounced_path_test(Some(21_000_000_0000_0000_000), 50000, 21_000_000_0000_0000_000, 21_000_000_0000_0000_000).is_err());
}
#[test]
fn available_amount_while_routing_test() {
let (secp_ctx, network_graph, gossip_sync, chain_monitor, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let payment_params = PaymentParameters::from_node_id(nodes[2], 42)
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap();
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 2,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 2,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 1_000_000_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 250_000_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 250_000_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 250_000_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
assert_eq!(path.final_value_msat(), 250_000_000);
}
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 3,
flags: 2,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 1_000_000_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
let our_chans = vec![get_channel_details(Some(42), nodes[0].clone(), InitFeatures::from_le_bytes(vec![0b11]), 200_000_000)];
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 200_000_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(),
Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 200_000_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(),
Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
assert_eq!(path.final_value_msat(), 200_000_000);
}
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 4,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 1_000_000_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 3,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 15_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 15_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 15_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
assert_eq!(path.final_value_msat(), 15_000);
}
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 4,
flags: 2,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
let good_script = Builder::new().push_opcode(opcodes::all::OP_PUSHNUM_2)
.push_slice(&PublicKey::from_secret_key(&secp_ctx, &privkeys[0]).serialize())
.push_slice(&PublicKey::from_secret_key(&secp_ctx, &privkeys[2]).serialize())
.push_opcode(opcodes::all::OP_PUSHNUM_2)
.push_opcode(opcodes::all::OP_CHECKMULTISIG).into_script().to_v0_p2wsh();
*chain_monitor.utxo_ret.lock().unwrap() =
UtxoResult::Sync(Ok(TxOut { value: 15, script_pubkey: good_script.clone() }));
gossip_sync.add_utxo_lookup(Some(chain_monitor));
add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(3)), 333);
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 333,
timestamp: 1,
flags: 0,
cltv_expiry_delta: (3 << 4) | 1,
htlc_minimum_msat: 0,
htlc_maximum_msat: 15_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 333,
timestamp: 1,
flags: 1,
cltv_expiry_delta: (3 << 4) | 2,
htlc_minimum_msat: 0,
htlc_maximum_msat: 15_000,
fee_base_msat: 100,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 15_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 15_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
assert_eq!(path.final_value_msat(), 15_000);
}
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 333,
timestamp: 6,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 10_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 10_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 10_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
assert_eq!(path.final_value_msat(), 10_000);
}
}
#[test]
fn available_liquidity_last_hop_test() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let payment_params = PaymentParameters::from_node_id(nodes[3], 42)
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap();
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 2,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 50_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 11,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 60_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 49_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
assert_eq!(path.hops.len(), 4);
assert_eq!(path.hops.last().unwrap().pubkey, nodes[3]);
total_amount_paid_msat += path.final_value_msat();
}
assert_eq!(total_amount_paid_msat, 49_000);
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 50_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
assert_eq!(path.hops.len(), 4);
assert_eq!(path.hops.last().unwrap().pubkey, nodes[3]);
total_amount_paid_msat += path.final_value_msat();
}
assert_eq!(total_amount_paid_msat, 50_000);
}
}
#[test]
fn ignore_fee_first_hop_test() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[2], 42);
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 1_000_000,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 50_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 50_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
assert_eq!(path.hops.len(), 2);
assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
total_amount_paid_msat += path.final_value_msat();
}
assert_eq!(total_amount_paid_msat, 50_000);
}
}
#[test]
fn simple_mpp_route_test() {
let (secp_ctx, _, _, _, _) = build_graph();
let (_, _, _, nodes) = get_nodes(&secp_ctx);
let config = UserConfig::default();
let clear_payment_params = PaymentParameters::from_node_id(nodes[2], 42)
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap();
do_simple_mpp_route_test(clear_payment_params);
let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let blinded_path = BlindedPath {
introduction_node_id: 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() }],
};
let blinded_payinfo = BlindedPayInfo { fee_base_msat: 0,
fee_proportional_millionths: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 0,
cltv_expiry_delta: 0,
features: BlindedHopFeatures::empty(),
};
let one_hop_blinded_payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), blinded_path.clone())])
.with_bolt12_features(bolt12_features.clone()).unwrap();
do_simple_mpp_route_test(one_hop_blinded_payment_params.clone());
let mut blinded_path_node_0 = blinded_path.clone();
blinded_path_node_0.introduction_node_id = 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];
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];
let mut node_1_payinfo = blinded_payinfo.clone();
node_1_payinfo.htlc_maximum_msat = 180_000;
let two_hop_blinded_payment_params = PaymentParameters::blinded(
vec![
(node_0_payinfo, blinded_path_node_0),
(node_7_payinfo, blinded_path_node_7),
(node_1_payinfo, blinded_path_node_1)
])
.with_bolt12_features(bolt12_features).unwrap();
do_simple_mpp_route_test(two_hop_blinded_payment_params);
}
fn do_simple_mpp_route_test(payment_params: PaymentParameters) {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 50_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 60_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 60_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 200_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 180_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 300_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
let zero_payment_params = payment_params.clone().with_max_path_count(0);
let route_params = RouteParameters::from_payment_params_and_value(
zero_payment_params, 100);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Can't find a route with no paths allowed.");
} else { panic!(); }
}
{
let fail_payment_params = payment_params.clone().with_max_path_count(3);
let route_params = RouteParameters::from_payment_params_and_value(
fail_payment_params, 250_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 250_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
if let Some(bt) = &path.blinded_tail {
assert_eq!(path.hops.len() + if bt.hops.len() == 1 { 0 } else { 1 }, 2);
} else {
assert_eq!(path.hops.len(), 2);
assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
}
total_amount_paid_msat += path.final_value_msat();
}
assert_eq!(total_amount_paid_msat, 250_000);
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 290_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
if payment_params.payee.blinded_route_hints().len() != 0 {
assert!(path.blinded_tail.is_some()) } else { assert!(path.blinded_tail.is_none()) }
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,
payment_params.payee.blinded_route_hints().iter()
.find(|(p, _)| p.htlc_maximum_msat == path.final_value_msat())
.map(|(_, p)| p.introduction_node_id).unwrap());
} else {
assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
}
} else {
assert_eq!(path.hops.len(), 2);
assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
}
total_amount_paid_msat += path.final_value_msat();
}
assert_eq!(total_amount_paid_msat, 290_000);
}
}
#[test]
fn long_mpp_route_test() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let payment_params = PaymentParameters::from_node_id(nodes[3], 42)
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap();
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 2,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), 5);
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 5,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 200_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 200_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 200_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 11,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 350_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 300_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
assert_eq!(path.hops.last().unwrap().pubkey, nodes[3]);
total_amount_paid_msat += path.final_value_msat();
}
assert_eq!(total_amount_paid_msat, 300_000);
}
}
#[test]
fn mpp_cheaper_route_test() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let payment_params = PaymentParameters::from_node_id(nodes[3], 42)
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap();
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 2,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), 5);
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 5,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 200_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 200_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 200_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 1_000,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 11,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 180_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_value_transferred_msat = 0;
let mut total_paid_msat = 0;
for path in &route.paths {
assert_eq!(path.hops.last().unwrap().pubkey, nodes[3]);
total_value_transferred_msat += path.final_value_msat();
for hop in &path.hops {
total_paid_msat += hop.fee_msat;
}
}
assert_eq!(total_value_transferred_msat, 180_000);
let total_fees_paid = total_paid_msat - total_value_transferred_msat;
assert_eq!(total_fees_paid, 0);
}
}
#[test]
fn fees_on_mpp_route_test() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let payment_params = PaymentParameters::from_node_id(nodes[3], 42)
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap();
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 2,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 7,
timestamp: 2,
flags: 2,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), 5);
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 5,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 250_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 150_000,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 11,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 210_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
let route_params = RouteParameters { payment_params: payment_params.clone(), final_value_msat: 200_000,
max_total_routing_fee_msat: Some(149_999) };
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
let route_params = RouteParameters { payment_params: payment_params.clone(), final_value_msat: 200_000,
max_total_routing_fee_msat: Some(150_000) };
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
assert_eq!(path.hops.last().unwrap().pubkey, nodes[3]);
total_amount_paid_msat += path.final_value_msat();
}
assert_eq!(total_amount_paid_msat, 200_000);
assert_eq!(route.get_total_fees(), 150_000);
}
}
#[test]
fn mpp_with_last_hops() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let payment_params = PaymentParameters::from_node_id(PublicKey::from_slice(&[02; 33]).unwrap(), 42)
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap()
.with_route_hints(vec![RouteHint(vec![RouteHintHop {
src_node_id: nodes[2],
short_channel_id: 42,
fees: RoutingFees { base_msat: 0, proportional_millionths: 0 },
cltv_expiry_delta: 42,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}])]).unwrap().with_max_channel_saturation_power_of_half(0);
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
cltv_expiry_delta: (5 << 4) | 5,
htlc_minimum_msat: 0,
htlc_maximum_msat: 99_000,
fee_base_msat: u32::max_value(),
fee_proportional_millionths: u32::max_value(),
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 0,
cltv_expiry_delta: (5 << 4) | 3,
htlc_minimum_msat: 0,
htlc_maximum_msat: 99_000,
fee_base_msat: u32::max_value(),
fee_proportional_millionths: u32::max_value(),
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
cltv_expiry_delta: (4 << 4) | 1,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 1,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0|2, cltv_expiry_delta: (13 << 4) | 1,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 2000000,
excess_data: Vec::new()
});
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 100_000);
let mut route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
route.paths.sort_by_key(|path| path.hops[0].short_channel_id);
assert_eq!(route.paths[0].hops[0].short_channel_id, 1);
assert_eq!(route.paths[0].hops[0].fee_msat, 0);
assert_eq!(route.paths[0].hops[2].fee_msat, 99_000);
assert_eq!(route.paths[1].hops[0].short_channel_id, 2);
assert_eq!(route.paths[1].hops[0].fee_msat, 1);
assert_eq!(route.paths[1].hops[2].fee_msat, 1_000);
assert_eq!(route.get_total_fees(), 1);
assert_eq!(route.get_total_amount(), 100_000);
}
#[test]
fn drop_lowest_channel_mpp_route_test() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let payment_params = PaymentParameters::from_node_id(nodes[2], 42)
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap()
.with_max_channel_saturation_power_of_half(0);
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 100_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 50_000,
fee_base_msat: 100,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 60_000,
fee_base_msat: 100,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 60_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 20_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 20_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 150_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
&our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 125_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
assert_eq!(path.hops.len(), 2);
assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
total_amount_paid_msat += path.final_value_msat();
}
assert_eq!(total_amount_paid_msat, 125_000);
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 90_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
assert_eq!(path.hops.len(), 2);
assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
total_amount_paid_msat += path.final_value_msat();
}
assert_eq!(total_amount_paid_msat, 90_000);
}
}
#[test]
fn min_criteria_consistency() {
let secp_ctx = Secp256k1::new();
let logger = Arc::new(ln_test_utils::TestLogger::new());
let network = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
let gossip_sync = P2PGossipSync::new(Arc::clone(&network), None, Arc::clone(&logger));
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[6], 42);
add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[1], ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), 6);
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 1,
flags: 0,
cltv_expiry_delta: (6 << 4) | 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[1], NodeFeatures::from_le_bytes(id_to_feature_flags(1)), 0);
add_channel(&gossip_sync, &secp_ctx, &privkeys[1], &privkeys[4], ChannelFeatures::from_le_bytes(id_to_feature_flags(5)), 5);
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 5,
timestamp: 1,
flags: 0,
cltv_expiry_delta: (5 << 4) | 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 100,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[4], NodeFeatures::from_le_bytes(id_to_feature_flags(4)), 0);
add_channel(&gossip_sync, &secp_ctx, &privkeys[4], &privkeys[3], ChannelFeatures::from_le_bytes(id_to_feature_flags(4)), 4);
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 1,
flags: 0,
cltv_expiry_delta: (4 << 4) | 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[3], NodeFeatures::from_le_bytes(id_to_feature_flags(3)), 0);
add_channel(&gossip_sync, &secp_ctx, &privkeys[3], &privkeys[2], ChannelFeatures::from_le_bytes(id_to_feature_flags(3)), 3);
update_channel(&gossip_sync, &secp_ctx, &privkeys[3], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 3,
timestamp: 1,
flags: 0,
cltv_expiry_delta: (3 << 4) | 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[2], NodeFeatures::from_le_bytes(id_to_feature_flags(2)), 0);
add_channel(&gossip_sync, &secp_ctx, &privkeys[2], &privkeys[4], ChannelFeatures::from_le_bytes(id_to_feature_flags(2)), 2);
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 1,
flags: 0,
cltv_expiry_delta: (2 << 4) | 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
add_channel(&gossip_sync, &secp_ctx, &privkeys[4], &privkeys[6], ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1);
update_channel(&gossip_sync, &secp_ctx, &privkeys[4], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 1,
flags: 0,
cltv_expiry_delta: (1 << 4) | 0,
htlc_minimum_msat: 100,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[6], NodeFeatures::from_le_bytes(id_to_feature_flags(6)), 0);
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 10_000);
let route = get_route(&our_id, &route_params, &network.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 3);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 6);
assert_eq!(route.paths[0].hops[0].fee_msat, 100);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (5 << 4) | 0);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(1));
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(6));
assert_eq!(route.paths[0].hops[1].pubkey, nodes[4]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 5);
assert_eq!(route.paths[0].hops[1].fee_msat, 0);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, (1 << 4) | 0);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(5));
assert_eq!(route.paths[0].hops[2].pubkey, nodes[6]);
assert_eq!(route.paths[0].hops[2].short_channel_id, 1);
assert_eq!(route.paths[0].hops[2].fee_msat, 10_000);
assert_eq!(route.paths[0].hops[2].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[2].node_features.le_flags(), &id_to_feature_flags(6));
assert_eq!(route.paths[0].hops[2].channel_features.le_flags(), &id_to_feature_flags(1));
}
}
#[test]
fn exact_fee_liquidity_limit() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, _, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[2], 42);
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 85_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 12,
timestamp: 2,
flags: 0,
cltv_expiry_delta: (4 << 4) | 1,
htlc_minimum_msat: 0,
htlc_maximum_msat: 270_000,
fee_base_msat: 0,
fee_proportional_millionths: 1000000,
excess_data: Vec::new()
});
{
let mut route_params = RouteParameters::from_payment_params_and_value(
payment_params, 90_000);
route_params.max_total_routing_fee_msat = Some(90_000*2);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 12);
assert_eq!(route.paths[0].hops[0].fee_msat, 90_000*2);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(8));
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(12));
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
assert_eq!(route.paths[0].hops[1].fee_msat, 90_000);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(13));
}
}
#[test]
fn htlc_max_reduction_below_min() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let payment_params = PaymentParameters::from_node_id(nodes[2], 42)
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap();
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 80_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
cltv_expiry_delta: (4 << 4) | 1,
htlc_minimum_msat: 90_000,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
{
let mut route_params = RouteParameters::from_payment_params_and_value(
payment_params, 90_000);
route_params.max_total_routing_fee_msat = Some(90_000*2);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 12);
assert_eq!(route.paths[0].hops[0].fee_msat, 90_000*2);
assert_eq!(route.paths[0].hops[0].cltv_expiry_delta, (13 << 4) | 1);
assert_eq!(route.paths[0].hops[0].node_features.le_flags(), &id_to_feature_flags(8));
assert_eq!(route.paths[0].hops[0].channel_features.le_flags(), &id_to_feature_flags(12));
assert_eq!(route.paths[0].hops[1].pubkey, nodes[2]);
assert_eq!(route.paths[0].hops[1].short_channel_id, 13);
assert_eq!(route.paths[0].hops[1].fee_msat, 90_000);
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), channelmanager::provided_bolt11_invoice_features(&config).le_flags());
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(13));
}
}
#[test]
fn multiple_direct_first_hops() {
let secp_ctx = Secp256k1::new();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let logger = Arc::new(ln_test_utils::TestLogger::new());
let network_graph = NetworkGraph::new(Network::Testnet, Arc::clone(&logger));
let scorer = ln_test_utils::TestScorer::new();
let config = UserConfig::default();
let payment_params = PaymentParameters::from_node_id(nodes[0], 42)
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 100_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), Some(&[
&get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 200_000),
&get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 10_000),
]), Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 1);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[0]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 3);
assert_eq!(route.paths[0].hops[0].fee_msat, 100_000);
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 100_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), Some(&[
&get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 50_000),
]), Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert_eq!(route.paths[0].hops.len(), 1);
assert_eq!(route.paths[1].hops.len(), 1);
assert!((route.paths[0].hops[0].short_channel_id == 3 && route.paths[1].hops[0].short_channel_id == 2) ||
(route.paths[0].hops[0].short_channel_id == 2 && route.paths[1].hops[0].short_channel_id == 3));
assert_eq!(route.paths[0].hops[0].pubkey, nodes[0]);
assert_eq!(route.paths[0].hops[0].fee_msat, 50_000);
assert_eq!(route.paths[1].hops[0].pubkey, nodes[0]);
assert_eq!(route.paths[1].hops[0].fee_msat, 50_000);
}
{
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 100_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), Some(&[
&get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(5), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(6), nodes[0], channelmanager::provided_init_features(&config), 300_000),
&get_channel_details(Some(7), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(8), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(9), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(4), nodes[0], channelmanager::provided_init_features(&config), 1_000_000),
]), Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 1);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[0]);
assert_eq!(route.paths[0].hops[0].short_channel_id, 6);
assert_eq!(route.paths[0].hops[0].fee_msat, 100_000);
}
}
#[test]
fn prefers_shorter_route_with_higher_fees() {
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)).unwrap();
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 100);
let route = get_route( &our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_eq!(route.get_total_fees(), 100);
assert_eq!(route.get_total_amount(), 100);
assert_eq!(path, vec![2, 4, 6, 11, 8]);
let scorer = FixedPenaltyScorer::with_penalty(100);
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 100);
let route = get_route( &our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_eq!(route.get_total_fees(), 300);
assert_eq!(route.get_total_amount(), 100);
assert_eq!(path, vec![2, 4, 7, 10]);
}
struct BadChannelScorer {
short_channel_id: u64,
}
#[cfg(c_bindings)]
impl Writeable for BadChannelScorer {
fn write<W: Writer>(&self, _w: &mut W) -> Result<(), crate::io::Error> { unimplemented!() }
}
impl ScoreLookUp for BadChannelScorer {
type ScoreParams = ();
fn channel_penalty_msat(&self, candidate: &CandidateRouteHop, _: ChannelUsage, _score_params:&Self::ScoreParams) -> u64 {
if candidate.short_channel_id() == Some(self.short_channel_id) { u64::max_value() } else { 0 }
}
}
struct BadNodeScorer {
node_id: NodeId,
}
#[cfg(c_bindings)]
impl Writeable for BadNodeScorer {
fn write<W: Writer>(&self, _w: &mut W) -> Result<(), crate::io::Error> { unimplemented!() }
}
impl ScoreLookUp for BadNodeScorer {
type ScoreParams = ();
fn channel_penalty_msat(&self, candidate: &CandidateRouteHop, _: ChannelUsage, _score_params:&Self::ScoreParams) -> u64 {
if candidate.target() == Some(self.node_id) { u64::max_value() } else { 0 }
}
}
#[test]
fn avoids_routing_through_bad_channels_and_nodes() {
let (secp_ctx, network, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)).unwrap();
let network_graph = network.read_only();
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 100);
let route = get_route( &our_id, &route_params, &network_graph, None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_eq!(route.get_total_fees(), 100);
assert_eq!(route.get_total_amount(), 100);
assert_eq!(path, vec![2, 4, 6, 11, 8]);
let scorer = BadChannelScorer { short_channel_id: 6 };
let route = get_route( &our_id, &route_params, &network_graph, None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_eq!(route.get_total_fees(), 300);
assert_eq!(route.get_total_amount(), 100);
assert_eq!(path, vec![2, 4, 7, 10]);
let scorer = BadNodeScorer { node_id: NodeId::from_pubkey(&nodes[2]) };
match get_route( &our_id, &route_params, &network_graph, None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes) {
Err(LightningError { err, .. } ) => {
assert_eq!(err, "Failed to find a path to the given destination");
},
Ok(_) => panic!("Expected error"),
}
}
#[test]
fn total_fees_single_path() {
let route = Route {
paths: vec![Path { hops: vec![
RouteHop {
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
RouteHop {
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
RouteHop {
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
short_channel_id: 0, fee_msat: 225, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
], blinded_tail: None }],
route_params: None,
};
assert_eq!(route.get_total_fees(), 250);
assert_eq!(route.get_total_amount(), 225);
}
#[test]
fn total_fees_multi_path() {
let route = Route {
paths: vec![Path { hops: vec![
RouteHop {
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
RouteHop {
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
], blinded_tail: None }, Path { hops: vec![
RouteHop {
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
RouteHop {
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0, maybe_announced_channel: true,
},
], blinded_tail: None }],
route_params: None,
};
assert_eq!(route.get_total_fees(), 200);
assert_eq!(route.get_total_amount(), 300);
}
#[test]
fn total_empty_route_no_panic() {
let route = Route { paths: Vec::new(), route_params: None };
assert_eq!(route.get_total_fees(), 0);
assert_eq!(route.get_total_amount(), 0);
}
#[test]
fn limits_total_cltv_delta() {
let (secp_ctx, network, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let network_graph = network.read_only();
let scorer = ln_test_utils::TestScorer::new();
let feasible_max_total_cltv_delta = 1008;
let feasible_payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)).unwrap()
.with_max_total_cltv_expiry_delta(feasible_max_total_cltv_delta);
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route_params = RouteParameters::from_payment_params_and_value(
feasible_payment_params, 100);
let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_ne!(path.len(), 0);
let fail_max_total_cltv_delta = 23;
let fail_payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)).unwrap()
.with_max_total_cltv_expiry_delta(fail_max_total_cltv_delta);
let route_params = RouteParameters::from_payment_params_and_value(
fail_payment_params, 100);
match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes)
{
Err(LightningError { err, .. } ) => {
assert_eq!(err, "Failed to find a path to the given destination");
},
Ok(_) => panic!("Expected error"),
}
}
#[test]
fn avoids_recently_failed_paths() {
let (secp_ctx, network, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let network_graph = network.read_only();
let scorer = ln_test_utils::TestScorer::new();
let mut payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)).unwrap()
.with_max_path_count(1);
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 100);
assert!(get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes).is_ok());
loop {
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 100);
if let Ok(route) = get_route(&our_id, &route_params, &network_graph, None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes)
{
for chan in route.paths[0].hops.iter() {
assert!(!payment_params.previously_failed_channels.contains(&chan.short_channel_id));
}
let victim = (u64::from_ne_bytes(random_seed_bytes[0..8].try_into().unwrap()) as usize)
% route.paths[0].hops.len();
payment_params.previously_failed_channels.push(route.paths[0].hops[victim].short_channel_id);
} else { break; }
}
}
#[test]
fn limits_path_length() {
let (secp_ctx, network, _, _, logger) = build_line_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let network_graph = network.read_only();
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let feasible_payment_params = PaymentParameters::from_node_id(nodes[18], 0);
let route_params = RouteParameters::from_payment_params_and_value(
feasible_payment_params, 100);
let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert!(path.len() == MAX_PATH_LENGTH_ESTIMATE.into());
let fail_payment_params = PaymentParameters::from_node_id(nodes[19], 0);
let route_params = RouteParameters::from_payment_params_and_value(
fail_payment_params, 100);
match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes)
{
Err(LightningError { err, .. } ) => {
assert_eq!(err, "Failed to find a path to the given destination");
},
Ok(_) => panic!("Expected error"),
}
}
#[test]
fn adds_and_limits_cltv_offset() {
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)).unwrap();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 100);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let cltv_expiry_deltas_before = route.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
let mut route_default = route.clone();
add_random_cltv_offset(&mut route_default, &payment_params, &network_graph.read_only(), &random_seed_bytes);
let cltv_expiry_deltas_default = route_default.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
assert_eq!(cltv_expiry_deltas_before.split_last().unwrap().1, cltv_expiry_deltas_default.split_last().unwrap().1);
assert!(cltv_expiry_deltas_default.last() > cltv_expiry_deltas_before.last());
assert!(cltv_expiry_deltas_default.last().unwrap() <= &DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA);
let mut route_limited = route.clone();
let limited_max_total_cltv_expiry_delta = cltv_expiry_deltas_before.iter().sum();
let limited_payment_params = payment_params.with_max_total_cltv_expiry_delta(limited_max_total_cltv_expiry_delta);
add_random_cltv_offset(&mut route_limited, &limited_payment_params, &network_graph.read_only(), &random_seed_bytes);
let cltv_expiry_deltas_limited = route_limited.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
assert_eq!(cltv_expiry_deltas_before, cltv_expiry_deltas_limited);
}
#[test]
fn adds_plausible_cltv_offset() {
let (secp_ctx, network, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let network_graph = network.read_only();
let network_nodes = network_graph.nodes();
let network_channels = network_graph.channels();
let scorer = ln_test_utils::TestScorer::new();
let payment_params = PaymentParameters::from_node_id(nodes[3], 0);
let keys_manager = ln_test_utils::TestKeysInterface::new(&[4u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), 100);
let mut route = get_route(&our_id, &route_params, &network_graph, None,
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
add_random_cltv_offset(&mut route, &payment_params, &network_graph, &random_seed_bytes);
let mut path_plausibility = vec![];
for p in route.paths {
let mut prng = ChaCha20::new(&random_seed_bytes, &[0u8; 12]);
let mut random_bytes = [0u8; ::core::mem::size_of::<usize>()];
prng.process_in_place(&mut random_bytes);
let random_path_index = usize::from_be_bytes(random_bytes).wrapping_rem(p.hops.len());
let observation_point = NodeId::from_pubkey(&p.hops.get(random_path_index).unwrap().pubkey);
let observed_cltv_expiry_delta: u32 = p.hops[random_path_index..].iter().map(|h| h.cltv_expiry_delta).sum();
let mut candidates: VecDeque<(NodeId, Vec<u32>)> = VecDeque::new();
candidates.push_back((observation_point, vec![]));
let mut found_plausible_candidate = false;
'candidate_loop: while let Some((cur_node_id, cur_path_cltv_deltas)) = candidates.pop_front() {
if let Some(remaining) = observed_cltv_expiry_delta.checked_sub(cur_path_cltv_deltas.iter().sum::<u32>()) {
if remaining == 0 || remaining.wrapping_rem(40) == 0 || remaining.wrapping_rem(144) == 0 {
found_plausible_candidate = true;
break 'candidate_loop;
}
}
if let Some(cur_node) = network_nodes.get(&cur_node_id) {
for channel_id in &cur_node.channels {
if let Some(channel_info) = network_channels.get(&channel_id) {
if let Some((dir_info, next_id)) = channel_info.as_directed_from(&cur_node_id) {
let next_cltv_expiry_delta = dir_info.direction().cltv_expiry_delta as u32;
if cur_path_cltv_deltas.iter().sum::<u32>()
.saturating_add(next_cltv_expiry_delta) <= observed_cltv_expiry_delta {
let mut new_path_cltv_deltas = cur_path_cltv_deltas.clone();
new_path_cltv_deltas.push(next_cltv_expiry_delta);
candidates.push_back((*next_id, new_path_cltv_deltas));
}
}
}
}
}
}
path_plausibility.push(found_plausible_candidate);
}
assert!(path_plausibility.iter().all(|x| *x));
}
#[test]
fn builds_correct_path_from_hops() {
let (secp_ctx, network, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let network_graph = network.read_only();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[3], 0);
let hops = [nodes[1], nodes[2], nodes[4], nodes[3]];
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
let route = build_route_from_hops_internal(&our_id, &hops, &route_params, &network_graph,
Arc::clone(&logger), &random_seed_bytes).unwrap();
let route_hop_pubkeys = route.paths[0].hops.iter().map(|hop| hop.pubkey).collect::<Vec<_>>();
assert_eq!(hops.len(), route.paths[0].hops.len());
for (idx, hop_pubkey) in hops.iter().enumerate() {
assert!(*hop_pubkey == route_hop_pubkeys[idx]);
}
}
#[test]
fn avoids_saturating_channels() {
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let decay_params = ProbabilisticScoringDecayParameters::default();
let scorer = ProbabilisticScorer::new(decay_params, &*network_graph, Arc::clone(&logger));
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 4,
timestamp: 2,
flags: 0,
cltv_expiry_delta: (4 << 4) | 1,
htlc_minimum_msat: 0,
htlc_maximum_msat: 250_000_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 13,
timestamp: 2,
flags: 0,
cltv_expiry_delta: (13 << 4) | 1,
htlc_minimum_msat: 0,
htlc_maximum_msat: 250_000_000,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
let config = UserConfig::default();
let payment_params = PaymentParameters::from_node_id(nodes[2], 42)
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 100_000_000);
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert!((route.paths[0].hops[1].short_channel_id == 4 && route.paths[1].hops[1].short_channel_id == 13) ||
(route.paths[1].hops[1].short_channel_id == 4 && route.paths[0].hops[1].short_channel_id == 13));
}
#[cfg(not(feature = "no-std"))]
pub(super) fn random_init_seed() -> u64 {
use core::hash::{BuildHasher, Hasher};
let seed = std::collections::hash_map::RandomState::new().build_hasher().finish();
println!("Using seed of {}", seed);
seed
}
#[test]
#[cfg(not(feature = "no-std"))]
fn generate_routes() {
use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters};
let logger = ln_test_utils::TestLogger::new();
let graph = match super::bench_utils::read_network_graph(&logger) {
Ok(f) => f,
Err(e) => {
eprintln!("{}", e);
return;
},
};
let params = ProbabilisticScoringFeeParameters::default();
let mut scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &graph, &logger);
let features = super::Bolt11InvoiceFeatures::empty();
super::bench_utils::generate_test_routes(&graph, &mut scorer, ¶ms, features, random_init_seed(), 0, 2);
}
#[test]
#[cfg(not(feature = "no-std"))]
fn generate_routes_mpp() {
use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters};
let logger = ln_test_utils::TestLogger::new();
let graph = match super::bench_utils::read_network_graph(&logger) {
Ok(f) => f,
Err(e) => {
eprintln!("{}", e);
return;
},
};
let params = ProbabilisticScoringFeeParameters::default();
let mut scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &graph, &logger);
let features = channelmanager::provided_bolt11_invoice_features(&UserConfig::default());
super::bench_utils::generate_test_routes(&graph, &mut scorer, ¶ms, features, random_init_seed(), 0, 2);
}
#[test]
#[cfg(not(feature = "no-std"))]
fn generate_large_mpp_routes() {
use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters};
let logger = ln_test_utils::TestLogger::new();
let graph = match super::bench_utils::read_network_graph(&logger) {
Ok(f) => f,
Err(e) => {
eprintln!("{}", e);
return;
},
};
let params = ProbabilisticScoringFeeParameters::default();
let mut scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &graph, &logger);
let features = channelmanager::provided_bolt11_invoice_features(&UserConfig::default());
super::bench_utils::generate_test_routes(&graph, &mut scorer, ¶ms, features, random_init_seed(), 1_000_000, 2);
}
#[test]
fn honors_manual_penalties() {
let (secp_ctx, network_graph, _, _, logger) = build_line_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let mut scorer_params = ProbabilisticScoringFeeParameters::default();
let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), Arc::clone(&network_graph), Arc::clone(&logger));
let usage = ChannelUsage {
amount_msat: 0,
inflight_htlc_msat: 0,
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000, htlc_maximum_msat: 1_000 },
};
scorer_params.set_manual_penalty(&NodeId::from_pubkey(&nodes[3]), 123);
scorer_params.set_manual_penalty(&NodeId::from_pubkey(&nodes[4]), 456);
let network_graph = network_graph.read_only();
let channels = network_graph.channels();
let channel = channels.get(&5).unwrap();
let info = channel.as_directed_from(&NodeId::from_pubkey(&nodes[3])).unwrap();
let candidate: CandidateRouteHop = CandidateRouteHop::PublicHop(PublicHopCandidate {
info: info.0,
short_channel_id: 5,
});
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, &scorer_params), 456);
let payment_params = PaymentParameters::from_node_id(nodes[10], 42);
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 100);
let route = get_route(&our_id, &route_params, &network_graph, None,
Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes);
assert!(route.is_ok());
scorer_params.add_banned(&NodeId::from_pubkey(&nodes[3]));
let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer, &scorer_params,&random_seed_bytes);
assert!(route.is_err());
scorer_params.remove_banned(&NodeId::from_pubkey(&nodes[3]));
let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer, &scorer_params,&random_seed_bytes);
assert!(route.is_ok());
}
#[test]
fn abide_by_route_hint_max_htlc() {
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let netgraph = network_graph.read_only();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let max_htlc_msat = 50_000;
let route_hint_1 = RouteHint(vec![RouteHintHop {
src_node_id: nodes[2],
short_channel_id: 42,
fees: RoutingFees {
base_msat: 100,
proportional_millionths: 0,
},
cltv_expiry_delta: 10,
htlc_minimum_msat: None,
htlc_maximum_msat: Some(max_htlc_msat),
}]);
let dest_node_id = ln_test_utils::pubkey(42);
let payment_params = PaymentParameters::from_node_id(dest_node_id, 42)
.with_route_hints(vec![route_hint_1.clone()]).unwrap()
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap();
let mut route_params = RouteParameters::from_payment_params_and_value(
payment_params, max_htlc_msat + 1);
route_params.max_total_routing_fee_msat = None;
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
&route_params, &netgraph, None, Arc::clone(&logger), &scorer, &Default::default(),
&random_seed_bytes)
{
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
let mut route_hint_2 = route_hint_1.clone();
route_hint_2.0[0].short_channel_id = 43;
let payment_params = PaymentParameters::from_node_id(dest_node_id, 42)
.with_route_hints(vec![route_hint_1, route_hint_2]).unwrap()
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap();
let mut route_params = RouteParameters::from_payment_params_and_value(
payment_params, max_htlc_msat + 1);
route_params.max_total_routing_fee_msat = Some(max_htlc_msat * 2);
let route = get_route(&our_id, &route_params, &netgraph, None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat);
}
#[test]
fn direct_channel_to_hints_with_max_htlc() {
let logger = Arc::new(ln_test_utils::TestLogger::new());
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let our_node_id = ln_test_utils::pubkey(42);
let intermed_node_id = ln_test_utils::pubkey(43);
let first_hop = vec![get_channel_details(Some(42), intermed_node_id, InitFeatures::from_le_bytes(vec![0b11]), 10_000_000)];
let amt_msat = 900_000;
let max_htlc_msat = 500_000;
let route_hint_1 = RouteHint(vec![RouteHintHop {
src_node_id: intermed_node_id,
short_channel_id: 44,
fees: RoutingFees {
base_msat: 100,
proportional_millionths: 0,
},
cltv_expiry_delta: 10,
htlc_minimum_msat: None,
htlc_maximum_msat: Some(max_htlc_msat),
}, RouteHintHop {
src_node_id: intermed_node_id,
short_channel_id: 45,
fees: RoutingFees {
base_msat: 100,
proportional_millionths: 0,
},
cltv_expiry_delta: 10,
htlc_minimum_msat: None,
htlc_maximum_msat: Some(max_htlc_msat - 50),
}]);
let mut route_hint_2 = route_hint_1.clone();
route_hint_2.0[0].short_channel_id = 46;
route_hint_2.0[1].short_channel_id = 47;
let dest_node_id = ln_test_utils::pubkey(44);
let payment_params = PaymentParameters::from_node_id(dest_node_id, 42)
.with_route_hints(vec![route_hint_1, route_hint_2]).unwrap()
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
.unwrap();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
let route = get_route(&our_node_id, &route_params, &network_graph.read_only(),
Some(&first_hop.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert_eq!(route.get_total_amount(), amt_msat);
let first_hops = vec![
get_channel_details(Some(42), intermed_node_id, InitFeatures::from_le_bytes(vec![0b11]), amt_msat - 10),
get_channel_details(Some(43), intermed_node_id, InitFeatures::from_le_bytes(vec![0b11]), amt_msat - 10),
];
let route = get_route(&our_node_id, &route_params, &network_graph.read_only(),
Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert_eq!(route.get_total_amount(), amt_msat);
let blinded_path = BlindedPath {
introduction_node_id: 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![] },
BlindedHop { blinded_node_id: ln_test_utils::pubkey(43), encrypted_payload: vec![] },
],
};
let blinded_payinfo = BlindedPayInfo {
fee_base_msat: 100,
fee_proportional_millionths: 0,
htlc_minimum_msat: 1,
htlc_maximum_msat: max_htlc_msat,
cltv_expiry_delta: 10,
features: BlindedHopFeatures::empty(),
};
let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let payment_params = PaymentParameters::blinded(vec![
(blinded_payinfo.clone(), blinded_path.clone()),
(blinded_payinfo.clone(), blinded_path.clone())])
.with_bolt12_features(bolt12_features).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
let route = get_route(&our_node_id, &route_params, &network_graph.read_only(),
Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert_eq!(route.get_total_amount(), amt_msat);
}
#[test]
fn blinded_route_ser() {
let blinded_path_1 = BlindedPath {
introduction_node_id: 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() },
BlindedHop { blinded_node_id: ln_test_utils::pubkey(45), encrypted_payload: Vec::new() }
],
};
let blinded_path_2 = BlindedPath {
introduction_node_id: 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() },
BlindedHop { blinded_node_id: ln_test_utils::pubkey(49), encrypted_payload: Vec::new() }
],
};
let mut route = Route { paths: vec![Path {
hops: vec![RouteHop {
pubkey: ln_test_utils::pubkey(50),
node_features: NodeFeatures::empty(),
short_channel_id: 42,
channel_features: ChannelFeatures::empty(),
fee_msat: 100,
cltv_expiry_delta: 0,
maybe_announced_channel: true,
}],
blinded_tail: Some(BlindedTail {
hops: blinded_path_1.blinded_hops,
blinding_point: blinded_path_1.blinding_point,
excess_final_cltv_expiry_delta: 40,
final_value_msat: 100,
})}, Path {
hops: vec![RouteHop {
pubkey: ln_test_utils::pubkey(51),
node_features: NodeFeatures::empty(),
short_channel_id: 43,
channel_features: ChannelFeatures::empty(),
fee_msat: 100,
cltv_expiry_delta: 0,
maybe_announced_channel: true,
}], blinded_tail: None }],
route_params: None,
};
let encoded_route = route.encode();
let decoded_route: Route = Readable::read(&mut Cursor::new(&encoded_route[..])).unwrap();
assert_eq!(decoded_route.paths[0].blinded_tail, route.paths[0].blinded_tail);
assert_eq!(decoded_route.paths[1].blinded_tail, route.paths[1].blinded_tail);
route.paths[1].blinded_tail = Some(BlindedTail {
hops: blinded_path_2.blinded_hops,
blinding_point: blinded_path_2.blinding_point,
excess_final_cltv_expiry_delta: 41,
final_value_msat: 101,
});
let encoded_route = route.encode();
let decoded_route: Route = Readable::read(&mut Cursor::new(&encoded_route[..])).unwrap();
assert_eq!(decoded_route.paths[0].blinded_tail, route.paths[0].blinded_tail);
assert_eq!(decoded_route.paths[1].blinded_tail, route.paths[1].blinded_tail);
}
#[test]
fn blinded_path_inflight_processing() {
let mut inflight_htlcs = InFlightHtlcs::new();
let blinded_path = BlindedPath {
introduction_node_id: 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() }],
};
let path = Path {
hops: vec![RouteHop {
pubkey: ln_test_utils::pubkey(42),
node_features: NodeFeatures::empty(),
short_channel_id: 42,
channel_features: ChannelFeatures::empty(),
fee_msat: 100,
cltv_expiry_delta: 0,
maybe_announced_channel: false,
},
RouteHop {
pubkey: blinded_path.introduction_node_id,
node_features: NodeFeatures::empty(),
short_channel_id: 43,
channel_features: ChannelFeatures::empty(),
fee_msat: 1,
cltv_expiry_delta: 0,
maybe_announced_channel: false,
}],
blinded_tail: Some(BlindedTail {
hops: blinded_path.blinded_hops,
blinding_point: blinded_path.blinding_point,
excess_final_cltv_expiry_delta: 0,
final_value_msat: 200,
}),
};
inflight_htlcs.process_path(&path, ln_test_utils::pubkey(44));
assert_eq!(*inflight_htlcs.0.get(&(42, true)).unwrap(), 301);
assert_eq!(*inflight_htlcs.0.get(&(43, false)).unwrap(), 201);
}
#[test]
fn blinded_path_cltv_shadow_offset() {
let blinded_path = BlindedPath {
introduction_node_id: 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() },
BlindedHop { blinded_node_id: ln_test_utils::pubkey(46), encrypted_payload: Vec::new() }
],
};
let mut route = Route { paths: vec![Path {
hops: vec![RouteHop {
pubkey: ln_test_utils::pubkey(42),
node_features: NodeFeatures::empty(),
short_channel_id: 42,
channel_features: ChannelFeatures::empty(),
fee_msat: 100,
cltv_expiry_delta: 0,
maybe_announced_channel: false,
},
RouteHop {
pubkey: blinded_path.introduction_node_id,
node_features: NodeFeatures::empty(),
short_channel_id: 43,
channel_features: ChannelFeatures::empty(),
fee_msat: 1,
cltv_expiry_delta: 0,
maybe_announced_channel: false,
}
],
blinded_tail: Some(BlindedTail {
hops: blinded_path.blinded_hops,
blinding_point: blinded_path.blinding_point,
excess_final_cltv_expiry_delta: 0,
final_value_msat: 200,
}),
}], route_params: None};
let payment_params = PaymentParameters::from_node_id(ln_test_utils::pubkey(47), 18);
let (_, network_graph, _, _, _) = build_line_graph();
add_random_cltv_offset(&mut route, &payment_params, &network_graph.read_only(), &[0; 32]);
assert_eq!(route.paths[0].blinded_tail.as_ref().unwrap().excess_final_cltv_expiry_delta, 40);
assert_eq!(route.paths[0].hops.last().unwrap().cltv_expiry_delta, 40);
}
#[test]
fn simple_blinded_route_hints() {
do_simple_blinded_route_hints(1);
do_simple_blinded_route_hints(2);
do_simple_blinded_route_hints(3);
}
fn do_simple_blinded_route_hints(num_blinded_hops: usize) {
let (secp_ctx, network, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let network_graph = network.read_only();
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let mut blinded_path = BlindedPath {
introduction_node_id: nodes[2],
blinding_point: ln_test_utils::pubkey(42),
blinded_hops: Vec::with_capacity(num_blinded_hops),
};
for i in 0..num_blinded_hops {
blinded_path.blinded_hops.push(
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 + i as u8), encrypted_payload: Vec::new() },
);
}
let blinded_payinfo = BlindedPayInfo {
fee_base_msat: 100,
fee_proportional_millionths: 500,
htlc_minimum_msat: 1000,
htlc_maximum_msat: 100_000_000,
cltv_expiry_delta: 15,
features: BlindedHopFeatures::empty(),
};
let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), blinded_path.clone())]);
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, 1001);
let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 2);
let tail = route.paths[0].blinded_tail.as_ref().unwrap();
assert_eq!(tail.hops, blinded_path.blinded_hops);
assert_eq!(tail.excess_final_cltv_expiry_delta, 0);
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);
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);
assert_eq!(final_hop.cltv_expiry_delta, blinded_payinfo.cltv_expiry_delta as u32);
} else {
assert_eq!(final_hop.fee_msat, 0);
assert_eq!(final_hop.cltv_expiry_delta, 0);
}
}
#[test]
fn blinded_path_routing_errors() {
let (secp_ctx, network, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let network_graph = network.read_only();
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let mut invalid_blinded_path = BlindedPath {
introduction_node_id: 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 blinded_payinfo = BlindedPayInfo {
fee_base_msat: 100,
fee_proportional_millionths: 500,
htlc_minimum_msat: 1000,
htlc_maximum_msat: 100_000_000,
cltv_expiry_delta: 15,
features: BlindedHopFeatures::empty(),
};
let mut invalid_blinded_path_2 = invalid_blinded_path.clone();
invalid_blinded_path_2.introduction_node_id = 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)]);
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, &Default::default(), &random_seed_bytes)
{
Err(LightningError { err, .. }) => {
assert_eq!(err, "1-hop blinded paths must all have matching introduction node ids");
},
_ => panic!("Expected error")
}
invalid_blinded_path.introduction_node_id = 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,
&Default::default(), &random_seed_bytes)
{
Err(LightningError { err, .. }) => {
assert_eq!(err, "Cannot generate a route to blinded paths if we are the introduction node to all of them");
},
_ => panic!("Expected error")
}
invalid_blinded_path.introduction_node_id = 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);
match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes)
{
Err(LightningError { err, .. }) => {
assert_eq!(err, "0-hop blinded path provided");
},
_ => panic!("Expected error")
}
}
#[test]
fn matching_intro_node_paths_provided() {
let (secp_ctx, network, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let network_graph = network.read_only();
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let blinded_path_1 = BlindedPath {
introduction_node_id: 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() },
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
],
};
let blinded_payinfo_1 = BlindedPayInfo {
fee_base_msat: 0,
fee_proportional_millionths: 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: 30_000,
cltv_expiry_delta: 0,
features: BlindedHopFeatures::empty(),
};
let mut blinded_path_2 = blinded_path_1.clone();
blinded_path_2.blinding_point = ln_test_utils::pubkey(43);
let mut blinded_payinfo_2 = blinded_payinfo_1.clone();
blinded_payinfo_2.htlc_maximum_msat = 70_000;
let blinded_hints = vec![
(blinded_payinfo_1.clone(), blinded_path_1.clone()),
(blinded_payinfo_2.clone(), blinded_path_2.clone()),
];
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features).unwrap();
let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100_000);
route_params.max_total_routing_fee_msat = Some(100_000);
let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
&scorer, &Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_amount_paid_msat = 0;
for path in route.paths.into_iter() {
assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
if let Some(bt) = &path.blinded_tail {
assert_eq!(bt.blinding_point,
blinded_hints.iter().find(|(p, _)| p.htlc_maximum_msat == path.final_value_msat())
.map(|(_, bp)| bp.blinding_point).unwrap());
} else { panic!(); }
total_amount_paid_msat += path.final_value_msat();
}
assert_eq!(total_amount_paid_msat, 100_000);
}
#[test]
fn direct_to_intro_node() {
let secp_ctx = Secp256k1::new();
let logger = Arc::new(ln_test_utils::TestLogger::new());
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
let gossip_sync = P2PGossipSync::new(Arc::clone(&network_graph), None, Arc::clone(&logger));
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let amt_msat = 10_000_000;
let (_, _, privkeys, nodes) = get_nodes(&secp_ctx);
add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[1],
ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1);
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 1,
flags: 0,
cltv_expiry_delta: 42,
htlc_minimum_msat: 1_000,
htlc_maximum_msat: 10_000_000,
fee_base_msat: 800,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 1,
flags: 1,
cltv_expiry_delta: 42,
htlc_minimum_msat: 1_000,
htlc_maximum_msat: 10_000_000,
fee_base_msat: 800,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
let first_hops = vec![
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],
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() },
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
],
};
let blinded_payinfo = BlindedPayInfo {
fee_base_msat: 1000,
fee_proportional_millionths: 0,
htlc_minimum_msat: 1000,
htlc_maximum_msat: MAX_VALUE_MSAT,
cltv_expiry_delta: 0,
features: BlindedHopFeatures::empty(),
};
let blinded_hints = vec![(blinded_payinfo.clone(), blinded_path)];
let payment_params = PaymentParameters::blinded(blinded_hints.clone());
let netgraph = network_graph.read_only();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params.clone(), amt_msat);
if let Err(LightningError { err, .. }) = get_route(&nodes[0], &route_params, &netgraph,
Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!("Expected error") }
let amt_minus_blinded_path_fee = amt_msat - blinded_payinfo.fee_base_msat as u64;
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_minus_blinded_path_fee);
let route = get_route(&nodes[0], &route_params, &netgraph,
Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64);
assert_eq!(route.get_total_amount(), amt_minus_blinded_path_fee);
}
#[test]
fn direct_to_matching_intro_nodes() {
let secp_ctx = Secp256k1::new();
let logger = Arc::new(ln_test_utils::TestLogger::new());
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let amt_msat = 21_7020_5185_1403_2640;
let (_, _, _, nodes) = get_nodes(&secp_ctx);
let first_hops = vec![
get_channel_details(Some(1), nodes[1], channelmanager::provided_init_features(&config),
18446744073709551615)];
let blinded_path = BlindedPath {
introduction_node_id: 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() },
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
],
};
let blinded_payinfo = BlindedPayInfo {
fee_base_msat: 5046_2720,
fee_proportional_millionths: 0,
htlc_minimum_msat: 4503_5996_2737_0496,
htlc_maximum_msat: 45_0359_9627_3704_9600,
cltv_expiry_delta: 0,
features: BlindedHopFeatures::empty(),
};
let mut blinded_hints = vec![
(blinded_payinfo.clone(), blinded_path.clone()),
(blinded_payinfo.clone(), blinded_path.clone()),
];
blinded_hints[1].0.fee_base_msat = 419_4304;
blinded_hints[1].0.fee_proportional_millionths = 257;
blinded_hints[1].0.htlc_minimum_msat = 280_8908_6115_8400;
blinded_hints[1].0.htlc_maximum_msat = 2_8089_0861_1584_0000;
blinded_hints[1].0.cltv_expiry_delta = 0;
let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features).unwrap();
let netgraph = network_graph.read_only();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
let route = get_route(&nodes[0], &route_params, &netgraph,
Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64);
assert_eq!(route.get_total_amount(), amt_msat);
}
#[test]
fn we_are_intro_node_candidate_hops() {
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let amt_msat = 21_7020_5185_1423_0019;
let blinded_path = BlindedPath {
introduction_node_id: 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() },
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
],
};
let blinded_payinfo = BlindedPayInfo {
fee_base_msat: 5052_9027,
fee_proportional_millionths: 0,
htlc_minimum_msat: 21_7020_5185_1423_0019,
htlc_maximum_msat: 1844_6744_0737_0955_1615,
cltv_expiry_delta: 0,
features: BlindedHopFeatures::empty(),
};
let mut blinded_hints = vec![
(blinded_payinfo.clone(), blinded_path.clone()),
(blinded_payinfo.clone(), blinded_path.clone()),
];
blinded_hints[1].1.introduction_node_id = nodes[6];
let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features.clone()).unwrap();
let netgraph = network_graph.read_only();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
if let Err(LightningError { err, .. }) = get_route(
&our_id, &route_params, &netgraph, None, Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes
) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!() }
}
#[test]
fn we_are_intro_node_bp_in_final_path_fee_calc() {
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let amt_msat = 21_7020_5185_1423_0019;
let blinded_path = BlindedPath {
introduction_node_id: 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() },
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
],
};
let blinded_payinfo = BlindedPayInfo {
fee_base_msat: 10_4425_1395,
fee_proportional_millionths: 0,
htlc_minimum_msat: 21_7301_9934_9094_0931,
htlc_maximum_msat: 1844_6744_0737_0955_1615,
cltv_expiry_delta: 0,
features: BlindedHopFeatures::empty(),
};
let mut blinded_hints = vec![
(blinded_payinfo.clone(), blinded_path.clone()),
(blinded_payinfo.clone(), blinded_path.clone()),
(blinded_payinfo.clone(), blinded_path.clone()),
];
blinded_hints[1].0.fee_base_msat = 5052_9027;
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];
let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features.clone()).unwrap();
let netgraph = network_graph.read_only();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
if let Err(LightningError { err, .. }) = get_route(
&our_id, &route_params, &netgraph, None, Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes
) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!() }
}
#[test]
fn min_htlc_overpay_violates_max_htlc() {
do_min_htlc_overpay_violates_max_htlc(true);
do_min_htlc_overpay_violates_max_htlc(false);
}
fn do_min_htlc_overpay_violates_max_htlc(blinded_payee: bool) {
let secp_ctx = Secp256k1::new();
let logger = Arc::new(ln_test_utils::TestLogger::new());
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let amt_msat = 7_4009_8048;
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let first_hop_outbound_capacity = 2_7345_2000;
let first_hops = vec![get_channel_details(
Some(200), nodes[0], channelmanager::provided_init_features(&config),
first_hop_outbound_capacity
)];
let base_fee = 1_6778_3453;
let htlc_min = 2_5165_8240;
let payment_params = if blinded_payee {
let blinded_path = BlindedPath {
introduction_node_id: 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() },
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
],
};
let blinded_payinfo = BlindedPayInfo {
fee_base_msat: base_fee,
fee_proportional_millionths: 0,
htlc_minimum_msat: htlc_min,
htlc_maximum_msat: htlc_min * 1000,
cltv_expiry_delta: 0,
features: BlindedHopFeatures::empty(),
};
let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
PaymentParameters::blinded(vec![(blinded_payinfo, blinded_path)])
.with_bolt12_features(bolt12_features.clone()).unwrap()
} else {
let route_hint = RouteHint(vec![RouteHintHop {
src_node_id: nodes[0],
short_channel_id: 42,
fees: RoutingFees {
base_msat: base_fee,
proportional_millionths: 0,
},
cltv_expiry_delta: 10,
htlc_minimum_msat: Some(htlc_min),
htlc_maximum_msat: None,
}]);
PaymentParameters::from_node_id(nodes[1], 42)
.with_route_hints(vec![route_hint]).unwrap()
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap()
};
let netgraph = network_graph.read_only();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
if let Err(LightningError { err, .. }) = get_route(
&our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::<Vec<_>>()),
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes
) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!() }
}
#[test]
fn previously_used_liquidity_violates_max_htlc() {
do_previously_used_liquidity_violates_max_htlc(true);
do_previously_used_liquidity_violates_max_htlc(false);
}
fn do_previously_used_liquidity_violates_max_htlc(blinded_payee: bool) {
let secp_ctx = Secp256k1::new();
let logger = Arc::new(ln_test_utils::TestLogger::new());
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let amt_msat = 52_4288;
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let first_hops = vec![get_channel_details(
Some(161), nodes[0], channelmanager::provided_init_features(&config), 486_4000
), get_channel_details(
Some(122), nodes[0], channelmanager::provided_init_features(&config), 179_5000
)];
let base_fees = [0, 425_9840, 0, 0];
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],
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() },
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
],
};
let mut blinded_hints = Vec::new();
for (base_fee, htlc_min) in base_fees.iter().zip(htlc_mins.iter()) {
blinded_hints.push((BlindedPayInfo {
fee_base_msat: *base_fee,
fee_proportional_millionths: 0,
htlc_minimum_msat: *htlc_min,
htlc_maximum_msat: htlc_min * 100,
cltv_expiry_delta: 10,
features: BlindedHopFeatures::empty(),
}, blinded_path.clone()));
}
let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features.clone()).unwrap()
} else {
let mut route_hints = Vec::new();
for (idx, (base_fee, htlc_min)) in base_fees.iter().zip(htlc_mins.iter()).enumerate() {
route_hints.push(RouteHint(vec![RouteHintHop {
src_node_id: nodes[0],
short_channel_id: 42 + idx as u64,
fees: RoutingFees {
base_msat: *base_fee,
proportional_millionths: 0,
},
cltv_expiry_delta: 10,
htlc_minimum_msat: Some(*htlc_min),
htlc_maximum_msat: Some(htlc_min * 100),
}]));
}
PaymentParameters::from_node_id(nodes[1], 42)
.with_route_hints(route_hints).unwrap()
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap()
};
let netgraph = network_graph.read_only();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
let route = get_route(
&our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::<Vec<_>>()),
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes
).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.get_total_amount(), amt_msat);
}
#[test]
fn candidate_path_min() {
let secp_ctx = Secp256k1::new();
let logger = Arc::new(ln_test_utils::TestLogger::new());
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
let gossip_sync = P2PGossipSync::new(network_graph.clone(), None, logger.clone());
let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), network_graph.clone(), logger.clone());
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let amt_msat = 7_4009_8048;
let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let first_hops = vec![get_channel_details(
Some(200), nodes[0], channelmanager::provided_init_features(&config), 2_7345_2000
)];
add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[6], ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), 6);
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 6,
timestamp: 1,
flags: 0,
cltv_expiry_delta: (6 << 4) | 0,
htlc_minimum_msat: 0,
htlc_maximum_msat: MAX_VALUE_MSAT,
fee_base_msat: 0,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[0], NodeFeatures::from_le_bytes(id_to_feature_flags(1)), 0);
let htlc_min = 2_5165_8240;
let blinded_hints = vec![
(BlindedPayInfo {
fee_base_msat: 1_6778_3453,
fee_proportional_millionths: 0,
htlc_minimum_msat: htlc_min,
htlc_maximum_msat: htlc_min * 100,
cltv_expiry_delta: 10,
features: BlindedHopFeatures::empty(),
}, BlindedPath {
introduction_node_id: 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() },
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
],
})
];
let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features.clone()).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
let netgraph = network_graph.read_only();
if let Err(LightningError { err, .. }) = get_route(
&our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::<Vec<_>>()),
Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(),
&random_seed_bytes
) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!() }
}
#[test]
fn path_contribution_includes_min_htlc_overpay() {
let secp_ctx = Secp256k1::new();
let logger = Arc::new(ln_test_utils::TestLogger::new());
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), network_graph.clone(), logger.clone());
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let amt_msat = 562_0000;
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let first_hops = vec![
get_channel_details(
Some(83), nodes[0], channelmanager::provided_init_features(&config), 2199_0000,
),
];
let htlc_mins = [49_0000, 1125_0000];
let payment_params = {
let blinded_path = BlindedPath {
introduction_node_id: 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() },
BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
],
};
let mut blinded_hints = Vec::new();
for htlc_min in htlc_mins.iter() {
blinded_hints.push((BlindedPayInfo {
fee_base_msat: 0,
fee_proportional_millionths: 0,
htlc_minimum_msat: *htlc_min,
htlc_maximum_msat: *htlc_min * 100,
cltv_expiry_delta: 10,
features: BlindedHopFeatures::empty(),
}, blinded_path.clone()));
}
let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config);
PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features.clone()).unwrap()
};
let netgraph = network_graph.read_only();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
let route = get_route(
&our_id, &route_params, &netgraph, Some(&first_hops.iter().collect::<Vec<_>>()),
Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(),
&random_seed_bytes
).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.get_total_amount(), amt_msat);
}
#[test]
fn first_hop_preferred_over_hint() {
let secp_ctx = Secp256k1::new();
let logger = Arc::new(ln_test_utils::TestLogger::new());
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
let gossip_sync = P2PGossipSync::new(Arc::clone(&network_graph), None, Arc::clone(&logger));
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let amt_msat = 1_000_000;
let (our_privkey, our_node_id, privkeys, nodes) = get_nodes(&secp_ctx);
add_channel(&gossip_sync, &secp_ctx, &our_privkey, &privkeys[0],
ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 1);
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 1,
flags: 0,
cltv_expiry_delta: 42,
htlc_minimum_msat: 1_000,
htlc_maximum_msat: 10_000_000,
fee_base_msat: 800,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 1,
timestamp: 1,
flags: 1,
cltv_expiry_delta: 42,
htlc_minimum_msat: 1_000,
htlc_maximum_msat: 10_000_000,
fee_base_msat: 800,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
add_channel(&gossip_sync, &secp_ctx, &privkeys[0], &privkeys[1],
ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), 2);
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 0,
cltv_expiry_delta: 42,
htlc_minimum_msat: 1_000,
htlc_maximum_msat: 10_000_000,
fee_base_msat: 800,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
short_channel_id: 2,
timestamp: 2,
flags: 1,
cltv_expiry_delta: 42,
htlc_minimum_msat: 1_000,
htlc_maximum_msat: 10_000_000,
fee_base_msat: 800,
fee_proportional_millionths: 0,
excess_data: Vec::new()
});
let dest_node_id = nodes[2];
let route_hint = RouteHint(vec![RouteHintHop {
src_node_id: our_node_id,
short_channel_id: 44,
fees: RoutingFees {
base_msat: 234,
proportional_millionths: 0,
},
cltv_expiry_delta: 10,
htlc_minimum_msat: None,
htlc_maximum_msat: Some(5_000_000),
},
RouteHintHop {
src_node_id: nodes[0],
short_channel_id: 45,
fees: RoutingFees {
base_msat: 123,
proportional_millionths: 0,
},
cltv_expiry_delta: 10,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}]);
let payment_params = PaymentParameters::from_node_id(dest_node_id, 42)
.with_route_hints(vec![route_hint]).unwrap()
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
let first_hop = get_channel_details(Some(1), nodes[0],
channelmanager::provided_init_features(&config), 999_999);
let first_hops = vec![first_hop];
let route = get_route(&our_node_id, &route_params.clone(), &network_graph.read_only(),
Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.get_total_amount(), amt_msat);
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].short_channel_id, 44);
assert_eq!(route.paths[0].hops[1].short_channel_id, 45);
assert_eq!(route.get_total_fees(), 123);
let mut first_hop = get_channel_details(Some(1), nodes[0], channelmanager::provided_init_features(&config), 999_999);
first_hop.outbound_scid_alias = Some(44);
let first_hops = vec![first_hop];
let route_res = get_route(&our_node_id, &route_params.clone(), &network_graph.read_only(),
Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes);
assert!(route_res.is_err());
let mut first_hop = get_channel_details(Some(1), nodes[0],
channelmanager::provided_init_features(&config), 10_000_000);
first_hop.outbound_scid_alias = Some(44);
let first_hops = vec![first_hop];
let route = get_route(&our_node_id, &route_params.clone(), &network_graph.read_only(),
Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.get_total_amount(), amt_msat);
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].short_channel_id, 1);
assert_eq!(route.paths[0].hops[1].short_channel_id, 45);
assert_eq!(route.get_total_fees(), 123);
}
#[test]
fn allow_us_being_first_hint() {
let secp_ctx = Secp256k1::new();
let logger = Arc::new(ln_test_utils::TestLogger::new());
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, Arc::clone(&logger)));
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
let (_, our_node_id, _, nodes) = get_nodes(&secp_ctx);
let amt_msat = 1_000_000;
let dest_node_id = nodes[1];
let first_hop = get_channel_details(Some(1), nodes[0], channelmanager::provided_init_features(&config), 10_000_000);
let first_hops = vec![first_hop];
let route_hint = RouteHint(vec![RouteHintHop {
src_node_id: our_node_id,
short_channel_id: 44,
fees: RoutingFees {
base_msat: 123,
proportional_millionths: 0,
},
cltv_expiry_delta: 10,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}]);
let payment_params = PaymentParameters::from_node_id(dest_node_id, 42)
.with_route_hints(vec![route_hint]).unwrap()
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config)).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(
payment_params, amt_msat);
let route = get_route(&our_node_id, &route_params, &network_graph.read_only(),
Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
&Default::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.get_total_amount(), amt_msat);
assert_eq!(route.get_total_fees(), 0);
assert_eq!(route.paths[0].hops.len(), 1);
assert_eq!(route.paths[0].hops[0].short_channel_id, 44);
}
}
#[cfg(all(any(test, ldk_bench), not(feature = "no-std")))]
pub(crate) mod bench_utils {
use super::*;
use std::fs::File;
use std::time::Duration;
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use crate::chain::transaction::OutPoint;
use crate::routing::scoring::ScoreUpdate;
use crate::sign::{EntropySource, KeysManager};
use crate::ln::ChannelId;
use crate::ln::channelmanager::{self, ChannelCounterparty, ChannelDetails};
use crate::ln::features::Bolt11InvoiceFeatures;
use crate::routing::gossip::NetworkGraph;
use crate::util::config::UserConfig;
use crate::util::ser::ReadableArgs;
use crate::util::test_utils::TestLogger;
pub(crate) fn get_route_file() -> Result<std::fs::File, &'static str> {
let res = File::open("net_graph-2023-01-18.bin") .or_else(|_| File::open("lightning/net_graph-2023-01-18.bin")) .or_else(|_| { let mut path = std::env::current_exe().unwrap();
path.pop(); path.pop(); path.pop(); path.pop(); path.push("lightning");
path.push("net_graph-2023-01-18.bin");
File::open(path)
})
.or_else(|_| { let mut path = std::env::current_exe().unwrap();
path.pop(); path.pop(); path.pop(); path.pop(); path.pop(); path.push("lightning");
path.push("net_graph-2023-01-18.bin");
File::open(path)
})
.map_err(|_| "Please fetch https://bitcoin.ninja/ldk-net_graph-v0.0.113-2023-01-18.bin and place it at lightning/net_graph-2023-01-18.bin");
#[cfg(require_route_graph_test)]
return Ok(res.unwrap());
#[cfg(not(require_route_graph_test))]
return res;
}
pub(crate) fn read_network_graph(logger: &TestLogger) -> Result<NetworkGraph<&TestLogger>, &'static str> {
get_route_file().map(|mut f| NetworkGraph::read(&mut f, logger).unwrap())
}
pub(crate) fn payer_pubkey() -> PublicKey {
let secp_ctx = Secp256k1::new();
PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())
}
#[inline]
pub(crate) fn first_hop(node_id: PublicKey) -> ChannelDetails {
ChannelDetails {
channel_id: ChannelId::new_zero(),
counterparty: ChannelCounterparty {
features: channelmanager::provided_init_features(&UserConfig::default()),
node_id,
unspendable_punishment_reserve: 0,
forwarding_info: None,
outbound_htlc_minimum_msat: None,
outbound_htlc_maximum_msat: None,
},
funding_txo: Some(OutPoint {
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0
}),
channel_type: None,
short_channel_id: Some(1),
inbound_scid_alias: None,
outbound_scid_alias: None,
channel_value_satoshis: 10_000_000_000,
user_channel_id: 0,
balance_msat: 10_000_000_000,
outbound_capacity_msat: 10_000_000_000,
next_outbound_htlc_minimum_msat: 0,
next_outbound_htlc_limit_msat: 10_000_000_000,
inbound_capacity_msat: 0,
unspendable_punishment_reserve: None,
confirmations_required: None,
confirmations: None,
force_close_spend_delay: None,
is_outbound: true,
is_channel_ready: true,
is_usable: true,
is_public: true,
inbound_htlc_minimum_msat: None,
inbound_htlc_maximum_msat: None,
config: None,
feerate_sat_per_1000_weight: None,
channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown),
}
}
pub(crate) fn generate_test_routes<S: ScoreLookUp + ScoreUpdate>(graph: &NetworkGraph<&TestLogger>, scorer: &mut S,
score_params: &S::ScoreParams, features: Bolt11InvoiceFeatures, mut seed: u64,
starting_amount: u64, route_count: usize,
) -> Vec<(ChannelDetails, PaymentParameters, u64)> {
let payer = payer_pubkey();
let keys_manager = KeysManager::new(&[0u8; 32], 42, 42);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let nodes = graph.read_only().nodes().clone();
let mut route_endpoints = Vec::new();
for _ in 0..route_count * 3 / 2 {
loop {
seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0;
let src = PublicKey::from_slice(nodes.unordered_keys()
.skip((seed as usize) % nodes.len()).next().unwrap().as_slice()).unwrap();
seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0;
let dst = PublicKey::from_slice(nodes.unordered_keys()
.skip((seed as usize) % nodes.len()).next().unwrap().as_slice()).unwrap();
let params = PaymentParameters::from_node_id(dst, 42)
.with_bolt11_features(features.clone()).unwrap();
let first_hop = first_hop(src);
let amt_msat = starting_amount + seed % 1_000_000;
let route_params = RouteParameters::from_payment_params_and_value(
params.clone(), amt_msat);
let path_exists =
get_route(&payer, &route_params, &graph.read_only(), Some(&[&first_hop]),
&TestLogger::new(), scorer, score_params, &random_seed_bytes).is_ok();
if path_exists {
seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0;
let mut score_amt = seed % 1_000_000_000;
loop {
let mpp_features = channelmanager::provided_bolt11_invoice_features(&UserConfig::default());
let params = PaymentParameters::from_node_id(dst, 42)
.with_bolt11_features(mpp_features).unwrap();
let route_params = RouteParameters::from_payment_params_and_value(
params.clone(), score_amt);
let route_res = get_route(&payer, &route_params, &graph.read_only(),
Some(&[&first_hop]), &TestLogger::new(), scorer, score_params,
&random_seed_bytes);
if let Ok(route) = route_res {
for path in route.paths {
if seed & 0x80 == 0 {
scorer.payment_path_successful(&path, Duration::ZERO);
} else {
let short_channel_id = path.hops[path.hops.len() / 2].short_channel_id;
scorer.payment_path_failed(&path, short_channel_id, Duration::ZERO);
}
seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0;
}
break;
}
score_amt /= 100;
}
route_endpoints.push((first_hop, params, amt_msat));
break;
}
}
}
route_endpoints.retain(|(first_hop, params, amt_msat)| {
let route_params = RouteParameters::from_payment_params_and_value(
params.clone(), *amt_msat);
get_route(&payer, &route_params, &graph.read_only(), Some(&[first_hop]),
&TestLogger::new(), scorer, score_params, &random_seed_bytes).is_ok()
});
route_endpoints.truncate(route_count);
assert_eq!(route_endpoints.len(), route_count);
route_endpoints
}
}
#[cfg(ldk_bench)]
pub mod benches {
use super::*;
use crate::routing::scoring::{ScoreUpdate, ScoreLookUp};
use crate::sign::{EntropySource, KeysManager};
use crate::ln::channelmanager;
use crate::ln::features::Bolt11InvoiceFeatures;
use crate::routing::gossip::NetworkGraph;
use crate::routing::scoring::{FixedPenaltyScorer, ProbabilisticScorer, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters};
use crate::util::config::UserConfig;
use crate::util::logger::{Logger, Record};
use crate::util::test_utils::TestLogger;
use criterion::Criterion;
struct DummyLogger {}
impl Logger for DummyLogger {
fn log(&self, _record: Record) {}
}
pub fn generate_routes_with_zero_penalty_scorer(bench: &mut Criterion) {
let logger = TestLogger::new();
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let scorer = FixedPenaltyScorer::with_penalty(0);
generate_routes(bench, &network_graph, scorer, &Default::default(),
Bolt11InvoiceFeatures::empty(), 0, "generate_routes_with_zero_penalty_scorer");
}
pub fn generate_mpp_routes_with_zero_penalty_scorer(bench: &mut Criterion) {
let logger = TestLogger::new();
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let scorer = FixedPenaltyScorer::with_penalty(0);
generate_routes(bench, &network_graph, scorer, &Default::default(),
channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 0,
"generate_mpp_routes_with_zero_penalty_scorer");
}
pub fn generate_routes_with_probabilistic_scorer(bench: &mut Criterion) {
let logger = TestLogger::new();
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let params = ProbabilisticScoringFeeParameters::default();
let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
generate_routes(bench, &network_graph, scorer, ¶ms, Bolt11InvoiceFeatures::empty(), 0,
"generate_routes_with_probabilistic_scorer");
}
pub fn generate_mpp_routes_with_probabilistic_scorer(bench: &mut Criterion) {
let logger = TestLogger::new();
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let params = ProbabilisticScoringFeeParameters::default();
let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
generate_routes(bench, &network_graph, scorer, ¶ms,
channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 0,
"generate_mpp_routes_with_probabilistic_scorer");
}
pub fn generate_large_mpp_routes_with_probabilistic_scorer(bench: &mut Criterion) {
let logger = TestLogger::new();
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let params = ProbabilisticScoringFeeParameters::default();
let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
generate_routes(bench, &network_graph, scorer, ¶ms,
channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 100_000_000,
"generate_large_mpp_routes_with_probabilistic_scorer");
}
pub fn generate_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) {
let logger = TestLogger::new();
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let mut params = ProbabilisticScoringFeeParameters::default();
params.linear_success_probability = false;
let scorer = ProbabilisticScorer::new(
ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
generate_routes(bench, &network_graph, scorer, ¶ms,
channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 0,
"generate_routes_with_nonlinear_probabilistic_scorer");
}
pub fn generate_mpp_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) {
let logger = TestLogger::new();
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let mut params = ProbabilisticScoringFeeParameters::default();
params.linear_success_probability = false;
let scorer = ProbabilisticScorer::new(
ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
generate_routes(bench, &network_graph, scorer, ¶ms,
channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 0,
"generate_mpp_routes_with_nonlinear_probabilistic_scorer");
}
pub fn generate_large_mpp_routes_with_nonlinear_probabilistic_scorer(bench: &mut Criterion) {
let logger = TestLogger::new();
let network_graph = bench_utils::read_network_graph(&logger).unwrap();
let mut params = ProbabilisticScoringFeeParameters::default();
params.linear_success_probability = false;
let scorer = ProbabilisticScorer::new(
ProbabilisticScoringDecayParameters::default(), &network_graph, &logger);
generate_routes(bench, &network_graph, scorer, ¶ms,
channelmanager::provided_bolt11_invoice_features(&UserConfig::default()), 100_000_000,
"generate_large_mpp_routes_with_nonlinear_probabilistic_scorer");
}
fn generate_routes<S: ScoreLookUp + ScoreUpdate>(
bench: &mut Criterion, graph: &NetworkGraph<&TestLogger>, mut scorer: S,
score_params: &S::ScoreParams, features: Bolt11InvoiceFeatures, starting_amount: u64,
bench_name: &'static str,
) {
let payer = bench_utils::payer_pubkey();
let keys_manager = KeysManager::new(&[0u8; 32], 42, 42);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route_endpoints = bench_utils::generate_test_routes(graph, &mut scorer, score_params, features, 0xdeadbeef, starting_amount, 50);
let mut idx = 0;
bench.bench_function(bench_name, |b| b.iter(|| {
let (first_hop, params, amt) = &route_endpoints[idx % route_endpoints.len()];
let route_params = RouteParameters::from_payment_params_and_value(params.clone(), *amt);
assert!(get_route(&payer, &route_params, &graph.read_only(), Some(&[first_hop]),
&DummyLogger{}, &scorer, score_params, &random_seed_bytes).is_ok());
idx += 1;
}));
}
}