Merge pull request #3107 from mhrheaume/mhr/closure_reason_abandoned
[rust-lightning] / lightning / src / ln / channelmanager.rs
index c165e81fcab8e924b7b35f7c7dc396690f09d2ae..f5e83dcc38252868807e1f0a5165769d2c6c7592 100644 (file)
@@ -636,7 +636,7 @@ impl MsgHandleErrInternal {
                                        err: msg,
                                        action: msgs::ErrorAction::IgnoreError,
                                },
-                               ChannelError::Close(msg) => LightningError {
+                               ChannelError::Close((msg, _reason)) => LightningError {
                                        err: msg.clone(),
                                        action: msgs::ErrorAction::SendErrorMessage {
                                                msg: msgs::ErrorMessage {
@@ -962,6 +962,11 @@ pub(super) struct InboundChannelRequest {
 /// accepted. An unaccepted channel that exceeds this limit will be abandoned.
 const UNACCEPTED_INBOUND_CHANNEL_AGE_LIMIT_TICKS: i32 = 2;
 
+/// The number of blocks of historical feerate estimates we keep around and consider when deciding
+/// to force-close a channel for having too-low fees. Also the number of blocks we have to see
+/// after startup before we consider force-closing channels for having too-low fees.
+pub(super) const FEERATE_TRACKING_BLOCKS: usize = 144;
+
 /// Stores a PaymentSecret and any other data we may need to validate an inbound payment is
 /// actually ours and not some duplicate HTLC sent to us by a node along the route.
 ///
@@ -1554,8 +1559,9 @@ where
 /// #
 /// # fn example<T: AChannelManager>(channel_manager: T) -> Result<(), Bolt12SemanticError> {
 /// # let channel_manager = channel_manager.get_cm();
+/// # let absolute_expiry = None;
 /// let offer = channel_manager
-///     .create_offer_builder()?
+///     .create_offer_builder(absolute_expiry)?
 /// # ;
 /// # // Needed for compiling for c_bindings
 /// # let builder: lightning::offers::offer::OfferBuilder<_, _> = offer.into();
@@ -2097,6 +2103,21 @@ where
        /// Tracks the message events that are to be broadcasted when we are connected to some peer.
        pending_broadcast_messages: Mutex<Vec<MessageSendEvent>>,
 
+       /// We only want to force-close our channels on peers based on stale feerates when we're
+       /// confident the feerate on the channel is *really* stale, not just became stale recently.
+       /// Thus, we store the fee estimates we had as of the last [`FEERATE_TRACKING_BLOCKS`] blocks
+       /// (after startup completed) here, and only force-close when channels have a lower feerate
+       /// than we predicted any time in the last [`FEERATE_TRACKING_BLOCKS`] blocks.
+       ///
+       /// We only keep this in memory as we assume any feerates we receive immediately after startup
+       /// may be bunk (as they often are if Bitcoin Core crashes) and want to delay taking any
+       /// actions for a day anyway.
+       ///
+       /// The first element in the pair is the
+       /// [`ConfirmationTarget::MinAllowedAnchorChannelRemoteFee`] estimate, the second the
+       /// [`ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee`] estimate.
+       last_days_feerates: Mutex<VecDeque<(u32, u32)>>,
+
        entropy_source: ES,
        node_signer: NS,
        signer_provider: SP,
@@ -2287,6 +2308,19 @@ const MAX_UNFUNDED_CHANNEL_PEERS: usize = 50;
 /// many peers we reject new (inbound) connections.
 const MAX_NO_CHANNEL_PEERS: usize = 250;
 
+/// The maximum expiration from the current time where an [`Offer`] or [`Refund`] is considered
+/// short-lived, while anything with a greater expiration is considered long-lived.
+///
+/// Using [`ChannelManager::create_offer_builder`] or [`ChannelManager::create_refund_builder`],
+/// will included a [`BlindedPath`] created using:
+/// - [`MessageRouter::create_compact_blinded_paths`] when short-lived, and
+/// - [`MessageRouter::create_blinded_paths`] when long-lived.
+///
+/// Using compact [`BlindedPath`]s may provide better privacy as the [`MessageRouter`] could select
+/// more hops. However, since they use short channel ids instead of pubkeys, they are more likely to
+/// become invalid over time as channels are closed. Thus, they are only suitable for short-term use.
+pub const MAX_SHORT_LIVED_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24);
+
 /// Used by [`ChannelManager::list_recent_payments`] to express the status of recent payments.
 /// These include payments that have yet to find a successful path, or have unresolved HTLCs.
 #[derive(Debug, PartialEq)]
@@ -2432,11 +2466,10 @@ macro_rules! convert_chan_phase_err {
                        ChannelError::Ignore(msg) => {
                                (false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Ignore(msg), *$channel_id))
                        },
-                       ChannelError::Close(msg) => {
+                       ChannelError::Close((msg, reason)) => {
                                let logger = WithChannelContext::from(&$self.logger, &$channel.context, None);
                                log_error!(logger, "Closing channel {} due to close-required error: {}", $channel_id, msg);
                                update_maps_on_chan_removal!($self, $channel.context);
-                               let reason = ClosureReason::ProcessingError { err: msg.clone() };
                                let shutdown_res = $channel.context.force_shutdown(true, reason);
                                let err =
                                        MsgHandleErrInternal::from_finish_shutdown(msg, *$channel_id, shutdown_res, $channel_update);
@@ -2862,6 +2895,8 @@ where
                        pending_offers_messages: Mutex::new(Vec::new()),
                        pending_broadcast_messages: Mutex::new(Vec::new()),
 
+                       last_days_feerates: Mutex::new(VecDeque::new()),
+
                        entropy_source,
                        node_signer,
                        signer_provider,
@@ -4187,10 +4222,9 @@ where
                        Some(ChannelPhase::UnfundedOutboundV1(mut chan)) => {
                                macro_rules! close_chan { ($err: expr, $api_err: expr, $chan: expr) => { {
                                        let counterparty;
-                                       let err = if let ChannelError::Close(msg) = $err {
+                                       let err = if let ChannelError::Close((msg, reason)) = $err {
                                                let channel_id = $chan.context.channel_id();
                                                counterparty = chan.context.get_counterparty_node_id();
-                                               let reason = ClosureReason::ProcessingError { err: msg.clone() };
                                                let shutdown_res = $chan.context.force_shutdown(false, reason);
                                                MsgHandleErrInternal::from_finish_shutdown(msg, channel_id, shutdown_res, None)
                                        } else { unreachable!(); };
@@ -4203,7 +4237,7 @@ where
                                match find_funding_output(&chan, &funding_transaction) {
                                        Ok(found_funding_txo) => funding_txo = found_funding_txo,
                                        Err(err) => {
-                                               let chan_err = ChannelError::Close(err.to_owned());
+                                               let chan_err = ChannelError::close(err.to_owned());
                                                let api_err = APIError::APIMisuseError { err: err.to_owned() };
                                                return close_chan!(chan_err, api_err, chan);
                                        },
@@ -7002,7 +7036,7 @@ where
                                },
                                Some(mut phase) => {
                                        let err_msg = format!("Got an unexpected funding_created message from peer with counterparty_node_id {}", counterparty_node_id);
-                                       let err = ChannelError::Close(err_msg);
+                                       let err = ChannelError::close(err_msg);
                                        return Err(convert_chan_phase_err!(self, err, &mut phase, &msg.temporary_channel_id).1);
                                },
                                None => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.temporary_channel_id))
@@ -7017,7 +7051,7 @@ where
                        // `update_maps_on_chan_removal`), we'll remove the existing channel
                        // from `outpoint_to_peer`. Thus, we must first unset the funding outpoint
                        // on the channel.
-                       let err = ChannelError::Close($err.to_owned());
+                       let err = ChannelError::close($err.to_owned());
                        chan.unset_funding_info(msg.temporary_channel_id);
                        return Err(convert_chan_phase_err!(self, err, chan, &funded_channel_id, UNFUNDED_CHANNEL).1);
                } } }
@@ -7102,7 +7136,7 @@ where
                                                                } else { unreachable!(); }
                                                                Ok(())
                                                        } else {
-                                                               let e = ChannelError::Close("Channel funding outpoint was a duplicate".to_owned());
+                                                               let e = ChannelError::close("Channel funding outpoint was a duplicate".to_owned());
                                                                // We weren't able to watch the channel to begin with, so no
                                                                // updates should be made on it. Previously, full_stack_target
                                                                // found an (unreachable) panic when the monitor update contained
@@ -7173,7 +7207,7 @@ where
 
                                        Ok(())
                                } else {
-                                       try_chan_phase_entry!(self, Err(ChannelError::Close(
+                                       try_chan_phase_entry!(self, Err(ChannelError::close(
                                                "Got a channel_ready message for an unfunded channel!".into())), chan_phase_entry)
                                }
                        },
@@ -7288,7 +7322,7 @@ where
                                                        (tx, Some(remove_channel_phase!(self, chan_phase_entry)), shutdown_result)
                                                } else { (tx, None, shutdown_result) }
                                        } else {
-                                               return try_chan_phase_entry!(self, Err(ChannelError::Close(
+                                               return try_chan_phase_entry!(self, Err(ChannelError::close(
                                                        "Got a closing_signed message for an unfunded channel!".into())), chan_phase_entry);
                                        }
                                },
@@ -7388,7 +7422,7 @@ where
                                        }
                                        try_chan_phase_entry!(self, chan.update_add_htlc(&msg, pending_forward_info, &self.fee_estimator), chan_phase_entry);
                                } else {
-                                       return try_chan_phase_entry!(self, Err(ChannelError::Close(
+                                       return try_chan_phase_entry!(self, Err(ChannelError::close(
                                                "Got an update_add_htlc message for an unfunded channel!".into())), chan_phase_entry);
                                }
                        },
@@ -7432,7 +7466,7 @@ where
                                                next_user_channel_id = chan.context.get_user_id();
                                                res
                                        } else {
-                                               return try_chan_phase_entry!(self, Err(ChannelError::Close(
+                                               return try_chan_phase_entry!(self, Err(ChannelError::close(
                                                        "Got an update_fulfill_htlc message for an unfunded channel!".into())), chan_phase_entry);
                                        }
                                },
@@ -7463,7 +7497,7 @@ where
                                if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
                                        try_chan_phase_entry!(self, chan.update_fail_htlc(&msg, HTLCFailReason::from_msg(msg)), chan_phase_entry);
                                } else {
-                                       return try_chan_phase_entry!(self, Err(ChannelError::Close(
+                                       return try_chan_phase_entry!(self, Err(ChannelError::close(
                                                "Got an update_fail_htlc message for an unfunded channel!".into())), chan_phase_entry);
                                }
                        },
@@ -7486,13 +7520,13 @@ where
                match peer_state.channel_by_id.entry(msg.channel_id) {
                        hash_map::Entry::Occupied(mut chan_phase_entry) => {
                                if (msg.failure_code & 0x8000) == 0 {
-                                       let chan_err: ChannelError = ChannelError::Close("Got update_fail_malformed_htlc with BADONION not set".to_owned());
+                                       let chan_err = ChannelError::close("Got update_fail_malformed_htlc with BADONION not set".to_owned());
                                        try_chan_phase_entry!(self, Err(chan_err), chan_phase_entry);
                                }
                                if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
                                        try_chan_phase_entry!(self, chan.update_fail_malformed_htlc(&msg, HTLCFailReason::reason(msg.failure_code, msg.sha256_of_onion.to_vec())), chan_phase_entry);
                                } else {
-                                       return try_chan_phase_entry!(self, Err(ChannelError::Close(
+                                       return try_chan_phase_entry!(self, Err(ChannelError::close(
                                                "Got an update_fail_malformed_htlc message for an unfunded channel!".into())), chan_phase_entry);
                                }
                                Ok(())
@@ -7522,7 +7556,7 @@ where
                                        }
                                        Ok(())
                                } else {
-                                       return try_chan_phase_entry!(self, Err(ChannelError::Close(
+                                       return try_chan_phase_entry!(self, Err(ChannelError::close(
                                                "Got a commitment_signed message for an unfunded channel!".into())), chan_phase_entry);
                                }
                        },
@@ -7718,7 +7752,7 @@ where
                                                }
                                                htlcs_to_fail
                                        } else {
-                                               return try_chan_phase_entry!(self, Err(ChannelError::Close(
+                                               return try_chan_phase_entry!(self, Err(ChannelError::close(
                                                        "Got a revoke_and_ack message for an unfunded channel!".into())), chan_phase_entry);
                                        }
                                },
@@ -7744,7 +7778,7 @@ where
                                        let logger = WithChannelContext::from(&self.logger, &chan.context, None);
                                        try_chan_phase_entry!(self, chan.update_fee(&self.fee_estimator, &msg, &&logger), chan_phase_entry);
                                } else {
-                                       return try_chan_phase_entry!(self, Err(ChannelError::Close(
+                                       return try_chan_phase_entry!(self, Err(ChannelError::close(
                                                "Got an update_fee message for an unfunded channel!".into())), chan_phase_entry);
                                }
                        },
@@ -7779,7 +7813,7 @@ where
                                                update_msg: Some(self.get_channel_update_for_broadcast(chan).unwrap()),
                                        });
                                } else {
-                                       return try_chan_phase_entry!(self, Err(ChannelError::Close(
+                                       return try_chan_phase_entry!(self, Err(ChannelError::close(
                                                "Got an announcement_signatures message for an unfunded channel!".into())), chan_phase_entry);
                                }
                        },
@@ -7831,7 +7865,7 @@ where
                                                }
                                        }
                                } else {
-                                       return try_chan_phase_entry!(self, Err(ChannelError::Close(
+                                       return try_chan_phase_entry!(self, Err(ChannelError::close(
                                                "Got a channel_update for an unfunded channel!".into())), chan_phase_entry);
                                }
                        },
@@ -7893,7 +7927,7 @@ where
                                                }
                                                need_lnd_workaround
                                        } else {
-                                               return try_chan_phase_entry!(self, Err(ChannelError::Close(
+                                               return try_chan_phase_entry!(self, Err(ChannelError::close(
                                                        "Got a channel_reestablish message for an unfunded channel!".into())), chan_phase_entry);
                                        }
                                },
@@ -8240,16 +8274,15 @@ where
 
 macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
        /// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the
-       /// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer will
-       /// not have an expiration unless otherwise set on the builder.
+       /// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer's
+       /// expiration will be `absolute_expiry` if `Some`, otherwise it will not expire.
        ///
        /// # Privacy
        ///
-       /// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the offer.
-       /// However, if one is not found, uses a one-hop [`BlindedPath`] with
-       /// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
-       /// the node must be announced, otherwise, there is no way to find a path to the introduction in
-       /// order to send the [`InvoiceRequest`].
+       /// Uses [`MessageRouter`] to construct a [`BlindedPath`] for the offer based on the given
+       /// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for
+       /// privacy implications as well as those of the parameterized [`Router`], which implements
+       /// [`MessageRouter`].
        ///
        /// Also, uses a derived signing pubkey in the offer for recipient privacy.
        ///
@@ -8264,19 +8297,27 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
        ///
        /// [`Offer`]: crate::offers::offer::Offer
        /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
-       pub fn create_offer_builder(&$self) -> Result<$builder, Bolt12SemanticError> {
+       pub fn create_offer_builder(
+               &$self, absolute_expiry: Option<Duration>
+       ) -> Result<$builder, Bolt12SemanticError> {
                let node_id = $self.get_our_node_id();
                let expanded_key = &$self.inbound_payment_key;
                let entropy = &*$self.entropy_source;
                let secp_ctx = &$self.secp_ctx;
 
-               let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
+               let path = $self.create_blinded_path_using_absolute_expiry(absolute_expiry)
+                       .map_err(|_| Bolt12SemanticError::MissingPaths)?;
                let builder = OfferBuilder::deriving_signing_pubkey(
                        node_id, expanded_key, entropy, secp_ctx
                )
                        .chain_hash($self.chain_hash)
                        .path(path);
 
+               let builder = match absolute_expiry {
+                       None => builder,
+                       Some(absolute_expiry) => builder.absolute_expiry(absolute_expiry),
+               };
+
                Ok(builder.into())
        }
 } }
@@ -8304,11 +8345,10 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
        ///
        /// # Privacy
        ///
-       /// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the refund.
-       /// However, if one is not found, uses a one-hop [`BlindedPath`] with
-       /// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
-       /// the node must be announced, otherwise, there is no way to find a path to the introduction in
-       /// order to send the [`Bolt12Invoice`].
+       /// Uses [`MessageRouter`] to construct a [`BlindedPath`] for the refund based on the given
+       /// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for
+       /// privacy implications as well as those of the parameterized [`Router`], which implements
+       /// [`MessageRouter`].
        ///
        /// Also, uses a derived payer id in the refund for payer privacy.
        ///
@@ -8337,7 +8377,8 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
                let entropy = &*$self.entropy_source;
                let secp_ctx = &$self.secp_ctx;
 
-               let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
+               let path = $self.create_blinded_path_using_absolute_expiry(Some(absolute_expiry))
+                       .map_err(|_| Bolt12SemanticError::MissingPaths)?;
                let builder = RefundBuilder::deriving_payer_id(
                        node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
                )?
@@ -8406,10 +8447,9 @@ where
        ///
        /// # Privacy
        ///
-       /// Uses a one-hop [`BlindedPath`] for the reply path with [`ChannelManager::get_our_node_id`]
-       /// as the introduction node and a derived payer id for payer privacy. As such, currently, the
-       /// node must be announced. Otherwise, there is no way to find a path to the introduction node
-       /// in order to send the [`Bolt12Invoice`].
+       /// For payer privacy, uses a derived payer id and uses [`MessageRouter::create_blinded_paths`]
+       /// to construct a [`BlindedPath`] for the reply path. For further privacy implications, see the
+       /// docs of the parameterized [`Router`], which implements [`MessageRouter`].
        ///
        /// # Limitations
        ///
@@ -8686,6 +8726,38 @@ where
                inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
        }
 
+       /// Creates a blinded path by delegating to [`MessageRouter`] based on the path's intended
+       /// lifetime.
+       ///
+       /// Whether or not the path is compact depends on whether the path is short-lived or long-lived,
+       /// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch. See
+       /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`].
+       fn create_blinded_path_using_absolute_expiry(
+               &self, absolute_expiry: Option<Duration>
+       ) -> Result<BlindedPath, ()> {
+               let now = self.duration_since_epoch();
+               let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY);
+
+               if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry {
+                       self.create_compact_blinded_path()
+               } else {
+                       self.create_blinded_path()
+               }
+       }
+
+       pub(super) fn duration_since_epoch(&self) -> Duration {
+               #[cfg(not(feature = "std"))]
+               let now = Duration::from_secs(
+                       self.highest_seen_timestamp.load(Ordering::Acquire) as u64
+               );
+               #[cfg(feature = "std")]
+               let now = std::time::SystemTime::now()
+                       .duration_since(std::time::SystemTime::UNIX_EPOCH)
+                       .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
+
+               now
+       }
+
        /// Creates a blinded path by delegating to [`MessageRouter::create_blinded_paths`].
        ///
        /// Errors if the `MessageRouter` errors or returns an empty `Vec`.
@@ -8696,6 +8768,27 @@ where
                let peers = self.per_peer_state.read().unwrap()
                        .iter()
                        .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap()))
+                       .filter(|(_, peer)| peer.is_connected)
+                       .filter(|(_, peer)| peer.latest_features.supports_onion_messages())
+                       .map(|(node_id, _)| *node_id)
+                       .collect::<Vec<_>>();
+
+               self.router
+                       .create_blinded_paths(recipient, peers, secp_ctx)
+                       .and_then(|paths| paths.into_iter().next().ok_or(()))
+       }
+
+       /// Creates a blinded path by delegating to [`MessageRouter::create_compact_blinded_paths`].
+       ///
+       /// Errors if the `MessageRouter` errors or returns an empty `Vec`.
+       fn create_compact_blinded_path(&self) -> Result<BlindedPath, ()> {
+               let recipient = self.get_our_node_id();
+               let secp_ctx = &self.secp_ctx;
+
+               let peers = self.per_peer_state.read().unwrap()
+                       .iter()
+                       .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap()))
+                       .filter(|(_, peer)| peer.is_connected)
                        .filter(|(_, peer)| peer.latest_features.supports_onion_messages())
                        .map(|(node_id, peer)| ForwardNode {
                                node_id: *node_id,
@@ -8708,7 +8801,7 @@ where
                        .collect::<Vec<_>>();
 
                self.router
-                       .create_blinded_paths(recipient, peers, secp_ctx)
+                       .create_compact_blinded_paths(recipient, peers, secp_ctx)
                        .and_then(|paths| paths.into_iter().next().ok_or(()))
        }
 
@@ -9102,7 +9195,38 @@ where
                                self, || -> NotifyOption { NotifyOption::DoPersist });
                *self.best_block.write().unwrap() = BestBlock::new(block_hash, height);
 
-               self.do_chain_event(Some(height), |channel| channel.best_block_updated(height, header.time, self.chain_hash, &self.node_signer, &self.default_configuration, &&WithChannelContext::from(&self.logger, &channel.context, None)));
+               let mut min_anchor_feerate = None;
+               let mut min_non_anchor_feerate = None;
+               if self.background_events_processed_since_startup.load(Ordering::Relaxed) {
+                       // If we're past the startup phase, update our feerate cache
+                       let mut last_days_feerates = self.last_days_feerates.lock().unwrap();
+                       if last_days_feerates.len() >= FEERATE_TRACKING_BLOCKS {
+                               last_days_feerates.pop_front();
+                       }
+                       let anchor_feerate = self.fee_estimator
+                               .bounded_sat_per_1000_weight(ConfirmationTarget::MinAllowedAnchorChannelRemoteFee);
+                       let non_anchor_feerate = self.fee_estimator
+                               .bounded_sat_per_1000_weight(ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee);
+                       last_days_feerates.push_back((anchor_feerate, non_anchor_feerate));
+                       if last_days_feerates.len() >= FEERATE_TRACKING_BLOCKS {
+                               min_anchor_feerate = last_days_feerates.iter().map(|(f, _)| f).min().copied();
+                               min_non_anchor_feerate = last_days_feerates.iter().map(|(_, f)| f).min().copied();
+                       }
+               }
+
+               self.do_chain_event(Some(height), |channel| {
+                       let logger = WithChannelContext::from(&self.logger, &channel.context, None);
+                       if channel.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
+                               if let Some(feerate) = min_anchor_feerate {
+                                       channel.check_for_stale_feerate(&logger, feerate)?;
+                               }
+                       } else {
+                               if let Some(feerate) = min_non_anchor_feerate {
+                                       channel.check_for_stale_feerate(&logger, feerate)?;
+                               }
+                       }
+                       channel.best_block_updated(height, header.time, self.chain_hash, &self.node_signer, &self.default_configuration, &&WithChannelContext::from(&self.logger, &channel.context, None))
+               });
 
                macro_rules! max_time {
                        ($timestamp: expr) => {
@@ -11921,6 +12045,8 @@ where
                        node_signer: args.node_signer,
                        signer_provider: args.signer_provider,
 
+                       last_days_feerates: Mutex::new(VecDeque::new()),
+
                        logger: args.logger,
                        default_configuration: args.default_config,
                };