Merge pull request #1444 from ViktorTigerstrom/2022-04-use-counterparty-htlc-max...
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Tue, 3 May 2022 22:44:26 +0000 (22:44 +0000)
committerGitHub <noreply@github.com>
Tue, 3 May 2022 22:44:26 +0000 (22:44 +0000)
Set `ChannelUpdate` `htlc_maximum_msat` using the peer's value

lightning/src/ln/channel.rs
lightning/src/ln/functional_tests.rs
lightning/src/util/config.rs

index 1cb7a689a21a1b710413e93afbba8e1881d48e90..10f18cec32d456d16b5ab2e7867ffadfb3ba5125 100644 (file)
@@ -39,7 +39,7 @@ use util::events::ClosureReason;
 use util::ser::{Readable, ReadableArgs, Writeable, Writer, VecWriter};
 use util::logger::Logger;
 use util::errors::APIError;
-use util::config::{UserConfig, ChannelConfig, ChannelHandshakeLimits};
+use util::config::{UserConfig, ChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits};
 use util::scid_utils::scid_from_parts;
 
 use io;
@@ -745,6 +745,12 @@ pub const COMMITMENT_TX_WEIGHT_PER_HTLC: u64 = 172;
 
 pub const ANCHOR_OUTPUT_VALUE_SATOSHI: u64 = 330;
 
+/// The percentage of the channel value `holder_max_htlc_value_in_flight_msat` used to be set to,
+/// before this was made configurable. The percentage was made configurable in LDK 0.0.107,
+/// although LDK 0.0.104+ enabled serialization of channels with a different value set for
+/// `holder_max_htlc_value_in_flight_msat`.
+pub const MAX_IN_FLIGHT_PERCENT_LEGACY: u8 = 10;
+
 /// Maximum `funding_satoshis` value according to the BOLT #2 specification, if
 /// `option_support_large_channel` (aka wumbo channels) is not supported.
 /// It's 2^24 - 1.
@@ -803,9 +809,22 @@ macro_rules! secp_check {
 }
 
 impl<Signer: Sign> Channel<Signer> {
-       // Convert constants + channel value to limits:
-       fn get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis: u64) -> u64 {
-               channel_value_satoshis * 1000 / 10 //TODO
+       /// Returns the value to use for `holder_max_htlc_value_in_flight_msat` as a percentage of the
+       /// `channel_value_satoshis` in msat, set through
+       /// [`ChannelHandshakeConfig::max_inbound_htlc_value_in_flight_percent_of_channel`]
+       ///
+       /// The effective percentage is lower bounded by 1% and upper bounded by 100%.
+       ///
+       /// [`ChannelHandshakeConfig::max_inbound_htlc_value_in_flight_percent_of_channel`]: crate::util::config::ChannelHandshakeConfig::max_inbound_htlc_value_in_flight_percent_of_channel
+       fn get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis: u64, config: &ChannelHandshakeConfig) -> u64 {
+               let configured_percent = if config.max_inbound_htlc_value_in_flight_percent_of_channel < 1 {
+                       1
+               } else if config.max_inbound_htlc_value_in_flight_percent_of_channel > 100 {
+                       100
+               } else {
+                       config.max_inbound_htlc_value_in_flight_percent_of_channel as u64
+               };
+               channel_value_satoshis * 10 * configured_percent
        }
 
        /// Returns a minimum channel reserve value the remote needs to maintain,
@@ -964,7 +983,7 @@ impl<Signer: Sign> Channel<Signer> {
                        counterparty_dust_limit_satoshis: 0,
                        holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS,
                        counterparty_max_htlc_value_in_flight_msat: 0,
-                       holder_max_htlc_value_in_flight_msat: Self::get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis),
+                       holder_max_htlc_value_in_flight_msat: Self::get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis, &config.own_channel_config),
                        counterparty_selected_channel_reserve_satoshis: None, // Filled in in accept_channel
                        holder_selected_channel_reserve_satoshis,
                        counterparty_htlc_minimum_msat: 0,
@@ -1282,7 +1301,7 @@ impl<Signer: Sign> Channel<Signer> {
                        counterparty_dust_limit_satoshis: msg.dust_limit_satoshis,
                        holder_dust_limit_satoshis: MIN_CHAN_DUST_LIMIT_SATOSHIS,
                        counterparty_max_htlc_value_in_flight_msat: cmp::min(msg.max_htlc_value_in_flight_msat, msg.funding_satoshis * 1000),
-                       holder_max_htlc_value_in_flight_msat: Self::get_holder_max_htlc_value_in_flight_msat(msg.funding_satoshis),
+                       holder_max_htlc_value_in_flight_msat: Self::get_holder_max_htlc_value_in_flight_msat(msg.funding_satoshis, &config.own_channel_config),
                        counterparty_selected_channel_reserve_satoshis: Some(msg.channel_reserve_satoshis),
                        holder_selected_channel_reserve_satoshis,
                        counterparty_htlc_minimum_msat: msg.htlc_minimum_msat,
@@ -4360,7 +4379,7 @@ impl<Signer: Sign> Channel<Signer> {
                        // channel might have been used to route very small values (either by honest users or as DoS).
                        self.channel_value_satoshis * 1000 * 9 / 10,
 
-                       self.holder_max_htlc_value_in_flight_msat
+                       self.counterparty_max_htlc_value_in_flight_msat
                );
        }
 
@@ -5877,13 +5896,18 @@ impl<Signer: Sign> Writeable for Channel<Signer> {
                let chan_type = if self.channel_type != ChannelTypeFeatures::only_static_remote_key() {
                        Some(&self.channel_type) } else { None };
 
-               // The same logic applies for `holder_selected_channel_reserve_satoshis` and
-               // `holder_max_htlc_value_in_flight_msat` values other than the defaults.
+               // The same logic applies for `holder_selected_channel_reserve_satoshis` values other than
+               // the default, and when `holder_max_htlc_value_in_flight_msat` is configured to be set to
+               // a different percentage of the channel value then 10%, which older versions of LDK used
+               // to set it to before the percentage was made configurable.
                let serialized_holder_selected_reserve =
                        if self.holder_selected_channel_reserve_satoshis != Self::get_holder_selected_channel_reserve_satoshis(self.channel_value_satoshis)
                        { Some(self.holder_selected_channel_reserve_satoshis) } else { None };
+
+               let mut old_max_in_flight_percent_config = UserConfig::default().own_channel_config;
+               old_max_in_flight_percent_config.max_inbound_htlc_value_in_flight_percent_of_channel = MAX_IN_FLIGHT_PERCENT_LEGACY;
                let serialized_holder_htlc_max_in_flight =
-                       if self.holder_max_htlc_value_in_flight_msat != Self::get_holder_max_htlc_value_in_flight_msat(self.channel_value_satoshis)
+                       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)
                        { Some(self.holder_max_htlc_value_in_flight_msat) } else { None };
 
                write_tlv_fields!(writer, {
@@ -6153,7 +6177,7 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel<Signer>
                let mut target_closing_feerate_sats_per_kw = None;
                let mut monitor_pending_finalized_fulfills = Some(Vec::new());
                let mut holder_selected_channel_reserve_satoshis = Some(Self::get_holder_selected_channel_reserve_satoshis(channel_value_satoshis));
-               let mut holder_max_htlc_value_in_flight_msat = Some(Self::get_holder_max_htlc_value_in_flight_msat(channel_value_satoshis));
+               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));
                // Prior to supporting channel type negotiation, all of our channels were static_remotekey
                // only, so we default to that if none was written.
                let mut channel_type = Some(ChannelTypeFeatures::only_static_remote_key());
@@ -6656,6 +6680,79 @@ mod tests {
                }
        }
 
+       #[test]
+       fn test_configured_holder_max_htlc_value_in_flight() {
+               let feeest = TestFeeEstimator{fee_est: 15000};
+               let logger = test_utils::TestLogger::new();
+               let secp_ctx = Secp256k1::new();
+               let seed = [42; 32];
+               let network = Network::Testnet;
+               let keys_provider = test_utils::TestKeysInterface::new(&seed, network);
+               let outbound_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
+               let inbound_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
+
+               let mut config_2_percent = UserConfig::default();
+               config_2_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 2;
+               let mut config_99_percent = UserConfig::default();
+               config_99_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 99;
+               let mut config_0_percent = UserConfig::default();
+               config_0_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 0;
+               let mut config_101_percent = UserConfig::default();
+               config_101_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 101;
+
+               // Test that `new_outbound` creates a channel with the correct value for
+               // `holder_max_htlc_value_in_flight_msat`, when configured with a valid percentage value,
+               // which is set to the lower bound + 1 (2%) of the `channel_value`.
+               let chan_1 = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, outbound_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config_2_percent, 0, 42).unwrap();
+               let chan_1_value_msat = chan_1.channel_value_satoshis * 1000;
+               assert_eq!(chan_1.holder_max_htlc_value_in_flight_msat, (chan_1_value_msat as f64 * 0.02) as u64);
+
+               // Test with the upper bound - 1 of valid values (99%).
+               let chan_2 = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, outbound_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config_99_percent, 0, 42).unwrap();
+               let chan_2_value_msat = chan_2.channel_value_satoshis * 1000;
+               assert_eq!(chan_2.holder_max_htlc_value_in_flight_msat, (chan_2_value_msat as f64 * 0.99) as u64);
+
+               let chan_1_open_channel_msg = chan_1.get_open_channel(genesis_block(network).header.block_hash());
+
+               // Test that `new_from_req` creates a channel with the correct value for
+               // `holder_max_htlc_value_in_flight_msat`, when configured with a valid percentage value,
+               // which is set to the lower bound - 1 (2%) of the `channel_value`.
+               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();
+               let chan_3_value_msat = chan_3.channel_value_satoshis * 1000;
+               assert_eq!(chan_3.holder_max_htlc_value_in_flight_msat, (chan_3_value_msat as f64 * 0.02) as u64);
+
+               // Test with the upper bound - 1 of valid values (99%).
+               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();
+               let chan_4_value_msat = chan_4.channel_value_satoshis * 1000;
+               assert_eq!(chan_4.holder_max_htlc_value_in_flight_msat, (chan_4_value_msat as f64 * 0.99) as u64);
+
+               // Test that `new_outbound` uses the lower bound of the configurable percentage values (1%)
+               // if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a value less than 1.
+               let chan_5 = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, outbound_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config_0_percent, 0, 42).unwrap();
+               let chan_5_value_msat = chan_5.channel_value_satoshis * 1000;
+               assert_eq!(chan_5.holder_max_htlc_value_in_flight_msat, (chan_5_value_msat as f64 * 0.01) as u64);
+
+               // Test that `new_outbound` uses the upper bound of the configurable percentage values
+               // (100%) if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a larger value
+               // than 100.
+               let chan_6 = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, outbound_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config_101_percent, 0, 42).unwrap();
+               let chan_6_value_msat = chan_6.channel_value_satoshis * 1000;
+               assert_eq!(chan_6.holder_max_htlc_value_in_flight_msat, chan_6_value_msat);
+
+               // Test that `new_from_req` uses the lower bound of the configurable percentage values (1%)
+               // if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a value less than 1.
+               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();
+               let chan_7_value_msat = chan_7.channel_value_satoshis * 1000;
+               assert_eq!(chan_7.holder_max_htlc_value_in_flight_msat, (chan_7_value_msat as f64 * 0.01) as u64);
+
+               // Test that `new_from_req` uses the upper bound of the configurable percentage values
+               // (100%) if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a larger value
+               // than 100.
+               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();
+               let chan_8_value_msat = chan_8.channel_value_satoshis * 1000;
+               assert_eq!(chan_8.holder_max_htlc_value_in_flight_msat, chan_8_value_msat);
+       }
+
        #[test]
        fn channel_update() {
                let feeest = TestFeeEstimator{fee_est: 15000};
index 4defbaaa2931d061ca7bb399d45cb0178a7240ee..73c3a86d782315f87b67b3f1875b09b0fa974e9f 100644 (file)
@@ -26,7 +26,7 @@ use ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight, HTLCOutputI
 use routing::router::{PaymentParameters, Route, RouteHop, RouteParameters, find_route, get_route};
 use ln::features::{ChannelFeatures, InitFeatures, InvoiceFeatures, NodeFeatures};
 use ln::msgs;
-use ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, ErrorAction};
+use ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, OptionalField, ErrorAction};
 use util::enforcing_trait_impls::EnforcingSigner;
 use util::{byte_utils, test_utils};
 use util::events::{Event, MessageSendEvent, MessageSendEventsProvider, PaymentPurpose, ClosureReason};
@@ -8143,6 +8143,58 @@ fn test_override_0msat_htlc_minimum() {
        assert_eq!(res.htlc_minimum_msat, 1);
 }
 
+#[test]
+fn test_channel_update_has_correct_htlc_maximum_msat() {
+       // Tests that the `ChannelUpdate` message has the correct values for `htlc_maximum_msat` set.
+       // Bolt 7 specifies that if present `htlc_maximum_msat`:
+       // 1. MUST be set to less than or equal to the channel capacity. In LDK, this is capped to
+       // 90% of the `channel_value`.
+       // 2. MUST be set to less than or equal to the `max_htlc_value_in_flight_msat` received from the peer.
+
+       let mut config_30_percent = UserConfig::default();
+       config_30_percent.channel_options.announced_channel = true;
+       config_30_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 30;
+       let mut config_50_percent = UserConfig::default();
+       config_50_percent.channel_options.announced_channel = true;
+       config_50_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 50;
+       let mut config_95_percent = UserConfig::default();
+       config_95_percent.channel_options.announced_channel = true;
+       config_95_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 95;
+       let mut config_100_percent = UserConfig::default();
+       config_100_percent.channel_options.announced_channel = true;
+       config_100_percent.own_channel_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100;
+
+       let chanmon_cfgs = create_chanmon_cfgs(4);
+       let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
+       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)]);
+       let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
+
+       let channel_value_satoshis = 100000;
+       let channel_value_msat = channel_value_satoshis * 1000;
+       let channel_value_30_percent_msat = (channel_value_msat as f64 * 0.3) as u64;
+       let channel_value_50_percent_msat = (channel_value_msat as f64 * 0.5) as u64;
+       let channel_value_90_percent_msat = (channel_value_msat as f64 * 0.9) as u64;
+
+       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());
+       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());
+
+       // Assert that `node[0]`'s `ChannelUpdate` is capped at 50 percent of the `channel_value`, as
+       // that's the value of `node[1]`'s `holder_max_htlc_value_in_flight_msat`.
+       assert_eq!(node_0_chan_update.contents.htlc_maximum_msat, OptionalField::Present(channel_value_50_percent_msat));
+       // Assert that `node[1]`'s `ChannelUpdate` is capped at 30 percent of the `channel_value`, as
+       // that's the value of `node[0]`'s `holder_max_htlc_value_in_flight_msat`.
+       assert_eq!(node_1_chan_update.contents.htlc_maximum_msat, OptionalField::Present(channel_value_30_percent_msat));
+
+       // Assert that `node[2]`'s `ChannelUpdate` is capped at 90 percent of the `channel_value`, as
+       // the value of `node[3]`'s `holder_max_htlc_value_in_flight_msat` (100%), exceeds 90% of the
+       // `channel_value`.
+       assert_eq!(node_2_chan_update.contents.htlc_maximum_msat, OptionalField::Present(channel_value_90_percent_msat));
+       // Assert that `node[3]`'s `ChannelUpdate` is capped at 90 percent of the `channel_value`, as
+       // the value of `node[2]`'s `holder_max_htlc_value_in_flight_msat` (95%), exceeds 90% of the
+       // `channel_value`.
+       assert_eq!(node_3_chan_update.contents.htlc_maximum_msat, OptionalField::Present(channel_value_90_percent_msat));
+}
+
 #[test]
 fn test_manually_accept_inbound_channel_request() {
        let mut manually_accept_conf = UserConfig::default();
index 0f625098f79f9030ae25ddc1979cc6b5fcba30ae..3868d29aab49f66789c49c970a662cba19000e2e 100644 (file)
@@ -48,6 +48,30 @@ pub struct ChannelHandshakeConfig {
        /// Default value: 1. If the value is less than 1, it is ignored and set to 1, as is required
        /// by the protocol.
        pub our_htlc_minimum_msat: u64,
+       /// Sets the percentage of the channel value we will cap the total value of outstanding inbound
+       /// HTLCs to.
+       ///
+       /// This can be set to a value between 1-100, where the value corresponds to the percent of the
+       /// channel value in whole percentages.
+       ///
+       /// Note that:
+       /// * If configured to another value than the default value 10, any new channels created with
+       /// the non default value will cause versions of LDK prior to 0.0.104 to refuse to read the
+       /// `ChannelManager`.
+       ///
+       /// * This caps the total value for inbound HTLCs in-flight only, and there's currently
+       /// no way to configure the cap for the total value of outbound HTLCs in-flight.
+       ///
+       /// * The requirements for your node being online to ensure the safety of HTLC-encumbered funds
+       /// are different from the non-HTLC-encumbered funds. This makes this an important knob to
+       /// restrict exposure to loss due to being offline for too long.
+       /// See [`ChannelHandshakeConfig::our_to_self_delay`] and [`ChannelConfig::cltv_expiry_delta`]
+       /// for more information.
+       ///
+       /// Default value: 10.
+       /// Minimum value: 1, any values less than 1 will be treated as 1 instead.
+       /// Maximum value: 100, any values larger than 100 will be treated as 100 instead.
+       pub max_inbound_htlc_value_in_flight_percent_of_channel: u8,
        /// If set, we attempt to negotiate the `scid_privacy` (referred to as `scid_alias` in the
        /// BOLTs) option for outbound private channels. This provides better privacy by not including
        /// our real on-chain channel UTXO in each invoice and requiring that our counterparty only
@@ -78,6 +102,7 @@ impl Default for ChannelHandshakeConfig {
                        minimum_depth: 6,
                        our_to_self_delay: BREAKDOWN_TIMEOUT,
                        our_htlc_minimum_msat: 1,
+                       max_inbound_htlc_value_in_flight_percent_of_channel: 10,
                        negotiate_scid_privacy: false,
                }
        }