Skip to content

Commit ca40d8a

Browse files
committed
Consider RouteParameters::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 d7e2ff6 commit ca40d8a

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
@@ -1693,6 +1693,7 @@ where L::Target: Logger {
16931693
let mut num_ignored_path_length_limit = 0;
16941694
let mut num_ignored_cltv_delta_limit = 0;
16951695
let mut num_ignored_previously_failed = 0;
1696+
let mut num_ignored_total_fee_limit = 0;
16961697

16971698
macro_rules! add_entry {
16981699
// Adds entry which goes from $src_node_id to $dest_node_id over the $candidate hop.
@@ -1854,89 +1855,98 @@ where L::Target: Logger {
18541855
total_fee_msat = total_fee_msat.saturating_add(hop_use_fee_msat);
18551856
}
18561857

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

24092419
let num_ignored_total = num_ignored_value_contribution + num_ignored_path_length_limit +
2410-
num_ignored_cltv_delta_limit + num_ignored_previously_failed;
2420+
num_ignored_cltv_delta_limit + num_ignored_previously_failed + num_ignored_total_fee_limit;
24112421
if num_ignored_total > 0 {
2412-
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. Total: {} ignored candidates.", num_ignored_value_contribution, num_ignored_path_length_limit, num_ignored_cltv_delta_limit, num_ignored_previously_failed, num_ignored_total);
2422+
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 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);
24132423
}
24142424

24152425
// Step (5).
@@ -2551,6 +2561,14 @@ where L::Target: Logger {
25512561
// Make sure we would never create a route with more paths than we allow.
25522562
debug_assert!(paths.len() <= payment_params.max_path_count.into());
25532563

2564+
// Make sure we would never create a route whose total fees exceed max_total_routing_fee_msat.
2565+
if let Some(max_total_routing_fee_msat) = route_params.max_total_routing_fee_msat {
2566+
if paths.iter().map(|p| p.fee_msat()).sum::<u64>() > max_total_routing_fee_msat {
2567+
return Err(LightningError{err: format!("Failed to find route that adheres to the maximum total fee limit of {}msat",
2568+
max_total_routing_fee_msat), action: ErrorAction::IgnoreError});
2569+
}
2570+
}
2571+
25542572
if let Some(node_features) = payment_params.payee.node_features() {
25552573
for path in paths.iter_mut() {
25562574
path.hops.last_mut().unwrap().node_features = node_features.clone();

0 commit comments

Comments
 (0)