Skip to content

Commit f87b312

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 741084d commit f87b312

File tree

1 file changed

+98
-83
lines changed

1 file changed

+98
-83
lines changed

lightning/src/routing/router.rs

+98-83
Original file line numberDiff line numberDiff line change
@@ -1791,89 +1791,96 @@ where L::Target: Logger {
17911791
total_fee_msat = total_fee_msat.saturating_add(hop_use_fee_msat);
17921792
}
17931793

1794-
let channel_usage = ChannelUsage {
1795-
amount_msat: amount_to_transfer_over_msat,
1796-
inflight_htlc_msat: used_liquidity_msat,
1797-
effective_capacity,
1798-
};
1799-
let channel_penalty_msat = scid_opt.map_or(0,
1800-
|scid| scorer.channel_penalty_msat(scid, &$src_node_id, &$dest_node_id,
1801-
channel_usage, score_params));
1802-
let path_penalty_msat = $next_hops_path_penalty_msat
1803-
.saturating_add(channel_penalty_msat);
1804-
let new_graph_node = RouteGraphNode {
1805-
node_id: $src_node_id,
1806-
lowest_fee_to_node: total_fee_msat,
1807-
total_cltv_delta: hop_total_cltv_delta,
1808-
value_contribution_msat,
1809-
path_htlc_minimum_msat,
1810-
path_penalty_msat,
1811-
path_length_to_node,
1812-
};
1813-
1814-
// Update the way of reaching $src_node_id with the given short_channel_id (from $dest_node_id),
1815-
// if this way is cheaper than the already known
1816-
// (considering the cost to "reach" this channel from the route destination,
1817-
// the cost of using this channel,
1818-
// and the cost of routing to the source node of this channel).
1819-
// Also, consider that htlc_minimum_msat_difference, because we might end up
1820-
// paying it. Consider the following exploit:
1821-
// we use 2 paths to transfer 1.5 BTC. One of them is 0-fee normal 1 BTC path,
1822-
// and for the other one we picked a 1sat-fee path with htlc_minimum_msat of
1823-
// 1 BTC. Now, since the latter is more expensive, we gonna try to cut it
1824-
// by 0.5 BTC, but then match htlc_minimum_msat by paying a fee of 0.5 BTC
1825-
// to this channel.
1826-
// Ideally the scoring could be smarter (e.g. 0.5*htlc_minimum_msat here),
1827-
// but it may require additional tracking - we don't want to double-count
1828-
// the fees included in $next_hops_path_htlc_minimum_msat, but also
1829-
// can't use something that may decrease on future hops.
1830-
let old_cost = cmp::max(old_entry.total_fee_msat, old_entry.path_htlc_minimum_msat)
1831-
.saturating_add(old_entry.path_penalty_msat);
1832-
let new_cost = cmp::max(total_fee_msat, path_htlc_minimum_msat)
1833-
.saturating_add(path_penalty_msat);
1834-
1835-
if !old_entry.was_processed && new_cost < old_cost {
1836-
targets.push(new_graph_node);
1837-
old_entry.next_hops_fee_msat = $next_hops_fee_msat;
1838-
old_entry.hop_use_fee_msat = hop_use_fee_msat;
1839-
old_entry.total_fee_msat = total_fee_msat;
1840-
old_entry.node_id = $dest_node_id.clone();
1841-
old_entry.candidate = $candidate.clone();
1842-
old_entry.fee_msat = 0; // This value will be later filled with hop_use_fee_msat of the following channel
1843-
old_entry.path_htlc_minimum_msat = path_htlc_minimum_msat;
1844-
old_entry.path_penalty_msat = path_penalty_msat;
1845-
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
1846-
{
1847-
old_entry.value_contribution_msat = value_contribution_msat;
1848-
}
1849-
did_add_update_path_to_src_node = Some(value_contribution_msat);
1850-
} else if old_entry.was_processed && new_cost < old_cost {
1851-
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
1852-
{
1853-
// If we're skipping processing a node which was previously
1854-
// processed even though we found another path to it with a
1855-
// cheaper fee, check that it was because the second path we
1856-
// found (which we are processing now) has a lower value
1857-
// contribution due to an HTLC minimum limit.
1858-
//
1859-
// e.g. take a graph with two paths from node 1 to node 2, one
1860-
// through channel A, and one through channel B. Channel A and
1861-
// B are both in the to-process heap, with their scores set by
1862-
// a higher htlc_minimum than fee.
1863-
// Channel A is processed first, and the channels onwards from
1864-
// node 1 are added to the to-process heap. Thereafter, we pop
1865-
// Channel B off of the heap, note that it has a much more
1866-
// restrictive htlc_maximum_msat, and recalculate the fees for
1867-
// all of node 1's channels using the new, reduced, amount.
1868-
//
1869-
// This would be bogus - we'd be selecting a higher-fee path
1870-
// with a lower htlc_maximum_msat instead of the one we'd
1871-
// already decided to use.
1872-
debug_assert!(path_htlc_minimum_msat < old_entry.path_htlc_minimum_msat);
1873-
debug_assert!(
1874-
value_contribution_msat + path_penalty_msat <
1875-
old_entry.value_contribution_msat + old_entry.path_penalty_msat
1876-
);
1794+
// Ignore hops if they by themselves would already put us over `max_total_routing_fee_msat`
1795+
let max_total_routing_fee_msat = payment_params.max_total_routing_fee_msat.unwrap_or(u64::max_value());
1796+
if total_fee_msat > max_total_routing_fee_msat {
1797+
log_trace!(logger, "Ignoring candidate hop {} as the path's total fee {} would exceed the maximum total routing fee limit {}",
1798+
LoggedCandidateHop(&$candidate), total_fee_msat, max_total_routing_fee_msat);
1799+
} else {
1800+
let channel_usage = ChannelUsage {
1801+
amount_msat: amount_to_transfer_over_msat,
1802+
inflight_htlc_msat: used_liquidity_msat,
1803+
effective_capacity,
1804+
};
1805+
let channel_penalty_msat = scid_opt.map_or(0,
1806+
|scid| scorer.channel_penalty_msat(scid, &$src_node_id, &$dest_node_id,
1807+
channel_usage, score_params));
1808+
let path_penalty_msat = $next_hops_path_penalty_msat
1809+
.saturating_add(channel_penalty_msat);
1810+
let new_graph_node = RouteGraphNode {
1811+
node_id: $src_node_id,
1812+
lowest_fee_to_node: total_fee_msat,
1813+
total_cltv_delta: hop_total_cltv_delta,
1814+
value_contribution_msat,
1815+
path_htlc_minimum_msat,
1816+
path_penalty_msat,
1817+
path_length_to_node,
1818+
};
1819+
1820+
// Update the way of reaching $src_node_id with the given short_channel_id (from $dest_node_id),
1821+
// if this way is cheaper than the already known
1822+
// (considering the cost to "reach" this channel from the route destination,
1823+
// the cost of using this channel,
1824+
// and the cost of routing to the source node of this channel).
1825+
// Also, consider that htlc_minimum_msat_difference, because we might end up
1826+
// paying it. Consider the following exploit:
1827+
// we use 2 paths to transfer 1.5 BTC. One of them is 0-fee normal 1 BTC path,
1828+
// and for the other one we picked a 1sat-fee path with htlc_minimum_msat of
1829+
// 1 BTC. Now, since the latter is more expensive, we gonna try to cut it
1830+
// by 0.5 BTC, but then match htlc_minimum_msat by paying a fee of 0.5 BTC
1831+
// to this channel.
1832+
// Ideally the scoring could be smarter (e.g. 0.5*htlc_minimum_msat here),
1833+
// but it may require additional tracking - we don't want to double-count
1834+
// the fees included in $next_hops_path_htlc_minimum_msat, but also
1835+
// can't use something that may decrease on future hops.
1836+
let old_cost = cmp::max(old_entry.total_fee_msat, old_entry.path_htlc_minimum_msat)
1837+
.saturating_add(old_entry.path_penalty_msat);
1838+
let new_cost = cmp::max(total_fee_msat, path_htlc_minimum_msat)
1839+
.saturating_add(path_penalty_msat);
1840+
1841+
if !old_entry.was_processed && new_cost < old_cost {
1842+
targets.push(new_graph_node);
1843+
old_entry.next_hops_fee_msat = $next_hops_fee_msat;
1844+
old_entry.hop_use_fee_msat = hop_use_fee_msat;
1845+
old_entry.total_fee_msat = total_fee_msat;
1846+
old_entry.node_id = $dest_node_id.clone();
1847+
old_entry.candidate = $candidate.clone();
1848+
old_entry.fee_msat = 0; // This value will be later filled with hop_use_fee_msat of the following channel
1849+
old_entry.path_htlc_minimum_msat = path_htlc_minimum_msat;
1850+
old_entry.path_penalty_msat = path_penalty_msat;
1851+
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
1852+
{
1853+
old_entry.value_contribution_msat = value_contribution_msat;
1854+
}
1855+
did_add_update_path_to_src_node = Some(value_contribution_msat);
1856+
} else if old_entry.was_processed && new_cost < old_cost {
1857+
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
1858+
{
1859+
// If we're skipping processing a node which was previously
1860+
// processed even though we found another path to it with a
1861+
// cheaper fee, check that it was because the second path we
1862+
// found (which we are processing now) has a lower value
1863+
// contribution due to an HTLC minimum limit.
1864+
//
1865+
// e.g. take a graph with two paths from node 1 to node 2, one
1866+
// through channel A, and one through channel B. Channel A and
1867+
// B are both in the to-process heap, with their scores set by
1868+
// a higher htlc_minimum than fee.
1869+
// Channel A is processed first, and the channels onwards from
1870+
// node 1 are added to the to-process heap. Thereafter, we pop
1871+
// Channel B off of the heap, note that it has a much more
1872+
// restrictive htlc_maximum_msat, and recalculate the fees for
1873+
// all of node 1's channels using the new, reduced, amount.
1874+
//
1875+
// This would be bogus - we'd be selecting a higher-fee path
1876+
// with a lower htlc_maximum_msat instead of the one we'd
1877+
// already decided to use.
1878+
debug_assert!(path_htlc_minimum_msat < old_entry.path_htlc_minimum_msat);
1879+
debug_assert!(
1880+
value_contribution_msat + path_penalty_msat <
1881+
old_entry.value_contribution_msat + old_entry.path_penalty_msat
1882+
);
1883+
}
18771884
}
18781885
}
18791886
}
@@ -2455,6 +2462,14 @@ where L::Target: Logger {
24552462
// Make sure we would never create a route with more paths than we allow.
24562463
debug_assert!(paths.len() <= payment_params.max_path_count.into());
24572464

2465+
// Make sure we would never create a route whose total fees exceed max_total_routing_fee_msat.
2466+
if let Some(max_total_routing_fee_msat) = payment_params.max_total_routing_fee_msat {
2467+
if paths.iter().map(|p| p.fee_msat()).sum::<u64>() > max_total_routing_fee_msat {
2468+
return Err(LightningError{err: format!("Failed to find route that adheres to the maximum total fee limit of {}msat",
2469+
max_total_routing_fee_msat), action: ErrorAction::IgnoreError});
2470+
}
2471+
}
2472+
24582473
if let Some(node_features) = payment_params.payee.node_features() {
24592474
for path in paths.iter_mut() {
24602475
path.hops.last_mut().unwrap().node_features = node_features.clone();

0 commit comments

Comments
 (0)