Skip to content

Commit ee74209

Browse files
committed
Support async signing of interactive-tx initial commitment signatures
This commit allows for an async signer to immediately return upon a call to `EcdsaChannelSigner::sign_counterparty_commitment` for the initial commitment signatures of an interactively funded transaction, such that they can call back in via `ChannelManager::signer_unblocked` once the signatures are ready. This is done for both splices and dual-funded channels, though note that the latter still require more work to be integrated. Since `tx_signatures` must be sent only after exchanging `commitment_signed`, we make sure to hold them back if they're ready to be sent until our `commitment_signed` is also ready.
1 parent 86f914c commit ee74209

File tree

3 files changed

+237
-51
lines changed

3 files changed

+237
-51
lines changed

lightning/src/ln/async_signer_tests.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010
//! Tests for asynchronous signing. These tests verify that the channel state machine behaves
1111
//! properly with a signer implementation that asynchronously derives signatures.
1212
13+
use crate::events::bump_transaction::sync::WalletSourceSync;
14+
use crate::ln::funding::SpliceContribution;
15+
use crate::ln::splicing_tests::negotiate_splice_tx;
1316
use crate::prelude::*;
1417
use crate::util::ser::Writeable;
1518
use bitcoin::secp256k1::Secp256k1;
19+
use bitcoin::{Amount, TxOut};
1620

1721
use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS;
1822
use crate::chain::ChannelMonitorUpdateStatus;
@@ -1550,3 +1554,105 @@ fn test_async_force_close_on_invalid_secret_for_stale_state() {
15501554
check_closed_broadcast(&nodes[1], 1, true);
15511555
check_closed_event(&nodes[1], 1, closure_reason, &[node_id_0], 100_000);
15521556
}
1557+
1558+
#[test]
1559+
fn test_async_splice_initial_commit_sig() {
1560+
let chanmon_cfgs = create_chanmon_cfgs(2);
1561+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1562+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
1563+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
1564+
1565+
let channel_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
1566+
send_payment(&nodes[0], &[&nodes[1]], 1_000);
1567+
1568+
let (initiator, acceptor) = (&nodes[0], &nodes[1]);
1569+
let initiator_node_id = initiator.node.get_our_node_id();
1570+
let acceptor_node_id = acceptor.node.get_our_node_id();
1571+
1572+
initiator.disable_channel_signer_op(
1573+
&acceptor_node_id,
1574+
&channel_id,
1575+
SignerOp::SignCounterpartyCommitment,
1576+
);
1577+
acceptor.disable_channel_signer_op(
1578+
&initiator_node_id,
1579+
&channel_id,
1580+
SignerOp::SignCounterpartyCommitment,
1581+
);
1582+
1583+
// Negotiate a splice up until the signature exchange.
1584+
let contribution = SpliceContribution::splice_out(vec![TxOut {
1585+
value: Amount::from_sat(1_000),
1586+
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
1587+
}]);
1588+
negotiate_splice_tx(initiator, acceptor, channel_id, contribution);
1589+
1590+
assert!(initiator.node.get_and_clear_pending_msg_events().is_empty());
1591+
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
1592+
1593+
// Have the initiator sign the funding transaction. We won't see their initial commitment signed
1594+
// go out until their signer returns.
1595+
let event = get_event!(initiator, Event::FundingTransactionReadyForSigning);
1596+
if let Event::FundingTransactionReadyForSigning { unsigned_transaction, .. } = event {
1597+
let partially_signed_tx = initiator.wallet_source.sign_tx(unsigned_transaction).unwrap();
1598+
initiator
1599+
.node
1600+
.funding_transaction_signed(&channel_id, &acceptor_node_id, partially_signed_tx)
1601+
.unwrap();
1602+
}
1603+
1604+
assert!(initiator.node.get_and_clear_pending_msg_events().is_empty());
1605+
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
1606+
1607+
initiator.enable_channel_signer_op(
1608+
&acceptor_node_id,
1609+
&channel_id,
1610+
SignerOp::SignCounterpartyCommitment,
1611+
);
1612+
initiator.node.signer_unblocked(None);
1613+
1614+
// Have the acceptor process the message. They should be able to send their `tx_signatures` as
1615+
// they go first, but it is held back as their initial `commitment_signed` is not ready yet.
1616+
let initiator_commit_sig = get_htlc_update_msgs(initiator, &acceptor_node_id);
1617+
acceptor
1618+
.node
1619+
.handle_commitment_signed(initiator_node_id, &initiator_commit_sig.commitment_signed[0]);
1620+
check_added_monitors(acceptor, 1);
1621+
assert!(acceptor.node.get_and_clear_pending_msg_events().is_empty());
1622+
1623+
// Reestablish the channel to make sure the acceptor doesn't attempt to retransmit any messages
1624+
// that are not ready yet.
1625+
initiator.node.peer_disconnected(acceptor_node_id);
1626+
acceptor.node.peer_disconnected(initiator_node_id);
1627+
reconnect_nodes(ReconnectArgs::new(initiator, acceptor));
1628+
1629+
// Re-enable the acceptor's signer. We should see both their initial `commitment_signed` and
1630+
// `tx_signatures` go out.
1631+
acceptor.enable_channel_signer_op(
1632+
&initiator_node_id,
1633+
&channel_id,
1634+
SignerOp::SignCounterpartyCommitment,
1635+
);
1636+
acceptor.node.signer_unblocked(None);
1637+
1638+
let msg_events = acceptor.node.get_and_clear_pending_msg_events();
1639+
assert_eq!(msg_events.len(), 2, "{msg_events:?}");
1640+
if let MessageSendEvent::UpdateHTLCs { updates, .. } = &msg_events[0] {
1641+
initiator.node.handle_commitment_signed(acceptor_node_id, &updates.commitment_signed[0]);
1642+
check_added_monitors(initiator, 1);
1643+
} else {
1644+
panic!("Unexpected event");
1645+
}
1646+
if let MessageSendEvent::SendTxSignatures { msg, .. } = &msg_events[1] {
1647+
initiator.node.handle_tx_signatures(acceptor_node_id, &msg);
1648+
} else {
1649+
panic!("Unexpected event");
1650+
}
1651+
1652+
let tx_signatures =
1653+
get_event_msg!(initiator, MessageSendEvent::SendTxSignatures, acceptor_node_id);
1654+
acceptor.node.handle_tx_signatures(initiator_node_id, &tx_signatures);
1655+
1656+
let _ = get_event!(initiator, Event::SplicePending);
1657+
let _ = get_event!(acceptor, Event::SplicePending);
1658+
}

lightning/src/ln/channel.rs

Lines changed: 113 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,8 @@ pub(super) struct SignerResumeUpdates {
11581158
pub accept_channel: Option<msgs::AcceptChannel>,
11591159
pub funding_created: Option<msgs::FundingCreated>,
11601160
pub funding_signed: Option<msgs::FundingSigned>,
1161+
pub funding_commit_sig: Option<msgs::CommitmentSigned>,
1162+
pub tx_signatures: Option<msgs::TxSignatures>,
11611163
pub channel_ready: Option<msgs::ChannelReady>,
11621164
pub order: RAACommitmentOrder,
11631165
pub closing_signed: Option<msgs::ClosingSigned>,
@@ -1605,6 +1607,8 @@ where
16051607
accept_channel: None,
16061608
funding_created,
16071609
funding_signed: None,
1610+
funding_commit_sig: None,
1611+
tx_signatures: None,
16081612
channel_ready: None,
16091613
order: chan.context.resend_order.clone(),
16101614
closing_signed: None,
@@ -1621,6 +1625,8 @@ where
16211625
accept_channel,
16221626
funding_created: None,
16231627
funding_signed: None,
1628+
funding_commit_sig: None,
1629+
tx_signatures: None,
16241630
channel_ready: None,
16251631
order: chan.context.resend_order.clone(),
16261632
closing_signed: None,
@@ -3046,8 +3052,9 @@ pub(super) struct ChannelContext<SP: SignerProvider> {
30463052
/// setting it again as a side-effect of [`FundedChannel::channel_reestablish`].
30473053
signer_pending_commitment_update: bool,
30483054
/// Similar to [`Self::signer_pending_commitment_update`] but we're waiting to send either a
3049-
/// [`msgs::FundingCreated`] or [`msgs::FundingSigned`] depending on if this channel is
3050-
/// outbound or inbound.
3055+
/// [`msgs::FundingCreated`] for an outbound V1 channel, [`msgs::FundingSigned`] for an inbound
3056+
/// V1 channel, or [`msgs::CommitmentSigned`] for a V2 channel (dual-funded) or a funded channel
3057+
/// with a pending splice.
30513058
signer_pending_funding: bool,
30523059
/// If we attempted to sign a cooperative close transaction but the signer wasn't ready, then this
30533060
/// will be set to `true`.
@@ -6351,7 +6358,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
63516358
}
63526359

63536360
fn get_initial_commitment_signed_v2<L: Logger>(
6354-
&self, funding: &FundingScope, logger: &L,
6361+
&mut self, funding: &FundingScope, logger: &L,
63556362
) -> Option<msgs::CommitmentSigned> {
63566363
let signatures = self.get_initial_counterparty_commitment_signatures(funding, logger);
63576364
if let Some((signature, htlc_signatures)) = signatures {
@@ -6360,6 +6367,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
63606367
// We shouldn't expect any HTLCs before `ChannelReady`.
63616368
debug_assert!(htlc_signatures.is_empty());
63626369
}
6370+
self.signer_pending_funding = false;
63636371
Some(msgs::CommitmentSigned {
63646372
channel_id: self.channel_id,
63656373
htlc_signatures,
@@ -6369,7 +6377,11 @@ impl<SP: SignerProvider> ChannelContext<SP> {
63696377
partial_signature_with_nonce: None,
63706378
})
63716379
} else {
6372-
// TODO(splicing): Support async signing
6380+
log_debug!(
6381+
logger,
6382+
"Initial counterparty commitment signature not available, waiting on async signer"
6383+
);
6384+
self.signer_pending_funding = true;
63736385
None
63746386
}
63756387
}
@@ -9352,6 +9364,11 @@ where
93529364
// We want to clear that the monitor update for our `tx_signatures` has completed, but
93539365
// we may still need to hold back the message until it's ready to be sent.
93549366
self.context.monitor_pending_tx_signatures = false;
9367+
9368+
if self.context.signer_pending_funding {
9369+
tx_signatures.take();
9370+
}
9371+
93559372
let signing_session = self.context.interactive_tx_signing_session.as_ref()
93569373
.expect("We have a tx_signatures message so we must have a valid signing session");
93579374
if !signing_session.holder_sends_tx_signatures_first()
@@ -9527,7 +9544,12 @@ where
95279544
log_trace!(logger, "Attempting to update holder per-commitment point...");
95289545
self.holder_commitment_point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);
95299546
}
9530-
let funding_signed = if self.context.signer_pending_funding && !self.funding.is_outbound() {
9547+
9548+
let funding_signed = if self.context.signer_pending_funding
9549+
&& !self.is_v2_established()
9550+
&& !self.funding.is_outbound()
9551+
&& self.pending_splice.is_none()
9552+
{
95319553
let commitment_data = self.context.build_commitment_transaction(&self.funding,
95329554
// The previous transaction number (i.e., when adding 1) is used because this field
95339555
// is advanced when handling funding_created, but the point is not advanced until
@@ -9537,6 +9559,43 @@ where
95379559
let counterparty_initial_commitment_tx = commitment_data.tx;
95389560
self.context.get_funding_signed_msg(&self.funding.channel_transaction_parameters, logger, counterparty_initial_commitment_tx)
95399561
} else { None };
9562+
9563+
let funding_commit_sig = if self.context.signer_pending_funding
9564+
&& (self.is_v2_established() || self.pending_splice.is_some())
9565+
{
9566+
log_debug!(logger, "Attempting to generate pending initial commitment_signed...");
9567+
let funding = self
9568+
.pending_splice
9569+
.as_ref()
9570+
.and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
9571+
.and_then(|funding_negotiation| {
9572+
debug_assert!(matches!(
9573+
funding_negotiation,
9574+
FundingNegotiation::AwaitingSignatures { .. }
9575+
));
9576+
funding_negotiation.as_funding()
9577+
})
9578+
.unwrap_or(&self.funding);
9579+
self.context.get_initial_commitment_signed_v2(funding, logger)
9580+
} else {
9581+
None
9582+
};
9583+
9584+
let tx_signatures = if funding_commit_sig.is_some() {
9585+
if let Some(signing_session) = self.context.interactive_tx_signing_session.as_ref() {
9586+
let should_send_tx_signatures = signing_session.holder_sends_tx_signatures_first()
9587+
|| signing_session.has_received_tx_signatures();
9588+
should_send_tx_signatures
9589+
.then(|| ())
9590+
.and_then(|_| signing_session.holder_tx_signatures().clone())
9591+
} else {
9592+
debug_assert!(false);
9593+
None
9594+
}
9595+
} else {
9596+
None
9597+
};
9598+
95409599
// Provide a `channel_ready` message if we need to, but only if we're _not_ still pending
95419600
// funding.
95429601
let channel_ready = if self.context.signer_pending_channel_ready && !self.context.signer_pending_funding {
@@ -9595,12 +9654,14 @@ where
95959654
} else { (None, None, None) }
95969655
} else { (None, None, None) };
95979656

9598-
log_trace!(logger, "Signer unblocked with {} commitment_update, {} revoke_and_ack, with resend order {:?}, {} funding_signed, {} channel_ready,
9599-
{} closing_signed, {} signed_closing_tx, and {} shutdown result",
9657+
log_trace!(logger, "Signer unblocked with {} commitment_update, {} revoke_and_ack, with resend order {:?}, {} funding_signed, \
9658+
{} funding commit_sig, {} tx_signatures, {} channel_ready, {} closing_signed, {} signed_closing_tx, and {} shutdown result",
96009659
if commitment_update.is_some() { "a" } else { "no" },
96019660
if revoke_and_ack.is_some() { "a" } else { "no" },
96029661
self.context.resend_order,
96039662
if funding_signed.is_some() { "a" } else { "no" },
9663+
if funding_commit_sig.is_some() { "a" } else { "no" },
9664+
if tx_signatures.is_some() { "a" } else { "no" },
96049665
if channel_ready.is_some() { "a" } else { "no" },
96059666
if closing_signed.is_some() { "a" } else { "no" },
96069667
if signed_closing_tx.is_some() { "a" } else { "no" },
@@ -9613,6 +9674,8 @@ where
96139674
accept_channel: None,
96149675
funding_created: None,
96159676
funding_signed,
9677+
funding_commit_sig,
9678+
tx_signatures,
96169679
channel_ready,
96179680
order: self.context.resend_order.clone(),
96189681
closing_signed,
@@ -9910,6 +9973,7 @@ where
99109973

99119974
// A receiving node:
99129975
// - if the `next_funding` TLV is set:
9976+
let mut retransmit_funding_commit_sig = None;
99139977
if let Some(next_funding) = &msg.next_funding {
99149978
// - if `next_funding_txid` matches the latest interactive funding transaction
99159979
// or the current channel funding transaction:
@@ -9932,49 +9996,7 @@ where
99329996
&& next_funding.should_retransmit(msgs::NextFundingFlag::CommitmentSigned)
99339997
{
99349998
// - MUST retransmit its `commitment_signed` for that funding transaction.
9935-
let funding = self
9936-
.pending_splice
9937-
.as_ref()
9938-
.and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
9939-
.and_then(|funding_negotiation| {
9940-
if let FundingNegotiation::AwaitingSignatures { funding, .. } = &funding_negotiation {
9941-
Some(funding)
9942-
} else {
9943-
None
9944-
}
9945-
})
9946-
.or_else(|| Some(&self.funding))
9947-
.filter(|funding| funding.get_funding_txid() == Some(next_funding.txid))
9948-
.ok_or_else(|| {
9949-
let message = "Failed to find funding for new commitment_signed".to_owned();
9950-
ChannelError::Close(
9951-
(
9952-
message.clone(),
9953-
ClosureReason::HolderForceClosed { message, broadcasted_latest_txn: Some(false) },
9954-
)
9955-
)
9956-
})?;
9957-
9958-
let commitment_signed = self.context.get_initial_commitment_signed_v2(&funding, logger)
9959-
// TODO(splicing): Support async signing
9960-
.ok_or_else(|| {
9961-
let message = "Failed to get signatures for new commitment_signed".to_owned();
9962-
ChannelError::Close(
9963-
(
9964-
message.clone(),
9965-
ClosureReason::HolderForceClosed { message, broadcasted_latest_txn: Some(false) },
9966-
)
9967-
)
9968-
})?;
9969-
9970-
commitment_update = Some(msgs::CommitmentUpdate {
9971-
commitment_signed: vec![commitment_signed],
9972-
update_add_htlcs: vec![],
9973-
update_fulfill_htlcs: vec![],
9974-
update_fail_htlcs: vec![],
9975-
update_fail_malformed_htlcs: vec![],
9976-
update_fee: None,
9977-
});
9999+
retransmit_funding_commit_sig = Some(next_funding.txid);
997810000
}
997910001

998010002
// - if it has already received `commitment_signed` and it should sign first
@@ -10006,6 +10028,47 @@ where
1000610028
"No active signing session. The associated funding transaction may have already been broadcast.".as_bytes().to_vec() });
1000710029
}
1000810030
}
10031+
if let Some(funding_txid) = retransmit_funding_commit_sig {
10032+
let funding = self
10033+
.pending_splice
10034+
.as_ref()
10035+
.and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
10036+
.and_then(|funding_negotiation| {
10037+
if let FundingNegotiation::AwaitingSignatures { funding, .. } = &funding_negotiation {
10038+
Some(funding)
10039+
} else {
10040+
None
10041+
}
10042+
})
10043+
.or_else(|| Some(&self.funding))
10044+
.filter(|funding| funding.get_funding_txid() == Some(funding_txid))
10045+
.ok_or_else(|| {
10046+
let message = "Failed to find funding for new commitment_signed".to_owned();
10047+
ChannelError::Close(
10048+
(
10049+
message.clone(),
10050+
ClosureReason::HolderForceClosed { message, broadcasted_latest_txn: Some(false) },
10051+
)
10052+
)
10053+
})?;
10054+
10055+
commitment_update = self
10056+
.context
10057+
.get_initial_commitment_signed_v2(&funding, logger)
10058+
.map(|commitment_signed|
10059+
msgs::CommitmentUpdate {
10060+
commitment_signed: vec![commitment_signed],
10061+
update_add_htlcs: vec![],
10062+
update_fulfill_htlcs: vec![],
10063+
update_fail_htlcs: vec![],
10064+
update_fail_malformed_htlcs: vec![],
10065+
update_fee: None,
10066+
}
10067+
);
10068+
if commitment_update.is_none() {
10069+
tx_signatures.take();
10070+
}
10071+
}
1000910072

1001010073
if matches!(self.context.channel_state, ChannelState::AwaitingChannelReady(_)) {
1001110074
// If we're waiting on a monitor update, we shouldn't re-send any channel_ready's.

0 commit comments

Comments
 (0)