Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/ffi/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub use bitcoin::{Address, BlockHash, FeeRate, Network, OutPoint, ScriptBuf, Txi
pub use lightning::chain::channelmonitor::BalanceSource;
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
pub use lightning::events::{ClosureReason, PaymentFailureReason};
use lightning::ln::channel_state::ChannelShutdownState;
use lightning::ln::channelmanager::PaymentId;
use lightning::ln::msgs::DecodeError;
pub use lightning::ln::types::ChannelId;
Expand Down Expand Up @@ -1408,6 +1409,26 @@ uniffi::custom_type!(LSPSDateTime, String, {
},
});

/// The shutdown state of a channel as returned in [`ChannelDetails::channel_shutdown_state`].
///
/// [`ChannelDetails::channel_shutdown_state`]: crate::ChannelDetails::channel_shutdown_state
#[uniffi::remote(Enum)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChannelShutdownState {
/// Channel has not sent or received a shutdown message.
NotShuttingDown,
/// Local node has sent a shutdown message for this channel.
ShutdownInitiated,
/// Shutdown message exchanges have concluded and the channels are in the midst of
/// resolving all existing open HTLCs before closing can continue.
ResolvingHTLCs,
/// All HTLCs have been resolved, nodes are currently negotiating channel close onchain fee rates.
NegotiatingClosingFee,
/// We've successfully negotiated a closing_signed dance. At this point `ChannelManager` is about
/// to drop the channel.
ShutdownComplete,
}

/// The reason the channel was closed. See individual variants for more details.
#[uniffi::remote(Enum)]
#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ pub use lightning;
use lightning::chain::BestBlock;
use lightning::impl_writeable_tlv_based;
use lightning::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT;
use lightning::ln::channel_state::{ChannelDetails as LdkChannelDetails, ChannelShutdownState};
use lightning::ln::channel_state::ChannelDetails as LdkChannelDetails;
pub use lightning::ln::channel_state::ChannelShutdownState;
use lightning::ln::channelmanager::PaymentId;
use lightning::ln::msgs::SocketAddress;
use lightning::routing::gossip::NodeAlias;
Expand Down
5 changes: 4 additions & 1 deletion src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use bitcoin::{OutPoint, ScriptBuf};
use bitcoin_payment_instructions::onion_message_resolver::LDKOnionMessageDNSSECHrnResolver;
use lightning::chain::chainmonitor;
use lightning::impl_writeable_tlv_based;
use lightning::ln::channel_state::ChannelDetails as LdkChannelDetails;
use lightning::ln::channel_state::{ChannelDetails as LdkChannelDetails, ChannelShutdownState};
use lightning::ln::msgs::{RoutingMessageHandler, SocketAddress};
use lightning::ln::peer_handler::IgnoringMessageHandler;
use lightning::ln::types::ChannelId;
Expand Down Expand Up @@ -529,6 +529,8 @@ pub struct ChannelDetails {
pub inbound_htlc_maximum_msat: Option<u64>,
/// Set of configurable parameters that affect channel operation.
pub config: ChannelConfig,
/// The current shutdown state of the channel, if any.
pub channel_shutdown_state: Option<ChannelShutdownState>,
}

impl From<LdkChannelDetails> for ChannelDetails {
Expand Down Expand Up @@ -584,6 +586,7 @@ impl From<LdkChannelDetails> 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(),
channel_shutdown_state: value.channel_shutdown_state,
}
}
}
Expand Down
68 changes: 66 additions & 2 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy};
use ldk_node::io::sqlite_store::SqliteStore;
use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus};
use ldk_node::{
Builder, CustomTlvRecord, Event, LightningBalance, Node, NodeError, PendingSweepBalance,
UserChannelId,
Builder, ChannelShutdownState, CustomTlvRecord, Event, LightningBalance, Node, NodeError,
PendingSweepBalance, UserChannelId,
};
use lightning::io;
use lightning::ln::msgs::SocketAddress;
Expand Down Expand Up @@ -918,6 +918,28 @@ pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
let user_channel_id_a = expect_channel_ready_event!(node_a, node_b.node_id());
let user_channel_id_b = expect_channel_ready_event!(node_b, node_a.node_id());

// After channel_ready, no shutdown should be in progress on either side.
for channel in node_a.list_channels() {
assert!(
matches!(
channel.channel_shutdown_state,
None | Some(ChannelShutdownState::NotShuttingDown)
),
"Expected no shutdown in progress on node_a, got {:?}",
channel.channel_shutdown_state,
);
}
for channel in node_b.list_channels() {
assert!(
matches!(
channel.channel_shutdown_state,
None | Some(ChannelShutdownState::NotShuttingDown)
),
"Expected no shutdown in progress on node_b, got {:?}",
channel.channel_shutdown_state,
);
}

println!("\nB receive");
let invoice_amount_1_msat = 2500_000;
let invoice_description: Bolt11InvoiceDescription =
Expand Down Expand Up @@ -1233,6 +1255,20 @@ pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
expect_channel_ready_event!(node_a, node_b.node_id());
expect_channel_ready_event!(node_b, node_a.node_id());

// After the splice-out, the channel must still report no shutdown in progress.
for channel in node_a.list_channels() {
assert!(matches!(
channel.channel_shutdown_state,
None | Some(ChannelShutdownState::NotShuttingDown)
));
}
for channel in node_b.list_channels() {
assert!(matches!(
channel.channel_shutdown_state,
None | Some(ChannelShutdownState::NotShuttingDown)
));
}

assert_eq!(
node_a
.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound
Expand All @@ -1255,6 +1291,20 @@ pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
expect_channel_ready_event!(node_a, node_b.node_id());
expect_channel_ready_event!(node_b, node_a.node_id());

// After the splice-in, the channel must still report no shutdown in progress.
for channel in node_a.list_channels() {
assert!(matches!(
channel.channel_shutdown_state,
None | Some(ChannelShutdownState::NotShuttingDown)
));
}
for channel in node_b.list_channels() {
assert!(matches!(
channel.channel_shutdown_state,
None | Some(ChannelShutdownState::NotShuttingDown)
));
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, if we add all these assertions for NotShuttingDown, should we at least also add one later for a case when we are shutting down (i.e., close/force close)?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been trying to test this part, but the problem is that for cooperative close, the shutdown completes very quickly in tests, so by the time we call list_channels() after close_channel(), the channel may already be gone.

In the force close phase, LDK doesn't use the cooperative shutdown state machine for force closes, so the field would still be None/NotShuttingDown right up until the channel disappears.

assert_eq!(
node_a
.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound
Expand All @@ -1269,6 +1319,20 @@ pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
node_a.force_close_channel(&user_channel_id_a, node_b.node_id(), None).unwrap();
} else {
node_a.close_channel(&user_channel_id_a, node_b.node_id()).unwrap();
// The cooperative shutdown may complete before we get to check, but if the channel
// is still visible it must already be in a shutdown state.
if let Some(channel) =
node_a.list_channels().into_iter().find(|c| c.user_channel_id == user_channel_id_a)
{
assert!(
!matches!(
channel.channel_shutdown_state,
None | Some(ChannelShutdownState::NotShuttingDown)
),
"Expected shutdown in progress on node_a, got {:?}",
channel.channel_shutdown_state,
);
}
}

expect_event!(node_a, ChannelClosed);
Expand Down
Loading