Skip to content

Commit fa6de18

Browse files
committed
Allow sending closing tx signatures asynchronously
1 parent 5f3c6bf commit fa6de18

File tree

2 files changed

+113
-45
lines changed

2 files changed

+113
-45
lines changed

lightning/src/ln/channel.rs

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,8 @@ pub(super) struct SignerResumeUpdates {
906906
pub funding_signed: Option<msgs::FundingSigned>,
907907
pub channel_ready: Option<msgs::ChannelReady>,
908908
pub order: RAACommitmentOrder,
909+
pub closing_signed: Option<msgs::ClosingSigned>,
910+
pub signed_closing_tx: Option<Transaction>,
909911
}
910912

911913
/// The return value of `channel_reestablish`
@@ -1268,6 +1270,9 @@ pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
12681270
/// [`msgs::FundingCreated`] or [`msgs::FundingSigned`] depending on if this channel is
12691271
/// outbound or inbound.
12701272
signer_pending_funding: bool,
1273+
/// If we attempted to sign a cooperative close transaction but the signer wasn't ready, then this
1274+
/// will be set to `true`.
1275+
signer_pending_closing: bool,
12711276

12721277
// pending_update_fee is filled when sending and receiving update_fee.
12731278
//
@@ -1299,7 +1304,9 @@ pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
12991304
/// Max to_local and to_remote outputs in a remote-generated commitment transaction
13001305
counterparty_max_commitment_tx_output: Mutex<(u64, u64)>,
13011306

1302-
last_sent_closing_fee: Option<(u64, Signature)>, // (fee, holder_sig)
1307+
// (fee_sats, skip_remote_output, fee_range, holder_sig)
1308+
last_sent_closing_fee: Option<(u64, bool, ClosingSignedFeeRange, Option<Signature>)>,
1309+
last_received_closing_sig: Option<Signature>,
13031310
target_closing_feerate_sats_per_kw: Option<u32>,
13041311

13051312
/// If our counterparty sent us a closing_signed while we were waiting for a `ChannelMonitor`
@@ -1736,6 +1743,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
17361743
signer_pending_revoke_and_ack: false,
17371744
signer_pending_commitment_update: false,
17381745
signer_pending_funding: false,
1746+
signer_pending_closing: false,
17391747

17401748

17411749
#[cfg(debug_assertions)]
@@ -1744,6 +1752,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
17441752
counterparty_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))),
17451753

17461754
last_sent_closing_fee: None,
1755+
last_received_closing_sig: None,
17471756
pending_counterparty_closing_signed: None,
17481757
expecting_peer_commitment_signed: false,
17491758
closing_fee_limits: None,
@@ -1968,6 +1977,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
19681977
signer_pending_revoke_and_ack: false,
19691978
signer_pending_commitment_update: false,
19701979
signer_pending_funding: false,
1980+
signer_pending_closing: false,
19711981

19721982
// We'll add our counterparty's `funding_satoshis` to these max commitment output assertions
19731983
// when we receive `accept_channel2`.
@@ -1977,6 +1987,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
19771987
counterparty_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)),
19781988

19791989
last_sent_closing_fee: None,
1990+
last_received_closing_sig: None,
19801991
pending_counterparty_closing_signed: None,
19811992
expecting_peer_commitment_signed: false,
19821993
closing_fee_limits: None,
@@ -5490,19 +5501,44 @@ impl<SP: Deref> Channel<SP> where
54905501
commitment_update = None;
54915502
}
54925503

5493-
log_trace!(logger, "Signer unblocked with {} commitment_update, {} revoke_and_ack, {} funding_signed and {} channel_ready, with resend order {:?}",
5504+
let (closing_signed, signed_closing_tx) = if self.context.signer_pending_closing {
5505+
debug_assert!(self.context.last_sent_closing_fee.is_some());
5506+
if let Some((fee, skip_remote_output, fee_range, holder_sig)) = self.context.last_sent_closing_fee.clone() {
5507+
debug_assert!(holder_sig.is_none());
5508+
log_trace!(logger, "Attempting to generate pending closing_signed...");
5509+
let (closing_tx, fee) = self.build_closing_transaction(fee, skip_remote_output);
5510+
let closing_signed = self.get_closing_signed_msg(&closing_tx, skip_remote_output,
5511+
fee, fee_range.min_fee_satoshis, fee_range.max_fee_satoshis, logger);
5512+
let signed_tx = if let (Some(ClosingSigned { signature, .. }), Some(counterparty_sig)) =
5513+
(closing_signed.as_ref(), self.context.last_received_closing_sig) {
5514+
let funding_redeemscript = self.context.get_funding_redeemscript();
5515+
let sighash = closing_tx.trust().get_sighash_all(&funding_redeemscript, self.context.channel_value_satoshis);
5516+
debug_assert!(self.context.secp_ctx.verify_ecdsa(&sighash, &counterparty_sig,
5517+
&self.context.get_counterparty_pubkeys().funding_pubkey).is_ok());
5518+
Some(self.build_signed_closing_transaction(&closing_tx, &counterparty_sig, signature))
5519+
} else { None };
5520+
(closing_signed, signed_tx)
5521+
} else { (None, None) }
5522+
} else { (None, None) };
5523+
5524+
log_trace!(logger, "Signer unblocked with {} commitment_update, {} revoke_and_ack, with resend order {:?}, {} funding_signed, {} channel_ready,
5525+
{} closing_signed, and {} signed_closing_tx",
54945526
if commitment_update.is_some() { "a" } else { "no" },
54955527
if revoke_and_ack.is_some() { "a" } else { "no" },
5528+
self.context.resend_order,
54965529
if funding_signed.is_some() { "a" } else { "no" },
54975530
if channel_ready.is_some() { "a" } else { "no" },
5498-
self.context.resend_order);
5531+
if closing_signed.is_some() { "a" } else { "no" },
5532+
if signed_closing_tx.is_some() { "a" } else { "no" });
54995533

55005534
SignerResumeUpdates {
55015535
commitment_update,
55025536
revoke_and_ack,
55035537
funding_signed,
55045538
channel_ready,
55055539
order: self.context.resend_order.clone(),
5540+
closing_signed,
5541+
signed_closing_tx,
55065542
}
55075543
}
55085544

@@ -5952,9 +5988,6 @@ impl<SP: Deref> Channel<SP> where
59525988
our_min_fee, our_max_fee, total_fee_satoshis);
59535989

59545990
let closing_signed = self.get_closing_signed_msg(&closing_tx, false, total_fee_satoshis, our_min_fee, our_max_fee, logger);
5955-
if closing_signed.is_none() {
5956-
return Err(ChannelError::close("Failed to get signature for closing transaction.".to_owned()));
5957-
}
59585991
Ok((closing_signed, None, None))
59595992
}
59605993

@@ -6108,33 +6141,41 @@ impl<SP: Deref> Channel<SP> where
61086141
) -> Option<msgs::ClosingSigned>
61096142
where L::Target: Logger
61106143
{
6111-
match &self.context.holder_signer {
6112-
ChannelSignerType::Ecdsa(ecdsa) => {
6113-
let fee_range = msgs::ClosingSignedFeeRange {
6114-
min_fee_satoshis,
6115-
max_fee_satoshis,
6116-
};
6117-
let sig = ecdsa.sign_closing_transaction(closing_tx, &self.context.secp_ctx).ok()?;
6118-
6119-
self.context.last_sent_closing_fee = Some((fee_satoshis, sig.clone()));
6120-
Some(msgs::ClosingSigned {
6121-
channel_id: self.context.channel_id,
6122-
fee_satoshis,
6123-
signature: sig,
6124-
fee_range: Some(fee_range),
6125-
})
6126-
},
6144+
let sig = match &self.context.holder_signer {
6145+
ChannelSignerType::Ecdsa(ecdsa) => ecdsa.sign_closing_transaction(closing_tx, &self.context.secp_ctx).ok(),
61276146
// TODO (taproot|arik)
61286147
#[cfg(taproot)]
61296148
_ => todo!()
6149+
};
6150+
if sig.is_none() {
6151+
log_trace!(logger, "Closing transaction signature unavailable, waiting on signer");
6152+
self.context.signer_pending_closing = true;
6153+
} else {
6154+
self.context.signer_pending_closing = false;
61306155
}
6156+
let fee_range = msgs::ClosingSignedFeeRange { min_fee_satoshis, max_fee_satoshis };
6157+
self.context.last_sent_closing_fee = Some((fee_satoshis, skip_remote_output, fee_range.clone(), sig.clone()));
6158+
sig.map(|signature| msgs::ClosingSigned {
6159+
channel_id: self.context.channel_id,
6160+
fee_satoshis,
6161+
signature,
6162+
fee_range: Some(fee_range),
6163+
})
61316164
}
61326165

61336166
pub fn closing_signed<F: Deref, L: Deref>(
61346167
&mut self, fee_estimator: &LowerBoundedFeeEstimator<F>, msg: &msgs::ClosingSigned, logger: &L)
61356168
-> Result<(Option<msgs::ClosingSigned>, Option<Transaction>, Option<ShutdownResult>), ChannelError>
61366169
where F::Target: FeeEstimator, L::Target: Logger
61376170
{
6171+
if matches!(self.context.channel_state, ChannelState::ShutdownComplete) {
6172+
// During async signing, we may need to keep the channel around
6173+
// even after it's been fully closed, so that we can construct
6174+
// and sign the final transaction to send back to the peer.
6175+
// If they send us another closing_signed, we should act as
6176+
// if the channel has already been fully closed.
6177+
return Err(ChannelError::Warn(String::from("Remote end sent us a closing_signed after shutdown complete")));
6178+
}
61386179
if !self.context.channel_state.is_both_sides_shutdown() {
61396180
return Err(ChannelError::close("Remote end sent us a closing_signed before both sides provided a shutdown".to_owned()));
61406181
}
@@ -6190,7 +6231,7 @@ impl<SP: Deref> Channel<SP> where
61906231
};
61916232

61926233
assert!(self.context.shutdown_scriptpubkey.is_some());
6193-
if let Some((last_fee, sig)) = self.context.last_sent_closing_fee {
6234+
if let Some((last_fee, _, _, Some(sig))) = self.context.last_sent_closing_fee {
61946235
if last_fee == msg.fee_satoshis {
61956236
let shutdown_result = ShutdownResult {
61966237
closure_reason,
@@ -6223,9 +6264,6 @@ impl<SP: Deref> Channel<SP> where
62236264
};
62246265

62256266
let closing_signed = self.get_closing_signed_msg(&closing_tx, skip_remote_output, used_fee, our_min_fee, our_max_fee, logger);
6226-
if closing_signed.is_none() {
6227-
return Err(ChannelError::close("Failed to get signature for closing transaction.".to_owned()));
6228-
}
62296267
let (signed_tx, shutdown_result) = if $new_fee == msg.fee_satoshis {
62306268
let shutdown_result = ShutdownResult {
62316269
closure_reason,
@@ -6241,6 +6279,7 @@ impl<SP: Deref> Channel<SP> where
62416279
};
62426280
self.context.channel_state = ChannelState::ShutdownComplete;
62436281
self.context.update_time_counter += 1;
6282+
self.context.last_received_closing_sig = Some(msg.signature.clone());
62446283
let tx = closing_signed.as_ref().map(|ClosingSigned { signature, .. }|
62456284
self.build_signed_closing_transaction(&closing_tx, &msg.signature, signature));
62466285
(tx, Some(shutdown_result))
@@ -6278,7 +6317,7 @@ impl<SP: Deref> Channel<SP> where
62786317
} else {
62796318
// Old fee style negotiation. We don't bother to enforce whether they are complying
62806319
// with the "making progress" requirements, we just comply and hope for the best.
6281-
if let Some((last_fee, _)) = self.context.last_sent_closing_fee {
6320+
if let Some((last_fee, _, _, _)) = self.context.last_sent_closing_fee {
62826321
if msg.fee_satoshis > last_fee {
62836322
if msg.fee_satoshis < our_max_fee {
62846323
propose_fee!(msg.fee_satoshis);
@@ -9466,6 +9505,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
94669505
signer_pending_revoke_and_ack: false,
94679506
signer_pending_commitment_update: false,
94689507
signer_pending_funding: false,
9508+
signer_pending_closing: false,
94699509

94709510
pending_update_fee,
94719511
holding_cell_update_fee,
@@ -9480,6 +9520,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
94809520
counterparty_max_commitment_tx_output: Mutex::new((0, 0)),
94819521

94829522
last_sent_closing_fee: None,
9523+
last_received_closing_sig: None,
94839524
pending_counterparty_closing_signed: None,
94849525
expecting_peer_commitment_signed: false,
94859526
closing_fee_limits: None,

lightning/src/ln/channelmanager.rs

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8617,7 +8617,8 @@ where
86178617
pub fn signer_unblocked(&self, channel_opt: Option<(PublicKey, ChannelId)>) {
86188618
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
86198619

8620-
let unblock_chan = |phase: &mut ChannelPhase<SP>, pending_msg_events: &mut Vec<MessageSendEvent>| {
8620+
// Returns whether we should remove this channel as it's just been closed.
8621+
let unblock_chan = |phase: &mut ChannelPhase<SP>, pending_msg_events: &mut Vec<MessageSendEvent>| -> bool {
86218622
let node_id = phase.context().get_counterparty_node_id();
86228623
match phase {
86238624
ChannelPhase::Funded(chan) => {
@@ -8652,6 +8653,29 @@ where
86528653
if let Some(msg) = msgs.channel_ready {
86538654
send_channel_ready!(self, pending_msg_events, chan, msg);
86548655
}
8656+
if let Some(msg) = msgs.closing_signed {
8657+
pending_msg_events.push(events::MessageSendEvent::SendClosingSigned {
8658+
node_id,
8659+
msg,
8660+
});
8661+
}
8662+
if let Some(broadcast_tx) = msgs.signed_closing_tx {
8663+
let channel_id = chan.context.channel_id();
8664+
let counterparty_node_id = chan.context.get_counterparty_node_id();
8665+
let logger = WithContext::from(&self.logger, Some(counterparty_node_id), Some(channel_id), None);
8666+
log_info!(logger, "Broadcasting closing tx {}", log_tx!(broadcast_tx));
8667+
self.tx_broadcaster.broadcast_transactions(&[&broadcast_tx]);
8668+
8669+
if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
8670+
pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
8671+
msg: update
8672+
});
8673+
}
8674+
8675+
// We should return true to remove the channel if we just
8676+
// broadcasted the closing transaction.
8677+
true
8678+
} else { false }
86558679
}
86568680
ChannelPhase::UnfundedOutboundV1(chan) => {
86578681
if let Some(msg) = chan.signer_maybe_unblocked(&self.logger) {
@@ -8660,28 +8684,31 @@ where
86608684
msg,
86618685
});
86628686
}
8687+
false
86638688
}
8664-
ChannelPhase::UnfundedInboundV1(_) => {},
8689+
ChannelPhase::UnfundedInboundV1(_) => false,
86658690
}
86668691
};
86678692

86688693
let per_peer_state = self.per_peer_state.read().unwrap();
8669-
if let Some((counterparty_node_id, channel_id)) = channel_opt {
8670-
if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) {
8671-
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
8672-
let peer_state = &mut *peer_state_lock;
8673-
if let Some(chan) = peer_state.channel_by_id.get_mut(&channel_id) {
8674-
unblock_chan(chan, &mut peer_state.pending_msg_events);
8675-
}
8676-
}
8677-
} else {
8678-
for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
8679-
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
8680-
let peer_state = &mut *peer_state_lock;
8681-
for (_, chan) in peer_state.channel_by_id.iter_mut() {
8682-
unblock_chan(chan, &mut peer_state.pending_msg_events);
8694+
let per_peer_state_iter = per_peer_state.iter().filter(|(cp_id, _)| {
8695+
if let Some((counterparty_node_id, _)) = channel_opt {
8696+
**cp_id == counterparty_node_id
8697+
} else { true }
8698+
});
8699+
for (_cp_id, peer_state_mutex) in per_peer_state_iter {
8700+
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
8701+
let peer_state = &mut *peer_state_lock;
8702+
peer_state.channel_by_id.retain(|_, chan| {
8703+
let should_remove = match channel_opt {
8704+
Some((_, channel_id)) if chan.context().channel_id() != channel_id => false,
8705+
_ => unblock_chan(chan, &mut peer_state.pending_msg_events),
8706+
};
8707+
if should_remove {
8708+
log_trace!(self.logger, "Removing channel after unblocking signer");
86838709
}
8684-
}
8710+
!should_remove
8711+
});
86858712
}
86868713
}
86878714

@@ -8711,8 +8738,8 @@ where
87118738
node_id: chan.context.get_counterparty_node_id(), msg,
87128739
});
87138740
}
8714-
debug_assert_eq!(shutdown_result_opt.is_some(), chan.is_shutdown());
87158741
if let Some(shutdown_result) = shutdown_result_opt {
8742+
debug_assert!(chan.is_shutdown());
87168743
shutdown_results.push(shutdown_result);
87178744
}
87188745
if let Some(tx) = tx_opt {

0 commit comments

Comments
 (0)