Skip to content

Commit 8048359

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 5744459 commit 8048359

2 files changed

Lines changed: 85 additions & 5 deletions

File tree

src/lib.rs

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

185187
use crate::ffi::maybe_wrap;
@@ -1145,7 +1147,11 @@ impl Node {
11451147

11461148
/// Retrieve a list of known channels.
11471149
pub fn list_channels(&self) -> Vec<ChannelDetails> {
1148-
self.channel_manager.list_channels().into_iter().map(|c| c.into()).collect()
1150+
self.channel_manager
1151+
.list_channels()
1152+
.into_iter()
1153+
.map(|c| ChannelDetails::from_ldk(c, self.config.anchor_channels_config.as_ref()))
1154+
.collect()
11491155
}
11501156

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

src/types.rs

Lines changed: 77 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;
@@ -380,6 +380,47 @@ pub struct ChannelCounterparty {
380380
pub outbound_htlc_maximum_msat: Option<u64>,
381381
}
382382

383+
/// Describes the reserve behavior of a channel based on its type and trust configuration.
384+
///
385+
/// This captures the combination of the channel's on-chain construction (anchor outputs vs legacy
386+
/// static_remote_key) and whether the counterparty is in our trusted peers list. It tells the
387+
/// user what reserve obligations exist for this channel without exposing internal protocol details.
388+
///
389+
/// See [`AnchorChannelsConfig`] for how reserve behavior is configured.
390+
///
391+
/// [`AnchorChannelsConfig`]: crate::config::AnchorChannelsConfig
392+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
393+
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
394+
pub enum ReserveType {
395+
/// An anchor outputs channel where we maintain a per-channel on-chain reserve for fee
396+
/// bumping force-close transactions.
397+
///
398+
/// Anchor channels allow either party to fee-bump commitment transactions via CPFP
399+
/// at broadcast time. Because the pre-signed commitment fee may be insufficient under
400+
/// current fee conditions, the broadcaster must supply additional funds (hence adaptive)
401+
/// through an anchor output spend. The reserve ensures sufficient on-chain funds are
402+
/// available to cover this.
403+
///
404+
/// This is the default for anchor channels when the counterparty is not in
405+
/// [`trusted_peers_no_reserve`].
406+
///
407+
/// [`trusted_peers_no_reserve`]: crate::config::AnchorChannelsConfig::trusted_peers_no_reserve
408+
Adaptive,
409+
/// An anchor outputs channel where we do not maintain any reserve, because the counterparty
410+
/// is in our [`trusted_peers_no_reserve`] list.
411+
///
412+
/// In this mode, we trust the counterparty to broadcast a valid commitment transaction on
413+
/// our behalf and do not set aside funds for fee bumping.
414+
///
415+
/// [`trusted_peers_no_reserve`]: crate::config::AnchorChannelsConfig::trusted_peers_no_reserve
416+
TrustedPeersNoReserve,
417+
/// A legacy (pre-anchor) channel using only `option_static_remotekey`.
418+
///
419+
/// These channels do not use anchor outputs and therefore do not require an on-chain reserve
420+
/// for fee bumping. Commitment transaction fees are pre-committed at channel open time.
421+
Legacy,
422+
}
423+
383424
/// Details of a channel as returned by [`Node::list_channels`].
384425
///
385426
/// When a channel is spliced, most fields continue to refer to the original pre-splice channel
@@ -544,10 +585,42 @@ pub struct ChannelDetails {
544585
///
545586
/// Will be `None` for objects serialized with LDK Node v0.1 and earlier.
546587
pub channel_shutdown_state: Option<ChannelShutdownState>,
588+
/// The type of on-chain reserve maintained for this channel.
589+
///
590+
/// Will be `None` until channel negotiation has completed and determined whether
591+
/// this channel uses anchor or legacy reserve behavior.
592+
///
593+
/// See [`ReserveType`] for details on how reserves differ between anchor and legacy channels.
594+
pub reserve_type: Option<ReserveType>,
547595
}
548596

549-
impl From<LdkChannelDetails> for ChannelDetails {
550-
fn from(value: LdkChannelDetails) -> Self {
597+
impl ChannelDetails {
598+
pub(crate) fn from_ldk(
599+
value: LdkChannelDetails, anchor_channels_config: Option<&AnchorChannelsConfig>,
600+
) -> Self {
601+
let reserve_type = value.channel_type.as_ref().map(|channel_type| {
602+
if channel_type.supports_anchors_zero_fee_htlc_tx() {
603+
if let Some(config) = anchor_channels_config {
604+
if config.trusted_peers_no_reserve.contains(&value.counterparty.node_id) {
605+
ReserveType::TrustedPeersNoReserve
606+
} else {
607+
ReserveType::Adaptive
608+
}
609+
} else {
610+
// Edge case: if `AnchorChannelsConfig` was previously set and later
611+
// removed, we can no longer distinguish whether this anchor channel's
612+
// reserve was `Adaptive` or `TrustedPeersNoReserve`. We default to
613+
// `Adaptive` here, which may incorrectly override a prior
614+
// `TrustedPeersNoReserve` designation. This is acceptable since
615+
// unsetting `AnchorChannelsConfig` on a node with existing anchor
616+
// channels is not an expected operation.
617+
ReserveType::Adaptive
618+
}
619+
} else {
620+
ReserveType::Legacy
621+
}
622+
});
623+
551624
ChannelDetails {
552625
channel_id: value.channel_id,
553626
counterparty: ChannelCounterparty {
@@ -590,6 +663,7 @@ impl From<LdkChannelDetails> for ChannelDetails {
590663
.map(|c| c.into())
591664
.expect("value is set for objects serialized with LDK v0.0.109+"),
592665
channel_shutdown_state: value.channel_shutdown_state,
666+
reserve_type,
593667
}
594668
}
595669
}

0 commit comments

Comments
 (0)