Skip to content

Commit 9ce3dd5

Browse files
authored
Merge pull request #3152 from alecchendev/2024-06-async-commit-secret-raa
Handle fallible commitment secret
2 parents 2bfddea + 3413032 commit 9ce3dd5

File tree

5 files changed

+93
-58
lines changed

5 files changed

+93
-58
lines changed

lightning/src/ln/async_signer_tests.rs

+52-31
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,21 @@
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 std::collections::HashSet;
14+
1315
use bitcoin::{Transaction, TxOut, TxIn, Amount};
1416
use bitcoin::blockdata::locktime::absolute::LockTime;
1517
use bitcoin::transaction::Version;
1618

1719
use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS;
1820
use crate::chain::ChannelMonitorUpdateStatus;
1921
use crate::events::bump_transaction::WalletSource;
20-
use crate::events::{ClosureReason, Event, MessageSendEvent, MessageSendEventsProvider, PaymentPurpose};
22+
use crate::events::{ClosureReason, Event, MessageSendEvent, MessageSendEventsProvider};
2123
use crate::ln::{functional_test_utils::*, msgs};
2224
use crate::ln::msgs::ChannelMessageHandler;
2325
use crate::ln::channelmanager::{PaymentId, RAACommitmentOrder, RecipientOnionFields};
2426
use crate::util::test_channel_signer::SignerOp;
27+
use crate::util::logger::Logger;
2528

2629
#[test]
2730
fn test_async_commitment_signature_for_funding_created() {
@@ -127,11 +130,17 @@ fn test_async_commitment_signature_for_funding_signed() {
127130

128131
#[test]
129132
fn test_async_commitment_signature_for_commitment_signed() {
130-
do_test_async_commitment_signature_for_commitment_signed_revoke_and_ack(true);
131-
do_test_async_commitment_signature_for_commitment_signed_revoke_and_ack(false);
133+
for i in 0..=8 {
134+
let enable_signer_op_order = vec![
135+
SignerOp::GetPerCommitmentPoint,
136+
SignerOp::ReleaseCommitmentSecret,
137+
SignerOp::SignCounterpartyCommitment,
138+
].into_iter().filter(|&op| i & (1 << op as u8) != 0).collect();
139+
do_test_async_commitment_signature_for_commitment_signed_revoke_and_ack(enable_signer_op_order);
140+
}
132141
}
133142

134-
fn do_test_async_commitment_signature_for_commitment_signed_revoke_and_ack(enable_sign_counterparty_commit_first: bool) {
143+
fn do_test_async_commitment_signature_for_commitment_signed_revoke_and_ack(enable_signer_op_order: Vec<SignerOp>) {
135144
let chanmon_cfgs = create_chanmon_cfgs(2);
136145
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
137146
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
@@ -160,31 +169,33 @@ fn do_test_async_commitment_signature_for_commitment_signed_revoke_and_ack(enabl
160169
// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
161170
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
162171
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::GetPerCommitmentPoint);
172+
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::ReleaseCommitmentSecret);
163173
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
164174
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
165175
check_added_monitors(dst, 1);
166176

167-
if enable_sign_counterparty_commit_first {
168-
// Unblock CS -> no messages should be sent, since we must send RAA first.
169-
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
177+
let mut enabled_signer_ops = HashSet::new();
178+
log_trace!(dst.logger, "enable_signer_op_order={:?}", enable_signer_op_order);
179+
for op in enable_signer_op_order {
180+
enabled_signer_ops.insert(op);
181+
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, op);
170182
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));
171-
let events = dst.node.get_and_clear_pending_msg_events();
172-
assert!(events.is_empty(), "expected no message, got {}", events.len());
173183

174-
// Unblock revoke_and_ack -> we should send both RAA + CS.
175-
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::GetPerCommitmentPoint);
176-
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));
177-
get_revoke_commit_msgs(&dst, &src.node.get_our_node_id());
178-
} else {
179-
// Unblock revoke_and_ack -> we should send just RAA.
180-
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::GetPerCommitmentPoint);
181-
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));
182-
get_event_msg!(dst, MessageSendEvent::SendRevokeAndACK, src.node.get_our_node_id());
183-
184-
// Unblock commitment signed -> we should send CS.
185-
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
186-
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));
187-
get_htlc_update_msgs(dst, &src.node.get_our_node_id());
184+
if enabled_signer_ops.contains(&SignerOp::GetPerCommitmentPoint) && enabled_signer_ops.contains(&SignerOp::ReleaseCommitmentSecret) {
185+
// We are just able to send revoke_and_ack
186+
if op == SignerOp::GetPerCommitmentPoint || op == SignerOp::ReleaseCommitmentSecret {
187+
get_event_msg!(dst, MessageSendEvent::SendRevokeAndACK, src.node.get_our_node_id());
188+
}
189+
// We either just sent or previously sent revoke_and_ack
190+
// and now we are able to send commitment_signed
191+
if op == SignerOp::SignCounterpartyCommitment {
192+
get_htlc_update_msgs(dst, &src.node.get_our_node_id());
193+
}
194+
} else {
195+
// We can't send either message until RAA is unblocked
196+
let events = dst.node.get_and_clear_pending_msg_events();
197+
assert!(events.is_empty(), "expected no message, got {}", events.len());
198+
}
188199
}
189200
}
190201

@@ -296,12 +307,22 @@ enum UnblockSignerAcrossDisconnectCase {
296307

297308
#[test]
298309
fn test_async_raa_peer_disconnect() {
299-
do_test_async_raa_peer_disconnect(UnblockSignerAcrossDisconnectCase::AtEnd);
300-
do_test_async_raa_peer_disconnect(UnblockSignerAcrossDisconnectCase::BeforeMonitorRestored);
301-
do_test_async_raa_peer_disconnect(UnblockSignerAcrossDisconnectCase::BeforeReestablish);
310+
do_test_async_raa_peer_disconnect(UnblockSignerAcrossDisconnectCase::AtEnd, true);
311+
do_test_async_raa_peer_disconnect(UnblockSignerAcrossDisconnectCase::AtEnd, false);
312+
do_test_async_raa_peer_disconnect(UnblockSignerAcrossDisconnectCase::BeforeMonitorRestored, true);
313+
do_test_async_raa_peer_disconnect(UnblockSignerAcrossDisconnectCase::BeforeMonitorRestored, false);
314+
do_test_async_raa_peer_disconnect(UnblockSignerAcrossDisconnectCase::BeforeReestablish, true);
315+
do_test_async_raa_peer_disconnect(UnblockSignerAcrossDisconnectCase::BeforeReestablish, false);
302316
}
303317

304-
fn do_test_async_raa_peer_disconnect(test_case: UnblockSignerAcrossDisconnectCase) {
318+
fn do_test_async_raa_peer_disconnect(test_case: UnblockSignerAcrossDisconnectCase, raa_blocked_by_commit_point: bool) {
319+
// `raa_blocked_by_commit_point` determines whether we block the RAA by blocking the
320+
// signer on `GetPerCommitmentPoint` or `ReleaseCommitmentSecret`.
321+
let block_raa_signer_op = if raa_blocked_by_commit_point {
322+
SignerOp::GetPerCommitmentPoint
323+
} else {
324+
SignerOp::ReleaseCommitmentSecret
325+
};
305326
let chanmon_cfgs = create_chanmon_cfgs(2);
306327
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
307328
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
@@ -334,7 +355,7 @@ fn do_test_async_raa_peer_disconnect(test_case: UnblockSignerAcrossDisconnectCas
334355

335356
// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
336357
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
337-
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::GetPerCommitmentPoint);
358+
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, block_raa_signer_op);
338359
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
339360
check_added_monitors(dst, 1);
340361

@@ -359,13 +380,13 @@ fn do_test_async_raa_peer_disconnect(test_case: UnblockSignerAcrossDisconnectCas
359380

360381
if test_case == UnblockSignerAcrossDisconnectCase::BeforeReestablish {
361382
// Reenable the signer before the reestablish.
362-
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::GetPerCommitmentPoint);
383+
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, block_raa_signer_op);
363384
}
364385

365386
dst.node.handle_channel_reestablish(&src.node.get_our_node_id(), &reestablish_1[0]);
366387

367388
if test_case == UnblockSignerAcrossDisconnectCase::BeforeMonitorRestored {
368-
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::GetPerCommitmentPoint);
389+
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, block_raa_signer_op);
369390
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
370391
let (outpoint, latest_update, _) = dst.chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id).unwrap().clone();
371392
dst.chain_monitor.chain_monitor.force_channel_monitor_updated(outpoint, latest_update);
@@ -384,7 +405,7 @@ fn do_test_async_raa_peer_disconnect(test_case: UnblockSignerAcrossDisconnectCas
384405
}
385406

386407
// Mark dst's signer as available and retry: we now expect to see dst's RAA + CS.
387-
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::GetPerCommitmentPoint);
408+
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, block_raa_signer_op);
388409
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));
389410

390411
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {

lightning/src/ln/channel.rs

+29-19
Original file line numberDiff line numberDiff line change
@@ -5500,32 +5500,42 @@ impl<SP: Deref> Channel<SP> where
55005500
fn get_last_revoke_and_ack<L: Deref>(&mut self, logger: &L) -> Option<msgs::RevokeAndACK> where L::Target: Logger {
55015501
debug_assert!(self.context.holder_commitment_point.transaction_number() <= INITIAL_COMMITMENT_NUMBER - 2);
55025502
self.context.holder_commitment_point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);
5503-
let per_commitment_secret = self.context.holder_signer.as_ref().release_commitment_secret(self.context.holder_commitment_point.transaction_number() + 2);
5504-
if let HolderCommitmentPoint::Available { transaction_number: _, current, next: _ } = self.context.holder_commitment_point {
5503+
let per_commitment_secret = self.context.holder_signer.as_ref()
5504+
.release_commitment_secret(self.context.holder_commitment_point.transaction_number() + 2).ok();
5505+
if let (HolderCommitmentPoint::Available { current, .. }, Some(per_commitment_secret)) =
5506+
(self.context.holder_commitment_point, per_commitment_secret) {
55055507
self.context.signer_pending_revoke_and_ack = false;
5506-
Some(msgs::RevokeAndACK {
5508+
return Some(msgs::RevokeAndACK {
55075509
channel_id: self.context.channel_id,
55085510
per_commitment_secret,
55095511
next_per_commitment_point: current,
55105512
#[cfg(taproot)]
55115513
next_local_nonce: None,
55125514
})
5513-
} else {
5514-
#[cfg(not(async_signing))] {
5515-
panic!("Holder commitment point must be Available when generating revoke_and_ack");
5516-
}
5517-
#[cfg(async_signing)] {
5518-
// Technically if we're at HolderCommitmentPoint::PendingNext,
5519-
// we have a commitment point ready to send in an RAA, however we
5520-
// choose to wait since if we send RAA now, we could get another
5521-
// CS before we have any commitment point available. Blocking our
5522-
// RAA here is a convenient way to make sure that post-funding
5523-
// we're only ever waiting on one commitment point at a time.
5524-
log_trace!(logger, "Last revoke-and-ack pending in channel {} for sequence {} because the next per-commitment point is not available",
5525-
&self.context.channel_id(), self.context.holder_commitment_point.transaction_number());
5526-
self.context.signer_pending_revoke_and_ack = true;
5527-
None
5528-
}
5515+
}
5516+
if !self.context.holder_commitment_point.is_available() {
5517+
log_trace!(logger, "Last revoke-and-ack pending in channel {} for sequence {} because the next per-commitment point is not available",
5518+
&self.context.channel_id(), self.context.holder_commitment_point.transaction_number());
5519+
}
5520+
if per_commitment_secret.is_none() {
5521+
log_trace!(logger, "Last revoke-and-ack pending in channel {} for sequence {} because the next per-commitment secret for {} is not available",
5522+
&self.context.channel_id(), self.context.holder_commitment_point.transaction_number(),
5523+
self.context.holder_commitment_point.transaction_number() + 2);
5524+
}
5525+
#[cfg(not(async_signing))] {
5526+
panic!("Holder commitment point and per commitment secret must be available when generating revoke_and_ack");
5527+
}
5528+
#[cfg(async_signing)] {
5529+
// Technically if we're at HolderCommitmentPoint::PendingNext,
5530+
// we have a commitment point ready to send in an RAA, however we
5531+
// choose to wait since if we send RAA now, we could get another
5532+
// CS before we have any commitment point available. Blocking our
5533+
// RAA here is a convenient way to make sure that post-funding
5534+
// we're only ever waiting on one commitment point at a time.
5535+
log_trace!(logger, "Last revoke-and-ack pending in channel {} for sequence {} because the next per-commitment point is not available",
5536+
&self.context.channel_id(), self.context.holder_commitment_point.transaction_number());
5537+
self.context.signer_pending_revoke_and_ack = true;
5538+
None
55295539
}
55305540
}
55315541

lightning/src/ln/functional_tests.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1474,7 +1474,7 @@ fn test_fee_spike_violation_fails_htlc() {
14741474

14751475
let pubkeys = chan_signer.as_ref().pubkeys();
14761476
(pubkeys.revocation_basepoint, pubkeys.htlc_basepoint,
1477-
chan_signer.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER),
1477+
chan_signer.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER).unwrap(),
14781478
chan_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 2, &secp_ctx).unwrap(),
14791479
chan_signer.as_ref().pubkeys().funding_pubkey)
14801480
};
@@ -7860,15 +7860,15 @@ fn test_counterparty_raa_skip_no_crash() {
78607860

78617861
// Make signer believe we got a counterparty signature, so that it allows the revocation
78627862
keys.as_ecdsa().unwrap().get_enforcement_state().last_holder_commitment -= 1;
7863-
per_commitment_secret = keys.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER);
7863+
per_commitment_secret = keys.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER).unwrap();
78647864

78657865
// Must revoke without gaps
78667866
keys.as_ecdsa().unwrap().get_enforcement_state().last_holder_commitment -= 1;
7867-
keys.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 1);
7867+
keys.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 1).unwrap();
78687868

78697869
keys.as_ecdsa().unwrap().get_enforcement_state().last_holder_commitment -= 1;
78707870
next_per_commitment_point = PublicKey::from_secret_key(&Secp256k1::new(),
7871-
&SecretKey::from_slice(&keys.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 2)).unwrap());
7871+
&SecretKey::from_slice(&keys.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 2).unwrap()).unwrap());
78727872
}
78737873

78747874
nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(),

lightning/src/sign/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ pub trait ChannelSigner {
752752
///
753753
/// Note that the commitment number starts at `(1 << 48) - 1` and counts backwards.
754754
// TODO: return a Result so we can signal a validation error
755-
fn release_commitment_secret(&self, idx: u64) -> [u8; 32];
755+
fn release_commitment_secret(&self, idx: u64) -> Result<[u8; 32], ()>;
756756

757757
/// Validate the counterparty's signatures on the holder commitment transaction and HTLCs.
758758
///
@@ -1361,8 +1361,8 @@ impl ChannelSigner for InMemorySigner {
13611361
Ok(PublicKey::from_secret_key(secp_ctx, &commitment_secret))
13621362
}
13631363

1364-
fn release_commitment_secret(&self, idx: u64) -> [u8; 32] {
1365-
chan_utils::build_commitment_secret(&self.commitment_seed, idx)
1364+
fn release_commitment_secret(&self, idx: u64) -> Result<[u8; 32], ()> {
1365+
Ok(chan_utils::build_commitment_secret(&self.commitment_seed, idx))
13661366
}
13671367

13681368
fn validate_holder_commitment(

lightning/src/util/test_channel_signer.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,11 @@ impl ChannelSigner for TestChannelSigner {
172172
self.inner.get_per_commitment_point(idx, secp_ctx)
173173
}
174174

175-
fn release_commitment_secret(&self, idx: u64) -> [u8; 32] {
175+
fn release_commitment_secret(&self, idx: u64) -> Result<[u8; 32], ()> {
176+
#[cfg(test)]
177+
if !self.is_signer_available(SignerOp::ReleaseCommitmentSecret) {
178+
return Err(());
179+
}
176180
{
177181
let mut state = self.state.lock().unwrap();
178182
assert!(idx == state.last_holder_revoked_commitment || idx == state.last_holder_revoked_commitment - 1, "can only revoke the current or next unrevoked commitment - trying {}, last revoked {}", idx, state.last_holder_revoked_commitment);

0 commit comments

Comments
 (0)