Handle retrying sign_counterparty_commitment outb funding failures
authorMatt Corallo <git@bluematt.me>
Tue, 5 Sep 2023 22:10:34 +0000 (22:10 +0000)
committerChris Waterson <waterson@gmail.com>
Wed, 1 Nov 2023 22:24:20 +0000 (15:24 -0700)
If sign_counterparty_commitment fails (i.e. because the signer is
temporarily disconnected), this really indicates that we should
retry the message sending which required the signature later,
rather than force-closing the channel (which probably won't even
work if the signer is missing).

This commit adds retrying of outbound funding_created signing
failures, regenerating the `FundingCreated` message, attempting to
re-sign, and sending it to our peers if we succeed.

lightning/src/ln/channel.rs

index 62a601f9347875781a84e09f6ae8eabcd389ea86..134be90ea1986fec44c291d1523caeb39894f652 100644 (file)
@@ -2100,6 +2100,35 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider  {
                        unbroadcasted_batch_funding_txid,
                }
        }
+
+       /// Only allowed after [`Self::channel_transaction_parameters`] is set.
+       fn get_funding_created_msg<L: Deref>(&mut self, logger: &L) -> Option<msgs::FundingCreated> where L::Target: Logger {
+               let counterparty_keys = self.build_remote_transaction_keys();
+               let counterparty_initial_commitment_tx = self.build_commitment_transaction(self.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx;
+               let signature = match &self.holder_signer {
+                       // TODO (taproot|arik): move match into calling method for Taproot
+                       ChannelSignerType::Ecdsa(ecdsa) => {
+                               ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), &self.secp_ctx)
+                                       .map(|(sig, _)| sig).ok()?
+                       }
+               };
+
+               if self.signer_pending_funding {
+                       log_trace!(logger, "Counterparty commitment signature ready for funding_created message: clearing signer_pending_funding");
+                       self.signer_pending_funding = false;
+               }
+
+               Some(msgs::FundingCreated {
+                       temporary_channel_id: self.temporary_channel_id.unwrap(),
+                       funding_txid: self.channel_transaction_parameters.funding_outpoint.as_ref().unwrap().txid,
+                       funding_output_index: self.channel_transaction_parameters.funding_outpoint.as_ref().unwrap().index,
+                       signature,
+                       #[cfg(taproot)]
+                       partial_signature_with_nonce: None,
+                       #[cfg(taproot)]
+                       next_local_nonce: None,
+               })
+       }
 }
 
 // Internal utility functions for channels
@@ -3930,7 +3959,9 @@ impl<SP: Deref> Channel<SP> where
                } else { None };
                let funding_signed = None;
                let channel_ready = None;
-               let funding_created = None;
+               let funding_created = if self.context.signer_pending_funding && self.context.is_outbound() {
+                       self.context.get_funding_created_msg(logger)
+               } else { None };
 
                log_trace!(logger, "Signer unblocked with {} commitment_update, {} funding_signed, {} funding_created, and {} channel_ready",
                        if commitment_update.is_some() { "a" } else { "no" },
@@ -6000,18 +6031,6 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
                })
        }
 
-       fn get_funding_created_signature<L: Deref>(&mut self, logger: &L) -> Result<Signature, ()> where L::Target: Logger {
-               let counterparty_keys = self.context.build_remote_transaction_keys();
-               let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx;
-               match &self.context.holder_signer {
-                       // TODO (taproot|arik): move match into calling method for Taproot
-                       ChannelSignerType::Ecdsa(ecdsa) => {
-                               ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), &self.context.secp_ctx)
-                                       .map(|(sig, _)| sig)
-                       }
-               }
-       }
-
        /// Updates channel state with knowledge of the funding transaction's txid/index, and generates
        /// a funding_created message for the remote peer.
        /// Panics if called at some time other than immediately after initial handshake, if called twice,
@@ -6036,8 +6055,6 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
                self.context.channel_transaction_parameters.funding_outpoint = Some(funding_txo);
                self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters);
 
-               let temporary_channel_id = self.context.channel_id;
-
                // Now that we're past error-generating stuff, update our local state:
 
                self.context.channel_state = ChannelState::FundingCreated as u32;
@@ -6054,21 +6071,13 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
                self.context.funding_transaction = Some(funding_transaction);
                self.context.is_batch_funding = Some(()).filter(|_| is_batch_funding);
 
-               let funding_created = if let Ok(signature) = self.get_funding_created_signature(logger) {
-                       Some(msgs::FundingCreated {
-                               temporary_channel_id,
-                               funding_txid: funding_txo.txid,
-                               funding_output_index: funding_txo.index,
-                               signature,
-                               #[cfg(taproot)]
-                               partial_signature_with_nonce: None,
-                               #[cfg(taproot)]
-                               next_local_nonce: None,
-                       })
-               } else {
-                       self.context.signer_pending_funding = true;
-                       None
-               };
+               let funding_created = self.context.get_funding_created_msg(logger);
+               if funding_created.is_none() {
+                       if !self.context.signer_pending_funding {
+                               log_trace!(logger, "funding_created awaiting signer; setting signer_pending_funding");
+                               self.context.signer_pending_funding = true;
+                       }
+               }
 
                let channel = Channel {
                        context: self.context,