Merge pull request #1548 from NicolaLS/serde-module
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Tue, 21 Jun 2022 16:20:38 +0000 (09:20 -0700)
committerGitHub <noreply@github.com>
Tue, 21 Jun 2022 16:20:38 +0000 (09:20 -0700)
add feature for Invoice ser/de

fuzz/src/router.rs
lightning/src/ln/channel.rs
lightning/src/ln/channelmanager.rs
lightning/src/ln/functional_test_utils.rs
lightning/src/ln/functional_tests.rs
lightning/src/ln/onion_route_tests.rs
lightning/src/ln/payment_tests.rs
lightning/src/routing/gossip.rs
lightning/src/routing/router.rs
lightning/src/routing/scoring.rs
lightning/src/util/config.rs

index f3af5bdc1eec018d4e51245821861ceebe325fa3..cccc7c4f5a7f6cd21eeac3d10560f3d8f2fae298 100644 (file)
@@ -235,6 +235,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
                                                                next_outbound_htlc_limit_msat: capacity.saturating_mul(1000),
                                                                inbound_htlc_minimum_msat: None,
                                                                inbound_htlc_maximum_msat: None,
+                                                               config: None,
                                                        });
                                                }
                                                Some(&first_hops_vec[..])
index 9b47770f120310cce58cc01bc4ab63d1a679b590..b88a38b56504eb652c68fb445c29b5e4e4addb5b 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, LegacyChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits};
+use util::config::{UserConfig, ChannelConfig, LegacyChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits};
 use util::scid_utils::scid_from_parts;
 
 use io;
@@ -482,6 +482,16 @@ pub(crate) const CONCURRENT_INBOUND_HTLC_FEE_BUFFER: u32 = 2;
 /// transaction (not counting the value of the HTLCs themselves).
 pub(crate) const MIN_AFFORDABLE_HTLC_COUNT: usize = 4;
 
+/// When a [`Channel`] has its [`ChannelConfig`] updated, its existing one is stashed for up to this
+/// number of ticks to allow forwarding HTLCs by nodes that have yet to receive the new
+/// ChannelUpdate prompted by the config update. This value was determined as follows:
+///
+///   * The expected interval between ticks (1 minute).
+///   * The average convergence delay of updates across the network, i.e., ~300 seconds on average
+///      for a node to see an update as seen on `<https://arxiv.org/pdf/2205.12737.pdf>`.
+///   * `EXPIRE_PREV_CONFIG_TICKS` = convergence_delay / tick_interval
+pub(crate) const EXPIRE_PREV_CONFIG_TICKS: usize = 5;
+
 // TODO: We should refactor this to be an Inbound/OutboundChannel until initial setup handshaking
 // has been completed, and then turn into a Channel to get compiler-time enforcement of things like
 // calling channel_id() before we're set up or things like get_outbound_funding_signed on an
@@ -490,11 +500,13 @@ pub(crate) const MIN_AFFORDABLE_HTLC_COUNT: usize = 4;
 // Holder designates channel data owned for the benefice of the user client.
 // Counterparty designates channel data owned by the another channel participant entity.
 pub(super) struct Channel<Signer: Sign> {
-       #[cfg(any(test, feature = "_test_utils"))]
-       pub(crate) config: LegacyChannelConfig,
-       #[cfg(not(any(test, feature = "_test_utils")))]
        config: LegacyChannelConfig,
 
+       // Track the previous `ChannelConfig` so that we can continue forwarding HTLCs that were
+       // constructed using it. The second element in the tuple corresponds to the number of ticks that
+       // have elapsed since the update occurred.
+       prev_config: Option<(ChannelConfig, usize)>,
+
        inbound_handshake_limits_override: Option<ChannelHandshakeLimits>,
 
        user_id: u64,
@@ -937,6 +949,8 @@ impl<Signer: Sign> Channel<Signer> {
                                commit_upfront_shutdown_pubkey: config.channel_handshake_config.commit_upfront_shutdown_pubkey,
                        },
 
+                       prev_config: None,
+
                        inbound_handshake_limits_override: Some(config.channel_handshake_limits.clone()),
 
                        channel_id: keys_provider.get_secure_random_bytes(),
@@ -1264,6 +1278,8 @@ impl<Signer: Sign> Channel<Signer> {
                                commit_upfront_shutdown_pubkey: config.channel_handshake_config.commit_upfront_shutdown_pubkey,
                        },
 
+                       prev_config: None,
+
                        inbound_handshake_limits_override: None,
 
                        channel_id: msg.temporary_channel_id,
@@ -4491,6 +4507,84 @@ impl<Signer: Sign> Channel<Signer> {
                self.config.options.max_dust_htlc_exposure_msat
        }
 
+       /// Returns the previous [`ChannelConfig`] applied to this channel, if any.
+       pub fn prev_config(&self) -> Option<ChannelConfig> {
+               self.prev_config.map(|prev_config| prev_config.0)
+       }
+
+       /// Tracks the number of ticks elapsed since the previous [`ChannelConfig`] was updated. Once
+       /// [`EXPIRE_PREV_CONFIG_TICKS`] is reached, the previous config is considered expired and will
+       /// no longer be considered when forwarding HTLCs.
+       pub fn maybe_expire_prev_config(&mut self) {
+               if self.prev_config.is_none() {
+                       return;
+               }
+               let prev_config = self.prev_config.as_mut().unwrap();
+               prev_config.1 += 1;
+               if prev_config.1 == EXPIRE_PREV_CONFIG_TICKS {
+                       self.prev_config = None;
+               }
+       }
+
+       /// Returns the current [`ChannelConfig`] applied to the channel.
+       pub fn config(&self) -> ChannelConfig {
+               self.config.options
+       }
+
+       /// Updates the channel's config. A bool is returned indicating whether the config update
+       /// applied resulted in a new ChannelUpdate message.
+       pub fn update_config(&mut self, config: &ChannelConfig) -> bool {
+               let did_channel_update =
+                       self.config.options.forwarding_fee_proportional_millionths != config.forwarding_fee_proportional_millionths ||
+                       self.config.options.forwarding_fee_base_msat != config.forwarding_fee_base_msat ||
+                       self.config.options.cltv_expiry_delta != config.cltv_expiry_delta;
+               if did_channel_update {
+                       self.prev_config = Some((self.config.options, 0));
+                       // Update the counter, which backs the ChannelUpdate timestamp, to allow the relay
+                       // policy change to propagate throughout the network.
+                       self.update_time_counter += 1;
+               }
+               self.config.options = *config;
+               did_channel_update
+       }
+
+       fn internal_htlc_satisfies_config(
+               &self, htlc: &msgs::UpdateAddHTLC, amt_to_forward: u64, outgoing_cltv_value: u32, config: &ChannelConfig,
+       ) -> Result<(), (&'static str, u16)> {
+               let fee = amt_to_forward.checked_mul(config.forwarding_fee_proportional_millionths as u64)
+                       .and_then(|prop_fee| (prop_fee / 1000000).checked_add(config.forwarding_fee_base_msat as u64));
+               if fee.is_none() || htlc.amount_msat < fee.unwrap() ||
+                       (htlc.amount_msat - fee.unwrap()) < amt_to_forward {
+                       return Err((
+                               "Prior hop has deviated from specified fees parameters or origin node has obsolete ones",
+                               0x1000 | 12, // fee_insufficient
+                       ));
+               }
+               if (htlc.cltv_expiry as u64) < outgoing_cltv_value as u64 + config.cltv_expiry_delta as u64 {
+                       return Err((
+                               "Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta",
+                               0x1000 | 13, // incorrect_cltv_expiry
+                       ));
+               }
+               Ok(())
+       }
+
+       /// Determines whether the parameters of an incoming HTLC to be forwarded satisfy the channel's
+       /// [`ChannelConfig`]. This first looks at the channel's current [`ChannelConfig`], and if
+       /// unsuccessful, falls back to the previous one if one exists.
+       pub fn htlc_satisfies_config(
+               &self, htlc: &msgs::UpdateAddHTLC, amt_to_forward: u64, outgoing_cltv_value: u32,
+       ) -> Result<(), (&'static str, u16)> {
+               self.internal_htlc_satisfies_config(&htlc, amt_to_forward, outgoing_cltv_value, &self.config())
+                       .or_else(|err| {
+                               if let Some(prev_config) = self.prev_config() {
+                                       self.internal_htlc_satisfies_config(htlc, amt_to_forward, outgoing_cltv_value, &prev_config)
+                               } else {
+                                       Err(err)
+                               }
+                       })
+       }
+
        pub fn get_feerate(&self) -> u32 {
                self.feerate_per_kw
        }
@@ -6339,6 +6433,8 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel<Signer>
 
                        config: config.unwrap(),
 
+                       prev_config: None,
+
                        // Note that we don't care about serializing handshake limits as we only ever serialize
                        // channel data after the handshake has completed.
                        inbound_handshake_limits_override: None,
index cf593068947478d7060b7603b93f13400e31bda2..9f18671064b66cc2da4478fbe9fc977c4b8f230e 100644 (file)
@@ -51,7 +51,7 @@ use ln::onion_utils;
 use ln::msgs::{ChannelMessageHandler, DecodeError, LightningError, MAX_VALUE_MSAT, OptionalField};
 use ln::wire::Encode;
 use chain::keysinterface::{Sign, KeysInterface, KeysManager, InMemorySigner, Recipient};
-use util::config::UserConfig;
+use util::config::{UserConfig, ChannelConfig};
 use util::events::{EventHandler, EventsProvider, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
 use util::{byte_utils, events};
 use util::scid_utils::fake_scid;
@@ -870,7 +870,13 @@ pub(crate) const MAX_LOCAL_BREAKDOWN_TIMEOUT: u16 = 2 * 6 * 24 * 7;
 // the HTLC via a full update_fail_htlc/commitment_signed dance before we hit the
 // CLTV_CLAIM_BUFFER point (we static assert that it's at least 3 blocks more).
 pub const MIN_CLTV_EXPIRY_DELTA: u16 = 6*7;
-pub(super) const CLTV_FAR_FAR_AWAY: u32 = 6 * 24 * 7; //TODO?
+// This should be long enough to allow a payment path drawn across multiple routing hops with substantial
+// `cltv_expiry_delta`. Indeed, the length of those values is the reaction delay offered to a routing node
+// in case of HTLC on-chain settlement. While appearing less competitive, a node operator could decide to
+// scale them up to suit its security policy. At the network-level, we shouldn't constrain them too much,
+// while avoiding to introduce a DoS vector. Further, a low CTLV_FAR_FAR_AWAY could be a source of
+// routing failure for any HTLC sender picking up an LDK node among the first hops.
+pub(super) const CLTV_FAR_FAR_AWAY: u32 = 14 * 24 * 6;
 
 /// Minimum CLTV difference between the current block height and received inbound payments.
 /// Invoices generated for payment to us must set their `min_final_cltv_expiry` field to at least
@@ -1095,6 +1101,10 @@ pub struct ChannelDetails {
        pub inbound_htlc_minimum_msat: Option<u64>,
        /// The largest value HTLC (in msat) we currently will accept, for this channel.
        pub inbound_htlc_maximum_msat: Option<u64>,
+       /// Set of configurable parameters that affect channel operation.
+       ///
+       /// This field is only `None` for `ChannelDetails` objects serialized prior to LDK 0.0.109.
+       pub config: Option<ChannelConfig>,
 }
 
 impl ChannelDetails {
@@ -1759,7 +1769,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                                        is_usable: channel.is_live(),
                                        is_public: channel.should_announce(),
                                        inbound_htlc_minimum_msat: Some(channel.get_holder_htlc_minimum_msat()),
-                                       inbound_htlc_maximum_msat: channel.get_holder_htlc_maximum_msat()
+                                       inbound_htlc_maximum_msat: channel.get_holder_htlc_maximum_msat(),
+                                       config: Some(channel.config()),
                                });
                        }
                }
@@ -2225,7 +2236,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                                                },
                                                Some(id) => Some(id.clone()),
                                        };
-                                       let (chan_update_opt, forwardee_cltv_expiry_delta) = if let Some(forwarding_id) = forwarding_id_opt {
+                                       let chan_update_opt = if let Some(forwarding_id) = forwarding_id_opt {
                                                let chan = channel_state.as_mut().unwrap().by_id.get_mut(&forwarding_id).unwrap();
                                                if !chan.should_announce() && !self.default_configuration.accept_forwards_to_priv_channels {
                                                        // Note that the behavior here should be identical to the above block - we
@@ -2252,18 +2263,20 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                                                if *amt_to_forward < chan.get_counterparty_htlc_minimum_msat() { // amount_below_minimum
                                                        break Some(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11, chan_update_opt));
                                                }
-                                               let fee = amt_to_forward.checked_mul(chan.get_fee_proportional_millionths() as u64)
-                                                       .and_then(|prop_fee| { (prop_fee / 1000000)
-                                                       .checked_add(chan.get_outbound_forwarding_fee_base_msat() as u64) });
-                                               if fee.is_none() || msg.amount_msat < fee.unwrap() || (msg.amount_msat - fee.unwrap()) < *amt_to_forward { // fee_insufficient
-                                                       break Some(("Prior hop has deviated from specified fees parameters or origin node has obsolete ones", 0x1000 | 12, chan_update_opt));
+                                               if let Err((err, code)) = chan.htlc_satisfies_config(&msg, *amt_to_forward, *outgoing_cltv_value) {
+                                                       break Some((err, code, chan_update_opt));
                                                }
-                                               (chan_update_opt, chan.get_cltv_expiry_delta())
-                                       } else { (None, MIN_CLTV_EXPIRY_DELTA) };
+                                               chan_update_opt
+                                       } else {
+                                               if (msg.cltv_expiry as u64) < (*outgoing_cltv_value) as u64 + MIN_CLTV_EXPIRY_DELTA as u64 { // incorrect_cltv_expiry
+                                                       break Some((
+                                                               "Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta",
+                                                               0x1000 | 13, None,
+                                                       ));
+                                               }
+                                               None
+                                       };
 
-                                       if (msg.cltv_expiry as u64) < (*outgoing_cltv_value) as u64 + forwardee_cltv_expiry_delta as u64 { // incorrect_cltv_expiry
-                                               break Some(("Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta", 0x1000 | 13, chan_update_opt));
-                                       }
                                        let cur_height = self.best_block.read().unwrap().height() + 1;
                                        // Theoretically, channel counterparty shouldn't send us a HTLC expiring now,
                                        // but we want to be robust wrt to counterparty packet sanitization (see
@@ -2777,6 +2790,9 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
        /// Returns an [`APIError::APIMisuseError`] if the funding_transaction spent non-SegWit outputs
        /// or if no output was found which matches the parameters in [`Event::FundingGenerationReady`].
        ///
+       /// Returns [`APIError::APIMisuseError`] if the funding transaction is not final for propagation
+       /// across the p2p network.
+       ///
        /// Returns [`APIError::ChannelUnavailable`] if a funding transaction has already been provided
        /// for the channel or if the channel has been closed as indicated by [`Event::ChannelClosed`].
        ///
@@ -2792,6 +2808,11 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
        /// not currently support replacing a funding transaction on an existing channel. Instead,
        /// create a new channel with a conflicting funding transaction.
        ///
+       /// Note to keep the miner incentives aligned in moving the blockchain forward, we recommend
+       /// the wallet software generating the funding transaction to apply anti-fee sniping as
+       /// implemented by Bitcoin Core wallet. See <https://bitcoinops.org/en/topics/fee-sniping/>
+       /// for more details.
+       ///
        /// [`Event::FundingGenerationReady`]: crate::util::events::Event::FundingGenerationReady
        /// [`Event::ChannelClosed`]: crate::util::events::Event::ChannelClosed
        pub fn funding_transaction_generated(&self, temporary_channel_id: &[u8; 32], counterparty_node_id: &PublicKey, funding_transaction: Transaction) -> Result<(), APIError> {
@@ -2804,6 +2825,18 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                                });
                        }
                }
+               {
+                       let height = self.best_block.read().unwrap().height();
+                       // Transactions are evaluated as final by network mempools at the next block. However, the modules
+                       // constituting our Lightning node might not have perfect sync about their blockchain views. Thus, if
+                       // the wallet module is in advance on the LDK view, allow one more block of headroom.
+                       // TODO: updated if/when https://github.com/rust-bitcoin/rust-bitcoin/pull/994 landed and rust-bitcoin bumped.
+                       if !funding_transaction.input.iter().all(|input| input.sequence == 0xffffffff) && funding_transaction.lock_time < 500_000_000 && funding_transaction.lock_time > height + 2 {
+                               return Err(APIError::APIMisuseError {
+                                       err: "Funding transaction absolute timelock is non-final".to_owned()
+                               });
+                       }
+               }
                self.funding_transaction_generated_intern(temporary_channel_id, counterparty_node_id, funding_transaction, |chan, tx| {
                        let mut output_index = None;
                        let expected_spk = chan.get_funding_redeemscript().to_v0_p2wsh();
@@ -2914,6 +2947,73 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                }
        }
 
+       /// Atomically updates the [`ChannelConfig`] for the given channels.
+       ///
+       /// Once the updates are applied, each eligible channel (advertised with a known short channel
+       /// ID and a change in [`forwarding_fee_proportional_millionths`], [`forwarding_fee_base_msat`],
+       /// or [`cltv_expiry_delta`]) has a [`BroadcastChannelUpdate`] event message generated
+       /// containing the new [`ChannelUpdate`] message which should be broadcast to the network.
+       ///
+       /// Returns [`ChannelUnavailable`] when a channel is not found or an incorrect
+       /// `counterparty_node_id` is provided.
+       ///
+       /// Returns [`APIMisuseError`] when a [`cltv_expiry_delta`] update is to be applied with a value
+       /// below [`MIN_CLTV_EXPIRY_DELTA`].
+       ///
+       /// If an error is returned, none of the updates should be considered applied.
+       ///
+       /// [`forwarding_fee_proportional_millionths`]: ChannelConfig::forwarding_fee_proportional_millionths
+       /// [`forwarding_fee_base_msat`]: ChannelConfig::forwarding_fee_base_msat
+       /// [`cltv_expiry_delta`]: ChannelConfig::cltv_expiry_delta
+       /// [`BroadcastChannelUpdate`]: events::MessageSendEvent::BroadcastChannelUpdate
+       /// [`ChannelUpdate`]: msgs::ChannelUpdate
+       /// [`ChannelUnavailable`]: APIError::ChannelUnavailable
+       /// [`APIMisuseError`]: APIError::APIMisuseError
+       pub fn update_channel_config(
+               &self, counterparty_node_id: &PublicKey, channel_ids: &[[u8; 32]], config: &ChannelConfig,
+       ) -> Result<(), APIError> {
+               if config.cltv_expiry_delta < MIN_CLTV_EXPIRY_DELTA {
+                       return Err(APIError::APIMisuseError {
+                               err: format!("The chosen CLTV expiry delta is below the minimum of {}", MIN_CLTV_EXPIRY_DELTA),
+                       });
+               }
+
+               let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(
+                       &self.total_consistency_lock, &self.persistence_notifier,
+               );
+               {
+                       let mut channel_state_lock = self.channel_state.lock().unwrap();
+                       let channel_state = &mut *channel_state_lock;
+                       for channel_id in channel_ids {
+                               let channel_counterparty_node_id = channel_state.by_id.get(channel_id)
+                                       .ok_or(APIError::ChannelUnavailable {
+                                               err: format!("Channel with ID {} was not found", log_bytes!(*channel_id)),
+                                       })?
+                                       .get_counterparty_node_id();
+                               if channel_counterparty_node_id != *counterparty_node_id {
+                                       return Err(APIError::APIMisuseError {
+                                               err: "counterparty node id mismatch".to_owned(),
+                                       });
+                               }
+                       }
+                       for channel_id in channel_ids {
+                               let channel = channel_state.by_id.get_mut(channel_id).unwrap();
+                               if !channel.update_config(config) {
+                                       continue;
+                               }
+                               if let Ok(msg) = self.get_channel_update_for_broadcast(channel) {
+                                       channel_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate { msg });
+                               } else if let Ok(msg) = self.get_channel_update_for_unicast(channel) {
+                                       channel_state.pending_msg_events.push(events::MessageSendEvent::SendChannelUpdate {
+                                               node_id: channel.get_counterparty_node_id(),
+                                               msg,
+                                       });
+                               }
+                       }
+               }
+               Ok(())
+       }
+
        /// Processes HTLCs which are pending waiting on random forward delay.
        ///
        /// Should only really ever be called in response to a PendingHTLCsForwardable event.
@@ -3439,6 +3539,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
        ///  * Broadcasting `ChannelUpdate` messages if we've been disconnected from our peer for more
        ///    than a minute, informing the network that they should no longer attempt to route over
        ///    the channel.
+       ///  * Expiring a channel's previous `ChannelConfig` if necessary to only allow forwarding HTLCs
+       ///    with the current `ChannelConfig`.
        ///
        /// Note that this may cause reentrancy through `chain::Watch::update_channel` calls or feerate
        /// estimate fetches.
@@ -3497,6 +3599,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
                                                _ => {},
                                        }
 
+                                       chan.maybe_expire_prev_config();
+
                                        true
                                });
 
@@ -6089,6 +6193,7 @@ impl_writeable_tlv_based!(ChannelDetails, {
        (4, counterparty, required),
        (5, outbound_scid_alias, option),
        (6, funding_txo, option),
+       (7, config, option),
        (8, short_channel_id, option),
        (10, channel_value_satoshis, required),
        (12, unspendable_punishment_reserve, option),
index 38c05998e6a2769db297179d4f0ce0dc76589b9e..fe78395e2f97e685698968b0053d44dbc8a772ce 100644 (file)
@@ -1689,9 +1689,16 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>,
                        ($node: expr, $prev_node: expr, $next_node: expr, $new_msgs: expr) => {
                                {
                                        $node.node.handle_update_fulfill_htlc(&$prev_node.node.get_our_node_id(), &next_msgs.as_ref().unwrap().0);
-                                       let fee = $node.node.channel_state.lock().unwrap()
-                                               .by_id.get(&next_msgs.as_ref().unwrap().0.channel_id).unwrap()
-                                               .config.options.forwarding_fee_base_msat;
+                                       let fee = {
+                                               let channel_state = $node.node.channel_state.lock().unwrap();
+                                               let channel = channel_state
+                                                       .by_id.get(&next_msgs.as_ref().unwrap().0.channel_id).unwrap();
+                                               if let Some(prev_config) = channel.prev_config() {
+                                                       prev_config.forwarding_fee_base_msat
+                                               } else {
+                                                       channel.config().forwarding_fee_base_msat
+                                               }
+                                       };
                                        expect_payment_forwarded!($node, $next_node, $prev_node, Some(fee as u64), false, false);
                                        expected_total_fee_msat += fee as u64;
                                        check_added_monitors!($node, 1);
index 76487ddcfd16967369bfde624ac1df4a4b77bb83..f99a5af6e22bcbd9d56dcad26611efc43104b83e 100644 (file)
@@ -41,6 +41,8 @@ use bitcoin::blockdata::script::Builder;
 use bitcoin::blockdata::opcodes;
 use bitcoin::blockdata::constants::genesis_block;
 use bitcoin::network::constants::Network;
+use bitcoin::{Transaction, TxIn, TxOut, Witness};
+use bitcoin::OutPoint as BitcoinOutPoint;
 
 use bitcoin::secp256k1::Secp256k1;
 use bitcoin::secp256k1::{PublicKey,SecretKey};
@@ -10329,3 +10331,45 @@ fn test_max_dust_htlc_exposure() {
        do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, false);
        do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, true);
 }
+
+#[test]
+fn test_non_final_funding_tx() {
+       let chanmon_cfgs = create_chanmon_cfgs(2);
+       let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+       let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+       let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+       let temp_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None).unwrap();
+       let open_channel_message = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+       nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), InitFeatures::known(), &open_channel_message);
+       let accept_channel_message = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
+       nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), InitFeatures::known(), &accept_channel_message);
+
+       let best_height = nodes[0].node.best_block.read().unwrap().height();
+
+       let chan_id = *nodes[0].network_chan_count.borrow();
+       let events = nodes[0].node.get_and_clear_pending_events();
+       let input = TxIn { previous_output: BitcoinOutPoint::null(), script_sig: bitcoin::Script::new(), sequence: 0x1, witness: Witness::from_vec(vec!(vec!(1))) };
+       assert_eq!(events.len(), 1);
+       let mut tx = match events[0] {
+               Event::FundingGenerationReady { ref channel_value_satoshis, ref output_script, .. } => {
+                       // Timelock the transaction _beyond_ the best client height + 2.
+                       Transaction { version: chan_id as i32, lock_time: best_height + 3, input: vec![input], output: vec![TxOut {
+                               value: *channel_value_satoshis, script_pubkey: output_script.clone(),
+                       }]}
+               },
+               _ => panic!("Unexpected event"),
+       };
+       // Transaction should fail as it's evaluated as non-final for propagation.
+       match nodes[0].node.funding_transaction_generated(&temp_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()) {
+               Err(APIError::APIMisuseError { err }) => {
+                       assert_eq!(format!("Funding transaction absolute timelock is non-final"), err);
+               },
+               _ => panic!()
+       }
+
+       // However, transaction should be accepted if it's in a +2 headroom from best block.
+       tx.lock_time -= 1;
+       assert!(nodes[0].node.funding_transaction_generated(&temp_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).is_ok());
+       get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
+}
index 1e340d4a15e0f06a998c541d949b808e5f5dc672..c66f45a44923b596e1e31ac0a08d2ee531826128 100644 (file)
 //! These tests work by standing up full nodes and route payments across the network, checking the
 //! returned errors decode to the correct thing.
 
-use chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS};
+use chain::channelmonitor::{ChannelMonitor, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS};
 use chain::keysinterface::{KeysInterface, Recipient};
 use ln::{PaymentHash, PaymentSecret};
-use ln::channelmanager::{HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting};
+use ln::channel::EXPIRE_PREV_CONFIG_TICKS;
+use ln::channelmanager::{ChannelManager, ChannelManagerReadArgs, HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting};
 use ln::onion_utils;
 use routing::gossip::{NetworkUpdate, RoutingFees, NodeId};
 use routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
@@ -23,9 +24,10 @@ use ln::msgs;
 use ln::msgs::{ChannelMessageHandler, ChannelUpdate, OptionalField};
 use ln::wire::Encode;
 use util::events::{Event, MessageSendEvent, MessageSendEventsProvider};
-use util::ser::{Writeable, Writer};
+use util::ser::{ReadableArgs, Writeable, Writer};
 use util::{byte_utils, test_utils};
-use util::config::UserConfig;
+use util::config::{UserConfig, ChannelConfig};
+use util::errors::APIError;
 
 use bitcoin::hash_types::BlockHash;
 
@@ -506,8 +508,6 @@ fn test_onion_failure() {
        let preimage = send_along_route(&nodes[0], bogus_route, &[&nodes[1], &nodes[2]], amt_to_forward+1).0;
        claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], preimage);
 
-       //TODO: with new config API, we will be able to generate both valid and
-       //invalid channel_update cases.
        let short_channel_id = channels[0].0.contents.short_channel_id;
        run_onion_failure_test("fee_insufficient", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| {
                msg.amount_msat -= 1;
@@ -594,6 +594,202 @@ fn test_onion_failure() {
        }, true, Some(23), None, None);
 }
 
+fn do_test_onion_failure_stale_channel_update(announced_channel: bool) {
+       // Create a network of three nodes and two channels connecting them. We'll be updating the
+       // HTLC relay policy of the second channel, causing forwarding failures at the first hop.
+       let mut config = UserConfig::default();
+       config.channel_handshake_config.announced_channel = announced_channel;
+       config.channel_handshake_limits.force_announced_channel_preference = false;
+       config.accept_forwards_to_priv_channels = !announced_channel;
+       let chanmon_cfgs = create_chanmon_cfgs(3);
+       let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+       let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(config), None]);
+       let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+       let other_channel = create_chan_between_nodes(
+               &nodes[0], &nodes[1], InitFeatures::known(), InitFeatures::known(),
+       );
+       let channel_to_update = if announced_channel {
+               let channel = create_announced_chan_between_nodes(
+                       &nodes, 1, 2, InitFeatures::known(), InitFeatures::known(),
+               );
+               (channel.2, channel.0.contents.short_channel_id)
+       } else {
+               let channel = create_unannounced_chan_between_nodes_with_value(
+                       &nodes, 1, 2, 100000, 10001, InitFeatures::known(), InitFeatures::known(),
+               );
+               (channel.0.channel_id, channel.0.short_channel_id_alias.unwrap())
+       };
+       let channel_to_update_counterparty = &nodes[2].node.get_our_node_id();
+
+       let default_config = ChannelConfig::default();
+
+       // A test payment should succeed as the ChannelConfig has not been changed yet.
+       const PAYMENT_AMT: u64 = 40000;
+       let (route, payment_hash, payment_preimage, payment_secret) = if announced_channel {
+               get_route_and_payment_hash!(nodes[0], nodes[2], PAYMENT_AMT)
+       } else {
+               let hop_hints = vec![RouteHint(vec![RouteHintHop {
+                       src_node_id: nodes[1].node.get_our_node_id(),
+                       short_channel_id: channel_to_update.1,
+                       fees: RoutingFees {
+                               base_msat: default_config.forwarding_fee_base_msat,
+                               proportional_millionths: default_config.forwarding_fee_proportional_millionths,
+                       },
+                       cltv_expiry_delta: default_config.cltv_expiry_delta,
+                       htlc_maximum_msat: None,
+                       htlc_minimum_msat: None,
+               }])];
+               let payment_params = PaymentParameters::from_node_id(*channel_to_update_counterparty)
+                       .with_features(InvoiceFeatures::known())
+                       .with_route_hints(hop_hints);
+               get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, PAYMENT_AMT, TEST_FINAL_CLTV)
+       };
+       send_along_route_with_secret(&nodes[0], route.clone(), &[&[&nodes[1], &nodes[2]]], PAYMENT_AMT,
+               payment_hash, payment_secret);
+       claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
+
+       // Closure to force expiry of a channel's previous config.
+       let expire_prev_config = || {
+               for _ in 0..EXPIRE_PREV_CONFIG_TICKS {
+                       nodes[1].node.timer_tick_occurred();
+               }
+       };
+
+       // Closure to update and retrieve the latest ChannelUpdate.
+       let update_and_get_channel_update = |config: &ChannelConfig, expect_new_update: bool,
+               prev_update: Option<&msgs::ChannelUpdate>, should_expire_prev_config: bool| -> Option<msgs::ChannelUpdate> {
+               nodes[1].node.update_channel_config(
+                       channel_to_update_counterparty, &[channel_to_update.0], config,
+               ).unwrap();
+               let events = nodes[1].node.get_and_clear_pending_msg_events();
+               assert_eq!(events.len(), expect_new_update as usize);
+               if !expect_new_update {
+                       return None;
+               }
+               let new_update = match &events[0] {
+                       MessageSendEvent::BroadcastChannelUpdate { msg } => {
+                               assert!(announced_channel);
+                               msg.clone()
+                       },
+                       MessageSendEvent::SendChannelUpdate { node_id, msg } => {
+                               assert_eq!(node_id, channel_to_update_counterparty);
+                               assert!(!announced_channel);
+                               msg.clone()
+                       },
+                       _ => panic!("expected Broadcast/SendChannelUpdate event"),
+               };
+               if prev_update.is_some() {
+                       assert!(new_update.contents.timestamp > prev_update.unwrap().contents.timestamp)
+               }
+               if should_expire_prev_config {
+                       expire_prev_config();
+               }
+               Some(new_update)
+       };
+
+       // We'll be attempting to route payments using the default ChannelUpdate for channels. This will
+       // lead to onion failures at the first hop once we update the ChannelConfig for the
+       // second hop.
+       let expect_onion_failure = |name: &str, error_code: u16, channel_update: &msgs::ChannelUpdate| {
+               let short_channel_id = channel_to_update.1;
+               let network_update = NetworkUpdate::ChannelUpdateMessage { msg: channel_update.clone() };
+               run_onion_failure_test(
+                       name, 0, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || {}, true,
+                       Some(error_code), Some(network_update), Some(short_channel_id),
+               );
+       };
+
+       // Updates to cltv_expiry_delta below MIN_CLTV_EXPIRY_DELTA should fail with APIMisuseError.
+       let mut invalid_config = default_config.clone();
+       invalid_config.cltv_expiry_delta = 0;
+       match nodes[1].node.update_channel_config(
+               channel_to_update_counterparty, &[channel_to_update.0], &invalid_config,
+       ) {
+               Err(APIError::APIMisuseError{ .. }) => {},
+               _ => panic!("unexpected result applying invalid cltv_expiry_delta"),
+       }
+
+       // Increase the base fee which should trigger a new ChannelUpdate.
+       let mut config = nodes[1].node.list_usable_channels().iter()
+               .find(|channel| channel.channel_id == channel_to_update.0).unwrap()
+               .config.unwrap();
+       config.forwarding_fee_base_msat = u32::max_value();
+       let msg = update_and_get_channel_update(&config, true, None, false).unwrap();
+
+       // The old policy should still be in effect until a new block is connected.
+       send_along_route_with_secret(&nodes[0], route.clone(), &[&[&nodes[1], &nodes[2]]], PAYMENT_AMT,
+               payment_hash, payment_secret);
+       claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
+
+       // Connect a block, which should expire the previous config, leading to a failure when
+       // forwarding the HTLC.
+       expire_prev_config();
+       expect_onion_failure("fee_insufficient", UPDATE|12, &msg);
+
+       // Redundant updates should not trigger a new ChannelUpdate.
+       assert!(update_and_get_channel_update(&config, false, None, false).is_none());
+
+       // Similarly, updates that do not have an affect on ChannelUpdate should not trigger a new one.
+       config.force_close_avoidance_max_fee_satoshis *= 2;
+       assert!(update_and_get_channel_update(&config, false, None, false).is_none());
+
+       // Reset the base fee to the default and increase the proportional fee which should trigger a
+       // new ChannelUpdate.
+       config.forwarding_fee_base_msat = default_config.forwarding_fee_base_msat;
+       config.cltv_expiry_delta = u16::max_value();
+       let msg = update_and_get_channel_update(&config, true, Some(&msg), true).unwrap();
+       expect_onion_failure("incorrect_cltv_expiry", UPDATE|13, &msg);
+
+       // Reset the proportional fee and increase the CLTV expiry delta which should trigger a new
+       // ChannelUpdate.
+       config.cltv_expiry_delta = default_config.cltv_expiry_delta;
+       config.forwarding_fee_proportional_millionths = u32::max_value();
+       let msg = update_and_get_channel_update(&config, true, Some(&msg), true).unwrap();
+       expect_onion_failure("fee_insufficient", UPDATE|12, &msg);
+
+       // To test persistence of the updated config, we'll re-initialize the ChannelManager.
+       let config_after_restart = {
+               let persister = test_utils::TestPersister::new();
+               let chain_monitor = test_utils::TestChainMonitor::new(
+                       Some(nodes[1].chain_source), nodes[1].tx_broadcaster.clone(), nodes[1].logger,
+                       node_cfgs[1].fee_estimator, &persister, nodes[1].keys_manager,
+               );
+
+               let mut chanmon_1 = <(_, ChannelMonitor<_>)>::read(
+                       &mut &get_monitor!(nodes[1], other_channel.3).encode()[..], nodes[1].keys_manager,
+               ).unwrap().1;
+               let mut chanmon_2 = <(_, ChannelMonitor<_>)>::read(
+                       &mut &get_monitor!(nodes[1], channel_to_update.0).encode()[..], nodes[1].keys_manager,
+               ).unwrap().1;
+               let mut channel_monitors = HashMap::new();
+               channel_monitors.insert(chanmon_1.get_funding_txo().0, &mut chanmon_1);
+               channel_monitors.insert(chanmon_2.get_funding_txo().0, &mut chanmon_2);
+
+               let chanmgr = <(_, ChannelManager<_, _, _, _, _, _>)>::read(
+                       &mut &nodes[1].node.encode()[..], ChannelManagerReadArgs {
+                               default_config: *nodes[1].node.get_current_default_configuration(),
+                               keys_manager: nodes[1].keys_manager,
+                               fee_estimator: node_cfgs[1].fee_estimator,
+                               chain_monitor: &chain_monitor,
+                               tx_broadcaster: nodes[1].tx_broadcaster.clone(),
+                               logger: nodes[1].logger,
+                               channel_monitors: channel_monitors,
+                       },
+               ).unwrap().1;
+               chanmgr.list_channels().iter()
+                       .find(|channel| channel.channel_id == channel_to_update.0).unwrap()
+                       .config.unwrap()
+       };
+       assert_eq!(config, config_after_restart);
+}
+
+#[test]
+fn test_onion_failure_stale_channel_update() {
+       do_test_onion_failure_stale_channel_update(false);
+       do_test_onion_failure_stale_channel_update(true);
+}
+
 #[test]
 fn test_default_to_onion_payload_tlv_format() {
        // Tests that we default to creating tlv format onion payloads when no `NodeAnnouncementInfo`
index 935cb8f9ba46433d146176a4e185825a63763d21..37ca25babe9eb48e48a443a143633d5996c467a1 100644 (file)
@@ -15,6 +15,7 @@ use chain::{ChannelMonitorUpdateErr, Confirm, Listen, Watch};
 use chain::channelmonitor::{ANTI_REORG_DELAY, ChannelMonitor, LATENCY_GRACE_PERIOD_BLOCKS};
 use chain::transaction::OutPoint;
 use chain::keysinterface::KeysInterface;
+use ln::channel::EXPIRE_PREV_CONFIG_TICKS;
 use ln::channelmanager::{BREAKDOWN_TIMEOUT, ChannelManager, ChannelManagerReadArgs, MPP_TIMEOUT_TICKS, PaymentId, PaymentSendFailure};
 use ln::features::{InitFeatures, InvoiceFeatures};
 use ln::msgs;
@@ -531,9 +532,19 @@ fn do_retry_with_no_persist(confirm_before_reload: bool) {
        // Update the fee on the middle hop to ensure PaymentSent events have the correct (retried) fee
        // and not the original fee. We also update node[1]'s relevant config as
        // do_claim_payment_along_route expects us to never overpay.
-       nodes[1].node.channel_state.lock().unwrap().by_id.get_mut(&chan_id_2).unwrap()
-               .config.options.forwarding_fee_base_msat += 100_000;
-       new_route.paths[0][0].fee_msat += 100_000;
+       {
+               let mut channel_state = nodes[1].node.channel_state.lock().unwrap();
+               let mut channel = channel_state.by_id.get_mut(&chan_id_2).unwrap();
+               let mut new_config = channel.config();
+               new_config.forwarding_fee_base_msat += 100_000;
+               channel.update_config(&new_config);
+               new_route.paths[0][0].fee_msat += 100_000;
+       }
+
+       // Force expiration of the channel's previous config.
+       for _ in 0..EXPIRE_PREV_CONFIG_TICKS {
+               nodes[1].node.timer_tick_occurred();
+       }
 
        assert!(nodes[0].node.retry_payment(&new_route, payment_id_1).is_err()); // Shouldn't be allowed to retry a fulfilled payment
        nodes[0].node.retry_payment(&new_route, payment_id).unwrap();
index 0699333ec2335821c7e459279b11c6e970c319ce..736d2f0580002db9fed2957e99c6c22e4994b8ed 100644 (file)
@@ -904,7 +904,7 @@ pub struct NodeAnnouncementInfo {
        /// Moniker assigned to the node.
        /// May be invalid or malicious (eg control chars),
        /// should not be exposed to the user.
-       pub alias: [u8; 32],
+       pub alias: NodeAlias,
        /// Internet-level addresses via which one can connect to the node
        pub addresses: Vec<NetAddress>,
        /// An initial announcement of the node
@@ -923,6 +923,51 @@ impl_writeable_tlv_based!(NodeAnnouncementInfo, {
        (10, addresses, vec_type),
 });
 
+/// A user-defined name for a node, which may be used when displaying the node in a graph.
+///
+/// Since node aliases are provided by third parties, they are a potential avenue for injection
+/// attacks. Care must be taken when processing.
+#[derive(Clone, Debug, PartialEq)]
+pub struct NodeAlias(pub [u8; 32]);
+
+impl fmt::Display for NodeAlias {
+       fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+               let control_symbol = core::char::REPLACEMENT_CHARACTER;
+               let first_null = self.0.iter().position(|b| *b == 0).unwrap_or(self.0.len());
+               let bytes = self.0.split_at(first_null).0;
+               match core::str::from_utf8(bytes) {
+                       Ok(alias) => {
+                               for c in alias.chars() {
+                                       let mut bytes = [0u8; 4];
+                                       let c = if !c.is_control() { c } else { control_symbol };
+                                       f.write_str(c.encode_utf8(&mut bytes))?;
+                               }
+                       },
+                       Err(_) => {
+                               for c in bytes.iter().map(|b| *b as char) {
+                                       // Display printable ASCII characters
+                                       let mut bytes = [0u8; 4];
+                                       let c = if c >= '\x20' && c <= '\x7e' { c } else { control_symbol };
+                                       f.write_str(c.encode_utf8(&mut bytes))?;
+                               }
+                       },
+               };
+               Ok(())
+       }
+}
+
+impl Writeable for NodeAlias {
+       fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+               self.0.write(w)
+       }
+}
+
+impl Readable for NodeAlias {
+       fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
+               Ok(NodeAlias(Readable::read(r)?))
+       }
+}
+
 #[derive(Clone, Debug, PartialEq)]
 /// Details about a node in the network, known from the network announcement.
 pub struct NodeInfo {
@@ -1126,7 +1171,7 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
                                        features: msg.features.clone(),
                                        last_update: msg.timestamp,
                                        rgb: msg.rgb,
-                                       alias: msg.alias,
+                                       alias: NodeAlias(msg.alias),
                                        addresses: msg.addresses.clone(),
                                        announcement_message: if should_relay { full_msg.cloned() } else { None },
                                });
@@ -1627,7 +1672,7 @@ mod tests {
        use chain;
        use ln::PaymentHash;
        use ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
-       use routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate, MAX_EXCESS_BYTES_FOR_RELAY};
+       use routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate, NodeAlias, MAX_EXCESS_BYTES_FOR_RELAY};
        use ln::msgs::{Init, OptionalField, RoutingMessageHandler, UnsignedNodeAnnouncement, NodeAnnouncement,
                UnsignedChannelAnnouncement, ChannelAnnouncement, UnsignedChannelUpdate, ChannelUpdate,
                ReplyChannelRange, QueryChannelRange, QueryShortChannelIds, MAX_VALUE_MSAT};
@@ -2730,6 +2775,29 @@ mod tests {
                });
                assert!(result.is_err());
        }
+
+       #[test]
+       fn displays_node_alias() {
+               let format_str_alias = |alias: &str| {
+                       let mut bytes = [0u8; 32];
+                       bytes[..alias.as_bytes().len()].copy_from_slice(alias.as_bytes());
+                       format!("{}", NodeAlias(bytes))
+               };
+
+               assert_eq!(format_str_alias("I\u{1F496}LDK! \u{26A1}"), "I\u{1F496}LDK! \u{26A1}");
+               assert_eq!(format_str_alias("I\u{1F496}LDK!\0\u{26A1}"), "I\u{1F496}LDK!");
+               assert_eq!(format_str_alias("I\u{1F496}LDK!\t\u{26A1}"), "I\u{1F496}LDK!\u{FFFD}\u{26A1}");
+
+               let format_bytes_alias = |alias: &[u8]| {
+                       let mut bytes = [0u8; 32];
+                       bytes[..alias.len()].copy_from_slice(alias);
+                       format!("{}", NodeAlias(bytes))
+               };
+
+               assert_eq!(format_bytes_alias(b"\xFFI <heart> LDK!"), "\u{FFFD}I <heart> LDK!");
+               assert_eq!(format_bytes_alias(b"\xFFI <heart>\0LDK!"), "\u{FFFD}I <heart>");
+               assert_eq!(format_bytes_alias(b"\xFFI <heart>\tLDK!"), "\u{FFFD}I <heart>\u{FFFD}LDK!");
+       }
 }
 
 #[cfg(all(test, feature = "_bench_unstable"))]
index 2c25b277f264198a931476ab97ea7e0fac6e7452..634282d6c4b1f202fd0867312fc3dd83fc9b57fb 100644 (file)
@@ -1937,6 +1937,7 @@ mod tests {
                        is_usable: true, is_public: true,
                        inbound_htlc_minimum_msat: None,
                        inbound_htlc_maximum_msat: None,
+                       config: None,
                }
        }
 
@@ -5806,6 +5807,7 @@ mod benches {
                        is_public: true,
                        inbound_htlc_minimum_msat: None,
                        inbound_htlc_maximum_msat: None,
+                       config: None,
                }
        }
 
index bfcefc96f5b21b56a3d205d501948a7c9651a686..f7fe5863838f2aefbab77e173fe7fe4ae72c4c0d 100644 (file)
@@ -434,6 +434,23 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ProbabilisticScorerU
                        }
                }
        }
+
+       /// Query the estimated minimum and maximum liquidity available for sending a payment over the
+       /// channel with `scid` towards the given `target` node.
+       pub fn estimated_channel_liquidity_range(&self, scid: u64, target: &NodeId) -> Option<(u64, u64)> {
+               let graph = self.network_graph.read_only();
+
+               if let Some(chan) = graph.channels().get(&scid) {
+                       if let Some(liq) = self.channel_liquidities.get(&scid) {
+                               if let Some((directed_info, source)) = chan.as_directed_to(target) {
+                                       let amt = directed_info.effective_capacity().as_msat();
+                                       let dir_liq = liq.as_directed(source, target, amt, self.params.liquidity_offset_half_life);
+                                       return Some((dir_liq.min_liquidity_msat(), dir_liq.max_liquidity_msat()));
+                               }
+                       }
+               }
+               None
+       }
 }
 
 impl ProbabilisticScoringParameters {
index a76f71621011d80b55c02fe898e769055427a808..be7accc18dacfc837d89bddcec937e0e9eb4b349 100644 (file)
@@ -249,7 +249,7 @@ impl Default for ChannelHandshakeLimits {
 
 /// Options which apply on a per-channel basis and may change at runtime or based on negotiation
 /// with our counterparty.
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, PartialEq)]
 pub struct ChannelConfig {
        /// Amount (in millionths of a satoshi) charged per satoshi for payments forwarded outbound
        /// over the channel.
@@ -345,6 +345,17 @@ impl Default for ChannelConfig {
        }
 }
 
+impl_writeable_tlv_based!(ChannelConfig, {
+       (0, forwarding_fee_proportional_millionths, required),
+       (2, forwarding_fee_base_msat, required),
+       (4, cltv_expiry_delta, required),
+       (6, max_dust_htlc_exposure_msat, required),
+       // ChannelConfig serialized this field with a required type of 8 prior to the introduction of
+       // LegacyChannelConfig. To make sure that serialization is not compatible with this one, we use
+       // the next required type of 10, which if seen by the old serialization will always fail.
+       (10, force_close_avoidance_max_fee_satoshis, required),
+});
+
 /// Legacy version of [`ChannelConfig`] that stored the static
 /// [`ChannelHandshakeConfig::announced_channel`] and
 /// [`ChannelHandshakeConfig::commit_upfront_shutdown_pubkey`] fields.