diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 5a1420882..37bf9d413 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -25,6 +25,7 @@ pub use bitcoin::{Address, BlockHash, Network, OutPoint, ScriptBuf, Txid}; pub use lightning::chain::channelmonitor::BalanceSource; use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice; pub use lightning::events::{ClosureReason, PaymentFailureReason}; +use lightning::ln::channel_state::CounterpartyForwardingInfo; use lightning::ln::channelmanager::PaymentId; use lightning::ln::msgs::DecodeError; pub use lightning::ln::types::ChannelId; @@ -43,6 +44,7 @@ pub use lightning_liquidity::lsps0::ser::LSPSDateTime; pub use lightning_liquidity::lsps1::msgs::{ LSPS1ChannelInfo, LSPS1OrderId, LSPS1OrderParams, LSPS1PaymentState, }; +use lightning_types::features::InitFeatures as LdkInitFeatures; pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; pub use lightning_types::string::UntrustedString; use vss_client::headers::{ @@ -1496,6 +1498,248 @@ pub enum ClosureReason { }, } +#[derive(Debug, Clone, PartialEq, Eq, uniffi::Object)] +#[uniffi::export(Debug, Eq)] +pub struct InitFeatures { + pub(crate) inner: LdkInitFeatures, +} + +impl InitFeatures { + /// Constructs init features from big-endian BOLT 9 encoded bytes. + #[uniffi::constructor] + pub fn from_bytes(bytes: &[u8]) -> Self { + Self { inner: LdkInitFeatures::from_be_bytes(bytes.to_vec()).into() } + } + + /// Returns the BOLT 9 big-endian encoded representation of these features. + pub fn to_bytes(&self) -> Vec { + self.inner.encode() + } + + /// Whether the peer supports `option_static_remotekey` (bit 13). + /// + /// This ensures the non-broadcaster's output pays directly to their specified key, + /// simplifying recovery if a channel is force-closed. + pub fn supports_static_remote_key(&self) -> bool { + self.inner.supports_static_remote_key() + } + + /// Whether the peer supports `option_anchors_zero_fee_htlc_tx` (bit 23). + /// + /// Anchor channels allow fee-bumping commitment transactions after broadcast, + /// improving on-chain fee management. + pub fn supports_anchors_zero_fee_htlc_tx(&self) -> bool { + self.inner.supports_anchors_zero_fee_htlc_tx() + } + + /// Whether the peer supports `option_anchors_nonzero_fee_htlc_tx` (bit 21). + /// + /// The initial version of anchor outputs, which was later found to be + /// vulnerable and superseded by `option_anchors_zero_fee_htlc_tx`. + pub fn supports_anchors_nonzero_fee_htlc_tx(&self) -> bool { + self.inner.supports_anchors_nonzero_fee_htlc_tx() + } + + /// Whether the peer supports `option_support_large_channel` (bit 19). + /// + /// When supported, channels larger than 2^24 satoshis (≈0.168 BTC) may be opened. + pub fn supports_wumbo(&self) -> bool { + self.inner.supports_wumbo() + } + + /// Whether the peer supports `option_route_blinding` (bit 25). + /// + /// Route blinding allows the recipient to hide their node identity and + /// last-hop channel from the sender. + pub fn supports_route_blinding(&self) -> bool { + self.inner.supports_route_blinding() + } + + /// Whether the peer supports `option_onion_messages` (bit 39). + /// + /// Onion messages enable communication over the Lightning Network without + /// requiring a payment, used by BOLT 12 offers and async payments. + pub fn supports_onion_messages(&self) -> bool { + self.inner.supports_onion_messages() + } + + /// Whether the peer supports `option_scid_alias` (bit 47). + /// + /// When supported, the peer will only forward using short channel ID aliases, + /// preventing the real channel UTXO from being revealed during routing. + pub fn supports_scid_privacy(&self) -> bool { + self.inner.supports_scid_privacy() + } + + /// Whether the peer requires `option_zeroconf` (bit 51). + /// + /// Zero-conf channels can be used immediately without waiting for + /// on-chain funding confirmations. + pub fn requires_zero_conf(&self) -> bool { + self.inner.requires_zero_conf() + } + + /// Whether the peer supports `option_dual_fund` (bit 29). + /// + /// Dual-funded channels allow both parties to contribute funds + /// to the channel opening transaction. + pub fn supports_dual_fund(&self) -> bool { + self.inner.supports_dual_fund() + } + + /// Whether the peer supports `option_quiesce` (bit 35). + /// + /// Quiescence is a prerequisite for splicing, allowing both sides to + /// pause HTLC activity before modifying the funding transaction. + pub fn supports_quiescence(&self) -> bool { + self.inner.supports_quiescence() + } + + /// Whether the peer supports `option_data_loss_protect` (bit 1). + /// + /// Allows a node that has fallen behind (e.g., restored from backup) + /// to detect that it is out of date and close the channel safely. + pub fn supports_data_loss_protect(&self) -> bool { + self.inner.supports_data_loss_protect() + } + + /// Whether the peer supports `option_upfront_shutdown_script` (bit 5). + /// + /// Commits to a shutdown scriptpubkey when opening a channel, + /// preventing a compromised key from redirecting closing funds. + pub fn supports_upfront_shutdown_script(&self) -> bool { + self.inner.supports_upfront_shutdown_script() + } + + /// Whether the peer supports `gossip_queries` (bit 7). + /// + /// Enables more sophisticated gossip synchronization, allowing + /// nodes to request specific ranges of channel announcements. + pub fn supports_gossip_queries(&self) -> bool { + self.inner.supports_gossip_queries() + } + + /// Whether the peer supports `var_onion_optin` (bit 9). + /// + /// Requires variable-length routing onion payloads, which is + /// assumed to be supported by all modern Lightning nodes. + pub fn supports_variable_length_onion(&self) -> bool { + self.inner.supports_variable_length_onion() + } + + /// Whether the peer supports `payment_secret` (bit 15). + /// + /// Payment secrets prevent forwarding nodes from probing + /// payment recipients. Assumed to be supported by all modern nodes. + pub fn supports_payment_secret(&self) -> bool { + self.inner.supports_payment_secret() + } + + /// Whether the peer supports `basic_mpp` (bit 17). + /// + /// Multi-part payments allow splitting a payment across multiple + /// routes for improved reliability and liquidity utilization. + pub fn supports_basic_mpp(&self) -> bool { + self.inner.supports_basic_mpp() + } + + /// Whether the peer supports `opt_shutdown_anysegwit` (bit 27). + /// + /// Allows future segwit versions in the shutdown script, + /// enabling closing to Taproot or later output types. + pub fn supports_shutdown_anysegwit(&self) -> bool { + self.inner.supports_shutdown_anysegwit() + } + + /// Whether the peer supports `option_channel_type` (bit 45). + /// + /// Supports explicit channel type negotiation during channel opening. + pub fn supports_channel_type(&self) -> bool { + self.inner.supports_channel_type() + } + + /// Whether the peer supports `option_trampoline` (bit 57). + /// + /// Trampoline routing allows lightweight nodes to delegate + /// pathfinding to an intermediate trampoline node. + pub fn supports_trampoline_routing(&self) -> bool { + self.inner.supports_trampoline_routing() + } + + /// Whether the peer supports `option_simple_close` (bit 61). + /// + /// Simplified closing negotiation reduces the number of + /// round trips needed for a cooperative channel close. + pub fn supports_simple_close(&self) -> bool { + self.inner.supports_simple_close() + } + + /// Whether the peer supports `option_splice` (bit 63). + /// + /// Splicing allows replacing the funding transaction with a new one, + /// enabling on-the-fly capacity changes without closing the channel. + pub fn supports_splicing(&self) -> bool { + self.inner.supports_splicing() + } + + /// Whether the peer supports `option_provide_storage` (bit 43). + /// + /// Indicates the node offers to store encrypted backup data + /// on behalf of its peers. + pub fn supports_provide_storage(&self) -> bool { + self.inner.supports_provide_storage() + } + + /// Whether the peer set `initial_routing_sync` (bit 3). + /// + /// Indicates the sending node needs a complete routing information dump. + /// Per BOLT #9, this feature has no even (required) bit. + pub fn initial_routing_sync(&self) -> bool { + self.inner.initial_routing_sync() + } + + /// Whether the peer supports `option_taproot` (bit 31). + /// + /// Taproot channels use MuSig2-based multisig for funding outputs, + /// improving privacy and efficiency. + pub fn supports_taproot(&self) -> bool { + self.inner.supports_taproot() + } + + /// Whether the peer supports `option_zero_fee_commitments` (bit 141, experimental). + /// + /// A channel type which always uses zero transaction fee on commitment + /// transactions, combined with anchor outputs. + pub fn supports_anchor_zero_fee_commitments(&self) -> bool { + self.inner.supports_anchor_zero_fee_commitments() + } + + /// Whether the peer supports HTLC hold (bit 153, experimental). + /// + /// Supports holding HTLCs and forwarding on receipt of an onion message. + pub fn supports_htlc_hold(&self) -> bool { + self.inner.supports_htlc_hold() + } +} + +impl From for InitFeatures { + fn from(ldk_init: LdkInitFeatures) -> Self { + Self { inner: ldk_init } + } +} + +/// Information needed for constructing an invoice route hint for this channel. +#[uniffi::remote(Record)] +pub struct CounterpartyForwardingInfo { + /// Base routing fee in millisatoshis. + pub fee_base_msat: u32, + /// Amount in millionths of a satoshi the channel will charge per transferred satoshi. + pub fee_proportional_millionths: u32, + /// The minimum difference in cltv_expiry between an ingoing HTLC and its outgoing counterpart, + /// such that the outgoing HTLC is forwardable to this counterparty. + pub cltv_expiry_delta: u16, +} + #[cfg(test)] mod tests { use std::num::NonZeroU64; diff --git a/src/lib.rs b/src/lib.rs index 2ac4697e8..ba75c2676 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -176,7 +176,9 @@ use types::{ HRNResolver, KeysManager, OnionMessenger, PaymentStore, PeerManager, Router, Scorer, Sweeper, Wallet, }; -pub use types::{ChannelDetails, CustomTlvRecord, PeerDetails, SyncAndAsyncKVStore, UserChannelId}; +pub use types::{ + ChannelDetails, CustomTlvRecord, PeerDetails, ReserveType, SyncAndAsyncKVStore, UserChannelId, +}; pub use vss_client; use crate::scoring::setup_background_pathfinding_scores_sync; @@ -1069,7 +1071,11 @@ impl Node { /// Retrieve a list of known channels. pub fn list_channels(&self) -> Vec { - self.channel_manager.list_channels().into_iter().map(|c| c.into()).collect() + self.channel_manager + .list_channels() + .into_iter() + .map(|c| ChannelDetails::from_ldk(c, self.config.anchor_channels_config.as_ref())) + .collect() } /// Connect to a node on the peer-to-peer network. diff --git a/src/types.rs b/src/types.rs index dae315ae0..b72bdca05 100644 --- a/src/types.rs +++ b/src/types.rs @@ -16,6 +16,7 @@ use bitcoin_payment_instructions::onion_message_resolver::LDKOnionMessageDNSSECH use lightning::chain::chainmonitor; use lightning::impl_writeable_tlv_based; use lightning::ln::channel_state::ChannelDetails as LdkChannelDetails; +use lightning::ln::channel_state::CounterpartyForwardingInfo; use lightning::ln::msgs::{RoutingMessageHandler, SocketAddress}; use lightning::ln::peer_handler::IgnoringMessageHandler; use lightning::ln::types::ChannelId; @@ -32,14 +33,20 @@ use lightning_net_tokio::SocketDescriptor; use crate::chain::bitcoind::UtxoSourceClient; use crate::chain::ChainSource; -use crate::config::ChannelConfig; +use crate::config::{AnchorChannelsConfig, ChannelConfig}; use crate::data_store::DataStore; use crate::fee_estimator::OnchainFeeEstimator; +use crate::ffi::maybe_wrap; use crate::logger::Logger; use crate::message_handler::NodeCustomMessageHandler; use crate::payment::{PaymentDetails, PendingPaymentDetails}; use crate::runtime::RuntimeSpawner; +#[cfg(not(feature = "uniffi"))] +type InitFeatures = lightning::types::features::InitFeatures; +#[cfg(feature = "uniffi")] +type InitFeatures = Arc; + /// A supertrait that requires that a type implements both [`KVStore`] and [`KVStoreSync`] at the /// same time. pub trait SyncAndAsyncKVStore: KVStore + KVStoreSync {} @@ -376,6 +383,75 @@ impl fmt::Display for UserChannelId { } } +/// Channel parameters which apply to our counterparty. These are split out from [`ChannelDetails`] +/// to better separate parameters. +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +pub struct ChannelCounterparty { + /// The node_id of our counterparty + pub node_id: PublicKey, + /// The Features the channel counterparty provided upon last connection. + /// Useful for routing as it is the most up-to-date copy of the counterparty's features and + /// many routing-relevant features are present in the init context. + pub features: InitFeatures, + /// The value, in satoshis, that must always be held in the channel for our counterparty. This + /// value ensures that if our counterparty broadcasts a revoked state, we can punish them by + /// claiming at least this value on chain. + /// + /// This value is not included in [`inbound_capacity_msat`] as it can never be spent. + /// + /// [`inbound_capacity_msat`]: ChannelDetails::inbound_capacity_msat + pub unspendable_punishment_reserve: u64, + /// Information on the fees and requirements that the counterparty requires when forwarding + /// payments to us through this channel. + pub forwarding_info: Option, + /// The smallest value HTLC (in msat) the remote peer will accept, for this channel. + pub outbound_htlc_minimum_msat: u64, + /// The largest value HTLC (in msat) the remote peer currently will accept, for this channel. + pub outbound_htlc_maximum_msat: Option, +} + +/// Describes the reserve behavior of a channel based on its type and trust configuration. +/// +/// This captures the combination of the channel's on-chain construction (anchor outputs vs legacy +/// static_remote_key) and whether the counterparty is in our trusted peers list. It tells the +/// user what reserve obligations exist for this channel without exposing internal protocol details. +/// +/// See [`AnchorChannelsConfig`] for how reserve behavior is configured. +/// +/// [`AnchorChannelsConfig`]: crate::config::AnchorChannelsConfig +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +pub enum ReserveType { + /// An anchor outputs channel where we maintain a per-channel on-chain reserve for fee + /// bumping force-close transactions. + /// + /// Anchor channels allow either party to fee-bump commitment transactions via CPFP + /// at broadcast time. Because the pre-signed commitment fee may be insufficient under + /// current fee conditions, the broadcaster must supply additional funds (hence adaptive) + /// through an anchor output spend. The reserve ensures sufficient on-chain funds are + /// available to cover this. + /// + /// This is the default for anchor channels when the counterparty is not in + /// [`trusted_peers_no_reserve`]. + /// + /// [`trusted_peers_no_reserve`]: crate::config::AnchorChannelsConfig::trusted_peers_no_reserve + Adaptive, + /// An anchor outputs channel where we do not maintain any reserve, because the counterparty + /// is in our [`trusted_peers_no_reserve`] list. + /// + /// In this mode, we trust the counterparty to broadcast a valid commitment transaction on + /// our behalf and do not set aside funds for fee bumping. + /// + /// [`trusted_peers_no_reserve`]: crate::config::AnchorChannelsConfig::trusted_peers_no_reserve + TrustedPeersNoReserve, + /// A legacy (pre-anchor) channel using only `option_static_remotekey`. + /// + /// These channels do not use anchor outputs and therefore do not require an on-chain reserve + /// for fee bumping. Commitment transaction fees are pre-committed at channel open time. + Legacy, +} + /// Details of a channel as returned by [`Node::list_channels`]. /// /// When a channel is spliced, most fields continue to refer to the original pre-splice channel @@ -392,8 +468,8 @@ pub struct ChannelDetails { /// Note that this means this value is *not* persistent - it can change once during the /// lifetime of the channel. pub channel_id: ChannelId, - /// The node ID of our the channel's counterparty. - pub counterparty_node_id: PublicKey, + /// Parameters which apply to our counterparty. See individual fields for more information. + pub counterparty: ChannelCounterparty, /// The channel's funding transaction output, if we've negotiated the funding transaction with /// our counterparty already. /// @@ -509,28 +585,6 @@ pub struct ChannelDetails { /// The difference in the CLTV value between incoming HTLCs and an outbound HTLC forwarded over /// the channel. pub cltv_expiry_delta: Option, - /// The value, in satoshis, that must always be held in the channel for our counterparty. This - /// value ensures that if our counterparty broadcasts a revoked state, we can punish them by - /// claiming at least this value on chain. - /// - /// This value is not included in [`inbound_capacity_msat`] as it can never be spent. - /// - /// [`inbound_capacity_msat`]: ChannelDetails::inbound_capacity_msat - pub counterparty_unspendable_punishment_reserve: u64, - /// The smallest value HTLC (in msat) the remote peer will accept, for this channel. - /// - /// This field is only `None` before we have received either the `OpenChannel` or - /// `AcceptChannel` message from the remote peer. - pub counterparty_outbound_htlc_minimum_msat: Option, - /// The largest value HTLC (in msat) the remote peer currently will accept, for this channel. - pub counterparty_outbound_htlc_maximum_msat: Option, - /// Base routing fee in millisatoshis. - pub counterparty_forwarding_info_fee_base_msat: Option, - /// Proportional fee, in millionths of a satoshi the channel will charge per transferred satoshi. - pub counterparty_forwarding_info_fee_proportional_millionths: Option, - /// The minimum difference in CLTV expiry between an ingoing HTLC and its outgoing counterpart, - /// such that the outgoing HTLC is forwardable to this counterparty. - pub counterparty_forwarding_info_cltv_expiry_delta: Option, /// The available outbound capacity for sending a single HTLC to the remote peer. This is /// similar to [`ChannelDetails::outbound_capacity_msat`] but it may be further restricted by /// the current state and per-HTLC limit(s). This is intended for use when routing, allowing us @@ -558,13 +612,51 @@ pub struct ChannelDetails { pub inbound_htlc_maximum_msat: Option, /// Set of configurable parameters that affect channel operation. pub config: ChannelConfig, + /// The type of on-chain reserve maintained for this channel. + /// + /// See [`ReserveType`] for details on how reserves differ between anchor and legacy channels. + pub reserve_type: ReserveType, } -impl From for ChannelDetails { - fn from(value: LdkChannelDetails) -> Self { +impl ChannelDetails { + pub(crate) fn from_ldk( + value: LdkChannelDetails, anchor_channels_config: Option<&AnchorChannelsConfig>, + ) -> Self { + let reserve_type = + if value.channel_type.as_ref().is_some_and(|ct| ct.supports_anchors_zero_fee_htlc_tx()) + { + if let Some(config) = anchor_channels_config { + if config.trusted_peers_no_reserve.contains(&value.counterparty.node_id) { + ReserveType::TrustedPeersNoReserve + } else { + ReserveType::Adaptive + } + } else { + // Edge case: if `AnchorChannelsConfig` was previously set and later + // removed, we can no longer distinguish whether this anchor channel's + // reserve was `Adaptive` or `TrustedPeersNoReserve`. We default to + // `Adaptive` here, which may incorrectly override a prior + // `TrustedPeersNoReserve` designation. This is acceptable since + // unsetting `AnchorChannelsConfig` on a node with existing anchor + // channels is not an expected operation. + ReserveType::Adaptive + } + } else { + ReserveType::Legacy + }; + ChannelDetails { channel_id: value.channel_id, - counterparty_node_id: value.counterparty.node_id, + counterparty: ChannelCounterparty { + node_id: value.counterparty.node_id, + features: maybe_wrap(value.counterparty.features), + unspendable_punishment_reserve: value.counterparty.unspendable_punishment_reserve, + forwarding_info: value.counterparty.forwarding_info, + // unwrap safety: This value will be `None` for objects serialized with LDK versions + // prior to 0.0.115. + outbound_htlc_minimum_msat: value.counterparty.outbound_htlc_minimum_msat.unwrap(), + outbound_htlc_maximum_msat: value.counterparty.outbound_htlc_maximum_msat, + }, funding_txo: value.funding_txo.map(|o| o.into_bitcoin_outpoint()), funding_redeem_script: value.funding_redeem_script, short_channel_id: value.short_channel_id, @@ -585,26 +677,6 @@ impl From for ChannelDetails { is_usable: value.is_usable, is_announced: value.is_announced, cltv_expiry_delta: value.config.map(|c| c.cltv_expiry_delta), - counterparty_unspendable_punishment_reserve: value - .counterparty - .unspendable_punishment_reserve, - counterparty_outbound_htlc_minimum_msat: value.counterparty.outbound_htlc_minimum_msat, - counterparty_outbound_htlc_maximum_msat: value.counterparty.outbound_htlc_maximum_msat, - counterparty_forwarding_info_fee_base_msat: value - .counterparty - .forwarding_info - .as_ref() - .map(|f| f.fee_base_msat), - counterparty_forwarding_info_fee_proportional_millionths: value - .counterparty - .forwarding_info - .as_ref() - .map(|f| f.fee_proportional_millionths), - counterparty_forwarding_info_cltv_expiry_delta: value - .counterparty - .forwarding_info - .as_ref() - .map(|f| f.cltv_expiry_delta), next_outbound_htlc_limit_msat: value.next_outbound_htlc_limit_msat, next_outbound_htlc_minimum_msat: value.next_outbound_htlc_minimum_msat, force_close_spend_delay: value.force_close_spend_delay, @@ -613,6 +685,7 @@ impl From for ChannelDetails { inbound_htlc_maximum_msat: value.inbound_htlc_maximum_msat, // unwrap safety: `config` is only `None` for LDK objects serialized prior to 0.0.109. config: value.config.map(|c| c.into()).unwrap(), + reserve_type, } } } diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 413b2d44a..e427b8b35 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -2114,7 +2114,7 @@ async fn lsps2_client_trusts_lsp() { client_node .list_channels() .iter() - .find(|c| c.counterparty_node_id == service_node_id) + .find(|c| c.counterparty.node_id == service_node_id) .unwrap() .confirmations, Some(0) @@ -2123,7 +2123,7 @@ async fn lsps2_client_trusts_lsp() { service_node .list_channels() .iter() - .find(|c| c.counterparty_node_id == client_node_id) + .find(|c| c.counterparty.node_id == client_node_id) .unwrap() .confirmations, Some(0) @@ -2158,7 +2158,7 @@ async fn lsps2_client_trusts_lsp() { client_node .list_channels() .iter() - .find(|c| c.counterparty_node_id == service_node_id) + .find(|c| c.counterparty.node_id == service_node_id) .unwrap() .confirmations, Some(6) @@ -2167,7 +2167,7 @@ async fn lsps2_client_trusts_lsp() { service_node .list_channels() .iter() - .find(|c| c.counterparty_node_id == client_node_id) + .find(|c| c.counterparty.node_id == client_node_id) .unwrap() .confirmations, Some(6) @@ -2286,7 +2286,7 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() { client_node .list_channels() .iter() - .find(|c| c.counterparty_node_id == service_node_id) + .find(|c| c.counterparty.node_id == service_node_id) .unwrap() .confirmations, Some(6) @@ -2295,7 +2295,7 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() { service_node .list_channels() .iter() - .find(|c| c.counterparty_node_id == client_node_id) + .find(|c| c.counterparty.node_id == client_node_id) .unwrap() .confirmations, Some(6) @@ -2738,7 +2738,7 @@ async fn open_channel_with_all_with_anchors() { assert_eq!(channels.len(), 1); let channel = &channels[0]; assert!(channel.channel_value_sats > premine_amount_sat - anchor_reserve_sat - 500); - assert_eq!(channel.counterparty_node_id, node_b.node_id()); + assert_eq!(channel.counterparty.node_id, node_b.node_id()); assert_eq!(channel.funding_txo.unwrap(), funding_txo); node_a.stop().unwrap(); @@ -2789,7 +2789,7 @@ async fn open_channel_with_all_without_anchors() { assert_eq!(channels.len(), 1); let channel = &channels[0]; assert!(channel.channel_value_sats > premine_amount_sat - 500); - assert_eq!(channel.counterparty_node_id, node_b.node_id()); + assert_eq!(channel.counterparty.node_id, node_b.node_id()); assert_eq!(channel.funding_txo.unwrap(), funding_txo); node_a.stop().unwrap();