Skip to content

Commit 91c95f2

Browse files
committed
Add ReserveType to ChannelDetails
We expose the reserve type of each channel through a new ReserveType enum on ChannelDetails. This tells users whether a channel uses adaptive anchor reserves, has no reserve due to a trusted peer, or is a legacy pre-anchor channel. The reserve type is derived at query time in list_channels by checking the channel's type features against trusted_peers_no_reserve. We replace the From<LdkChannelDetails> implementation with an explicit from_ldk method that takes the anchor channels config. Additionally, we document the rationale behind selecting adaptive reserve type in the unlikely event the anchor channels config was previously set and then later removed.
1 parent da4288a commit 91c95f2

2 files changed

Lines changed: 82 additions & 5 deletions

File tree

src/lib.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ use types::{
180180
HRNResolver, KeysManager, OnionMessenger, PaymentStore, PeerManager, Router, Scorer, Sweeper,
181181
Wallet,
182182
};
183-
pub use types::{ChannelDetails, CustomTlvRecord, PeerDetails, SyncAndAsyncKVStore, UserChannelId};
183+
pub use types::{
184+
ChannelDetails, CustomTlvRecord, PeerDetails, ReserveType, SyncAndAsyncKVStore, UserChannelId,
185+
};
184186
pub use vss_client;
185187

186188
use crate::scoring::setup_background_pathfinding_scores_sync;
@@ -1093,7 +1095,11 @@ impl Node {
10931095

10941096
/// Retrieve a list of known channels.
10951097
pub fn list_channels(&self) -> Vec<ChannelDetails> {
1096-
self.channel_manager.list_channels().into_iter().map(|c| c.into()).collect()
1098+
self.channel_manager
1099+
.list_channels()
1100+
.into_iter()
1101+
.map(|c| ChannelDetails::from_ldk(c, self.config.anchor_channels_config.as_ref()))
1102+
.collect()
10971103
}
10981104

10991105
/// Connect to a node on the peer-to-peer network.

src/types.rs

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use lightning_net_tokio::SocketDescriptor;
4040

4141
use crate::chain::bitcoind::UtxoSourceClient;
4242
use crate::chain::ChainSource;
43-
use crate::config::ChannelConfig;
43+
use crate::config::{AnchorChannelsConfig, ChannelConfig};
4444
use crate::data_store::DataStore;
4545
use crate::fee_estimator::OnchainFeeEstimator;
4646
use crate::ffi::maybe_wrap;
@@ -451,6 +451,47 @@ pub struct ChannelCounterparty {
451451
pub outbound_htlc_maximum_msat: Option<u64>,
452452
}
453453

454+
/// Describes the reserve behavior of a channel based on its type and trust configuration.
455+
///
456+
/// This captures the combination of the channel's on-chain construction (anchor outputs vs legacy
457+
/// static_remote_key) and whether the counterparty is in our trusted peers list. It tells the
458+
/// user what reserve obligations exist for this channel without exposing internal protocol details.
459+
///
460+
/// See [`AnchorChannelsConfig`] for how reserve behavior is configured.
461+
///
462+
/// [`AnchorChannelsConfig`]: crate::config::AnchorChannelsConfig
463+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
464+
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
465+
pub enum ReserveType {
466+
/// An anchor outputs channel where we maintain a per-channel on-chain reserve for fee
467+
/// bumping force-close transactions.
468+
///
469+
/// Anchor channels allow either party to fee-bump commitment transactions via CPFP
470+
/// at broadcast time. Because the pre-signed commitment fee may be insufficient under
471+
/// current fee conditions, the broadcaster must supply additional funds (hence adaptive)
472+
/// through an anchor output spend. The reserve ensures sufficient on-chain funds are
473+
/// available to cover this.
474+
///
475+
/// This is the default for anchor channels when the counterparty is not in
476+
/// [`trusted_peers_no_reserve`].
477+
///
478+
/// [`trusted_peers_no_reserve`]: crate::config::AnchorChannelsConfig::trusted_peers_no_reserve
479+
Adaptive,
480+
/// An anchor outputs channel where we do not maintain any reserve, because the counterparty
481+
/// is in our [`trusted_peers_no_reserve`] list.
482+
///
483+
/// In this mode, we trust the counterparty to broadcast a valid commitment transaction on
484+
/// our behalf and do not set aside funds for fee bumping.
485+
///
486+
/// [`trusted_peers_no_reserve`]: crate::config::AnchorChannelsConfig::trusted_peers_no_reserve
487+
TrustedPeersNoReserve,
488+
/// A legacy (pre-anchor) channel using only `option_static_remotekey`.
489+
///
490+
/// These channels do not use anchor outputs and therefore do not require an on-chain reserve
491+
/// for fee bumping. Commitment transaction fees are pre-committed at channel open time.
492+
Legacy,
493+
}
494+
454495
/// Details of a channel as returned by [`Node::list_channels`].
455496
///
456497
/// When a channel is spliced, most fields continue to refer to the original pre-splice channel
@@ -615,10 +656,39 @@ pub struct ChannelDetails {
615656
///
616657
/// Will be `None` for objects serialized with LDK Node v0.1 and earlier.
617658
pub channel_shutdown_state: Option<ChannelShutdownState>,
659+
/// The type of on-chain reserve maintained for this channel.
660+
///
661+
/// See [`ReserveType`] for details on how reserves differ between anchor and legacy channels.
662+
pub reserve_type: ReserveType,
618663
}
619664

620-
impl From<LdkChannelDetails> for ChannelDetails {
621-
fn from(value: LdkChannelDetails) -> Self {
665+
impl ChannelDetails {
666+
pub(crate) fn from_ldk(
667+
value: LdkChannelDetails, anchor_channels_config: Option<&AnchorChannelsConfig>,
668+
) -> Self {
669+
let reserve_type =
670+
if value.channel_type.as_ref().is_some_and(|ct| ct.supports_anchors_zero_fee_htlc_tx())
671+
{
672+
if let Some(config) = anchor_channels_config {
673+
if config.trusted_peers_no_reserve.contains(&value.counterparty.node_id) {
674+
ReserveType::TrustedPeersNoReserve
675+
} else {
676+
ReserveType::Adaptive
677+
}
678+
} else {
679+
// Edge case: if `AnchorChannelsConfig` was previously set and later
680+
// removed, we can no longer distinguish whether this anchor channel's
681+
// reserve was `Adaptive` or `TrustedPeersNoReserve`. We default to
682+
// `Adaptive` here, which may incorrectly override a prior
683+
// `TrustedPeersNoReserve` designation. This is acceptable since
684+
// unsetting `AnchorChannelsConfig` on a node with existing anchor
685+
// channels is not an expected operation.
686+
ReserveType::Adaptive
687+
}
688+
} else {
689+
ReserveType::Legacy
690+
};
691+
622692
ChannelDetails {
623693
channel_id: value.channel_id,
624694
counterparty: ChannelCounterparty {
@@ -666,6 +736,7 @@ impl From<LdkChannelDetails> for ChannelDetails {
666736
.map(|c| c.into())
667737
.expect("value is set for objects serialized with LDK v0.0.109+"),
668738
channel_shutdown_state: value.channel_shutdown_state,
739+
reserve_type,
669740
}
670741
}
671742
}

0 commit comments

Comments
 (0)