Skip to content

Commit 9933019

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 04a7560 commit 9933019

File tree

3 files changed

+234
-49
lines changed

3 files changed

+234
-49
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;
@@ -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,
@@ -6501,7 +6507,7 @@ where
65016507
}
65026508

65036509
fn get_initial_commitment_signed_v2<L: Deref>(
6504-
&self, funding: &FundingScope, logger: &L,
6510+
&mut self, funding: &FundingScope, logger: &L,
65056511
) -> Option<msgs::CommitmentSigned>
65066512
where
65076513
SP::Target: SignerProvider,
@@ -6514,6 +6520,7 @@ where
65146520
// We shouldn't expect any HTLCs before `ChannelReady`.
65156521
debug_assert!(htlc_signatures.is_empty());
65166522
}
6523+
self.signer_pending_funding = false;
65176524
Some(msgs::CommitmentSigned {
65186525
channel_id: self.channel_id,
65196526
htlc_signatures,
@@ -6523,7 +6530,11 @@ where
65236530
partial_signature_with_nonce: None,
65246531
})
65256532
} else {
6526-
// TODO(splicing): Support async signing
6533+
log_debug!(
6534+
logger,
6535+
"Initial counterparty commitment signature not available, waiting on async signer"
6536+
);
6537+
self.signer_pending_funding = true;
65276538
None
65286539
}
65296540
}
@@ -9581,6 +9592,11 @@ where
95819592
// We want to clear that the monitor update for our `tx_signatures` has completed, but
95829593
// we may still need to hold back the message until it's ready to be sent.
95839594
self.context.monitor_pending_tx_signatures = false;
9595+
9596+
if self.context.signer_pending_funding {
9597+
tx_signatures.take();
9598+
}
9599+
95849600
let signing_session = self.context.interactive_tx_signing_session.as_ref()
95859601
.expect("We have a tx_signatures message so we must have a valid signing session");
95869602
if !signing_session.holder_sends_tx_signatures_first()
@@ -9758,7 +9774,12 @@ where
97589774
log_trace!(logger, "Attempting to update holder per-commitment point...");
97599775
self.holder_commitment_point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);
97609776
}
9761-
let funding_signed = if self.context.signer_pending_funding && !self.funding.is_outbound() {
9777+
9778+
let funding_signed = if self.context.signer_pending_funding
9779+
&& !self.is_v2_established()
9780+
&& !self.funding.is_outbound()
9781+
&& self.pending_splice.is_none()
9782+
{
97629783
let commitment_data = self.context.build_commitment_transaction(&self.funding,
97639784
// The previous transaction number (i.e., when adding 1) is used because this field
97649785
// is advanced when handling funding_created, but the point is not advanced until
@@ -9768,6 +9789,43 @@ where
97689789
let counterparty_initial_commitment_tx = commitment_data.tx;
97699790
self.context.get_funding_signed_msg(&self.funding.channel_transaction_parameters, logger, counterparty_initial_commitment_tx)
97709791
} else { None };
9792+
9793+
let funding_commit_sig = if self.context.signer_pending_funding
9794+
&& (self.is_v2_established() || self.pending_splice.is_some())
9795+
{
9796+
log_debug!(logger, "Attempting to generate pending initial commitment_signed...");
9797+
let funding = self
9798+
.pending_splice
9799+
.as_ref()
9800+
.and_then(|pending_splice| pending_splice.funding_negotiation.as_ref())
9801+
.and_then(|funding_negotiation| {
9802+
debug_assert!(matches!(
9803+
funding_negotiation,
9804+
FundingNegotiation::AwaitingSignatures { .. }
9805+
));
9806+
funding_negotiation.as_funding()
9807+
})
9808+
.unwrap_or(&self.funding);
9809+
self.context.get_initial_commitment_signed_v2(funding, logger)
9810+
} else {
9811+
None
9812+
};
9813+
9814+
let tx_signatures = if funding_commit_sig.is_some() {
9815+
if let Some(signing_session) = self.context.interactive_tx_signing_session.as_ref() {
9816+
let should_send_tx_signatures = signing_session.holder_sends_tx_signatures_first()
9817+
|| signing_session.has_received_tx_signatures();
9818+
should_send_tx_signatures
9819+
.then(|| ())
9820+
.and_then(|_| signing_session.holder_tx_signatures().clone())
9821+
} else {
9822+
debug_assert!(false);
9823+
None
9824+
}
9825+
} else {
9826+
None
9827+
};
9828+
97719829
// Provide a `channel_ready` message if we need to, but only if we're _not_ still pending
97729830
// funding.
97739831
let channel_ready = if self.context.signer_pending_channel_ready && !self.context.signer_pending_funding {
@@ -9826,12 +9884,14 @@ where
98269884
} else { (None, None, None) }
98279885
} else { (None, None, None) };
98289886

9829-
log_trace!(logger, "Signer unblocked with {} commitment_update, {} revoke_and_ack, with resend order {:?}, {} funding_signed, {} channel_ready,
9830-
{} closing_signed, {} signed_closing_tx, and {} shutdown result",
9887+
log_trace!(logger, "Signer unblocked with {} commitment_update, {} revoke_and_ack, with resend order {:?}, {} funding_signed, \
9888+
{} funding commit_sig, {} tx_signatures, {} channel_ready, {} closing_signed, {} signed_closing_tx, and {} shutdown result",
98319889
if commitment_update.is_some() { "a" } else { "no" },
98329890
if revoke_and_ack.is_some() { "a" } else { "no" },
98339891
self.context.resend_order,
98349892
if funding_signed.is_some() { "a" } else { "no" },
9893+
if funding_commit_sig.is_some() { "a" } else { "no" },
9894+
if tx_signatures.is_some() { "a" } else { "no" },
98359895
if channel_ready.is_some() { "a" } else { "no" },
98369896
if closing_signed.is_some() { "a" } else { "no" },
98379897
if signed_closing_tx.is_some() { "a" } else { "no" },
@@ -9844,6 +9904,8 @@ where
98449904
accept_channel: None,
98459905
funding_created: None,
98469906
funding_signed,
9907+
funding_commit_sig,
9908+
tx_signatures,
98479909
channel_ready,
98489910
order: self.context.resend_order.clone(),
98499911
closing_signed,
@@ -10150,6 +10212,7 @@ where
1015010212

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

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

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

0 commit comments

Comments
 (0)