Skip to content

Commit eb9fc0c

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 eb9fc0c

3 files changed

Lines changed: 234 additions & 49 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: 110 additions & 48 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,
@@ -6498,7 +6504,7 @@ where
64986504
}
64996505

65006506
fn get_initial_commitment_signed_v2<L: Deref>(
6501-
&self, funding: &FundingScope, logger: &L,
6507+
&mut self, funding: &FundingScope, logger: &L,
65026508
) -> Option<msgs::CommitmentSigned>
65036509
where
65046510
SP::Target: SignerProvider,
@@ -6511,6 +6517,7 @@ where
65116517
// We shouldn't expect any HTLCs before `ChannelReady`.
65126518
debug_assert!(htlc_signatures.is_empty());
65136519
}
6520+
self.signer_pending_funding = false;
65146521
Some(msgs::CommitmentSigned {
65156522
channel_id: self.channel_id,
65166523
htlc_signatures,
@@ -6520,7 +6527,11 @@ where
65206527
partial_signature_with_nonce: None,
65216528
})
65226529
} else {
6523-
// TODO(splicing): Support async signing
6530+
log_debug!(
6531+
logger,
6532+
"Initial counterparty commitment signature not available, waiting on async signer"
6533+
);
6534+
self.signer_pending_funding = true;
65246535
None
65256536
}
65266537
}
@@ -9578,6 +9589,11 @@ where
95789589
// We want to clear that the monitor update for our `tx_signatures` has completed, but
95799590
// we may still need to hold back the message until it's ready to be sent.
95809591
self.context.monitor_pending_tx_signatures = false;
9592+
9593+
if self.context.signer_pending_funding {
9594+
tx_signatures.take();
9595+
}
9596+
95819597
let signing_session = self.context.interactive_tx_signing_session.as_ref()
95829598
.expect("We have a tx_signatures message so we must have a valid signing session");
95839599
if !signing_session.holder_sends_tx_signatures_first()
@@ -9755,7 +9771,12 @@ where
97559771
log_trace!(logger, "Attempting to update holder per-commitment point...");
97569772
self.holder_commitment_point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);
97579773
}
9758-
let funding_signed = if self.context.signer_pending_funding && !self.funding.is_outbound() {
9774+
9775+
let funding_signed = if self.context.signer_pending_funding
9776+
&& !self.is_v2_established()
9777+
&& !self.funding.is_outbound()
9778+
&& self.pending_splice.is_none()
9779+
{
97599780
let commitment_data = self.context.build_commitment_transaction(&self.funding,
97609781
// The previous transaction number (i.e., when adding 1) is used because this field
97619782
// is advanced when handling funding_created, but the point is not advanced until
@@ -9765,6 +9786,43 @@ where
97659786
let counterparty_initial_commitment_tx = commitment_data.tx;
97669787
self.context.get_funding_signed_msg(&self.funding.channel_transaction_parameters, logger, counterparty_initial_commitment_tx)
97679788
} else { None };
9789+
9790+
let funding_commit_sig = if self.context.signer_pending_funding
9791+
&& (self.is_v2_established() || self.pending_splice.is_some())
9792+
{
9793+
log_debug!(logger, "Attempting to generate pending initial commitment_signed...");
9794+
let funding = self
9795+
.pending_splice
9796+
.as_ref()
9797+
.and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
9798+
.and_then(|funding_negotiation| {
9799+
debug_assert!(matches!(
9800+
funding_negotiation,
9801+
FundingNegotiation::AwaitingSignatures { .. }
9802+
));
9803+
funding_negotiation.as_funding()
9804+
})
9805+
.unwrap_or(&self.funding);
9806+
self.context.get_initial_commitment_signed_v2(funding, logger)
9807+
} else {
9808+
None
9809+
};
9810+
9811+
let tx_signatures = if funding_commit_sig.is_some() {
9812+
if let Some(signing_session) = self.context.interactive_tx_signing_session.as_ref() {
9813+
let should_send_tx_signatures = signing_session.holder_sends_tx_signatures_first()
9814+
|| signing_session.has_received_tx_signatures();
9815+
should_send_tx_signatures
9816+
.then(|| ())
9817+
.and_then(|_| signing_session.holder_tx_signatures().clone())
9818+
} else {
9819+
debug_assert!(false);
9820+
None
9821+
}
9822+
} else {
9823+
None
9824+
};
9825+
97689826
// Provide a `channel_ready` message if we need to, but only if we're _not_ still pending
97699827
// funding.
97709828
let channel_ready = if self.context.signer_pending_channel_ready && !self.context.signer_pending_funding {
@@ -9823,12 +9881,14 @@ where
98239881
} else { (None, None, None) }
98249882
} else { (None, None, None) };
98259883

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",
9884+
log_trace!(logger, "Signer unblocked with {} commitment_update, {} revoke_and_ack, with resend order {:?}, {} funding_signed, \
9885+
{} funding commit_sig, {} tx_signatures, {} channel_ready, {} closing_signed, {} signed_closing_tx, and {} shutdown result",
98289886
if commitment_update.is_some() { "a" } else { "no" },
98299887
if revoke_and_ack.is_some() { "a" } else { "no" },
98309888
self.context.resend_order,
98319889
if funding_signed.is_some() { "a" } else { "no" },
9890+
if funding_commit_sig.is_some() { "a" } else { "no" },
9891+
if tx_signatures.is_some() { "a" } else { "no" },
98329892
if channel_ready.is_some() { "a" } else { "no" },
98339893
if closing_signed.is_some() { "a" } else { "no" },
98349894
if signed_closing_tx.is_some() { "a" } else { "no" },
@@ -9841,6 +9901,8 @@ where
98419901
accept_channel: None,
98429902
funding_created: None,
98439903
funding_signed,
9904+
funding_commit_sig,
9905+
tx_signatures,
98449906
channel_ready,
98459907
order: self.context.resend_order.clone(),
98469908
closing_signed,
@@ -10147,6 +10209,7 @@ where
1014710209

1014810210
// A receiving node:
1014910211
// - if the `next_funding` TLV is set:
10212+
let mut retransmit_funding_commit_sig = None;
1015010213
if let Some(next_funding) = &msg.next_funding {
1015110214
// - if `next_funding_txid` matches the latest interactive funding transaction
1015210215
// or the current channel funding transaction:
@@ -10169,49 +10232,7 @@ where
1016910232
&& next_funding.should_retransmit(msgs::NextFundingFlag::CommitmentSigned)
1017010233
{
1017110234
// - 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-
});
10235+
retransmit_funding_commit_sig = Some(next_funding.txid);
1021510236
}
1021610237

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

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

0 commit comments

Comments
 (0)