Skip to content

Commit 6418c9e

Browse files
authored
Merge pull request #1444 from ViktorTigerstrom/2022-04-use-counterparty-htlc-max-for-chan-updates
Set `ChannelUpdate` `htlc_maximum_msat` using the peer's value
2 parents 171dfee + 224d470 commit 6418c9e

File tree

3 files changed

+186
-12
lines changed

3 files changed

+186
-12
lines changed

lightning/src/ln/channel.rs

Lines changed: 108 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use util::events::ClosureReason;
3939
use util::ser::{Readable, ReadableArgs, Writeable, Writer, VecWriter};
4040
use util::logger::Logger;
4141
use util::errors::APIError;
42-
use util::config::{UserConfig, ChannelConfig, ChannelHandshakeLimits};
42+
use util::config::{UserConfig, ChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits};
4343
use util::scid_utils::scid_from_parts;
4444

4545
use io;
@@ -745,6 +745,12 @@ pub const COMMITMENT_TX_WEIGHT_PER_HTLC: u64 = 172;
745745

746746
pub const ANCHOR_OUTPUT_VALUE_SATOSHI: u64 = 330;
747747

748+
/// The percentage of the channel value `holder_max_htlc_value_in_flight_msat` used to be set to,
749+
/// before this was made configurable. The percentage was made configurable in LDK 0.0.107,
750+
/// although LDK 0.0.104+ enabled serialization of channels with a different value set for
751+
/// `holder_max_htlc_value_in_flight_msat`.
752+
pub const MAX_IN_FLIGHT_PERCENT_LEGACY: u8 = 10;
753+
748754
/// Maximum `funding_satoshis` value according to the BOLT #2 specification, if
749755
/// `option_support_large_channel` (aka wumbo channels) is not supported.
750756
/// It's 2^24 - 1.
@@ -803,9 +809,22 @@ macro_rules! secp_check {
803809
}
804810

805811
impl<Signer: Sign> Channel<Signer> {
806-
// Convert constants + channel value to limits:
807-
fn get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis: u64) -> u64 {
808-
channel_value_satoshis * 1000 / 10 //TODO
812+
/// Returns the value to use for `holder_max_htlc_value_in_flight_msat` as a percentage of the
813+
/// `channel_value_satoshis` in msat, set through
814+
/// [`ChannelHandshakeConfig::max_inbound_htlc_value_in_flight_percent_of_channel`]
815+
///
816+
/// The effective percentage is lower bounded by 1% and upper bounded by 100%.
817+
///
818+
/// [`ChannelHandshakeConfig::max_inbound_htlc_value_in_flight_percent_of_channel`]: crate::util::config::ChannelHandshakeConfig::max_inbound_htlc_value_in_flight_percent_of_channel
819+
fn get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis: u64, config: &ChannelHandshakeConfig) -> u64 {
820+
let configured_percent = if config.max_inbound_htlc_value_in_flight_percent_of_channel < 1 {
821+
1
822+
} else if config.max_inbound_htlc_value_in_flight_percent_of_channel > 100 {
823+
100
824+
} else {
825+
config.max_inbound_htlc_value_in_flight_percent_of_channel as u64
826+
};
827+
channel_value_satoshis * 10 * configured_percent
809828
}
810829

811830
/// Returns a minimum channel reserve value the remote needs to maintain,
@@ -964,7 +983,7 @@ impl<Signer: Sign> Channel<Signer> {
964983
counterparty_dust_limit_satoshis: 0,
965984
holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS,
966985
counterparty_max_htlc_value_in_flight_msat: 0,
967-
holder_max_htlc_value_in_flight_msat: Self::get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis),
986+
holder_max_htlc_value_in_flight_msat: Self::get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis, &config.own_channel_config),
968987
counterparty_selected_channel_reserve_satoshis: None, // Filled in in accept_channel
969988
holder_selected_channel_reserve_satoshis,
970989
counterparty_htlc_minimum_msat: 0,
@@ -1282,7 +1301,7 @@ impl<Signer: Sign> Channel<Signer> {
12821301
counterparty_dust_limit_satoshis: msg.dust_limit_satoshis,
12831302
holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS,
12841303
counterparty_max_htlc_value_in_flight_msat: cmp::min(msg.max_htlc_value_in_flight_msat, msg.funding_satoshis * 1000),
1285-
holder_max_htlc_value_in_flight_msat: Self::get_holder_max_htlc_value_in_flight_msat(msg.funding_satoshis),
1304+
holder_max_htlc_value_in_flight_msat: Self::get_holder_max_htlc_value_in_flight_msat(msg.funding_satoshis, &config.own_channel_config),
12861305
counterparty_selected_channel_reserve_satoshis: Some(msg.channel_reserve_satoshis),
12871306
holder_selected_channel_reserve_satoshis,
12881307
counterparty_htlc_minimum_msat: msg.htlc_minimum_msat,
@@ -4360,7 +4379,7 @@ impl<Signer: Sign> Channel<Signer> {
43604379
// channel might have been used to route very small values (either by honest users or as DoS).
43614380
self.channel_value_satoshis * 1000 * 9 / 10,
43624381

4363-
self.holder_max_htlc_value_in_flight_msat
4382+
self.counterparty_max_htlc_value_in_flight_msat
43644383
);
43654384
}
43664385

@@ -5877,13 +5896,18 @@ impl<Signer: Sign> Writeable for Channel<Signer> {
58775896
let chan_type = if self.channel_type != ChannelTypeFeatures::only_static_remote_key() {
58785897
Some(&self.channel_type) } else { None };
58795898

5880-
// The same logic applies for `holder_selected_channel_reserve_satoshis` and
5881-
// `holder_max_htlc_value_in_flight_msat` values other than the defaults.
5899+
// The same logic applies for `holder_selected_channel_reserve_satoshis` values other than
5900+
// the default, and when `holder_max_htlc_value_in_flight_msat` is configured to be set to
5901+
// a different percentage of the channel value then 10%, which older versions of LDK used
5902+
// to set it to before the percentage was made configurable.
58825903
let serialized_holder_selected_reserve =
58835904
if self.holder_selected_channel_reserve_satoshis != Self::get_holder_selected_channel_reserve_satoshis(self.channel_value_satoshis)
58845905
{ Some(self.holder_selected_channel_reserve_satoshis) } else { None };
5906+
5907+
let mut old_max_in_flight_percent_config = UserConfig::default().own_channel_config;
5908+
old_max_in_flight_percent_config.max_inbound_htlc_value_in_flight_percent_of_channel = MAX_IN_FLIGHT_PERCENT_LEGACY;
58855909
let serialized_holder_htlc_max_in_flight =
5886-
if self.holder_max_htlc_value_in_flight_msat != Self::get_holder_max_htlc_value_in_flight_msat(self.channel_value_satoshis)
5910+
if self.holder_max_htlc_value_in_flight_msat != Self::get_holder_max_htlc_value_in_flight_msat(self.channel_value_satoshis, &old_max_in_flight_percent_config)
58875911
{ Some(self.holder_max_htlc_value_in_flight_msat) } else { None };
58885912

58895913
write_tlv_fields!(writer, {
@@ -6153,7 +6177,7 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel<Signer>
61536177
let mut target_closing_feerate_sats_per_kw = None;
61546178
let mut monitor_pending_finalized_fulfills = Some(Vec::new());
61556179
let mut holder_selected_channel_reserve_satoshis = Some(Self::get_holder_selected_channel_reserve_satoshis(channel_value_satoshis));
6156-
let mut holder_max_htlc_value_in_flight_msat = Some(Self::get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis));
6180+
let mut holder_max_htlc_value_in_flight_msat = Some(Self::get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis, &UserConfig::default().own_channel_config));
61576181
// Prior to supporting channel type negotiation, all of our channels were static_remotekey
61586182
// only, so we default to that if none was written.
61596183
let mut channel_type = Some(ChannelTypeFeatures::only_static_remote_key());
@@ -6656,6 +6680,79 @@ mod tests {
66566680
}
66576681
}
66586682

6683+
#[test]
6684+
fn test_configured_holder_max_htlc_value_in_flight() {
6685+
let feeest = TestFeeEstimator{fee_est: 15000};
6686+
let logger = test_utils::TestLogger::new();
6687+
let secp_ctx = Secp256k1::new();
6688+
let seed = [42; 32];
6689+
let network = Network::Testnet;
6690+
let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
6691+
let outbound_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
6692+
let inbound_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
6693+
6694+
let mut config_2_percent = UserConfig::default();
6695+
config_2_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 2;
6696+
let mut config_99_percent = UserConfig::default();
6697+
config_99_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 99;
6698+
let mut config_0_percent = UserConfig::default();
6699+
config_0_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 0;
6700+
let mut config_101_percent = UserConfig::default();
6701+
config_101_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 101;
6702+
6703+
// Test that `new_outbound` creates a channel with the correct value for
6704+
// `holder_max_htlc_value_in_flight_msat`, when configured with a valid percentage value,
6705+
// which is set to the lower bound + 1 (2%) of the `channel_value`.
6706+
let chan_1 = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, outbound_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config_2_percent, 0, 42).unwrap();
6707+
let chan_1_value_msat = chan_1.channel_value_satoshis * 1000;
6708+
assert_eq!(chan_1.holder_max_htlc_value_in_flight_msat, (chan_1_value_msat as f64 * 0.02) as u64);
6709+
6710+
// Test with the upper bound - 1 of valid values (99%).
6711+
let chan_2 = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, outbound_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config_99_percent, 0, 42).unwrap();
6712+
let chan_2_value_msat = chan_2.channel_value_satoshis * 1000;
6713+
assert_eq!(chan_2.holder_max_htlc_value_in_flight_msat, (chan_2_value_msat as f64 * 0.99) as u64);
6714+
6715+
let chan_1_open_channel_msg = chan_1.get_open_channel(genesis_block(network).header.block_hash());
6716+
6717+
// Test that `new_from_req` creates a channel with the correct value for
6718+
// `holder_max_htlc_value_in_flight_msat`, when configured with a valid percentage value,
6719+
// which is set to the lower bound - 1 (2%) of the `channel_value`.
6720+
let chan_3 = Channel::<EnforcingSigner>::new_from_req(&&feeest, &&keys_provider, inbound_node_id, &InitFeatures::known(), &chan_1_open_channel_msg, 7, &config_2_percent, 0, &&logger, 42).unwrap();
6721+
let chan_3_value_msat = chan_3.channel_value_satoshis * 1000;
6722+
assert_eq!(chan_3.holder_max_htlc_value_in_flight_msat, (chan_3_value_msat as f64 * 0.02) as u64);
6723+
6724+
// Test with the upper bound - 1 of valid values (99%).
6725+
let chan_4 = Channel::<EnforcingSigner>::new_from_req(&&feeest, &&keys_provider, inbound_node_id, &InitFeatures::known(), &chan_1_open_channel_msg, 7, &config_99_percent, 0, &&logger, 42).unwrap();
6726+
let chan_4_value_msat = chan_4.channel_value_satoshis * 1000;
6727+
assert_eq!(chan_4.holder_max_htlc_value_in_flight_msat, (chan_4_value_msat as f64 * 0.99) as u64);
6728+
6729+
// Test that `new_outbound` uses the lower bound of the configurable percentage values (1%)
6730+
// if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a value less than 1.
6731+
let chan_5 = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, outbound_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config_0_percent, 0, 42).unwrap();
6732+
let chan_5_value_msat = chan_5.channel_value_satoshis * 1000;
6733+
assert_eq!(chan_5.holder_max_htlc_value_in_flight_msat, (chan_5_value_msat as f64 * 0.01) as u64);
6734+
6735+
// Test that `new_outbound` uses the upper bound of the configurable percentage values
6736+
// (100%) if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a larger value
6737+
// than 100.
6738+
let chan_6 = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, outbound_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config_101_percent, 0, 42).unwrap();
6739+
let chan_6_value_msat = chan_6.channel_value_satoshis * 1000;
6740+
assert_eq!(chan_6.holder_max_htlc_value_in_flight_msat, chan_6_value_msat);
6741+
6742+
// Test that `new_from_req` uses the lower bound of the configurable percentage values (1%)
6743+
// if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a value less than 1.
6744+
let chan_7 = Channel::<EnforcingSigner>::new_from_req(&&feeest, &&keys_provider, inbound_node_id, &InitFeatures::known(), &chan_1_open_channel_msg, 7, &config_0_percent, 0, &&logger, 42).unwrap();
6745+
let chan_7_value_msat = chan_7.channel_value_satoshis * 1000;
6746+
assert_eq!(chan_7.holder_max_htlc_value_in_flight_msat, (chan_7_value_msat as f64 * 0.01) as u64);
6747+
6748+
// Test that `new_from_req` uses the upper bound of the configurable percentage values
6749+
// (100%) if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a larger value
6750+
// than 100.
6751+
let chan_8 = Channel::<EnforcingSigner>::new_from_req(&&feeest, &&keys_provider, inbound_node_id, &InitFeatures::known(), &chan_1_open_channel_msg, 7, &config_101_percent, 0, &&logger, 42).unwrap();
6752+
let chan_8_value_msat = chan_8.channel_value_satoshis * 1000;
6753+
assert_eq!(chan_8.holder_max_htlc_value_in_flight_msat, chan_8_value_msat);
6754+
}
6755+
66596756
#[test]
66606757
fn channel_update() {
66616758
let feeest = TestFeeEstimator{fee_est: 15000};

lightning/src/ln/functional_tests.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight, HTLCOutputI
2626
use routing::router::{PaymentParameters, Route, RouteHop, RouteParameters, find_route, get_route};
2727
use ln::features::{ChannelFeatures, InitFeatures, InvoiceFeatures, NodeFeatures};
2828
use ln::msgs;
29-
use ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, ErrorAction};
29+
use ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, OptionalField, ErrorAction};
3030
use util::enforcing_trait_impls::EnforcingSigner;
3131
use util::{byte_utils, test_utils};
3232
use util::events::{Event, MessageSendEvent, MessageSendEventsProvider, PaymentPurpose, ClosureReason};
@@ -8143,6 +8143,58 @@ fn test_override_0msat_htlc_minimum() {
81438143
assert_eq!(res.htlc_minimum_msat, 1);
81448144
}
81458145

8146+
#[test]
8147+
fn test_channel_update_has_correct_htlc_maximum_msat() {
8148+
// Tests that the `ChannelUpdate` message has the correct values for `htlc_maximum_msat` set.
8149+
// Bolt 7 specifies that if present `htlc_maximum_msat`:
8150+
// 1. MUST be set to less than or equal to the channel capacity. In LDK, this is capped to
8151+
// 90% of the `channel_value`.
8152+
// 2. MUST be set to less than or equal to the `max_htlc_value_in_flight_msat` received from the peer.
8153+
8154+
let mut config_30_percent = UserConfig::default();
8155+
config_30_percent.channel_options.announced_channel = true;
8156+
config_30_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 30;
8157+
let mut config_50_percent = UserConfig::default();
8158+
config_50_percent.channel_options.announced_channel = true;
8159+
config_50_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 50;
8160+
let mut config_95_percent = UserConfig::default();
8161+
config_95_percent.channel_options.announced_channel = true;
8162+
config_95_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 95;
8163+
let mut config_100_percent = UserConfig::default();
8164+
config_100_percent.channel_options.announced_channel = true;
8165+
config_100_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100;
8166+
8167+
let chanmon_cfgs = create_chanmon_cfgs(4);
8168+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
8169+
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[Some(config_30_percent), Some(config_50_percent), Some(config_95_percent), Some(config_100_percent)]);
8170+
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
8171+
8172+
let channel_value_satoshis = 100000;
8173+
let channel_value_msat = channel_value_satoshis * 1000;
8174+
let channel_value_30_percent_msat = (channel_value_msat as f64 * 0.3) as u64;
8175+
let channel_value_50_percent_msat = (channel_value_msat as f64 * 0.5) as u64;
8176+
let channel_value_90_percent_msat = (channel_value_msat as f64 * 0.9) as u64;
8177+
8178+
let (node_0_chan_update, node_1_chan_update, _, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, channel_value_satoshis, 10001, InitFeatures::known(), InitFeatures::known());
8179+
let (node_2_chan_update, node_3_chan_update, _, _) = create_announced_chan_between_nodes_with_value(&nodes, 2, 3, channel_value_satoshis, 10001, InitFeatures::known(), InitFeatures::known());
8180+
8181+
// Assert that `node[0]`'s `ChannelUpdate` is capped at 50 percent of the `channel_value`, as
8182+
// that's the value of `node[1]`'s `holder_max_htlc_value_in_flight_msat`.
8183+
assert_eq!(node_0_chan_update.contents.htlc_maximum_msat, OptionalField::Present(channel_value_50_percent_msat));
8184+
// Assert that `node[1]`'s `ChannelUpdate` is capped at 30 percent of the `channel_value`, as
8185+
// that's the value of `node[0]`'s `holder_max_htlc_value_in_flight_msat`.
8186+
assert_eq!(node_1_chan_update.contents.htlc_maximum_msat, OptionalField::Present(channel_value_30_percent_msat));
8187+
8188+
// Assert that `node[2]`'s `ChannelUpdate` is capped at 90 percent of the `channel_value`, as
8189+
// the value of `node[3]`'s `holder_max_htlc_value_in_flight_msat` (100%), exceeds 90% of the
8190+
// `channel_value`.
8191+
assert_eq!(node_2_chan_update.contents.htlc_maximum_msat, OptionalField::Present(channel_value_90_percent_msat));
8192+
// Assert that `node[3]`'s `ChannelUpdate` is capped at 90 percent of the `channel_value`, as
8193+
// the value of `node[2]`'s `holder_max_htlc_value_in_flight_msat` (95%), exceeds 90% of the
8194+
// `channel_value`.
8195+
assert_eq!(node_3_chan_update.contents.htlc_maximum_msat, OptionalField::Present(channel_value_90_percent_msat));
8196+
}
8197+
81468198
#[test]
81478199
fn test_manually_accept_inbound_channel_request() {
81488200
let mut manually_accept_conf = UserConfig::default();

lightning/src/util/config.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,30 @@ pub struct ChannelHandshakeConfig {
4848
/// Default value: 1. If the value is less than 1, it is ignored and set to 1, as is required
4949
/// by the protocol.
5050
pub our_htlc_minimum_msat: u64,
51+
/// Sets the percentage of the channel value we will cap the total value of outstanding inbound
52+
/// HTLCs to.
53+
///
54+
/// This can be set to a value between 1-100, where the value corresponds to the percent of the
55+
/// channel value in whole percentages.
56+
///
57+
/// Note that:
58+
/// * If configured to another value than the default value 10, any new channels created with
59+
/// the non default value will cause versions of LDK prior to 0.0.104 to refuse to read the
60+
/// `ChannelManager`.
61+
///
62+
/// * This caps the total value for inbound HTLCs in-flight only, and there's currently
63+
/// no way to configure the cap for the total value of outbound HTLCs in-flight.
64+
///
65+
/// * The requirements for your node being online to ensure the safety of HTLC-encumbered funds
66+
/// are different from the non-HTLC-encumbered funds. This makes this an important knob to
67+
/// restrict exposure to loss due to being offline for too long.
68+
/// See [`ChannelHandshakeConfig::our_to_self_delay`] and [`ChannelConfig::cltv_expiry_delta`]
69+
/// for more information.
70+
///
71+
/// Default value: 10.
72+
/// Minimum value: 1, any values less than 1 will be treated as 1 instead.
73+
/// Maximum value: 100, any values larger than 100 will be treated as 100 instead.
74+
pub max_inbound_htlc_value_in_flight_percent_of_channel: u8,
5175
/// If set, we attempt to negotiate the `scid_privacy` (referred to as `scid_alias` in the
5276
/// BOLTs) option for outbound private channels. This provides better privacy by not including
5377
/// our real on-chain channel UTXO in each invoice and requiring that our counterparty only
@@ -78,6 +102,7 @@ impl Default for ChannelHandshakeConfig {
78102
minimum_depth: 6,
79103
our_to_self_delay: BREAKDOWN_TIMEOUT,
80104
our_htlc_minimum_msat: 1,
105+
max_inbound_htlc_value_in_flight_percent_of_channel: 10,
81106
negotiate_scid_privacy: false,
82107
}
83108
}

0 commit comments

Comments
 (0)