Skip to content

Add ability to set shutdown script when closing channel #2219

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
18 changes: 14 additions & 4 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6050,7 +6050,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
/// May jump to the channel being fully shutdown (see [`Self::is_shutdown`]) in which case no
/// [`ChannelMonitorUpdate`] will be returned).
pub fn get_shutdown<SP: Deref>(&mut self, signer_provider: &SP, their_features: &InitFeatures,
target_feerate_sats_per_kw: Option<u32>)
target_feerate_sats_per_kw: Option<u32>, override_shutdown_script: Option<ShutdownScript>)
-> Result<(msgs::Shutdown, Option<&ChannelMonitorUpdate>, Vec<(HTLCSource, PaymentHash)>), APIError>
where SP::Target: SignerProvider {
for htlc in self.pending_outbound_htlcs.iter() {
Expand All @@ -6066,6 +6066,9 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
return Err(APIError::ChannelUnavailable{err: "Shutdown already in progress by remote".to_owned()});
}
}
if self.shutdown_scriptpubkey.is_some() && override_shutdown_script.is_some() {
return Err(APIError::APIMisuseError{err: "Cannot override shutdown script for a channel with one already set".to_owned()});
}
assert_eq!(self.channel_state & ChannelState::ShutdownComplete as u32, 0);
if self.channel_state & (ChannelState::PeerDisconnected as u32 | ChannelState::MonitorUpdateInProgress as u32) != 0 {
return Err(APIError::ChannelUnavailable{err: "Cannot begin shutdown while peer is disconnected or we're waiting on a monitor update, maybe force-close instead?".to_owned()});
Expand All @@ -6081,9 +6084,16 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
let update_shutdown_script = match self.shutdown_scriptpubkey {
Some(_) => false,
None if !chan_closed => {
let shutdown_scriptpubkey = match signer_provider.get_shutdown_scriptpubkey() {
Ok(scriptpubkey) => scriptpubkey,
Err(_) => return Err(APIError::ChannelUnavailable { err: "Failed to get shutdown scriptpubkey".to_owned() }),
// use override shutdown script if provided
let shutdown_scriptpubkey = match override_shutdown_script {
Some(script) => script,
None => {
// otherwise, use the shutdown scriptpubkey provided by the signer
match signer_provider.get_shutdown_scriptpubkey() {
Ok(scriptpubkey) => scriptpubkey,
Err(_) => return Err(APIError::ChannelUnavailable{err: "Failed to get shutdown scriptpubkey".to_owned()}),
}
},
};
if !shutdown_scriptpubkey.is_compatible(their_features) {
return Err(APIError::IncompatibleShutdownScript { script: shutdown_scriptpubkey.clone() });
Expand Down
16 changes: 11 additions & 5 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ use core::ops::Deref;

// Re-export this for use in the public API.
pub use crate::ln::outbound_payment::{PaymentSendFailure, Retry, RetryableSendFailure, RecipientOnionFields};
use crate::ln::script::ShutdownScript;

// We hold various information about HTLC relay in the HTLC objects in Channel itself:
//
Expand Down Expand Up @@ -2028,7 +2029,7 @@ where
});
}

fn close_channel_internal(&self, channel_id: &[u8; 32], counterparty_node_id: &PublicKey, target_feerate_sats_per_1000_weight: Option<u32>) -> Result<(), APIError> {
fn close_channel_internal(&self, channel_id: &[u8; 32], counterparty_node_id: &PublicKey, target_feerate_sats_per_1000_weight: Option<u32>, override_shutdown_script: Option<ShutdownScript>) -> Result<(), APIError> {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);

let mut failed_htlcs: Vec<(HTLCSource, PaymentHash)>;
Expand All @@ -2045,7 +2046,7 @@ where
let funding_txo_opt = chan_entry.get().get_funding_txo();
let their_features = &peer_state.latest_features;
let (shutdown_msg, mut monitor_update_opt, htlcs) = chan_entry.get_mut()
.get_shutdown(&self.signer_provider, their_features, target_feerate_sats_per_1000_weight)?;
.get_shutdown(&self.signer_provider, their_features, target_feerate_sats_per_1000_weight, override_shutdown_script)?;
failed_htlcs = htlcs;

// We can send the `shutdown` message before updating the `ChannelMonitor`
Expand Down Expand Up @@ -2112,7 +2113,7 @@ where
/// [`Normal`]: crate::chain::chaininterface::ConfirmationTarget::Normal
/// [`SendShutdown`]: crate::events::MessageSendEvent::SendShutdown
pub fn close_channel(&self, channel_id: &[u8; 32], counterparty_node_id: &PublicKey) -> Result<(), APIError> {
self.close_channel_internal(channel_id, counterparty_node_id, None)
self.close_channel_internal(channel_id, counterparty_node_id, None, None)
}

/// Begins the process of closing a channel. After this call (plus some timeout), no new HTLCs
Expand All @@ -2129,6 +2130,11 @@ where
/// transaction feerate below `target_feerate_sat_per_1000_weight` (or the feerate which
/// will appear on a force-closure transaction, whichever is lower).
///
/// The `shutdown_script` provided will be used as the `scriptPubKey` for the closing transaction.
/// Will fail if a shutdown script has already been set for this channel by
/// ['ChannelHandshakeConfig::commit_upfront_shutdown_pubkey`]. The given shutdown script must
/// also be compatible with our and the counterparty's features.
///
/// May generate a [`SendShutdown`] message event on success, which should be relayed.
///
/// Raises [`APIError::ChannelUnavailable`] if the channel cannot be closed due to failing to
Expand All @@ -2140,8 +2146,8 @@ where
/// [`Background`]: crate::chain::chaininterface::ConfirmationTarget::Background
/// [`Normal`]: crate::chain::chaininterface::ConfirmationTarget::Normal
/// [`SendShutdown`]: crate::events::MessageSendEvent::SendShutdown
pub fn close_channel_with_target_feerate(&self, channel_id: &[u8; 32], counterparty_node_id: &PublicKey, target_feerate_sats_per_1000_weight: u32) -> Result<(), APIError> {
self.close_channel_internal(channel_id, counterparty_node_id, Some(target_feerate_sats_per_1000_weight))
pub fn close_channel_with_feerate_and_script(&self, channel_id: &[u8; 32], counterparty_node_id: &PublicKey, target_feerate_sats_per_1000_weight: Option<u32>, shutdown_script: Option<ShutdownScript>) -> Result<(), APIError> {
self.close_channel_internal(channel_id, counterparty_node_id, target_feerate_sats_per_1000_weight, shutdown_script)
}

#[inline]
Expand Down
57 changes: 55 additions & 2 deletions lightning/src/ln/shutdown_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use bitcoin::util::address::WitnessVersion;
use regex;

use core::default::Default;
use std::convert::TryFrom;

use crate::ln::functional_test_utils::*;
use crate::ln::msgs::OptionalField::Present;
Expand Down Expand Up @@ -723,6 +724,58 @@ fn test_invalid_shutdown_script() {
"Got a nonstandard scriptpubkey (00020000) from remote peer");
}

#[test]
fn test_user_shutdown_script() {
let mut config = test_default_channel_config();
config.channel_handshake_config.announced_channel = true;
config.channel_handshake_limits.force_announced_channel_preference = false;
config.channel_handshake_config.commit_upfront_shutdown_pubkey = false;
let user_cfgs = [None, Some(config), None];
let chanmon_cfgs = create_chanmon_cfgs(3);
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &user_cfgs);
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);

// Segwit v0 script of the form OP_0 <20-byte hash>
let script = Builder::new().push_int(0)
.push_slice(&[0; 20])
.into_script();

let shutdown_script = ShutdownScript::try_from(script.clone()).unwrap();

let chan = create_announced_chan_between_nodes(&nodes, 0, 1);
nodes[1].node.close_channel_with_feerate_and_script(&OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id(), &nodes[0].node.get_our_node_id(), None, Some(shutdown_script)).unwrap();
check_added_monitors!(nodes[1], 1);

let mut node_0_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());

assert_eq!(node_0_shutdown.scriptpubkey, script);
}

#[test]
fn test_already_set_user_shutdown_script() {
let mut config = test_default_channel_config();
config.channel_handshake_config.announced_channel = true;
config.channel_handshake_limits.force_announced_channel_preference = false;
let user_cfgs = [None, Some(config), None];
let chanmon_cfgs = create_chanmon_cfgs(3);
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &user_cfgs);
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);

// Segwit v0 script of the form OP_0 <20-byte hash>
let script = Builder::new().push_int(0)
.push_slice(&[0; 20])
.into_script();

let shutdown_script = ShutdownScript::try_from(script).unwrap();

let chan = create_announced_chan_between_nodes(&nodes, 0, 1);
let result = nodes[1].node.close_channel_with_feerate_and_script(&OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id(), &nodes[0].node.get_our_node_id(), None, Some(shutdown_script));

assert_eq!(result, Err(APIError::APIMisuseError { err: "Cannot override shutdown script for a channel with one already set".to_string() }));
}

#[derive(PartialEq)]
enum TimeoutStep {
AfterShutdown,
Expand Down Expand Up @@ -891,9 +944,9 @@ fn simple_target_feerate_shutdown() {
let chan = create_announced_chan_between_nodes(&nodes, 0, 1);
let chan_id = OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id();

nodes[0].node.close_channel_with_target_feerate(&chan_id, &nodes[1].node.get_our_node_id(), 253 * 10).unwrap();
nodes[0].node.close_channel_with_feerate_and_script(&chan_id, &nodes[1].node.get_our_node_id(), Some(253 * 10), None).unwrap();
let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
nodes[1].node.close_channel_with_target_feerate(&chan_id, &nodes[0].node.get_our_node_id(), 253 * 5).unwrap();
nodes[1].node.close_channel_with_feerate_and_script(&chan_id, &nodes[0].node.get_our_node_id(), Some(253 * 5), None).unwrap();
let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());

nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
Expand Down