Skip to content

Commit

Permalink
Merge pull request #2219 from benthecarman/custom-closing-address
Browse files Browse the repository at this point in the history
Add ability to set shutdown script when closing channel
  • Loading branch information
TheBlueMatt authored May 3, 2023
2 parents ec3de62 + 12b59b2 commit ca1d569
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 11 deletions.
18 changes: 14 additions & 4 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6040,7 +6040,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 @@ -6056,6 +6056,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 @@ -6071,9 +6074,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::*;

Expand Down Expand Up @@ -722,6 +723,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 @@ -890,9 +943,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

0 comments on commit ca1d569

Please sign in to comment.