Skip to content

Commit afe80e4

Browse files
committed
Consider PaymentParams::max_total_routeing_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_routeing_fee_msat`. Moreover, we return an error if the aggregated fees over all paths of the selected route would surpass `max_total_routeing_fee_msat`.
1 parent f9e5ba4 commit afe80e4

File tree

1 file changed

+98
-83
lines changed

1 file changed

+98
-83
lines changed

lightning/src/routing/router.rs

Lines changed: 98 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,89 +1829,96 @@ where L::Target: Logger {
18291829
total_fee_msat = total_fee_msat.saturating_add(hop_use_fee_msat);
18301830
}
18311831

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

2509+
// Make sure we would never create a route whose total fees exceed max_total_routing_fee_msat.
2510+
if let Some(max_total_routing_fee_msat) = payment_params.max_total_routing_fee_msat {
2511+
if paths.iter().map(|p| p.fee_msat()).sum::<u64>() > max_total_routing_fee_msat {
2512+
return Err(LightningError{err: format!("Failed to find route that adheres to the maximum total fee limit of {}msat",
2513+
max_total_routing_fee_msat), action: ErrorAction::IgnoreError});
2514+
}
2515+
}
2516+
25022517
if let Some(node_features) = payment_params.payee.node_features() {
25032518
for path in paths.iter_mut() {
25042519
path.hops.last_mut().unwrap().node_features = node_features.clone();

0 commit comments

Comments
 (0)