Skip to content

Commit 967e99d

Browse files
committed
Separate ChannelDetails' outbound capacity from the next HTLC max
`ChannelDetails::outbound_capacity_msat` describes the total amount available for sending across several HTLCs, basically just our balance minus the reserve value maintained by our counterparty. However, when routing we use it to guess the maximum amount we can send in a single additional HTLC, which it is not. There are numerous reasons why our balance may not match the amount we can send in a single HTLC, whether the HTLC in-flight limit, the channe's HTLC maximum, or our feerate buffer. This commit splits the `outbound_capacity_msat` field into two - `outbound_capacity_msat` and `outbound_htlc_limit_msat`, setting us up for correctly handling our next-HTLC-limit in the future. This also addresses the first of the reasons why the values may not match - the max-in-flight limit. The inaccuracy is ultimately tracked as #1126.
1 parent 637fb88 commit 967e99d

File tree

4 files changed

+41
-16
lines changed

4 files changed

+41
-16
lines changed

fuzz/src/router.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use bitcoin::blockdata::constants::genesis_block;
2929

3030
use utils::test_logger;
3131

32+
use std::convert::TryInto;
3233
use std::collections::HashSet;
3334
use std::sync::Arc;
3435
use std::sync::atomic::{AtomicUsize, Ordering};
@@ -205,7 +206,8 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
205206
count => {
206207
for _ in 0..count {
207208
scid += 1;
208-
let rnid = node_pks.iter().skip(slice_to_be16(get_slice!(2))as usize % node_pks.len()).next().unwrap();
209+
let rnid = node_pks.iter().skip(u16::from_be_bytes(get_slice!(2).try_into().unwrap()) as usize % node_pks.len()).next().unwrap();
210+
let capacity = u64::from_be_bytes(get_slice!(8).try_into().unwrap());
209211
first_hops_vec.push(ChannelDetails {
210212
channel_id: [0; 32],
211213
counterparty: ChannelCounterparty {
@@ -220,15 +222,16 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
220222
channel_type: None,
221223
short_channel_id: Some(scid),
222224
inbound_scid_alias: None,
223-
channel_value_satoshis: slice_to_be64(get_slice!(8)),
225+
channel_value_satoshis: capacity,
224226
user_channel_id: 0, inbound_capacity_msat: 0,
225227
unspendable_punishment_reserve: None,
226228
confirmations_required: None,
227229
force_close_spend_delay: None,
228230
is_outbound: true, is_funding_locked: true,
229231
is_usable: true, is_public: true,
230232
balance_msat: 0,
231-
outbound_capacity_msat: 0,
233+
outbound_capacity_msat: capacity * 1000,
234+
next_outbound_htlc_limit_msat: capacity * 1000
232235
inbound_htlc_minimum_msat: None,
233236
inbound_htlc_maximum_msat: None,
234237
});

lightning/src/ln/channel.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2330,23 +2330,30 @@ impl<Signer: Sign> Channel<Signer> {
23302330
stats
23312331
}
23322332

2333-
/// Get the available (ie not including pending HTLCs) inbound and outbound balance in msat.
2333+
/// Get the available (ie not including pending HTLCs) inbound and outbound balance, plus the
2334+
/// amount available for a single HTLC send, all in msat.
23342335
/// Doesn't bother handling the
23352336
/// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC
23362337
/// corner case properly.
23372338
/// The channel reserve is subtracted from each balance.
23382339
/// See also [`Channel::get_balance_msat`]
2339-
pub fn get_inbound_outbound_available_balance_msat(&self) -> (u64, u64) {
2340+
pub fn get_inbound_outbound_available_balance_msat(&self) -> (u64, u64, u64) {
23402341
// Note that we have to handle overflow due to the above case.
2342+
let outbound_stats = self.get_outbound_pending_htlc_stats(None);
2343+
let outbound_capacity_msat = cmp::max(self.value_to_self_msat as i64
2344+
- outbound_stats.pending_htlcs_value_msat as i64
2345+
- self.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) as i64 * 1000,
2346+
0) as u64;
23412347
(
23422348
cmp::max(self.channel_value_satoshis as i64 * 1000
23432349
- self.value_to_self_msat as i64
23442350
- self.get_inbound_pending_htlc_stats(None).pending_htlcs_value_msat as i64
23452351
- self.holder_selected_channel_reserve_satoshis as i64 * 1000,
23462352
0) as u64,
2347-
cmp::max(self.value_to_self_msat as i64
2348-
- self.get_outbound_pending_htlc_stats(None).pending_htlcs_value_msat as i64
2349-
- self.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) as i64 * 1000,
2353+
outbound_capacity_msat,
2354+
cmp::max(cmp::min(outbound_capacity_msat as i64,
2355+
self.counterparty_max_htlc_value_in_flight_msat as i64
2356+
- outbound_stats.pending_htlcs_value_msat as i64),
23502357
0) as u64
23512358
)
23522359
}

lightning/src/ln/channelmanager.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,13 @@ pub struct ChannelDetails {
10051005
/// conflict-avoidance policy, exactly this amount is not likely to be spendable. However, we
10061006
/// should be able to spend nearly this amount.
10071007
pub outbound_capacity_msat: u64,
1008+
/// The available outbound capacity for sending a single HTLC to the remote peer. This is
1009+
/// similar to [`ChannelDetails::outbound_capacity_msat`] but it may be further restricted by
1010+
/// the current state and per-HTLC limit(s). This is intended for use when routing, allowing us
1011+
/// to use a limit as close as possible to the HTLC limit we can currently send.
1012+
///
1013+
/// See also [`ChannelDetails::balance_msat`] and [`ChannelDetails::outbound_capacity_msat`].
1014+
pub next_outbound_htlc_limit_msat: u64,
10081015
/// The available inbound capacity for the remote peer to send HTLCs to us. This does not
10091016
/// include any pending HTLCs which are not yet fully resolved (and, thus, whose balance is not
10101017
/// available for inclusion in new inbound HTLCs).
@@ -1670,7 +1677,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
16701677
let channel_state = self.channel_state.lock().unwrap();
16711678
res.reserve(channel_state.by_id.len());
16721679
for (channel_id, channel) in channel_state.by_id.iter().filter(f) {
1673-
let (inbound_capacity_msat, outbound_capacity_msat) = channel.get_inbound_outbound_available_balance_msat();
1680+
let (inbound_capacity_msat, outbound_capacity_msat, next_outbound_htlc_limit_msat) =
1681+
channel.get_inbound_outbound_available_balance_msat();
16741682
let balance_msat = channel.get_balance_msat();
16751683
let (to_remote_reserve_satoshis, to_self_reserve_satoshis) =
16761684
channel.get_holder_counterparty_selected_channel_reserve_satoshis();
@@ -1701,6 +1709,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
17011709
balance_msat,
17021710
inbound_capacity_msat,
17031711
outbound_capacity_msat,
1712+
next_outbound_htlc_limit_msat,
17041713
user_channel_id: channel.get_user_id(),
17051714
confirmations_required: channel.minimum_depth(),
17061715
force_close_spend_delay: channel.get_counterparty_selected_contest_delay(),
@@ -5937,6 +5946,9 @@ impl_writeable_tlv_based!(ChannelDetails, {
59375946
(14, user_channel_id, required),
59385947
(16, balance_msat, required),
59395948
(18, outbound_capacity_msat, required),
5949+
// Note that by the time we get past the required read above, outbound_capacity_msat will be
5950+
// filled in, so we can safely unwrap it here.
5951+
(19, next_outbound_htlc_limit_msat, (default_value, outbound_capacity_msat.0.unwrap())),
59405952
(20, inbound_capacity_msat, required),
59415953
(22, confirmations_required, option),
59425954
(24, force_close_spend_delay, option),

lightning/src/routing/router.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ impl<'a> CandidateRouteHop<'a> {
412412
fn effective_capacity(&self) -> EffectiveCapacity {
413413
match self {
414414
CandidateRouteHop::FirstHop { details } => EffectiveCapacity::ExactLiquidity {
415-
liquidity_msat: details.outbound_capacity_msat,
415+
liquidity_msat: details.next_outbound_htlc_limit_msat,
416416
},
417417
CandidateRouteHop::PublicHop { info, .. } => info.effective_capacity(),
418418
CandidateRouteHop::PrivateHop { .. } => EffectiveCapacity::Infinite,
@@ -818,7 +818,8 @@ where L::Target: Logger {
818818
// We don't want multiple paths (as per MPP) share liquidity of the same channels.
819819
// This map allows paths to be aware of the channel use by other paths in the same call.
820820
// This would help to make a better path finding decisions and not "overbook" channels.
821-
// It is unaware of the directions (except for `outbound_capacity_msat` in `first_hops`).
821+
// It is unaware of the directions (except for `next_outbound_htlc_limit_msat` in
822+
// `first_hops`).
822823
let mut bookkept_channels_liquidity_available_msat = HashMap::with_capacity(network_nodes.len());
823824

824825
// Keeping track of how much value we already collected across other paths. Helps to decide:
@@ -841,12 +842,12 @@ where L::Target: Logger {
841842
// sort channels above `recommended_value_msat` in ascending order, preferring channels
842843
// which have enough, but not too much, capacity for the payment.
843844
channels.sort_unstable_by(|chan_a, chan_b| {
844-
if chan_b.outbound_capacity_msat < recommended_value_msat || chan_a.outbound_capacity_msat < recommended_value_msat {
845+
if chan_b.next_outbound_htlc_limit_msat < recommended_value_msat || chan_a.next_outbound_htlc_limit_msat < recommended_value_msat {
845846
// Sort in descending order
846-
chan_b.outbound_capacity_msat.cmp(&chan_a.outbound_capacity_msat)
847+
chan_b.next_outbound_htlc_limit_msat.cmp(&chan_a.next_outbound_htlc_limit_msat)
847848
} else {
848849
// Sort in ascending order
849-
chan_a.outbound_capacity_msat.cmp(&chan_b.outbound_capacity_msat)
850+
chan_a.next_outbound_htlc_limit_msat.cmp(&chan_b.next_outbound_htlc_limit_msat)
850851
}
851852
});
852853
}
@@ -1746,6 +1747,7 @@ mod tests {
17461747
user_channel_id: 0,
17471748
balance_msat: 0,
17481749
outbound_capacity_msat,
1750+
next_outbound_htlc_limit_msat: outbound_capacity_msat,
17491751
inbound_capacity_msat: 42,
17501752
unspendable_punishment_reserve: None,
17511753
confirmations_required: None,
@@ -3407,7 +3409,7 @@ mod tests {
34073409
assert_eq!(path.last().unwrap().fee_msat, 250_000_000);
34083410
}
34093411

3410-
// Check that setting outbound_capacity_msat in first_hops limits the channels.
3412+
// Check that setting next_outbound_htlc_limit_msat in first_hops limits the channels.
34113413
// Disable channel #1 and use another first hop.
34123414
update_channel(&net_graph_msg_handler, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
34133415
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
@@ -3422,7 +3424,7 @@ mod tests {
34223424
excess_data: Vec::new()
34233425
});
34243426

3425-
// Now, limit the first_hop by the outbound_capacity_msat of 200_000 sats.
3427+
// Now, limit the first_hop by the next_outbound_htlc_limit_msat of 200_000 sats.
34263428
let our_chans = vec![get_channel_details(Some(42), nodes[0].clone(), InitFeatures::from_le_bytes(vec![0b11]), 200_000_000)];
34273429

34283430
{
@@ -5473,6 +5475,7 @@ mod benches {
54735475
user_channel_id: 0,
54745476
balance_msat: 10_000_000,
54755477
outbound_capacity_msat: 10_000_000,
5478+
next_outbound_htlc_limit_msat: 10_000_000,
54765479
inbound_capacity_msat: 0,
54775480
unspendable_punishment_reserve: None,
54785481
confirmations_required: None,

0 commit comments

Comments
 (0)