Merge pull request #2219 from benthecarman/custom-closing-address
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Wed, 3 May 2023 16:33:57 +0000 (16:33 +0000)
committerGitHub <noreply@github.com>
Wed, 3 May 2023 16:33:57 +0000 (16:33 +0000)
Add ability to set shutdown script when closing channel

lightning/src/ln/channel.rs
lightning/src/ln/channelmanager.rs
lightning/src/ln/shutdown_tests.rs

index 3fef71e87dc7b4cbaf59447eeae89e70a2bf8331..43e328a70975e30e2172967dfa8aa5f202577881 100644 (file)
@@ -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() {
@@ -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()});
@@ -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() });
index c9a45c54d299b027765e615c435c5e6d8c2f7de4..18c770c500668c09ef66af240ab837b6169a5656 100644 (file)
@@ -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:
 //
@@ -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)>;
@@ -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`
@@ -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
@@ -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
@@ -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]
index aeba82b1cd0bc6fd54a1bba4583591a37d5848f7..28831e5b23eea0a6fa5a8b2f092a017cac120dab 100644 (file)
@@ -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::*;
 
@@ -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,
@@ -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);