Skip to content

Commit 000204b

Browse files
committed
Consider PaymentParams::max_total_routing_fee_msat in get_route
We exclude any candidate hops if we find that using them would let the aggregated path routing fees exceed `max_total_routing_fee_msat`. Moreover, we return an error if the aggregated fees over all paths of the selected route would surpass `max_total_routing_fee_msat`.
1 parent d4fc1b2 commit 000204b

File tree

1 file changed

+102
-84
lines changed

1 file changed

+102
-84
lines changed

lightning/src/routing/router.rs

Lines changed: 102 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,6 +1671,7 @@ where L::Target: Logger {
16711671
let mut num_ignored_path_length_limit = 0;
16721672
let mut num_ignored_cltv_delta_limit = 0;
16731673
let mut num_ignored_previously_failed = 0;
1674+
let mut num_ignored_total_fee_limit = 0;
16741675

16751676
macro_rules! add_entry {
16761677
// Adds entry which goes from $src_node_id to $dest_node_id over the $candidate hop.
@@ -1832,89 +1833,98 @@ where L::Target: Logger {
18321833
total_fee_msat = total_fee_msat.saturating_add(hop_use_fee_msat);
18331834
}
18341835

1835-
let channel_usage = ChannelUsage {
1836-
amount_msat: amount_to_transfer_over_msat,
1837-
inflight_htlc_msat: used_liquidity_msat,
1838-
effective_capacity,
1839-
};
1840-
let channel_penalty_msat = scid_opt.map_or(0,
1841-
|scid| scorer.channel_penalty_msat(scid, &$src_node_id, &$dest_node_id,
1842-
channel_usage, score_params));
1843-
let path_penalty_msat = $next_hops_path_penalty_msat
1844-
.saturating_add(channel_penalty_msat);
1845-
let new_graph_node = RouteGraphNode {
1846-
node_id: $src_node_id,
1847-
lowest_fee_to_node: total_fee_msat,
1848-
total_cltv_delta: hop_total_cltv_delta,
1849-
value_contribution_msat,
1850-
path_htlc_minimum_msat,
1851-
path_penalty_msat,
1852-
path_length_to_node,
1853-
};
1854-
1855-
// Update the way of reaching $src_node_id with the given short_channel_id (from $dest_node_id),
1856-
// if this way is cheaper than the already known
1857-
// (considering the cost to "reach" this channel from the route destination,
1858-
// the cost of using this channel,
1859-
// and the cost of routing to the source node of this channel).
1860-
// Also, consider that htlc_minimum_msat_difference, because we might end up
1861-
// paying it. Consider the following exploit:
1862-
// we use 2 paths to transfer 1.5 BTC. One of them is 0-fee normal 1 BTC path,
1863-
// and for the other one we picked a 1sat-fee path with htlc_minimum_msat of
1864-
// 1 BTC. Now, since the latter is more expensive, we gonna try to cut it
1865-
// by 0.5 BTC, but then match htlc_minimum_msat by paying a fee of 0.5 BTC
1866-
// to this channel.
1867-
// Ideally the scoring could be smarter (e.g. 0.5*htlc_minimum_msat here),
1868-
// but it may require additional tracking - we don't want to double-count
1869-
// the fees included in $next_hops_path_htlc_minimum_msat, but also
1870-
// can't use something that may decrease on future hops.
1871-
let old_cost = cmp::max(old_entry.total_fee_msat, old_entry.path_htlc_minimum_msat)
1872-
.saturating_add(old_entry.path_penalty_msat);
1873-
let new_cost = cmp::max(total_fee_msat, path_htlc_minimum_msat)
1874-
.saturating_add(path_penalty_msat);
1875-
1876-
if !old_entry.was_processed && new_cost < old_cost {
1877-
targets.push(new_graph_node);
1878-
old_entry.next_hops_fee_msat = $next_hops_fee_msat;
1879-
old_entry.hop_use_fee_msat = hop_use_fee_msat;
1880-
old_entry.total_fee_msat = total_fee_msat;
1881-
old_entry.node_id = $dest_node_id.clone();
1882-
old_entry.candidate = $candidate.clone();
1883-
old_entry.fee_msat = 0; // This value will be later filled with hop_use_fee_msat of the following channel
1884-
old_entry.path_htlc_minimum_msat = path_htlc_minimum_msat;
1885-
old_entry.path_penalty_msat = path_penalty_msat;
1886-
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
1887-
{
1888-
old_entry.value_contribution_msat = value_contribution_msat;
1836+
// Ignore hops if augmenting the current path to them would put us over `max_total_routing_fee_msat`
1837+
let max_total_routing_fee_msat = payment_params.max_total_routing_fee_msat.unwrap_or(u64::max_value());
1838+
if total_fee_msat > max_total_routing_fee_msat {
1839+
if should_log_candidate {
1840+
log_trace!(logger, "Ignoring {} due to exceeding max total routing fee limit.", LoggedCandidateHop(&$candidate));
18891841
}
1890-
did_add_update_path_to_src_node = Some(value_contribution_msat);
1891-
} else if old_entry.was_processed && new_cost < old_cost {
1892-
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
1893-
{
1894-
// If we're skipping processing a node which was previously
1895-
// processed even though we found another path to it with a
1896-
// cheaper fee, check that it was because the second path we
1897-
// found (which we are processing now) has a lower value
1898-
// contribution due to an HTLC minimum limit.
1899-
//
1900-
// e.g. take a graph with two paths from node 1 to node 2, one
1901-
// through channel A, and one through channel B. Channel A and
1902-
// B are both in the to-process heap, with their scores set by
1903-
// a higher htlc_minimum than fee.
1904-
// Channel A is processed first, and the channels onwards from
1905-
// node 1 are added to the to-process heap. Thereafter, we pop
1906-
// Channel B off of the heap, note that it has a much more
1907-
// restrictive htlc_maximum_msat, and recalculate the fees for
1908-
// all of node 1's channels using the new, reduced, amount.
1909-
//
1910-
// This would be bogus - we'd be selecting a higher-fee path
1911-
// with a lower htlc_maximum_msat instead of the one we'd
1912-
// already decided to use.
1913-
debug_assert!(path_htlc_minimum_msat < old_entry.path_htlc_minimum_msat);
1914-
debug_assert!(
1915-
value_contribution_msat + path_penalty_msat <
1916-
old_entry.value_contribution_msat + old_entry.path_penalty_msat
1917-
);
1842+
num_ignored_total_fee_limit += 1;
1843+
} else {
1844+
let channel_usage = ChannelUsage {
1845+
amount_msat: amount_to_transfer_over_msat,
1846+
inflight_htlc_msat: used_liquidity_msat,
1847+
effective_capacity,
1848+
};
1849+
let channel_penalty_msat = scid_opt.map_or(0,
1850+
|scid| scorer.channel_penalty_msat(scid, &$src_node_id, &$dest_node_id,
1851+
channel_usage, score_params));
1852+
let path_penalty_msat = $next_hops_path_penalty_msat
1853+
.saturating_add(channel_penalty_msat);
1854+
let new_graph_node = RouteGraphNode {
1855+
node_id: $src_node_id,
1856+
lowest_fee_to_node: total_fee_msat,
1857+
total_cltv_delta: hop_total_cltv_delta,
1858+
value_contribution_msat,
1859+
path_htlc_minimum_msat,
1860+
path_penalty_msat,
1861+
path_length_to_node,
1862+
};
1863+
1864+
// Update the way of reaching $src_node_id with the given short_channel_id (from $dest_node_id),
1865+
// if this way is cheaper than the already known
1866+
// (considering the cost to "reach" this channel from the route destination,
1867+
// the cost of using this channel,
1868+
// and the cost of routing to the source node of this channel).
1869+
// Also, consider that htlc_minimum_msat_difference, because we might end up
1870+
// paying it. Consider the following exploit:
1871+
// we use 2 paths to transfer 1.5 BTC. One of them is 0-fee normal 1 BTC path,
1872+
// and for the other one we picked a 1sat-fee path with htlc_minimum_msat of
1873+
// 1 BTC. Now, since the latter is more expensive, we gonna try to cut it
1874+
// by 0.5 BTC, but then match htlc_minimum_msat by paying a fee of 0.5 BTC
1875+
// to this channel.
1876+
// Ideally the scoring could be smarter (e.g. 0.5*htlc_minimum_msat here),
1877+
// but it may require additional tracking - we don't want to double-count
1878+
// the fees included in $next_hops_path_htlc_minimum_msat, but also
1879+
// can't use something that may decrease on future hops.
1880+
let old_cost = cmp::max(old_entry.total_fee_msat, old_entry.path_htlc_minimum_msat)
1881+
.saturating_add(old_entry.path_penalty_msat);
1882+
let new_cost = cmp::max(total_fee_msat, path_htlc_minimum_msat)
1883+
.saturating_add(path_penalty_msat);
1884+
1885+
if !old_entry.was_processed && new_cost < old_cost {
1886+
targets.push(new_graph_node);
1887+
old_entry.next_hops_fee_msat = $next_hops_fee_msat;
1888+
old_entry.hop_use_fee_msat = hop_use_fee_msat;
1889+
old_entry.total_fee_msat = total_fee_msat;
1890+
old_entry.node_id = $dest_node_id.clone();
1891+
old_entry.candidate = $candidate.clone();
1892+
old_entry.fee_msat = 0; // This value will be later filled with hop_use_fee_msat of the following channel
1893+
old_entry.path_htlc_minimum_msat = path_htlc_minimum_msat;
1894+
old_entry.path_penalty_msat = path_penalty_msat;
1895+
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
1896+
{
1897+
old_entry.value_contribution_msat = value_contribution_msat;
1898+
}
1899+
did_add_update_path_to_src_node = Some(value_contribution_msat);
1900+
} else if old_entry.was_processed && new_cost < old_cost {
1901+
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
1902+
{
1903+
// If we're skipping processing a node which was previously
1904+
// processed even though we found another path to it with a
1905+
// cheaper fee, check that it was because the second path we
1906+
// found (which we are processing now) has a lower value
1907+
// contribution due to an HTLC minimum limit.
1908+
//
1909+
// e.g. take a graph with two paths from node 1 to node 2, one
1910+
// through channel A, and one through channel B. Channel A and
1911+
// B are both in the to-process heap, with their scores set by
1912+
// a higher htlc_minimum than fee.
1913+
// Channel A is processed first, and the channels onwards from
1914+
// node 1 are added to the to-process heap. Thereafter, we pop
1915+
// Channel B off of the heap, note that it has a much more
1916+
// restrictive htlc_maximum_msat, and recalculate the fees for
1917+
// all of node 1's channels using the new, reduced, amount.
1918+
//
1919+
// This would be bogus - we'd be selecting a higher-fee path
1920+
// with a lower htlc_maximum_msat instead of the one we'd
1921+
// already decided to use.
1922+
debug_assert!(path_htlc_minimum_msat < old_entry.path_htlc_minimum_msat);
1923+
debug_assert!(
1924+
value_contribution_msat + path_penalty_msat <
1925+
old_entry.value_contribution_msat + old_entry.path_penalty_msat
1926+
);
1927+
}
19181928
}
19191929
}
19201930
}
@@ -2379,9 +2389,9 @@ where L::Target: Logger {
23792389
}
23802390

23812391
let num_ignored_total = num_ignored_value_contribution + num_ignored_path_length_limit +
2382-
num_ignored_cltv_delta_limit + num_ignored_previously_failed;
2392+
num_ignored_cltv_delta_limit + num_ignored_previously_failed + num_ignored_total_fee_limit;
23832393
if num_ignored_total > 0 {
2384-
log_trace!(logger, "Ignored {} candidate hops due to insufficient value contrib., {} due to path length limit, {} due to CLTV delta limit, {} due to previous payment failure. Total: {} ignored candidates.", num_ignored_value_contribution, num_ignored_path_length_limit, num_ignored_cltv_delta_limit, num_ignored_previously_failed, num_ignored_total);
2394+
log_trace!(logger, "Ignored {} candidate hops due to insufficient value contrib., {} due to path length limit, {} due to CLTV delta limit, {} due to previous payment failure, {} 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_total_fee_limit, num_ignored_total);
23852395
}
23862396

23872397
// Step (5).
@@ -2502,6 +2512,14 @@ where L::Target: Logger {
25022512
// Make sure we would never create a route with more paths than we allow.
25032513
debug_assert!(paths.len() <= payment_params.max_path_count.into());
25042514

2515+
// Make sure we would never create a route whose total fees exceed max_total_routing_fee_msat.
2516+
if let Some(max_total_routing_fee_msat) = payment_params.max_total_routing_fee_msat {
2517+
if paths.iter().map(|p| p.fee_msat()).sum::<u64>() > max_total_routing_fee_msat {
2518+
return Err(LightningError{err: format!("Failed to find route that adheres to the maximum total fee limit of {}msat",
2519+
max_total_routing_fee_msat), action: ErrorAction::IgnoreError});
2520+
}
2521+
}
2522+
25052523
if let Some(node_features) = payment_params.payee.node_features() {
25062524
for path in paths.iter_mut() {
25072525
path.hops.last_mut().unwrap().node_features = node_features.clone();

0 commit comments

Comments
 (0)