Skip to content

Commit 252545c

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 fcc3d33 commit 252545c

3 files changed

Lines changed: 237 additions & 51 deletions

File tree

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

lightning/src/ln/channel.rs

Lines changed: 113 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,8 @@ pub(super) struct SignerResumeUpdates {
11711171
pub accept_channel: Option<msgs::AcceptChannel>,
11721172
pub funding_created: Option<msgs::FundingCreated>,
11731173
pub funding_signed: Option<msgs::FundingSigned>,
1174+
pub funding_commit_sig: Option<msgs::CommitmentSigned>,
1175+
pub tx_signatures: Option<msgs::TxSignatures>,
11741176
pub channel_ready: Option<msgs::ChannelReady>,
11751177
pub order: RAACommitmentOrder,
11761178
pub closing_signed: Option<msgs::ClosingSigned>,
@@ -1634,6 +1636,8 @@ where
16341636
accept_channel: None,
16351637
funding_created,
16361638
funding_signed: None,
1639+
funding_commit_sig: None,
1640+
tx_signatures: None,
16371641
channel_ready: None,
16381642
order: chan.context.resend_order.clone(),
16391643
closing_signed: None,
@@ -1650,6 +1654,8 @@ where
16501654
accept_channel,
16511655
funding_created: None,
16521656
funding_signed: None,
1657+
funding_commit_sig: None,
1658+
tx_signatures: None,
16531659
channel_ready: None,
16541660
order: chan.context.resend_order.clone(),
16551661
closing_signed: None,
@@ -3131,8 +3137,9 @@ where
31313137
/// setting it again as a side-effect of [`FundedChannel::channel_reestablish`].
31323138
signer_pending_commitment_update: bool,
31333139
/// Similar to [`Self::signer_pending_commitment_update`] but we're waiting to send either a
3134-
/// [`msgs::FundingCreated`] or [`msgs::FundingSigned`] depending on if this channel is
3135-
/// outbound or inbound.
3140+
/// [`msgs::FundingCreated`] for an outbound V1 channel, [`msgs::FundingSigned`] for an inbound
3141+
/// V1 channel, or [`msgs::CommitmentSigned`] for a V2 channel (dual-funded) or a funded channel
3142+
/// with a pending splice.
31363143
signer_pending_funding: bool,
31373144
/// If we attempted to sign a cooperative close transaction but the signer wasn't ready, then this
31383145
/// will be set to `true`.
@@ -6498,7 +6505,7 @@ where
64986505
}
64996506

65006507
fn get_initial_commitment_signed_v2<L: Deref>(
6501-
&self, funding: &FundingScope, logger: &L,
6508+
&mut self, funding: &FundingScope, logger: &L,
65026509
) -> Option<msgs::CommitmentSigned>
65036510
where
65046511
SP::Target: SignerProvider,
@@ -6511,6 +6518,7 @@ where
65116518
// We shouldn't expect any HTLCs before `ChannelReady`.
65126519
debug_assert!(htlc_signatures.is_empty());
65136520
}
6521+
self.signer_pending_funding = false;
65146522
Some(msgs::CommitmentSigned {
65156523
channel_id: self.channel_id,
65166524
htlc_signatures,
@@ -6520,7 +6528,11 @@ where
65206528
partial_signature_with_nonce: None,
65216529
})
65226530
} else {
6523-
// TODO(splicing): Support async signing
6531+
log_debug!(
6532+
logger,
6533+
"Initial counterparty commitment signature not available, waiting on async signer"
6534+
);
6535+
self.signer_pending_funding = true;
65246536
None
65256537
}
65266538
}
@@ -9578,6 +9590,11 @@ where
95789590
// We want to clear that the monitor update for our `tx_signatures` has completed, but
95799591
// we may still need to hold back the message until it's ready to be sent.
95809592
self.context.monitor_pending_tx_signatures = false;
9593+
9594+
if self.context.signer_pending_funding {
9595+
tx_signatures.take();
9596+
}
9597+
95819598
let signing_session = self.context.interactive_tx_signing_session.as_ref()
95829599
.expect("We have a tx_signatures message so we must have a valid signing session");
95839600
if !signing_session.holder_sends_tx_signatures_first()
@@ -9755,7 +9772,12 @@ where
97559772
log_trace!(logger, "Attempting to update holder per-commitment point...");
97569773
self.holder_commitment_point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);
97579774
}
9758-
let funding_signed = if self.context.signer_pending_funding && !self.funding.is_outbound() {
9775+
9776+
let funding_signed = if self.context.signer_pending_funding
9777+
&& !self.is_v2_established()
9778+
&& !self.funding.is_outbound()
9779+
&& self.pending_splice.is_none()
9780+
{
97599781
let commitment_data = self.context.build_commitment_transaction(&self.funding,
97609782
// The previous transaction number (i.e., when adding 1) is used because this field
97619783
// is advanced when handling funding_created, but the point is not advanced until
@@ -9765,6 +9787,43 @@ where
97659787
let counterparty_initial_commitment_tx = commitment_data.tx;
97669788
self.context.get_funding_signed_msg(&self.funding.channel_transaction_parameters, logger, counterparty_initial_commitment_tx)
97679789
} else { None };
9790+
9791+
let funding_commit_sig = if self.context.signer_pending_funding
9792+
&& (self.is_v2_established() || self.pending_splice.is_some())
9793+
{
9794+
log_debug!(logger, "Attempting to generate pending initial commitment_signed...");
9795+
let funding = self
9796+
.pending_splice
9797+
.as_ref()
9798+
.and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
9799+
.and_then(|funding_negotiation| {
9800+
debug_assert!(matches!(
9801+
funding_negotiation,
9802+
FundingNegotiation::AwaitingSignatures { .. }
9803+
));
9804+
funding_negotiation.as_funding()
9805+
})
9806+
.unwrap_or(&self.funding);
9807+
self.context.get_initial_commitment_signed_v2(funding, logger)
9808+
} else {
9809+
None
9810+
};
9811+
9812+
let tx_signatures = if funding_commit_sig.is_some() {
9813+
if let Some(signing_session) = self.context.interactive_tx_signing_session.as_ref() {
9814+
let should_send_tx_signatures = signing_session.holder_sends_tx_signatures_first()
9815+
|| signing_session.has_received_tx_signatures();
9816+
should_send_tx_signatures
9817+
.then(|| ())
9818+
.and_then(|_| signing_session.holder_tx_signatures().clone())
9819+
} else {
9820+
debug_assert!(false);
9821+
None
9822+
}
9823+
} else {
9824+
None
9825+
};
9826+
97689827
// Provide a `channel_ready` message if we need to, but only if we're _not_ still pending
97699828
// funding.
97709829
let channel_ready = if self.context.signer_pending_channel_ready && !self.context.signer_pending_funding {
@@ -9823,12 +9882,14 @@ where
98239882
} else { (None, None, None) }
98249883
} else { (None, None, None) };
98259884

9826-
log_trace!(logger, "Signer unblocked with {} commitment_update, {} revoke_and_ack, with resend order {:?}, {} funding_signed, {} channel_ready,
9827-
{} closing_signed, {} signed_closing_tx, and {} shutdown result",
9885+
log_trace!(logger, "Signer unblocked with {} commitment_update, {} revoke_and_ack, with resend order {:?}, {} funding_signed, \
9886+
{} funding commit_sig, {} tx_signatures, {} channel_ready, {} closing_signed, {} signed_closing_tx, and {} shutdown result",
98289887
if commitment_update.is_some() { "a" } else { "no" },
98299888
if revoke_and_ack.is_some() { "a" } else { "no" },
98309889
self.context.resend_order,
98319890
if funding_signed.is_some() { "a" } else { "no" },
9891+
if funding_commit_sig.is_some() { "a" } else { "no" },
9892+
if tx_signatures.is_some() { "a" } else { "no" },
98329893
if channel_ready.is_some() { "a" } else { "no" },
98339894
if closing_signed.is_some() { "a" } else { "no" },
98349895
if signed_closing_tx.is_some() { "a" } else { "no" },
@@ -9841,6 +9902,8 @@ where
98419902
accept_channel: None,
98429903
funding_created: None,
98439904
funding_signed,
9905+
funding_commit_sig,
9906+
tx_signatures,
98449907
channel_ready,
98459908
order: self.context.resend_order.clone(),
98469909
closing_signed,
@@ -10147,6 +10210,7 @@ where
1014710210

1014810211
// A receiving node:
1014910212
// - if the `next_funding` TLV is set:
10213+
let mut retransmit_funding_commit_sig = None;
1015010214
if let Some(next_funding) = &msg.next_funding {
1015110215
// - if `next_funding_txid` matches the latest interactive funding transaction
1015210216
// or the current channel funding transaction:
@@ -10169,49 +10233,7 @@ where
1016910233
&& next_funding.should_retransmit(msgs::NextFundingFlag::CommitmentSigned)
1017010234
{
1017110235
// - MUST retransmit its `commitment_signed` for that funding transaction.
10172-
let funding = self
10173-
.pending_splice
10174-
.as_ref()
10175-
.and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
10176-
.and_then(|funding_negotiation| {
10177-
if let FundingNegotiation::AwaitingSignatures { funding, .. } = &funding_negotiation {
10178-
Some(funding)
10179-
} else {
10180-
None
10181-
}
10182-
})
10183-
.or_else(|| Some(&self.funding))
10184-
.filter(|funding| funding.get_funding_txid() == Some(next_funding.txid))
10185-
.ok_or_else(|| {
10186-
let message = "Failed to find funding for new commitment_signed".to_owned();
10187-
ChannelError::Close(
10188-
(
10189-
message.clone(),
10190-
ClosureReason::HolderForceClosed { message, broadcasted_latest_txn: Some(false) },
10191-
)
10192-
)
10193-
})?;
10194-
10195-
let commitment_signed = self.context.get_initial_commitment_signed_v2(&funding, logger)
10196-
// TODO(splicing): Support async signing
10197-
.ok_or_else(|| {
10198-
let message = "Failed to get signatures for new commitment_signed".to_owned();
10199-
ChannelError::Close(
10200-
(
10201-
message.clone(),
10202-
ClosureReason::HolderForceClosed { message, broadcasted_latest_txn: Some(false) },
10203-
)
10204-
)
10205-
})?;
10206-
10207-
commitment_update = Some(msgs::CommitmentUpdate {
10208-
commitment_signed: vec![commitment_signed],
10209-
update_add_htlcs: vec![],
10210-
update_fulfill_htlcs: vec![],
10211-
update_fail_htlcs: vec![],
10212-
update_fail_malformed_htlcs: vec![],
10213-
update_fee: None,
10214-
});
10236+
retransmit_funding_commit_sig = Some(next_funding.txid);
1021510237
}
1021610238

1021710239
// - if it has already received `commitment_signed` and it should sign first
@@ -10243,6 +10265,47 @@ where
1024310265
"No active signing session. The associated funding transaction may have already been broadcast.".as_bytes().to_vec() });
1024410266
}
1024510267
}
10268+
if let Some(funding_txid) = retransmit_funding_commit_sig {
10269+
let funding = self
10270+
.pending_splice
10271+
.as_ref()
10272+
.and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
10273+
.and_then(|funding_negotiation| {
10274+
if let FundingNegotiation::AwaitingSignatures { funding, .. } = &funding_negotiation {
10275+
Some(funding)
10276+
} else {
10277+
None
10278+
}
10279+
})
10280+
.or_else(|| Some(&self.funding))
10281+
.filter(|funding| funding.get_funding_txid() == Some(funding_txid))
10282+
.ok_or_else(|| {
10283+
let message = "Failed to find funding for new commitment_signed".to_owned();
10284+
ChannelError::Close(
10285+
(
10286+
message.clone(),
10287+
ClosureReason::HolderForceClosed { message, broadcasted_latest_txn: Some(false) },
10288+
)
10289+
)
10290+
})?;
10291+
10292+
commitment_update = self
10293+
.context
10294+
.get_initial_commitment_signed_v2(&funding, logger)
10295+
.map(|commitment_signed|
10296+
msgs::CommitmentUpdate {
10297+
commitment_signed: vec![commitment_signed],
10298+
update_add_htlcs: vec![],
10299+
update_fulfill_htlcs: vec![],
10300+
update_fail_htlcs: vec![],
10301+
update_fail_malformed_htlcs: vec![],
10302+
update_fee: None,
10303+
}
10304+
);
10305+
if commitment_update.is_none() {
10306+
tx_signatures.take();
10307+
}
10308+
}
1024610309

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

0 commit comments

Comments
 (0)