Merge pull request #3153 from alecchendev/2024-07-async-closing-signed

Allow sending closing tx signatures asynchronously
This commit is contained in:
Matt Corallo 2024-08-26 17:45:05 +00:00 committed by GitHub
commit ff0874adb7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 281 additions and 99 deletions

View file

@ -11,7 +11,7 @@
//! properly with a signer implementation that asynchronously derives signatures.
use std::collections::HashSet;
use bitcoin::key::Secp256k1;
use bitcoin::{Transaction, TxOut, TxIn, Amount};
use bitcoin::locktime::absolute::LockTime;
use bitcoin::transaction::Version;
@ -20,9 +20,13 @@ use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS;
use crate::chain::ChannelMonitorUpdateStatus;
use crate::events::bump_transaction::WalletSource;
use crate::events::{ClosureReason, Event, MessageSendEvent, MessageSendEventsProvider};
use crate::ln::{functional_test_utils::*, msgs};
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::chan_utils::ClosingTransaction;
use crate::ln::channel_state::{ChannelDetails, ChannelShutdownState};
use crate::ln::channelmanager::{PaymentId, RAACommitmentOrder, RecipientOnionFields};
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::{functional_test_utils::*, msgs};
use crate::sign::ecdsa::EcdsaChannelSigner;
use crate::sign::SignerProvider;
use crate::util::test_channel_signer::SignerOp;
use crate::util::logger::Logger;
@ -847,3 +851,105 @@ fn test_async_holder_signatures_anchors() {
fn test_async_holder_signatures_remote_commitment_anchors() {
do_test_async_holder_signatures(true, true);
}
#[test]
fn test_closing_signed() {
do_test_closing_signed(false);
do_test_closing_signed(true);
}
fn do_test_closing_signed(extra_closing_signed: bool) {
// Based off of `expect_channel_shutdown_state`.
// Test that we can asynchronously sign closing transactions.
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1);
expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::NotShuttingDown);
nodes[0].node.close_channel(&chan_1.2, &nodes[1].node.get_our_node_id()).unwrap();
expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::ShutdownInitiated);
expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::NotShuttingDown);
let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::ShutdownInitiated);
expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::NegotiatingClosingFee);
let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_1.2, SignerOp::SignClosingTransaction);
nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
expect_channel_shutdown_state!(nodes[0], chan_1.2, ChannelShutdownState::NegotiatingClosingFee);
expect_channel_shutdown_state!(nodes[1], chan_1.2, ChannelShutdownState::NegotiatingClosingFee);
let events = nodes[0].node.get_and_clear_pending_msg_events();
assert!(events.is_empty(), "Expected no events, got {:?}", events);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_1.2, SignerOp::SignClosingTransaction);
nodes[0].node.signer_unblocked(None);
let node_0_closing_signed = get_event_msg!(nodes[0], MessageSendEvent::SendClosingSigned, nodes[1].node.get_our_node_id());
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &chan_1.2, SignerOp::SignClosingTransaction);
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_closing_signed);
let events = nodes[1].node.get_and_clear_pending_msg_events();
assert!(events.is_empty(), "Expected no events, got {:?}", events);
nodes[1].enable_channel_signer_op(&nodes[0].node.get_our_node_id(), &chan_1.2, SignerOp::SignClosingTransaction);
nodes[1].node.signer_unblocked(None);
let node_1_closing_signed = get_event_msg!(nodes[1], MessageSendEvent::SendClosingSigned, nodes[0].node.get_our_node_id());
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_1.2, SignerOp::SignClosingTransaction);
nodes[0].node.handle_closing_signed(&nodes[1].node.get_our_node_id(), &node_1_closing_signed);
let events = nodes[0].node.get_and_clear_pending_msg_events();
assert!(events.is_empty(), "Expected no events, got {:?}", events);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_1.2, SignerOp::SignClosingTransaction);
if extra_closing_signed {
let node_1_closing_signed_2_bad = {
let mut node_1_closing_signed_2 = node_1_closing_signed.clone();
let holder_script = nodes[0].keys_manager.get_shutdown_scriptpubkey().unwrap();
let counterparty_script = nodes[1].keys_manager.get_shutdown_scriptpubkey().unwrap();
let funding_outpoint = bitcoin::OutPoint { txid: chan_1.3.txid(), vout: 0 };
let closing_tx_2 = ClosingTransaction::new(50000, 0, holder_script.into(),
counterparty_script.into(), funding_outpoint);
let per_peer_state = nodes[1].node.per_peer_state.read().unwrap();
let mut chan_lock = per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap();
let chan = chan_lock.channel_by_id.get_mut(&chan_1.2).map(|phase| phase.context_mut()).unwrap();
let signer = chan.get_mut_signer().as_mut_ecdsa().unwrap();
let signature = signer.sign_closing_transaction(&closing_tx_2, &Secp256k1::new()).unwrap();
node_1_closing_signed_2.signature = signature;
node_1_closing_signed_2
};
nodes[0].node.handle_closing_signed(&nodes[1].node.get_our_node_id(), &node_1_closing_signed_2_bad);
let events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
match events[0] {
MessageSendEvent::HandleError {
action: msgs::ErrorAction::SendWarningMessage { .. }, ref node_id
} => {
assert_eq!(node_id, &nodes[1].node.get_our_node_id());
},
_ => panic!("Unexpected event: {:?}", events[0]),
};
}
nodes[0].node.signer_unblocked(None);
let (_, node_0_2nd_closing_signed) = get_closing_signed_broadcast!(nodes[0].node, nodes[1].node.get_our_node_id());
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_2nd_closing_signed.unwrap());
let (_, node_1_closing_signed) = get_closing_signed_broadcast!(nodes[1].node, nodes[0].node.get_our_node_id());
assert!(node_1_closing_signed.is_none());
assert!(nodes[0].node.list_channels().is_empty());
assert!(nodes[1].node.list_channels().is_empty());
check_closed_event!(nodes[0], 1, ClosureReason::LocallyInitiatedCooperativeClosure, [nodes[1].node.get_our_node_id()], 100000);
check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyInitiatedCooperativeClosure, [nodes[0].node.get_our_node_id()], 100000);
}

View file

@ -28,7 +28,7 @@ use bitcoin::secp256k1;
use crate::ln::types::{ChannelId, PaymentPreimage, PaymentHash};
use crate::ln::features::{ChannelTypeFeatures, InitFeatures};
use crate::ln::msgs;
use crate::ln::msgs::DecodeError;
use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError};
use crate::ln::script::{self, ShutdownScript};
use crate::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo, InboundHTLCDetails, InboundHTLCStateDetails, OutboundHTLCDetails, OutboundHTLCStateDetails};
use crate::ln::channelmanager::{self, PendingHTLCStatus, HTLCSource, SentHTLCId, HTLCFailureMsg, PendingHTLCInfo, RAACommitmentOrder, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA, MAX_LOCAL_BREAKDOWN_TIMEOUT};
@ -907,6 +907,8 @@ pub(super) struct SignerResumeUpdates {
pub funding_signed: Option<msgs::FundingSigned>,
pub channel_ready: Option<msgs::ChannelReady>,
pub order: RAACommitmentOrder,
pub closing_signed: Option<msgs::ClosingSigned>,
pub signed_closing_tx: Option<Transaction>,
}
/// The return value of `channel_reestablish`
@ -1269,6 +1271,9 @@ pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
/// [`msgs::FundingCreated`] or [`msgs::FundingSigned`] depending on if this channel is
/// outbound or inbound.
signer_pending_funding: bool,
/// If we attempted to sign a cooperative close transaction but the signer wasn't ready, then this
/// will be set to `true`.
signer_pending_closing: bool,
// pending_update_fee is filled when sending and receiving update_fee.
//
@ -1300,7 +1305,9 @@ pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
/// Max to_local and to_remote outputs in a remote-generated commitment transaction
counterparty_max_commitment_tx_output: Mutex<(u64, u64)>,
last_sent_closing_fee: Option<(u64, Signature)>, // (fee, holder_sig)
// (fee_sats, skip_remote_output, fee_range, holder_sig)
last_sent_closing_fee: Option<(u64, bool, ClosingSignedFeeRange, Option<Signature>)>,
last_received_closing_sig: Option<Signature>,
target_closing_feerate_sats_per_kw: Option<u32>,
/// If our counterparty sent us a closing_signed while we were waiting for a `ChannelMonitor`
@ -1737,6 +1744,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
signer_pending_revoke_and_ack: false,
signer_pending_commitment_update: false,
signer_pending_funding: false,
signer_pending_closing: false,
#[cfg(debug_assertions)]
@ -1745,6 +1753,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
counterparty_max_commitment_tx_output: Mutex::new((value_to_self_msat, (channel_value_satoshis * 1000 - msg_push_msat).saturating_sub(value_to_self_msat))),
last_sent_closing_fee: None,
last_received_closing_sig: None,
pending_counterparty_closing_signed: None,
expecting_peer_commitment_signed: false,
closing_fee_limits: None,
@ -1969,6 +1978,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
signer_pending_revoke_and_ack: false,
signer_pending_commitment_update: false,
signer_pending_funding: false,
signer_pending_closing: false,
// We'll add our counterparty's `funding_satoshis` to these max commitment output assertions
// when we receive `accept_channel2`.
@ -1978,6 +1988,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
counterparty_max_commitment_tx_output: Mutex::new((channel_value_satoshis * 1000 - push_msat, push_msat)),
last_sent_closing_fee: None,
last_received_closing_sig: None,
pending_counterparty_closing_signed: None,
expecting_peer_commitment_signed: false,
closing_fee_limits: None,
@ -5492,12 +5503,35 @@ impl<SP: Deref> Channel<SP> where
commitment_update = None;
}
log_trace!(logger, "Signer unblocked with {} commitment_update, {} revoke_and_ack, {} funding_signed and {} channel_ready, with resend order {:?}",
let (closing_signed, signed_closing_tx) = if self.context.signer_pending_closing {
debug_assert!(self.context.last_sent_closing_fee.is_some());
if let Some((fee, skip_remote_output, fee_range, holder_sig)) = self.context.last_sent_closing_fee.clone() {
debug_assert!(holder_sig.is_none());
log_trace!(logger, "Attempting to generate pending closing_signed...");
let (closing_tx, fee) = self.build_closing_transaction(fee, skip_remote_output);
let closing_signed = self.get_closing_signed_msg(&closing_tx, skip_remote_output,
fee, fee_range.min_fee_satoshis, fee_range.max_fee_satoshis, logger);
let signed_tx = if let (Some(ClosingSigned { signature, .. }), Some(counterparty_sig)) =
(closing_signed.as_ref(), self.context.last_received_closing_sig) {
let funding_redeemscript = self.context.get_funding_redeemscript();
let sighash = closing_tx.trust().get_sighash_all(&funding_redeemscript, self.context.channel_value_satoshis);
debug_assert!(self.context.secp_ctx.verify_ecdsa(&sighash, &counterparty_sig,
&self.context.get_counterparty_pubkeys().funding_pubkey).is_ok());
Some(self.build_signed_closing_transaction(&closing_tx, &counterparty_sig, signature))
} else { None };
(closing_signed, signed_tx)
} else { (None, None) }
} else { (None, None) };
log_trace!(logger, "Signer unblocked with {} commitment_update, {} revoke_and_ack, with resend order {:?}, {} funding_signed, {} channel_ready,
{} closing_signed, and {} signed_closing_tx",
if commitment_update.is_some() { "a" } else { "no" },
if revoke_and_ack.is_some() { "a" } else { "no" },
self.context.resend_order,
if funding_signed.is_some() { "a" } else { "no" },
if channel_ready.is_some() { "a" } else { "no" },
self.context.resend_order);
if closing_signed.is_some() { "a" } else { "no" },
if signed_closing_tx.is_some() { "a" } else { "no" });
SignerResumeUpdates {
commitment_update,
@ -5505,6 +5539,8 @@ impl<SP: Deref> Channel<SP> where
funding_signed,
channel_ready,
order: self.context.resend_order.clone(),
closing_signed,
signed_closing_tx,
}
}
@ -5935,7 +5971,7 @@ impl<SP: Deref> Channel<SP> where
if !self.context.is_outbound() {
if let Some(msg) = &self.context.pending_counterparty_closing_signed.take() {
return self.closing_signed(fee_estimator, &msg);
return self.closing_signed(fee_estimator, &msg, logger);
}
return Ok((None, None, None));
}
@ -5953,27 +5989,8 @@ impl<SP: Deref> Channel<SP> where
log_trace!(logger, "Proposing initial closing_signed for our counterparty with a fee range of {}-{} sat (with initial proposal {} sats)",
our_min_fee, our_max_fee, total_fee_satoshis);
match &self.context.holder_signer {
ChannelSignerType::Ecdsa(ecdsa) => {
let sig = ecdsa
.sign_closing_transaction(&closing_tx, &self.context.secp_ctx)
.map_err(|()| ChannelError::close("Failed to get signature for closing transaction.".to_owned()))?;
self.context.last_sent_closing_fee = Some((total_fee_satoshis, sig.clone()));
Ok((Some(msgs::ClosingSigned {
channel_id: self.context.channel_id,
fee_satoshis: total_fee_satoshis,
signature: sig,
fee_range: Some(msgs::ClosingSignedFeeRange {
min_fee_satoshis: our_min_fee,
max_fee_satoshis: our_max_fee,
}),
}), None, None))
},
// TODO (taproot|arik)
#[cfg(taproot)]
_ => todo!()
}
let closing_signed = self.get_closing_signed_msg(&closing_tx, false, total_fee_satoshis, our_min_fee, our_max_fee, logger);
Ok((closing_signed, None, None))
}
// Marks a channel as waiting for a response from the counterparty. If it's not received
@ -6120,11 +6137,42 @@ impl<SP: Deref> Channel<SP> where
tx
}
pub fn closing_signed<F: Deref>(
&mut self, fee_estimator: &LowerBoundedFeeEstimator<F>, msg: &msgs::ClosingSigned)
-> Result<(Option<msgs::ClosingSigned>, Option<Transaction>, Option<ShutdownResult>), ChannelError>
where F::Target: FeeEstimator
fn get_closing_signed_msg<L: Deref>(
&mut self, closing_tx: &ClosingTransaction, skip_remote_output: bool,
fee_satoshis: u64, min_fee_satoshis: u64, max_fee_satoshis: u64, logger: &L
) -> Option<msgs::ClosingSigned>
where L::Target: Logger
{
let sig = match &self.context.holder_signer {
ChannelSignerType::Ecdsa(ecdsa) => ecdsa.sign_closing_transaction(closing_tx, &self.context.secp_ctx).ok(),
// TODO (taproot|arik)
#[cfg(taproot)]
_ => todo!()
};
if sig.is_none() {
log_trace!(logger, "Closing transaction signature unavailable, waiting on signer");
self.context.signer_pending_closing = true;
} else {
self.context.signer_pending_closing = false;
}
let fee_range = msgs::ClosingSignedFeeRange { min_fee_satoshis, max_fee_satoshis };
self.context.last_sent_closing_fee = Some((fee_satoshis, skip_remote_output, fee_range.clone(), sig.clone()));
sig.map(|signature| msgs::ClosingSigned {
channel_id: self.context.channel_id,
fee_satoshis,
signature,
fee_range: Some(fee_range),
})
}
pub fn closing_signed<F: Deref, L: Deref>(
&mut self, fee_estimator: &LowerBoundedFeeEstimator<F>, msg: &msgs::ClosingSigned, logger: &L)
-> Result<(Option<msgs::ClosingSigned>, Option<Transaction>, Option<ShutdownResult>), ChannelError>
where F::Target: FeeEstimator, L::Target: Logger
{
if self.is_shutdown_pending_signature() {
return Err(ChannelError::Warn(String::from("Remote end sent us a closing_signed while fully shutdown and just waiting on the final closing signature")));
}
if !self.context.channel_state.is_both_sides_shutdown() {
return Err(ChannelError::close("Remote end sent us a closing_signed before both sides provided a shutdown".to_owned()));
}
@ -6148,7 +6196,8 @@ impl<SP: Deref> Channel<SP> where
}
let funding_redeemscript = self.context.get_funding_redeemscript();
let (mut closing_tx, used_total_fee) = self.build_closing_transaction(msg.fee_satoshis, false);
let mut skip_remote_output = false;
let (mut closing_tx, used_total_fee) = self.build_closing_transaction(msg.fee_satoshis, skip_remote_output);
if used_total_fee != msg.fee_satoshis {
return Err(ChannelError::close(format!("Remote sent us a closing_signed with a fee other than the value they can claim. Fee in message: {}. Actual closing tx fee: {}", msg.fee_satoshis, used_total_fee)));
}
@ -6159,7 +6208,8 @@ impl<SP: Deref> Channel<SP> where
Err(_e) => {
// The remote end may have decided to revoke their output due to inconsistent dust
// limits, so check for that case by re-checking the signature here.
closing_tx = self.build_closing_transaction(msg.fee_satoshis, true).0;
skip_remote_output = true;
closing_tx = self.build_closing_transaction(msg.fee_satoshis, skip_remote_output).0;
let sighash = closing_tx.trust().get_sighash_all(&funding_redeemscript, self.context.channel_value_satoshis);
secp_check!(self.context.secp_ctx.verify_ecdsa(&sighash, &msg.signature, self.context.counterparty_funding_pubkey()), "Invalid closing tx signature from peer".to_owned());
},
@ -6178,7 +6228,7 @@ impl<SP: Deref> Channel<SP> where
};
assert!(self.context.shutdown_scriptpubkey.is_some());
if let Some((last_fee, sig)) = self.context.last_sent_closing_fee {
if let Some((last_fee, _, _, Some(sig))) = self.context.last_sent_closing_fee {
if last_fee == msg.fee_satoshis {
let shutdown_result = ShutdownResult {
closure_reason,
@ -6206,50 +6256,36 @@ impl<SP: Deref> Channel<SP> where
let (closing_tx, used_fee) = if $new_fee == msg.fee_satoshis {
(closing_tx, $new_fee)
} else {
self.build_closing_transaction($new_fee, false)
skip_remote_output = false;
self.build_closing_transaction($new_fee, skip_remote_output)
};
return match &self.context.holder_signer {
ChannelSignerType::Ecdsa(ecdsa) => {
let sig = ecdsa
.sign_closing_transaction(&closing_tx, &self.context.secp_ctx)
.map_err(|_| ChannelError::close("External signer refused to sign closing transaction".to_owned()))?;
let (signed_tx, shutdown_result) = if $new_fee == msg.fee_satoshis {
let shutdown_result = ShutdownResult {
closure_reason,
monitor_update: None,
dropped_outbound_htlcs: Vec::new(),
unbroadcasted_batch_funding_txid: self.context.unbroadcasted_batch_funding_txid(),
channel_id: self.context.channel_id,
user_channel_id: self.context.user_id,
channel_capacity_satoshis: self.context.channel_value_satoshis,
counterparty_node_id: self.context.counterparty_node_id,
unbroadcasted_funding_tx: self.context.unbroadcasted_funding(),
channel_funding_txo: self.context.get_funding_txo(),
};
self.context.channel_state = ChannelState::ShutdownComplete;
self.context.update_time_counter += 1;
let tx = self.build_signed_closing_transaction(&closing_tx, &msg.signature, &sig);
(Some(tx), Some(shutdown_result))
} else {
(None, None)
};
self.context.last_sent_closing_fee = Some((used_fee, sig.clone()));
Ok((Some(msgs::ClosingSigned {
channel_id: self.context.channel_id,
fee_satoshis: used_fee,
signature: sig,
fee_range: Some(msgs::ClosingSignedFeeRange {
min_fee_satoshis: our_min_fee,
max_fee_satoshis: our_max_fee,
}),
}), signed_tx, shutdown_result))
},
// TODO (taproot|arik)
#[cfg(taproot)]
_ => todo!()
}
let closing_signed = self.get_closing_signed_msg(&closing_tx, skip_remote_output, used_fee, our_min_fee, our_max_fee, logger);
let (signed_tx, shutdown_result) = if $new_fee == msg.fee_satoshis {
let shutdown_result = ShutdownResult {
closure_reason,
monitor_update: None,
dropped_outbound_htlcs: Vec::new(),
unbroadcasted_batch_funding_txid: self.context.unbroadcasted_batch_funding_txid(),
channel_id: self.context.channel_id,
user_channel_id: self.context.user_id,
channel_capacity_satoshis: self.context.channel_value_satoshis,
counterparty_node_id: self.context.counterparty_node_id,
unbroadcasted_funding_tx: self.context.unbroadcasted_funding(),
channel_funding_txo: self.context.get_funding_txo(),
};
if closing_signed.is_some() {
self.context.channel_state = ChannelState::ShutdownComplete;
}
self.context.update_time_counter += 1;
self.context.last_received_closing_sig = Some(msg.signature.clone());
let tx = closing_signed.as_ref().map(|ClosingSigned { signature, .. }|
self.build_signed_closing_transaction(&closing_tx, &msg.signature, signature));
(tx, Some(shutdown_result))
} else {
(None, None)
};
return Ok((closing_signed, signed_tx, shutdown_result))
}
}
@ -6280,7 +6316,7 @@ impl<SP: Deref> Channel<SP> where
} else {
// Old fee style negotiation. We don't bother to enforce whether they are complying
// with the "making progress" requirements, we just comply and hope for the best.
if let Some((last_fee, _)) = self.context.last_sent_closing_fee {
if let Some((last_fee, _, _, _)) = self.context.last_sent_closing_fee {
if msg.fee_satoshis > last_fee {
if msg.fee_satoshis < our_max_fee {
propose_fee!(msg.fee_satoshis);
@ -6603,6 +6639,12 @@ impl<SP: Deref> Channel<SP> where
matches!(self.context.channel_state, ChannelState::ShutdownComplete)
}
pub fn is_shutdown_pending_signature(&self) -> bool {
matches!(self.context.channel_state, ChannelState::ChannelReady(_))
&& self.context.signer_pending_closing
&& self.context.last_received_closing_sig.is_some()
}
pub fn channel_update_status(&self) -> ChannelUpdateStatus {
self.context.channel_update_status
}
@ -9468,6 +9510,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
signer_pending_revoke_and_ack: false,
signer_pending_commitment_update: false,
signer_pending_funding: false,
signer_pending_closing: false,
pending_update_fee,
holding_cell_update_fee,
@ -9482,6 +9525,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
counterparty_max_commitment_tx_output: Mutex::new((0, 0)),
last_sent_closing_fee: None,
last_received_closing_sig: None,
pending_counterparty_closing_signed: None,
expecting_peer_commitment_signed: false,
closing_fee_limits: None,

View file

@ -7816,8 +7816,9 @@ where
match peer_state.channel_by_id.entry(msg.channel_id.clone()) {
hash_map::Entry::Occupied(mut chan_phase_entry) => {
if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() {
let (closing_signed, tx, shutdown_result) = try_chan_phase_entry!(self, chan.closing_signed(&self.fee_estimator, &msg), chan_phase_entry);
debug_assert_eq!(shutdown_result.is_some(), chan.is_shutdown());
let logger = WithChannelContext::from(&self.logger, &chan.context, None);
let (closing_signed, tx, shutdown_result) = try_chan_phase_entry!(self, chan.closing_signed(&self.fee_estimator, &msg, &&logger), chan_phase_entry);
debug_assert_eq!(shutdown_result.is_some(), chan.is_shutdown() || chan.is_shutdown_pending_signature());
if let Some(msg) = closing_signed {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendClosingSigned {
node_id: counterparty_node_id.clone(),
@ -8639,7 +8640,8 @@ where
pub fn signer_unblocked(&self, channel_opt: Option<(PublicKey, ChannelId)>) {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
let unblock_chan = |phase: &mut ChannelPhase<SP>, pending_msg_events: &mut Vec<MessageSendEvent>| {
// Returns whether we should remove this channel as it's just been closed.
let unblock_chan = |phase: &mut ChannelPhase<SP>, pending_msg_events: &mut Vec<MessageSendEvent>| -> bool {
let node_id = phase.context().get_counterparty_node_id();
match phase {
ChannelPhase::Funded(chan) => {
@ -8674,6 +8676,29 @@ where
if let Some(msg) = msgs.channel_ready {
send_channel_ready!(self, pending_msg_events, chan, msg);
}
if let Some(msg) = msgs.closing_signed {
pending_msg_events.push(events::MessageSendEvent::SendClosingSigned {
node_id,
msg,
});
}
if let Some(broadcast_tx) = msgs.signed_closing_tx {
let channel_id = chan.context.channel_id();
let counterparty_node_id = chan.context.get_counterparty_node_id();
let logger = WithContext::from(&self.logger, Some(counterparty_node_id), Some(channel_id), None);
log_info!(logger, "Broadcasting closing tx {}", log_tx!(broadcast_tx));
self.tx_broadcaster.broadcast_transactions(&[&broadcast_tx]);
if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
msg: update
});
}
// We should return true to remove the channel if we just
// broadcasted the closing transaction.
true
} else { false }
}
ChannelPhase::UnfundedOutboundV1(chan) => {
if let Some(msg) = chan.signer_maybe_unblocked(&self.logger) {
@ -8682,28 +8707,31 @@ where
msg,
});
}
false
}
ChannelPhase::UnfundedInboundV1(_) => {},
ChannelPhase::UnfundedInboundV1(_) => false,
}
};
let per_peer_state = self.per_peer_state.read().unwrap();
if let Some((counterparty_node_id, channel_id)) = channel_opt {
if let Some(peer_state_mutex) = per_peer_state.get(&counterparty_node_id) {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
if let Some(chan) = peer_state.channel_by_id.get_mut(&channel_id) {
unblock_chan(chan, &mut peer_state.pending_msg_events);
let per_peer_state_iter = per_peer_state.iter().filter(|(cp_id, _)| {
if let Some((counterparty_node_id, _)) = channel_opt {
**cp_id == counterparty_node_id
} else { true }
});
for (_cp_id, peer_state_mutex) in per_peer_state_iter {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
peer_state.channel_by_id.retain(|_, chan| {
let should_remove = match channel_opt {
Some((_, channel_id)) if chan.context().channel_id() != channel_id => false,
_ => unblock_chan(chan, &mut peer_state.pending_msg_events),
};
if should_remove {
log_trace!(self.logger, "Removing channel after unblocking signer");
}
}
} else {
for (_cp_id, peer_state_mutex) in per_peer_state.iter() {
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
for (_, chan) in peer_state.channel_by_id.iter_mut() {
unblock_chan(chan, &mut peer_state.pending_msg_events);
}
}
!should_remove
});
}
}
@ -8733,8 +8761,8 @@ where
node_id: chan.context.get_counterparty_node_id(), msg,
});
}
debug_assert_eq!(shutdown_result_opt.is_some(), chan.is_shutdown());
if let Some(shutdown_result) = shutdown_result_opt {
debug_assert!(chan.is_shutdown() || chan.is_shutdown_pending_signature());
shutdown_results.push(shutdown_result);
}
if let Some(tx) = tx_opt {

View file

@ -325,6 +325,10 @@ impl EcdsaChannelSigner for TestChannelSigner {
}
fn sign_closing_transaction(&self, closing_tx: &ClosingTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()> {
#[cfg(test)]
if !self.is_signer_available(SignerOp::SignClosingTransaction) {
return Err(());
}
closing_tx.verify(self.inner.funding_outpoint().unwrap().into_bitcoin_outpoint())
.expect("derived different closing transaction");
Ok(self.inner.sign_closing_transaction(closing_tx, secp_ctx).unwrap())