Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -596,7 +596,7 @@ fn do_test_async_raa_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
assert!(revoke_and_ack.is_none());
Expand All@@ -612,14 +612,14 @@ fn do_test_async_raa_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
assert!(commitment_signed.is_some());
assert!(resend_order == RAACommitmentOrder::RevokeAndACKFirst);
} else {
// Make sure we don't double send the RAA.
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_none());
assert!(commitment_signed.is_none());
Expand DownExpand Up@@ -746,7 +746,7 @@ fn do_test_async_commitment_signature_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
Expand All@@ -760,11 +760,11 @@ fn do_test_async_commitment_signature_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_some());
} else {
// Make sure we don't double send the CS.
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_none());
}
}
Expand DownExpand Up@@ -882,6 +882,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

if monitor_update_failure {
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
Expand All@@ -904,6 +905,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

nodes[0].enable_channel_signer_op(&node_b_id, &chan_id, SignerOp::SignCounterpartyCommitment);
nodes[0].node.signer_unblocked(Some((node_b_id, chan_id)));
Expand All@@ -929,6 +931,9 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.6.is_none());
assert!(bs_resp.6.is_none());

assert!(as_resp.7.is_none());
assert!(bs_resp.7.is_none());

// Now that everything is restored, get the CS + RAA and handle them.
nodes[1]
.node
Expand Down
104 changes: 72 additions & 32 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1654,7 +1654,7 @@ where
}
chan.context.channel_state.clear_local_stfu_sent();
chan.context.channel_state.clear_remote_stfu_sent();
if chan.should_reset_pending_splice_state() {
if chan.should_reset_pending_splice_state(false) {
// If there was a pending splice negotiation that failed due to disconnecting, we
// also take the opportunity to clean up our state.
let splice_funding_failed = chan.reset_pending_splice_state();
Expand DownExpand Up@@ -1775,7 +1775,7 @@ where
None
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(false) {
funded_channel.reset_pending_splice_state()
} else {
debug_assert!(false, "We should never fail an interactive funding negotiation once we're exchanging tx_signatures");
Expand DownExpand Up@@ -1932,12 +1932,24 @@ where
(had_constructor, None)
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.has_pending_splice_awaiting_signatures() {
if funded_channel.has_pending_splice_awaiting_signatures()
&& funded_channel
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
{
// We only force close once the counterparty tries to abort after committing to

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that this is somehow a limitation in LDK, but it isn't, its a rather fundamental limitation of the protocol - if we have received the CS, the peer cannot know whether we have provided them with funding signatures, at which point cancelling the splice would be unsafe no matter the implementation in LDK.

// the splice via their initial `commitment_signed`. This is because our monitor
// state is updated with the post-splice commitment transaction upon receiving
// their `commitment_signed`, so we would need another monitor update to abandon
// it, which we don't currently support.
return Err(ChannelError::close(
"Received tx_abort while awaiting tx_signatures exchange".to_owned(),
));
}
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(true) {
let has_funding_negotiation = funded_channel
.pending_splice
.as_ref()
Expand DownExpand Up@@ -2681,19 +2693,6 @@ impl FundingNegotiation {
}

impl PendingFunding {
fn can_abandon_state(&self) -> bool {
self.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
!matches!(funding_negotiation, FundingNegotiation::AwaitingSignatures { .. })
})
.unwrap_or_else(|| {
let has_negotiated_candidates = !self.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
}

fn check_get_splice_locked<SP: Deref>(
&mut self, context: &ChannelContext<SP>, confirmed_funding_index: usize, height: u32,
) -> Option<msgs::SpliceLocked>
Expand DownExpand Up@@ -6873,7 +6872,7 @@ pub struct SpliceFundingFailed {
}

macro_rules! maybe_create_splice_funding_failed {
($pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
($funded_channel: expr, $pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
$pending_splice
.and_then(|pending_splice| pending_splice.funding_negotiation.$get())
.filter(|funding_negotiation| funding_negotiation.is_initiator())
Expand All@@ -6895,10 +6894,12 @@ macro_rules! maybe_create_splice_funding_failed {
interactive_tx_constructor,
..
} => interactive_tx_constructor.$contributed_inputs_and_outputs(),
FundingNegotiation::AwaitingSignatures { .. } => {
debug_assert!(false);
(Vec::new(), Vec::new())
},
FundingNegotiation::AwaitingSignatures { .. } => $funded_channel
.context
.interactive_tx_signing_session
.$get()
.expect("We have a pending splice awaiting signatures")
.$contributed_inputs_and_outputs(),
};

SpliceFundingFailed {
Expand DownExpand Up@@ -6937,7 +6938,7 @@ where

fn maybe_fail_splice_negotiation(&mut self) -> Option<SpliceFundingFailed> {
if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
self.reset_pending_splice_state()
} else {
match self.quiescent_action.take() {
Expand DownExpand Up@@ -7011,19 +7012,54 @@ where

/// Returns a boolean indicating whether we should reset the splice's
/// [`PendingFunding::funding_negotiation`].
fn should_reset_pending_splice_state(&self) -> bool {
fn should_reset_pending_splice_state(&self, counterparty_aborted: bool) -> bool {
self.pending_splice
.as_ref()
.map(|pending_splice| pending_splice.can_abandon_state())
.map(|pending_splice| {
pending_splice
.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
let is_awaiting_signatures = matches!(
funding_negotiation,
FundingNegotiation::AwaitingSignatures { .. }
);
if counterparty_aborted {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we condition on counterparty_aborted? Would it be a problem if always do the new check?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't, then we would always reset the splice state when we haven't received their initial commitment_signed.

!is_awaiting_signatures
|| !self
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
} else {
!is_awaiting_signatures
}
})
.unwrap_or_else(|| {
let has_negotiated_candidates =
!pending_splice.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
})
.unwrap_or(false)
}

fn reset_pending_splice_state(&mut self) -> Option<SpliceFundingFailed> {
debug_assert!(self.should_reset_pending_splice_state());
debug_assert!(self.context.interactive_tx_signing_session.is_none());
self.context.channel_state.clear_quiescent();
debug_assert!(self.should_reset_pending_splice_state(true));
debug_assert!(
self.context.interactive_tx_signing_session.is_none()
|| !self
.context
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
);

let splice_funding_failed = maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_mut(),
take,
into_contributed_inputs_and_outputs
Expand All@@ -7033,15 +7069,19 @@ where
self.pending_splice.take();
}

self.context.channel_state.clear_quiescent();
self.context.interactive_tx_signing_session.take();

splice_funding_failed
}

pub(super) fn maybe_splice_funding_failed(&self) -> Option<SpliceFundingFailed> {
if !self.should_reset_pending_splice_state() {
if !self.should_reset_pending_splice_state(false) {
return None;
}

maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_ref(),
as_ref,
to_contributed_inputs_and_outputs
Expand DownExpand Up@@ -11996,7 +12036,7 @@ where
pub fn abandon_splice(
&mut self,
) -> Result<(msgs::TxAbort, Option<SpliceFundingFailed>), APIError> {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
let tx_abort =
msgs::TxAbort { channel_id: self.context.channel_id(), data: Vec::new() };
let splice_funding_failed = self.reset_pending_splice_state();
Expand DownExpand Up@@ -14361,7 +14401,7 @@ where
}
channel_state.clear_local_stfu_sent();
channel_state.clear_remote_stfu_sent();
if self.should_reset_pending_splice_state()
if self.should_reset_pending_splice_state(false)
|| !self.has_pending_splice_awaiting_signatures()
{
// We shouldn't be quiescent anymore upon reconnecting if:
Expand DownExpand Up@@ -14735,7 +14775,7 @@ where
// We don't have to worry about resetting the pending `FundingNegotiation` because we
// can only read `FundingNegotiation::AwaitingSignatures` variants anyway.
let pending_splice =
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state());
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state(false));

write_tlv_fields!(writer, {
(0, self.context.announcement_sigs, option),
Expand Down
26 changes: 25 additions & 1 deletion lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4996,9 +4996,17 @@ macro_rules! handle_chan_reestablish_msgs {
($src_node: expr, $dst_node: expr) => {{
let msg_events = $src_node.node.get_and_clear_pending_msg_events();
let mut idx = 0;

let mut tx_abort = None;
if let Some(&MessageSendEvent::SendTxAbort { ref node_id, ref msg }) = msg_events.get(idx) {
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
tx_abort = Some(msg.clone());
}

let channel_ready =
if let Some(&MessageSendEvent::SendChannelReady { ref node_id, ref msg }) =
msg_events.get(0)
msg_events.get(idx)
{
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
Expand DownExpand Up@@ -5115,6 +5123,7 @@ macro_rules! handle_chan_reestablish_msgs {
announcement_sigs,
tx_signatures,
stfu,
tx_abort,
)
}};
}
Expand All@@ -5127,6 +5136,7 @@ pub struct ReconnectArgs<'a, 'b, 'c, 'd> {
pub send_stfu: (bool, bool),
pub send_interactive_tx_commit_sig: (bool, bool),
pub send_interactive_tx_sigs: (bool, bool),
pub send_tx_abort: (bool, bool),
pub expect_renegotiated_funding_locked_monitor_update: (bool, bool),
pub pending_responding_commitment_signed: (bool, bool),
/// Indicates that the pending responding commitment signed will be a dup for the recipient,
Expand All@@ -5150,6 +5160,7 @@ impl<'a, 'b, 'c, 'd> ReconnectArgs<'a, 'b, 'c, 'd> {
send_stfu: (false, false),
send_interactive_tx_commit_sig: (false, false),
send_interactive_tx_sigs: (false, false),
send_tx_abort: (false, false),
expect_renegotiated_funding_locked_monitor_update: (false, false),
pending_responding_commitment_signed: (false, false),
pending_responding_commitment_signed_dup_monitor: (false, false),
Expand All@@ -5174,6 +5185,7 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
send_stfu,
send_interactive_tx_commit_sig,
send_interactive_tx_sigs,
send_tx_abort,
expect_renegotiated_funding_locked_monitor_update,
pending_htlc_adds,
pending_htlc_claims,
Expand DownExpand Up@@ -5305,6 +5317,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.0 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.0 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_a.node.handle_tx_signatures(node_b_id, &tx_signatures);
Expand DownExpand Up@@ -5417,6 +5435,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.1 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.1 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_b.node.handle_tx_signatures(node_a_id, &tx_signatures);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Allow counterparty tx_abort before handling initial commitment signed by wpaulino · Pull Request #4204 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -596,7 +596,7 @@ fn do_test_async_raa_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
assert!(revoke_and_ack.is_none());
Expand All@@ -612,14 +612,14 @@ fn do_test_async_raa_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
assert!(commitment_signed.is_some());
assert!(resend_order == RAACommitmentOrder::RevokeAndACKFirst);
} else {
// Make sure we don't double send the RAA.
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_none());
assert!(commitment_signed.is_none());
Expand DownExpand Up@@ -746,7 +746,7 @@ fn do_test_async_commitment_signature_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
Expand All@@ -760,11 +760,11 @@ fn do_test_async_commitment_signature_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_some());
} else {
// Make sure we don't double send the CS.
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_none());
}
}
Expand DownExpand Up@@ -882,6 +882,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

if monitor_update_failure {
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
Expand All@@ -904,6 +905,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

nodes[0].enable_channel_signer_op(&node_b_id, &chan_id, SignerOp::SignCounterpartyCommitment);
nodes[0].node.signer_unblocked(Some((node_b_id, chan_id)));
Expand All@@ -929,6 +931,9 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.6.is_none());
assert!(bs_resp.6.is_none());

assert!(as_resp.7.is_none());
assert!(bs_resp.7.is_none());

// Now that everything is restored, get the CS + RAA and handle them.
nodes[1]
.node
Expand Down
104 changes: 72 additions & 32 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1654,7 +1654,7 @@ where
}
chan.context.channel_state.clear_local_stfu_sent();
chan.context.channel_state.clear_remote_stfu_sent();
if chan.should_reset_pending_splice_state() {
if chan.should_reset_pending_splice_state(false) {
// If there was a pending splice negotiation that failed due to disconnecting, we
// also take the opportunity to clean up our state.
let splice_funding_failed = chan.reset_pending_splice_state();
Expand DownExpand Up@@ -1775,7 +1775,7 @@ where
None
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(false) {
funded_channel.reset_pending_splice_state()
} else {
debug_assert!(false, "We should never fail an interactive funding negotiation once we're exchanging tx_signatures");
Expand DownExpand Up@@ -1932,12 +1932,24 @@ where
(had_constructor, None)
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.has_pending_splice_awaiting_signatures() {
if funded_channel.has_pending_splice_awaiting_signatures()
&& funded_channel
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
{
// We only force close once the counterparty tries to abort after committing to

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that this is somehow a limitation in LDK, but it isn't, its a rather fundamental limitation of the protocol - if we have received the CS, the peer cannot know whether we have provided them with funding signatures, at which point cancelling the splice would be unsafe no matter the implementation in LDK.

// the splice via their initial `commitment_signed`. This is because our monitor
// state is updated with the post-splice commitment transaction upon receiving
// their `commitment_signed`, so we would need another monitor update to abandon
// it, which we don't currently support.
return Err(ChannelError::close(
"Received tx_abort while awaiting tx_signatures exchange".to_owned(),
));
}
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(true) {
let has_funding_negotiation = funded_channel
.pending_splice
.as_ref()
Expand DownExpand Up@@ -2681,19 +2693,6 @@ impl FundingNegotiation {
}

impl PendingFunding {
fn can_abandon_state(&self) -> bool {
self.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
!matches!(funding_negotiation, FundingNegotiation::AwaitingSignatures { .. })
})
.unwrap_or_else(|| {
let has_negotiated_candidates = !self.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
}

fn check_get_splice_locked<SP: Deref>(
&mut self, context: &ChannelContext<SP>, confirmed_funding_index: usize, height: u32,
) -> Option<msgs::SpliceLocked>
Expand DownExpand Up@@ -6873,7 +6872,7 @@ pub struct SpliceFundingFailed {
}

macro_rules! maybe_create_splice_funding_failed {
($pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
($funded_channel: expr, $pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
$pending_splice
.and_then(|pending_splice| pending_splice.funding_negotiation.$get())
.filter(|funding_negotiation| funding_negotiation.is_initiator())
Expand All@@ -6895,10 +6894,12 @@ macro_rules! maybe_create_splice_funding_failed {
interactive_tx_constructor,
..
} => interactive_tx_constructor.$contributed_inputs_and_outputs(),
FundingNegotiation::AwaitingSignatures { .. } => {
debug_assert!(false);
(Vec::new(), Vec::new())
},
FundingNegotiation::AwaitingSignatures { .. } => $funded_channel
.context
.interactive_tx_signing_session
.$get()
.expect("We have a pending splice awaiting signatures")
.$contributed_inputs_and_outputs(),
};

SpliceFundingFailed {
Expand DownExpand Up@@ -6937,7 +6938,7 @@ where

fn maybe_fail_splice_negotiation(&mut self) -> Option<SpliceFundingFailed> {
if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
self.reset_pending_splice_state()
} else {
match self.quiescent_action.take() {
Expand DownExpand Up@@ -7011,19 +7012,54 @@ where

/// Returns a boolean indicating whether we should reset the splice's
/// [`PendingFunding::funding_negotiation`].
fn should_reset_pending_splice_state(&self) -> bool {
fn should_reset_pending_splice_state(&self, counterparty_aborted: bool) -> bool {
self.pending_splice
.as_ref()
.map(|pending_splice| pending_splice.can_abandon_state())
.map(|pending_splice| {
pending_splice
.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
let is_awaiting_signatures = matches!(
funding_negotiation,
FundingNegotiation::AwaitingSignatures { .. }
);
if counterparty_aborted {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we condition on counterparty_aborted? Would it be a problem if always do the new check?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't, then we would always reset the splice state when we haven't received their initial commitment_signed.

!is_awaiting_signatures
|| !self
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
} else {
!is_awaiting_signatures
}
})
.unwrap_or_else(|| {
let has_negotiated_candidates =
!pending_splice.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
})
.unwrap_or(false)
}

fn reset_pending_splice_state(&mut self) -> Option<SpliceFundingFailed> {
debug_assert!(self.should_reset_pending_splice_state());
debug_assert!(self.context.interactive_tx_signing_session.is_none());
self.context.channel_state.clear_quiescent();
debug_assert!(self.should_reset_pending_splice_state(true));
debug_assert!(
self.context.interactive_tx_signing_session.is_none()
|| !self
.context
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
);

let splice_funding_failed = maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_mut(),
take,
into_contributed_inputs_and_outputs
Expand All@@ -7033,15 +7069,19 @@ where
self.pending_splice.take();
}

self.context.channel_state.clear_quiescent();
self.context.interactive_tx_signing_session.take();

splice_funding_failed
}

pub(super) fn maybe_splice_funding_failed(&self) -> Option<SpliceFundingFailed> {
if !self.should_reset_pending_splice_state() {
if !self.should_reset_pending_splice_state(false) {
return None;
}

maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_ref(),
as_ref,
to_contributed_inputs_and_outputs
Expand DownExpand Up@@ -11996,7 +12036,7 @@ where
pub fn abandon_splice(
&mut self,
) -> Result<(msgs::TxAbort, Option<SpliceFundingFailed>), APIError> {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
let tx_abort =
msgs::TxAbort { channel_id: self.context.channel_id(), data: Vec::new() };
let splice_funding_failed = self.reset_pending_splice_state();
Expand DownExpand Up@@ -14361,7 +14401,7 @@ where
}
channel_state.clear_local_stfu_sent();
channel_state.clear_remote_stfu_sent();
if self.should_reset_pending_splice_state()
if self.should_reset_pending_splice_state(false)
|| !self.has_pending_splice_awaiting_signatures()
{
// We shouldn't be quiescent anymore upon reconnecting if:
Expand DownExpand Up@@ -14735,7 +14775,7 @@ where
// We don't have to worry about resetting the pending `FundingNegotiation` because we
// can only read `FundingNegotiation::AwaitingSignatures` variants anyway.
let pending_splice =
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state());
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state(false));

write_tlv_fields!(writer, {
(0, self.context.announcement_sigs, option),
Expand Down
26 changes: 25 additions & 1 deletion lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4996,9 +4996,17 @@ macro_rules! handle_chan_reestablish_msgs {
($src_node: expr, $dst_node: expr) => {{
let msg_events = $src_node.node.get_and_clear_pending_msg_events();
let mut idx = 0;

let mut tx_abort = None;
if let Some(&MessageSendEvent::SendTxAbort { ref node_id, ref msg }) = msg_events.get(idx) {
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
tx_abort = Some(msg.clone());
}

let channel_ready =
if let Some(&MessageSendEvent::SendChannelReady { ref node_id, ref msg }) =
msg_events.get(0)
msg_events.get(idx)
{
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
Expand DownExpand Up@@ -5115,6 +5123,7 @@ macro_rules! handle_chan_reestablish_msgs {
announcement_sigs,
tx_signatures,
stfu,
tx_abort,
)
}};
}
Expand All@@ -5127,6 +5136,7 @@ pub struct ReconnectArgs<'a, 'b, 'c, 'd> {
pub send_stfu: (bool, bool),
pub send_interactive_tx_commit_sig: (bool, bool),
pub send_interactive_tx_sigs: (bool, bool),
pub send_tx_abort: (bool, bool),
pub expect_renegotiated_funding_locked_monitor_update: (bool, bool),
pub pending_responding_commitment_signed: (bool, bool),
/// Indicates that the pending responding commitment signed will be a dup for the recipient,
Expand All@@ -5150,6 +5160,7 @@ impl<'a, 'b, 'c, 'd> ReconnectArgs<'a, 'b, 'c, 'd> {
send_stfu: (false, false),
send_interactive_tx_commit_sig: (false, false),
send_interactive_tx_sigs: (false, false),
send_tx_abort: (false, false),
expect_renegotiated_funding_locked_monitor_update: (false, false),
pending_responding_commitment_signed: (false, false),
pending_responding_commitment_signed_dup_monitor: (false, false),
Expand All@@ -5174,6 +5185,7 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
send_stfu,
send_interactive_tx_commit_sig,
send_interactive_tx_sigs,
send_tx_abort,
expect_renegotiated_funding_locked_monitor_update,
pending_htlc_adds,
pending_htlc_claims,
Expand DownExpand Up@@ -5305,6 +5317,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.0 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.0 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_a.node.handle_tx_signatures(node_b_id, &tx_signatures);
Expand DownExpand Up@@ -5417,6 +5435,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.1 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.1 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_b.node.handle_tx_signatures(node_a_id, &tx_signatures);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow counterparty tx_abort before handling initial commitment signed by wpaulino · Pull Request #4204 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -596,7 +596,7 @@ fn do_test_async_raa_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
assert!(revoke_and_ack.is_none());
Expand All@@ -612,14 +612,14 @@ fn do_test_async_raa_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
assert!(commitment_signed.is_some());
assert!(resend_order == RAACommitmentOrder::RevokeAndACKFirst);
} else {
// Make sure we don't double send the RAA.
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_none());
assert!(commitment_signed.is_none());
Expand DownExpand Up@@ -746,7 +746,7 @@ fn do_test_async_commitment_signature_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
Expand All@@ -760,11 +760,11 @@ fn do_test_async_commitment_signature_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_some());
} else {
// Make sure we don't double send the CS.
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_none());
}
}
Expand DownExpand Up@@ -882,6 +882,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

if monitor_update_failure {
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
Expand All@@ -904,6 +905,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

nodes[0].enable_channel_signer_op(&node_b_id, &chan_id, SignerOp::SignCounterpartyCommitment);
nodes[0].node.signer_unblocked(Some((node_b_id, chan_id)));
Expand All@@ -929,6 +931,9 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.6.is_none());
assert!(bs_resp.6.is_none());

assert!(as_resp.7.is_none());
assert!(bs_resp.7.is_none());

// Now that everything is restored, get the CS + RAA and handle them.
nodes[1]
.node
Expand Down
104 changes: 72 additions & 32 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1654,7 +1654,7 @@ where
}
chan.context.channel_state.clear_local_stfu_sent();
chan.context.channel_state.clear_remote_stfu_sent();
if chan.should_reset_pending_splice_state() {
if chan.should_reset_pending_splice_state(false) {
// If there was a pending splice negotiation that failed due to disconnecting, we
// also take the opportunity to clean up our state.
let splice_funding_failed = chan.reset_pending_splice_state();
Expand DownExpand Up@@ -1775,7 +1775,7 @@ where
None
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(false) {
funded_channel.reset_pending_splice_state()
} else {
debug_assert!(false, "We should never fail an interactive funding negotiation once we're exchanging tx_signatures");
Expand DownExpand Up@@ -1932,12 +1932,24 @@ where
(had_constructor, None)
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.has_pending_splice_awaiting_signatures() {
if funded_channel.has_pending_splice_awaiting_signatures()
&& funded_channel
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
{
// We only force close once the counterparty tries to abort after committing to

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that this is somehow a limitation in LDK, but it isn't, its a rather fundamental limitation of the protocol - if we have received the CS, the peer cannot know whether we have provided them with funding signatures, at which point cancelling the splice would be unsafe no matter the implementation in LDK.

// the splice via their initial `commitment_signed`. This is because our monitor
// state is updated with the post-splice commitment transaction upon receiving
// their `commitment_signed`, so we would need another monitor update to abandon
// it, which we don't currently support.
return Err(ChannelError::close(
"Received tx_abort while awaiting tx_signatures exchange".to_owned(),
));
}
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(true) {
let has_funding_negotiation = funded_channel
.pending_splice
.as_ref()
Expand DownExpand Up@@ -2681,19 +2693,6 @@ impl FundingNegotiation {
}

impl PendingFunding {
fn can_abandon_state(&self) -> bool {
self.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
!matches!(funding_negotiation, FundingNegotiation::AwaitingSignatures { .. })
})
.unwrap_or_else(|| {
let has_negotiated_candidates = !self.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
}

fn check_get_splice_locked<SP: Deref>(
&mut self, context: &ChannelContext<SP>, confirmed_funding_index: usize, height: u32,
) -> Option<msgs::SpliceLocked>
Expand DownExpand Up@@ -6873,7 +6872,7 @@ pub struct SpliceFundingFailed {
}

macro_rules! maybe_create_splice_funding_failed {
($pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
($funded_channel: expr, $pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
$pending_splice
.and_then(|pending_splice| pending_splice.funding_negotiation.$get())
.filter(|funding_negotiation| funding_negotiation.is_initiator())
Expand All@@ -6895,10 +6894,12 @@ macro_rules! maybe_create_splice_funding_failed {
interactive_tx_constructor,
..
} => interactive_tx_constructor.$contributed_inputs_and_outputs(),
FundingNegotiation::AwaitingSignatures { .. } => {
debug_assert!(false);
(Vec::new(), Vec::new())
},
FundingNegotiation::AwaitingSignatures { .. } => $funded_channel
.context
.interactive_tx_signing_session
.$get()
.expect("We have a pending splice awaiting signatures")
.$contributed_inputs_and_outputs(),
};

SpliceFundingFailed {
Expand DownExpand Up@@ -6937,7 +6938,7 @@ where

fn maybe_fail_splice_negotiation(&mut self) -> Option<SpliceFundingFailed> {
if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
self.reset_pending_splice_state()
} else {
match self.quiescent_action.take() {
Expand DownExpand Up@@ -7011,19 +7012,54 @@ where

/// Returns a boolean indicating whether we should reset the splice's
/// [`PendingFunding::funding_negotiation`].
fn should_reset_pending_splice_state(&self) -> bool {
fn should_reset_pending_splice_state(&self, counterparty_aborted: bool) -> bool {
self.pending_splice
.as_ref()
.map(|pending_splice| pending_splice.can_abandon_state())
.map(|pending_splice| {
pending_splice
.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
let is_awaiting_signatures = matches!(
funding_negotiation,
FundingNegotiation::AwaitingSignatures { .. }
);
if counterparty_aborted {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we condition on counterparty_aborted? Would it be a problem if always do the new check?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't, then we would always reset the splice state when we haven't received their initial commitment_signed.

!is_awaiting_signatures
|| !self
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
} else {
!is_awaiting_signatures
}
})
.unwrap_or_else(|| {
let has_negotiated_candidates =
!pending_splice.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
})
.unwrap_or(false)
}

fn reset_pending_splice_state(&mut self) -> Option<SpliceFundingFailed> {
debug_assert!(self.should_reset_pending_splice_state());
debug_assert!(self.context.interactive_tx_signing_session.is_none());
self.context.channel_state.clear_quiescent();
debug_assert!(self.should_reset_pending_splice_state(true));
debug_assert!(
self.context.interactive_tx_signing_session.is_none()
|| !self
.context
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
);

let splice_funding_failed = maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_mut(),
take,
into_contributed_inputs_and_outputs
Expand All@@ -7033,15 +7069,19 @@ where
self.pending_splice.take();
}

self.context.channel_state.clear_quiescent();
self.context.interactive_tx_signing_session.take();

splice_funding_failed
}

pub(super) fn maybe_splice_funding_failed(&self) -> Option<SpliceFundingFailed> {
if !self.should_reset_pending_splice_state() {
if !self.should_reset_pending_splice_state(false) {
return None;
}

maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_ref(),
as_ref,
to_contributed_inputs_and_outputs
Expand DownExpand Up@@ -11996,7 +12036,7 @@ where
pub fn abandon_splice(
&mut self,
) -> Result<(msgs::TxAbort, Option<SpliceFundingFailed>), APIError> {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
let tx_abort =
msgs::TxAbort { channel_id: self.context.channel_id(), data: Vec::new() };
let splice_funding_failed = self.reset_pending_splice_state();
Expand DownExpand Up@@ -14361,7 +14401,7 @@ where
}
channel_state.clear_local_stfu_sent();
channel_state.clear_remote_stfu_sent();
if self.should_reset_pending_splice_state()
if self.should_reset_pending_splice_state(false)
|| !self.has_pending_splice_awaiting_signatures()
{
// We shouldn't be quiescent anymore upon reconnecting if:
Expand DownExpand Up@@ -14735,7 +14775,7 @@ where
// We don't have to worry about resetting the pending `FundingNegotiation` because we
// can only read `FundingNegotiation::AwaitingSignatures` variants anyway.
let pending_splice =
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state());
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state(false));

write_tlv_fields!(writer, {
(0, self.context.announcement_sigs, option),
Expand Down
26 changes: 25 additions & 1 deletion lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4996,9 +4996,17 @@ macro_rules! handle_chan_reestablish_msgs {
($src_node: expr, $dst_node: expr) => {{
let msg_events = $src_node.node.get_and_clear_pending_msg_events();
let mut idx = 0;

let mut tx_abort = None;
if let Some(&MessageSendEvent::SendTxAbort { ref node_id, ref msg }) = msg_events.get(idx) {
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
tx_abort = Some(msg.clone());
}

let channel_ready =
if let Some(&MessageSendEvent::SendChannelReady { ref node_id, ref msg }) =
msg_events.get(0)
msg_events.get(idx)
{
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
Expand DownExpand Up@@ -5115,6 +5123,7 @@ macro_rules! handle_chan_reestablish_msgs {
announcement_sigs,
tx_signatures,
stfu,
tx_abort,
)
}};
}
Expand All@@ -5127,6 +5136,7 @@ pub struct ReconnectArgs<'a, 'b, 'c, 'd> {
pub send_stfu: (bool, bool),
pub send_interactive_tx_commit_sig: (bool, bool),
pub send_interactive_tx_sigs: (bool, bool),
pub send_tx_abort: (bool, bool),
pub expect_renegotiated_funding_locked_monitor_update: (bool, bool),
pub pending_responding_commitment_signed: (bool, bool),
/// Indicates that the pending responding commitment signed will be a dup for the recipient,
Expand All@@ -5150,6 +5160,7 @@ impl<'a, 'b, 'c, 'd> ReconnectArgs<'a, 'b, 'c, 'd> {
send_stfu: (false, false),
send_interactive_tx_commit_sig: (false, false),
send_interactive_tx_sigs: (false, false),
send_tx_abort: (false, false),
expect_renegotiated_funding_locked_monitor_update: (false, false),
pending_responding_commitment_signed: (false, false),
pending_responding_commitment_signed_dup_monitor: (false, false),
Expand All@@ -5174,6 +5185,7 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
send_stfu,
send_interactive_tx_commit_sig,
send_interactive_tx_sigs,
send_tx_abort,
expect_renegotiated_funding_locked_monitor_update,
pending_htlc_adds,
pending_htlc_claims,
Expand DownExpand Up@@ -5305,6 +5317,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.0 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.0 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_a.node.handle_tx_signatures(node_b_id, &tx_signatures);
Expand DownExpand Up@@ -5417,6 +5435,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.1 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.1 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_b.node.handle_tx_signatures(node_a_id, &tx_signatures);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow counterparty tx_abort before handling initial commitment signed by wpaulino · Pull Request #4204 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -596,7 +596,7 @@ fn do_test_async_raa_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
assert!(revoke_and_ack.is_none());
Expand All@@ -612,14 +612,14 @@ fn do_test_async_raa_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
assert!(commitment_signed.is_some());
assert!(resend_order == RAACommitmentOrder::RevokeAndACKFirst);
} else {
// Make sure we don't double send the RAA.
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_none());
assert!(commitment_signed.is_none());
Expand DownExpand Up@@ -746,7 +746,7 @@ fn do_test_async_commitment_signature_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
Expand All@@ -760,11 +760,11 @@ fn do_test_async_commitment_signature_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_some());
} else {
// Make sure we don't double send the CS.
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_none());
}
}
Expand DownExpand Up@@ -882,6 +882,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

if monitor_update_failure {
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
Expand All@@ -904,6 +905,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

nodes[0].enable_channel_signer_op(&node_b_id, &chan_id, SignerOp::SignCounterpartyCommitment);
nodes[0].node.signer_unblocked(Some((node_b_id, chan_id)));
Expand All@@ -929,6 +931,9 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.6.is_none());
assert!(bs_resp.6.is_none());

assert!(as_resp.7.is_none());
assert!(bs_resp.7.is_none());

// Now that everything is restored, get the CS + RAA and handle them.
nodes[1]
.node
Expand Down
104 changes: 72 additions & 32 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1654,7 +1654,7 @@ where
}
chan.context.channel_state.clear_local_stfu_sent();
chan.context.channel_state.clear_remote_stfu_sent();
if chan.should_reset_pending_splice_state() {
if chan.should_reset_pending_splice_state(false) {
// If there was a pending splice negotiation that failed due to disconnecting, we
// also take the opportunity to clean up our state.
let splice_funding_failed = chan.reset_pending_splice_state();
Expand DownExpand Up@@ -1775,7 +1775,7 @@ where
None
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(false) {
funded_channel.reset_pending_splice_state()
} else {
debug_assert!(false, "We should never fail an interactive funding negotiation once we're exchanging tx_signatures");
Expand DownExpand Up@@ -1932,12 +1932,24 @@ where
(had_constructor, None)
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.has_pending_splice_awaiting_signatures() {
if funded_channel.has_pending_splice_awaiting_signatures()
&& funded_channel
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
{
// We only force close once the counterparty tries to abort after committing to

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that this is somehow a limitation in LDK, but it isn't, its a rather fundamental limitation of the protocol - if we have received the CS, the peer cannot know whether we have provided them with funding signatures, at which point cancelling the splice would be unsafe no matter the implementation in LDK.

// the splice via their initial `commitment_signed`. This is because our monitor
// state is updated with the post-splice commitment transaction upon receiving
// their `commitment_signed`, so we would need another monitor update to abandon
// it, which we don't currently support.
return Err(ChannelError::close(
"Received tx_abort while awaiting tx_signatures exchange".to_owned(),
));
}
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(true) {
let has_funding_negotiation = funded_channel
.pending_splice
.as_ref()
Expand DownExpand Up@@ -2681,19 +2693,6 @@ impl FundingNegotiation {
}

impl PendingFunding {
fn can_abandon_state(&self) -> bool {
self.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
!matches!(funding_negotiation, FundingNegotiation::AwaitingSignatures { .. })
})
.unwrap_or_else(|| {
let has_negotiated_candidates = !self.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
}

fn check_get_splice_locked<SP: Deref>(
&mut self, context: &ChannelContext<SP>, confirmed_funding_index: usize, height: u32,
) -> Option<msgs::SpliceLocked>
Expand DownExpand Up@@ -6873,7 +6872,7 @@ pub struct SpliceFundingFailed {
}

macro_rules! maybe_create_splice_funding_failed {
($pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
($funded_channel: expr, $pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
$pending_splice
.and_then(|pending_splice| pending_splice.funding_negotiation.$get())
.filter(|funding_negotiation| funding_negotiation.is_initiator())
Expand All@@ -6895,10 +6894,12 @@ macro_rules! maybe_create_splice_funding_failed {
interactive_tx_constructor,
..
} => interactive_tx_constructor.$contributed_inputs_and_outputs(),
FundingNegotiation::AwaitingSignatures { .. } => {
debug_assert!(false);
(Vec::new(), Vec::new())
},
FundingNegotiation::AwaitingSignatures { .. } => $funded_channel
.context
.interactive_tx_signing_session
.$get()
.expect("We have a pending splice awaiting signatures")
.$contributed_inputs_and_outputs(),
};

SpliceFundingFailed {
Expand DownExpand Up@@ -6937,7 +6938,7 @@ where

fn maybe_fail_splice_negotiation(&mut self) -> Option<SpliceFundingFailed> {
if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
self.reset_pending_splice_state()
} else {
match self.quiescent_action.take() {
Expand DownExpand Up@@ -7011,19 +7012,54 @@ where

/// Returns a boolean indicating whether we should reset the splice's
/// [`PendingFunding::funding_negotiation`].
fn should_reset_pending_splice_state(&self) -> bool {
fn should_reset_pending_splice_state(&self, counterparty_aborted: bool) -> bool {
self.pending_splice
.as_ref()
.map(|pending_splice| pending_splice.can_abandon_state())
.map(|pending_splice| {
pending_splice
.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
let is_awaiting_signatures = matches!(
funding_negotiation,
FundingNegotiation::AwaitingSignatures { .. }
);
if counterparty_aborted {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we condition on counterparty_aborted? Would it be a problem if always do the new check?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't, then we would always reset the splice state when we haven't received their initial commitment_signed.

!is_awaiting_signatures
|| !self
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
} else {
!is_awaiting_signatures
}
})
.unwrap_or_else(|| {
let has_negotiated_candidates =
!pending_splice.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
})
.unwrap_or(false)
}

fn reset_pending_splice_state(&mut self) -> Option<SpliceFundingFailed> {
debug_assert!(self.should_reset_pending_splice_state());
debug_assert!(self.context.interactive_tx_signing_session.is_none());
self.context.channel_state.clear_quiescent();
debug_assert!(self.should_reset_pending_splice_state(true));
debug_assert!(
self.context.interactive_tx_signing_session.is_none()
|| !self
.context
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
);

let splice_funding_failed = maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_mut(),
take,
into_contributed_inputs_and_outputs
Expand All@@ -7033,15 +7069,19 @@ where
self.pending_splice.take();
}

self.context.channel_state.clear_quiescent();
self.context.interactive_tx_signing_session.take();

splice_funding_failed
}

pub(super) fn maybe_splice_funding_failed(&self) -> Option<SpliceFundingFailed> {
if !self.should_reset_pending_splice_state() {
if !self.should_reset_pending_splice_state(false) {
return None;
}

maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_ref(),
as_ref,
to_contributed_inputs_and_outputs
Expand DownExpand Up@@ -11996,7 +12036,7 @@ where
pub fn abandon_splice(
&mut self,
) -> Result<(msgs::TxAbort, Option<SpliceFundingFailed>), APIError> {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
let tx_abort =
msgs::TxAbort { channel_id: self.context.channel_id(), data: Vec::new() };
let splice_funding_failed = self.reset_pending_splice_state();
Expand DownExpand Up@@ -14361,7 +14401,7 @@ where
}
channel_state.clear_local_stfu_sent();
channel_state.clear_remote_stfu_sent();
if self.should_reset_pending_splice_state()
if self.should_reset_pending_splice_state(false)
|| !self.has_pending_splice_awaiting_signatures()
{
// We shouldn't be quiescent anymore upon reconnecting if:
Expand DownExpand Up@@ -14735,7 +14775,7 @@ where
// We don't have to worry about resetting the pending `FundingNegotiation` because we
// can only read `FundingNegotiation::AwaitingSignatures` variants anyway.
let pending_splice =
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state());
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state(false));

write_tlv_fields!(writer, {
(0, self.context.announcement_sigs, option),
Expand Down
26 changes: 25 additions & 1 deletion lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4996,9 +4996,17 @@ macro_rules! handle_chan_reestablish_msgs {
($src_node: expr, $dst_node: expr) => {{
let msg_events = $src_node.node.get_and_clear_pending_msg_events();
let mut idx = 0;

let mut tx_abort = None;
if let Some(&MessageSendEvent::SendTxAbort { ref node_id, ref msg }) = msg_events.get(idx) {
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
tx_abort = Some(msg.clone());
}

let channel_ready =
if let Some(&MessageSendEvent::SendChannelReady { ref node_id, ref msg }) =
msg_events.get(0)
msg_events.get(idx)
{
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
Expand DownExpand Up@@ -5115,6 +5123,7 @@ macro_rules! handle_chan_reestablish_msgs {
announcement_sigs,
tx_signatures,
stfu,
tx_abort,
)
}};
}
Expand All@@ -5127,6 +5136,7 @@ pub struct ReconnectArgs<'a, 'b, 'c, 'd> {
pub send_stfu: (bool, bool),
pub send_interactive_tx_commit_sig: (bool, bool),
pub send_interactive_tx_sigs: (bool, bool),
pub send_tx_abort: (bool, bool),
pub expect_renegotiated_funding_locked_monitor_update: (bool, bool),
pub pending_responding_commitment_signed: (bool, bool),
/// Indicates that the pending responding commitment signed will be a dup for the recipient,
Expand All@@ -5150,6 +5160,7 @@ impl<'a, 'b, 'c, 'd> ReconnectArgs<'a, 'b, 'c, 'd> {
send_stfu: (false, false),
send_interactive_tx_commit_sig: (false, false),
send_interactive_tx_sigs: (false, false),
send_tx_abort: (false, false),
expect_renegotiated_funding_locked_monitor_update: (false, false),
pending_responding_commitment_signed: (false, false),
pending_responding_commitment_signed_dup_monitor: (false, false),
Expand All@@ -5174,6 +5185,7 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
send_stfu,
send_interactive_tx_commit_sig,
send_interactive_tx_sigs,
send_tx_abort,
expect_renegotiated_funding_locked_monitor_update,
pending_htlc_adds,
pending_htlc_claims,
Expand DownExpand Up@@ -5305,6 +5317,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.0 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.0 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_a.node.handle_tx_signatures(node_b_id, &tx_signatures);
Expand DownExpand Up@@ -5417,6 +5435,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.1 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.1 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_b.node.handle_tx_signatures(node_a_id, &tx_signatures);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Allow counterparty tx_abort before handling initial commitment signed by wpaulino · Pull Request #4204 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -596,7 +596,7 @@ fn do_test_async_raa_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
assert!(revoke_and_ack.is_none());
Expand All@@ -612,14 +612,14 @@ fn do_test_async_raa_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
assert!(commitment_signed.is_some());
assert!(resend_order == RAACommitmentOrder::RevokeAndACKFirst);
} else {
// Make sure we don't double send the RAA.
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_none());
assert!(commitment_signed.is_none());
Expand DownExpand Up@@ -746,7 +746,7 @@ fn do_test_async_commitment_signature_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
Expand All@@ -760,11 +760,11 @@ fn do_test_async_commitment_signature_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_some());
} else {
// Make sure we don't double send the CS.
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_none());
}
}
Expand DownExpand Up@@ -882,6 +882,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

if monitor_update_failure {
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
Expand All@@ -904,6 +905,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

nodes[0].enable_channel_signer_op(&node_b_id, &chan_id, SignerOp::SignCounterpartyCommitment);
nodes[0].node.signer_unblocked(Some((node_b_id, chan_id)));
Expand All@@ -929,6 +931,9 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.6.is_none());
assert!(bs_resp.6.is_none());

assert!(as_resp.7.is_none());
assert!(bs_resp.7.is_none());

// Now that everything is restored, get the CS + RAA and handle them.
nodes[1]
.node
Expand Down
104 changes: 72 additions & 32 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1654,7 +1654,7 @@ where
}
chan.context.channel_state.clear_local_stfu_sent();
chan.context.channel_state.clear_remote_stfu_sent();
if chan.should_reset_pending_splice_state() {
if chan.should_reset_pending_splice_state(false) {
// If there was a pending splice negotiation that failed due to disconnecting, we
// also take the opportunity to clean up our state.
let splice_funding_failed = chan.reset_pending_splice_state();
Expand DownExpand Up@@ -1775,7 +1775,7 @@ where
None
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(false) {
funded_channel.reset_pending_splice_state()
} else {
debug_assert!(false, "We should never fail an interactive funding negotiation once we're exchanging tx_signatures");
Expand DownExpand Up@@ -1932,12 +1932,24 @@ where
(had_constructor, None)
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.has_pending_splice_awaiting_signatures() {
if funded_channel.has_pending_splice_awaiting_signatures()
&& funded_channel
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
{
// We only force close once the counterparty tries to abort after committing to

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that this is somehow a limitation in LDK, but it isn't, its a rather fundamental limitation of the protocol - if we have received the CS, the peer cannot know whether we have provided them with funding signatures, at which point cancelling the splice would be unsafe no matter the implementation in LDK.

// the splice via their initial `commitment_signed`. This is because our monitor
// state is updated with the post-splice commitment transaction upon receiving
// their `commitment_signed`, so we would need another monitor update to abandon
// it, which we don't currently support.
return Err(ChannelError::close(
"Received tx_abort while awaiting tx_signatures exchange".to_owned(),
));
}
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(true) {
let has_funding_negotiation = funded_channel
.pending_splice
.as_ref()
Expand DownExpand Up@@ -2681,19 +2693,6 @@ impl FundingNegotiation {
}

impl PendingFunding {
fn can_abandon_state(&self) -> bool {
self.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
!matches!(funding_negotiation, FundingNegotiation::AwaitingSignatures { .. })
})
.unwrap_or_else(|| {
let has_negotiated_candidates = !self.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
}

fn check_get_splice_locked<SP: Deref>(
&mut self, context: &ChannelContext<SP>, confirmed_funding_index: usize, height: u32,
) -> Option<msgs::SpliceLocked>
Expand DownExpand Up@@ -6873,7 +6872,7 @@ pub struct SpliceFundingFailed {
}

macro_rules! maybe_create_splice_funding_failed {
($pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
($funded_channel: expr, $pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
$pending_splice
.and_then(|pending_splice| pending_splice.funding_negotiation.$get())
.filter(|funding_negotiation| funding_negotiation.is_initiator())
Expand All@@ -6895,10 +6894,12 @@ macro_rules! maybe_create_splice_funding_failed {
interactive_tx_constructor,
..
} => interactive_tx_constructor.$contributed_inputs_and_outputs(),
FundingNegotiation::AwaitingSignatures { .. } => {
debug_assert!(false);
(Vec::new(), Vec::new())
},
FundingNegotiation::AwaitingSignatures { .. } => $funded_channel
.context
.interactive_tx_signing_session
.$get()
.expect("We have a pending splice awaiting signatures")
.$contributed_inputs_and_outputs(),
};

SpliceFundingFailed {
Expand DownExpand Up@@ -6937,7 +6938,7 @@ where

fn maybe_fail_splice_negotiation(&mut self) -> Option<SpliceFundingFailed> {
if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
self.reset_pending_splice_state()
} else {
match self.quiescent_action.take() {
Expand DownExpand Up@@ -7011,19 +7012,54 @@ where

/// Returns a boolean indicating whether we should reset the splice's
/// [`PendingFunding::funding_negotiation`].
fn should_reset_pending_splice_state(&self) -> bool {
fn should_reset_pending_splice_state(&self, counterparty_aborted: bool) -> bool {
self.pending_splice
.as_ref()
.map(|pending_splice| pending_splice.can_abandon_state())
.map(|pending_splice| {
pending_splice
.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
let is_awaiting_signatures = matches!(
funding_negotiation,
FundingNegotiation::AwaitingSignatures { .. }
);
if counterparty_aborted {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we condition on counterparty_aborted? Would it be a problem if always do the new check?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't, then we would always reset the splice state when we haven't received their initial commitment_signed.

!is_awaiting_signatures
|| !self
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
} else {
!is_awaiting_signatures
}
})
.unwrap_or_else(|| {
let has_negotiated_candidates =
!pending_splice.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
})
.unwrap_or(false)
}

fn reset_pending_splice_state(&mut self) -> Option<SpliceFundingFailed> {
debug_assert!(self.should_reset_pending_splice_state());
debug_assert!(self.context.interactive_tx_signing_session.is_none());
self.context.channel_state.clear_quiescent();
debug_assert!(self.should_reset_pending_splice_state(true));
debug_assert!(
self.context.interactive_tx_signing_session.is_none()
|| !self
.context
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
);

let splice_funding_failed = maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_mut(),
take,
into_contributed_inputs_and_outputs
Expand All@@ -7033,15 +7069,19 @@ where
self.pending_splice.take();
}

self.context.channel_state.clear_quiescent();
self.context.interactive_tx_signing_session.take();

splice_funding_failed
}

pub(super) fn maybe_splice_funding_failed(&self) -> Option<SpliceFundingFailed> {
if !self.should_reset_pending_splice_state() {
if !self.should_reset_pending_splice_state(false) {
return None;
}

maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_ref(),
as_ref,
to_contributed_inputs_and_outputs
Expand DownExpand Up@@ -11996,7 +12036,7 @@ where
pub fn abandon_splice(
&mut self,
) -> Result<(msgs::TxAbort, Option<SpliceFundingFailed>), APIError> {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
let tx_abort =
msgs::TxAbort { channel_id: self.context.channel_id(), data: Vec::new() };
let splice_funding_failed = self.reset_pending_splice_state();
Expand DownExpand Up@@ -14361,7 +14401,7 @@ where
}
channel_state.clear_local_stfu_sent();
channel_state.clear_remote_stfu_sent();
if self.should_reset_pending_splice_state()
if self.should_reset_pending_splice_state(false)
|| !self.has_pending_splice_awaiting_signatures()
{
// We shouldn't be quiescent anymore upon reconnecting if:
Expand DownExpand Up@@ -14735,7 +14775,7 @@ where
// We don't have to worry about resetting the pending `FundingNegotiation` because we
// can only read `FundingNegotiation::AwaitingSignatures` variants anyway.
let pending_splice =
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state());
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state(false));

write_tlv_fields!(writer, {
(0, self.context.announcement_sigs, option),
Expand Down
26 changes: 25 additions & 1 deletion lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4996,9 +4996,17 @@ macro_rules! handle_chan_reestablish_msgs {
($src_node: expr, $dst_node: expr) => {{
let msg_events = $src_node.node.get_and_clear_pending_msg_events();
let mut idx = 0;

let mut tx_abort = None;
if let Some(&MessageSendEvent::SendTxAbort { ref node_id, ref msg }) = msg_events.get(idx) {
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
tx_abort = Some(msg.clone());
}

let channel_ready =
if let Some(&MessageSendEvent::SendChannelReady { ref node_id, ref msg }) =
msg_events.get(0)
msg_events.get(idx)
{
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
Expand DownExpand Up@@ -5115,6 +5123,7 @@ macro_rules! handle_chan_reestablish_msgs {
announcement_sigs,
tx_signatures,
stfu,
tx_abort,
)
}};
}
Expand All@@ -5127,6 +5136,7 @@ pub struct ReconnectArgs<'a, 'b, 'c, 'd> {
pub send_stfu: (bool, bool),
pub send_interactive_tx_commit_sig: (bool, bool),
pub send_interactive_tx_sigs: (bool, bool),
pub send_tx_abort: (bool, bool),
pub expect_renegotiated_funding_locked_monitor_update: (bool, bool),
pub pending_responding_commitment_signed: (bool, bool),
/// Indicates that the pending responding commitment signed will be a dup for the recipient,
Expand All@@ -5150,6 +5160,7 @@ impl<'a, 'b, 'c, 'd> ReconnectArgs<'a, 'b, 'c, 'd> {
send_stfu: (false, false),
send_interactive_tx_commit_sig: (false, false),
send_interactive_tx_sigs: (false, false),
send_tx_abort: (false, false),
expect_renegotiated_funding_locked_monitor_update: (false, false),
pending_responding_commitment_signed: (false, false),
pending_responding_commitment_signed_dup_monitor: (false, false),
Expand All@@ -5174,6 +5185,7 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
send_stfu,
send_interactive_tx_commit_sig,
send_interactive_tx_sigs,
send_tx_abort,
expect_renegotiated_funding_locked_monitor_update,
pending_htlc_adds,
pending_htlc_claims,
Expand DownExpand Up@@ -5305,6 +5317,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.0 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.0 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_a.node.handle_tx_signatures(node_b_id, &tx_signatures);
Expand DownExpand Up@@ -5417,6 +5435,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.1 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.1 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_b.node.handle_tx_signatures(node_a_id, &tx_signatures);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow counterparty tx_abort before handling initial commitment signed by wpaulino · Pull Request #4204 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -596,7 +596,7 @@ fn do_test_async_raa_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
assert!(revoke_and_ack.is_none());
Expand All@@ -612,14 +612,14 @@ fn do_test_async_raa_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
assert!(commitment_signed.is_some());
assert!(resend_order == RAACommitmentOrder::RevokeAndACKFirst);
} else {
// Make sure we don't double send the RAA.
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_none());
assert!(commitment_signed.is_none());
Expand DownExpand Up@@ -746,7 +746,7 @@ fn do_test_async_commitment_signature_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
Expand All@@ -760,11 +760,11 @@ fn do_test_async_commitment_signature_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_some());
} else {
// Make sure we don't double send the CS.
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_none());
}
}
Expand DownExpand Up@@ -882,6 +882,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

if monitor_update_failure {
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
Expand All@@ -904,6 +905,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

nodes[0].enable_channel_signer_op(&node_b_id, &chan_id, SignerOp::SignCounterpartyCommitment);
nodes[0].node.signer_unblocked(Some((node_b_id, chan_id)));
Expand All@@ -929,6 +931,9 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.6.is_none());
assert!(bs_resp.6.is_none());

assert!(as_resp.7.is_none());
assert!(bs_resp.7.is_none());

// Now that everything is restored, get the CS + RAA and handle them.
nodes[1]
.node
Expand Down
104 changes: 72 additions & 32 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1654,7 +1654,7 @@ where
}
chan.context.channel_state.clear_local_stfu_sent();
chan.context.channel_state.clear_remote_stfu_sent();
if chan.should_reset_pending_splice_state() {
if chan.should_reset_pending_splice_state(false) {
// If there was a pending splice negotiation that failed due to disconnecting, we
// also take the opportunity to clean up our state.
let splice_funding_failed = chan.reset_pending_splice_state();
Expand DownExpand Up@@ -1775,7 +1775,7 @@ where
None
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(false) {
funded_channel.reset_pending_splice_state()
} else {
debug_assert!(false, "We should never fail an interactive funding negotiation once we're exchanging tx_signatures");
Expand DownExpand Up@@ -1932,12 +1932,24 @@ where
(had_constructor, None)
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.has_pending_splice_awaiting_signatures() {
if funded_channel.has_pending_splice_awaiting_signatures()
&& funded_channel
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
{
// We only force close once the counterparty tries to abort after committing to

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that this is somehow a limitation in LDK, but it isn't, its a rather fundamental limitation of the protocol - if we have received the CS, the peer cannot know whether we have provided them with funding signatures, at which point cancelling the splice would be unsafe no matter the implementation in LDK.

// the splice via their initial `commitment_signed`. This is because our monitor
// state is updated with the post-splice commitment transaction upon receiving
// their `commitment_signed`, so we would need another monitor update to abandon
// it, which we don't currently support.
return Err(ChannelError::close(
"Received tx_abort while awaiting tx_signatures exchange".to_owned(),
));
}
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(true) {
let has_funding_negotiation = funded_channel
.pending_splice
.as_ref()
Expand DownExpand Up@@ -2681,19 +2693,6 @@ impl FundingNegotiation {
}

impl PendingFunding {
fn can_abandon_state(&self) -> bool {
self.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
!matches!(funding_negotiation, FundingNegotiation::AwaitingSignatures { .. })
})
.unwrap_or_else(|| {
let has_negotiated_candidates = !self.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
}

fn check_get_splice_locked<SP: Deref>(
&mut self, context: &ChannelContext<SP>, confirmed_funding_index: usize, height: u32,
) -> Option<msgs::SpliceLocked>
Expand DownExpand Up@@ -6873,7 +6872,7 @@ pub struct SpliceFundingFailed {
}

macro_rules! maybe_create_splice_funding_failed {
($pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
($funded_channel: expr, $pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
$pending_splice
.and_then(|pending_splice| pending_splice.funding_negotiation.$get())
.filter(|funding_negotiation| funding_negotiation.is_initiator())
Expand All@@ -6895,10 +6894,12 @@ macro_rules! maybe_create_splice_funding_failed {
interactive_tx_constructor,
..
} => interactive_tx_constructor.$contributed_inputs_and_outputs(),
FundingNegotiation::AwaitingSignatures { .. } => {
debug_assert!(false);
(Vec::new(), Vec::new())
},
FundingNegotiation::AwaitingSignatures { .. } => $funded_channel
.context
.interactive_tx_signing_session
.$get()
.expect("We have a pending splice awaiting signatures")
.$contributed_inputs_and_outputs(),
};

SpliceFundingFailed {
Expand DownExpand Up@@ -6937,7 +6938,7 @@ where

fn maybe_fail_splice_negotiation(&mut self) -> Option<SpliceFundingFailed> {
if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
self.reset_pending_splice_state()
} else {
match self.quiescent_action.take() {
Expand DownExpand Up@@ -7011,19 +7012,54 @@ where

/// Returns a boolean indicating whether we should reset the splice's
/// [`PendingFunding::funding_negotiation`].
fn should_reset_pending_splice_state(&self) -> bool {
fn should_reset_pending_splice_state(&self, counterparty_aborted: bool) -> bool {
self.pending_splice
.as_ref()
.map(|pending_splice| pending_splice.can_abandon_state())
.map(|pending_splice| {
pending_splice
.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
let is_awaiting_signatures = matches!(
funding_negotiation,
FundingNegotiation::AwaitingSignatures { .. }
);
if counterparty_aborted {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we condition on counterparty_aborted? Would it be a problem if always do the new check?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't, then we would always reset the splice state when we haven't received their initial commitment_signed.

!is_awaiting_signatures
|| !self
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
} else {
!is_awaiting_signatures
}
})
.unwrap_or_else(|| {
let has_negotiated_candidates =
!pending_splice.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
})
.unwrap_or(false)
}

fn reset_pending_splice_state(&mut self) -> Option<SpliceFundingFailed> {
debug_assert!(self.should_reset_pending_splice_state());
debug_assert!(self.context.interactive_tx_signing_session.is_none());
self.context.channel_state.clear_quiescent();
debug_assert!(self.should_reset_pending_splice_state(true));
debug_assert!(
self.context.interactive_tx_signing_session.is_none()
|| !self
.context
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
);

let splice_funding_failed = maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_mut(),
take,
into_contributed_inputs_and_outputs
Expand All@@ -7033,15 +7069,19 @@ where
self.pending_splice.take();
}

self.context.channel_state.clear_quiescent();
self.context.interactive_tx_signing_session.take();

splice_funding_failed
}

pub(super) fn maybe_splice_funding_failed(&self) -> Option<SpliceFundingFailed> {
if !self.should_reset_pending_splice_state() {
if !self.should_reset_pending_splice_state(false) {
return None;
}

maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_ref(),
as_ref,
to_contributed_inputs_and_outputs
Expand DownExpand Up@@ -11996,7 +12036,7 @@ where
pub fn abandon_splice(
&mut self,
) -> Result<(msgs::TxAbort, Option<SpliceFundingFailed>), APIError> {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
let tx_abort =
msgs::TxAbort { channel_id: self.context.channel_id(), data: Vec::new() };
let splice_funding_failed = self.reset_pending_splice_state();
Expand DownExpand Up@@ -14361,7 +14401,7 @@ where
}
channel_state.clear_local_stfu_sent();
channel_state.clear_remote_stfu_sent();
if self.should_reset_pending_splice_state()
if self.should_reset_pending_splice_state(false)
|| !self.has_pending_splice_awaiting_signatures()
{
// We shouldn't be quiescent anymore upon reconnecting if:
Expand DownExpand Up@@ -14735,7 +14775,7 @@ where
// We don't have to worry about resetting the pending `FundingNegotiation` because we
// can only read `FundingNegotiation::AwaitingSignatures` variants anyway.
let pending_splice =
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state());
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state(false));

write_tlv_fields!(writer, {
(0, self.context.announcement_sigs, option),
Expand Down
26 changes: 25 additions & 1 deletion lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4996,9 +4996,17 @@ macro_rules! handle_chan_reestablish_msgs {
($src_node: expr, $dst_node: expr) => {{
let msg_events = $src_node.node.get_and_clear_pending_msg_events();
let mut idx = 0;

let mut tx_abort = None;
if let Some(&MessageSendEvent::SendTxAbort { ref node_id, ref msg }) = msg_events.get(idx) {
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
tx_abort = Some(msg.clone());
}

let channel_ready =
if let Some(&MessageSendEvent::SendChannelReady { ref node_id, ref msg }) =
msg_events.get(0)
msg_events.get(idx)
{
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
Expand DownExpand Up@@ -5115,6 +5123,7 @@ macro_rules! handle_chan_reestablish_msgs {
announcement_sigs,
tx_signatures,
stfu,
tx_abort,
)
}};
}
Expand All@@ -5127,6 +5136,7 @@ pub struct ReconnectArgs<'a, 'b, 'c, 'd> {
pub send_stfu: (bool, bool),
pub send_interactive_tx_commit_sig: (bool, bool),
pub send_interactive_tx_sigs: (bool, bool),
pub send_tx_abort: (bool, bool),
pub expect_renegotiated_funding_locked_monitor_update: (bool, bool),
pub pending_responding_commitment_signed: (bool, bool),
/// Indicates that the pending responding commitment signed will be a dup for the recipient,
Expand All@@ -5150,6 +5160,7 @@ impl<'a, 'b, 'c, 'd> ReconnectArgs<'a, 'b, 'c, 'd> {
send_stfu: (false, false),
send_interactive_tx_commit_sig: (false, false),
send_interactive_tx_sigs: (false, false),
send_tx_abort: (false, false),
expect_renegotiated_funding_locked_monitor_update: (false, false),
pending_responding_commitment_signed: (false, false),
pending_responding_commitment_signed_dup_monitor: (false, false),
Expand All@@ -5174,6 +5185,7 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
send_stfu,
send_interactive_tx_commit_sig,
send_interactive_tx_sigs,
send_tx_abort,
expect_renegotiated_funding_locked_monitor_update,
pending_htlc_adds,
pending_htlc_claims,
Expand DownExpand Up@@ -5305,6 +5317,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.0 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.0 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_a.node.handle_tx_signatures(node_b_id, &tx_signatures);
Expand DownExpand Up@@ -5417,6 +5435,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.1 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.1 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_b.node.handle_tx_signatures(node_a_id, &tx_signatures);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow counterparty tx_abort before handling initial commitment signed by wpaulino · Pull Request #4204 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -596,7 +596,7 @@ fn do_test_async_raa_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
assert!(revoke_and_ack.is_none());
Expand All@@ -612,14 +612,14 @@ fn do_test_async_raa_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
assert!(commitment_signed.is_some());
assert!(resend_order == RAACommitmentOrder::RevokeAndACKFirst);
} else {
// Make sure we don't double send the RAA.
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_none());
assert!(commitment_signed.is_none());
Expand DownExpand Up@@ -746,7 +746,7 @@ fn do_test_async_commitment_signature_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
Expand All@@ -760,11 +760,11 @@ fn do_test_async_commitment_signature_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_some());
} else {
// Make sure we don't double send the CS.
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_none());
}
}
Expand DownExpand Up@@ -882,6 +882,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

if monitor_update_failure {
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
Expand All@@ -904,6 +905,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

nodes[0].enable_channel_signer_op(&node_b_id, &chan_id, SignerOp::SignCounterpartyCommitment);
nodes[0].node.signer_unblocked(Some((node_b_id, chan_id)));
Expand All@@ -929,6 +931,9 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.6.is_none());
assert!(bs_resp.6.is_none());

assert!(as_resp.7.is_none());
assert!(bs_resp.7.is_none());

// Now that everything is restored, get the CS + RAA and handle them.
nodes[1]
.node
Expand Down
104 changes: 72 additions & 32 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1654,7 +1654,7 @@ where
}
chan.context.channel_state.clear_local_stfu_sent();
chan.context.channel_state.clear_remote_stfu_sent();
if chan.should_reset_pending_splice_state() {
if chan.should_reset_pending_splice_state(false) {
// If there was a pending splice negotiation that failed due to disconnecting, we
// also take the opportunity to clean up our state.
let splice_funding_failed = chan.reset_pending_splice_state();
Expand DownExpand Up@@ -1775,7 +1775,7 @@ where
None
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(false) {
funded_channel.reset_pending_splice_state()
} else {
debug_assert!(false, "We should never fail an interactive funding negotiation once we're exchanging tx_signatures");
Expand DownExpand Up@@ -1932,12 +1932,24 @@ where
(had_constructor, None)
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.has_pending_splice_awaiting_signatures() {
if funded_channel.has_pending_splice_awaiting_signatures()
&& funded_channel
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
{
// We only force close once the counterparty tries to abort after committing to

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that this is somehow a limitation in LDK, but it isn't, its a rather fundamental limitation of the protocol - if we have received the CS, the peer cannot know whether we have provided them with funding signatures, at which point cancelling the splice would be unsafe no matter the implementation in LDK.

// the splice via their initial `commitment_signed`. This is because our monitor
// state is updated with the post-splice commitment transaction upon receiving
// their `commitment_signed`, so we would need another monitor update to abandon
// it, which we don't currently support.
return Err(ChannelError::close(
"Received tx_abort while awaiting tx_signatures exchange".to_owned(),
));
}
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(true) {
let has_funding_negotiation = funded_channel
.pending_splice
.as_ref()
Expand DownExpand Up@@ -2681,19 +2693,6 @@ impl FundingNegotiation {
}

impl PendingFunding {
fn can_abandon_state(&self) -> bool {
self.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
!matches!(funding_negotiation, FundingNegotiation::AwaitingSignatures { .. })
})
.unwrap_or_else(|| {
let has_negotiated_candidates = !self.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
}

fn check_get_splice_locked<SP: Deref>(
&mut self, context: &ChannelContext<SP>, confirmed_funding_index: usize, height: u32,
) -> Option<msgs::SpliceLocked>
Expand DownExpand Up@@ -6873,7 +6872,7 @@ pub struct SpliceFundingFailed {
}

macro_rules! maybe_create_splice_funding_failed {
($pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
($funded_channel: expr, $pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
$pending_splice
.and_then(|pending_splice| pending_splice.funding_negotiation.$get())
.filter(|funding_negotiation| funding_negotiation.is_initiator())
Expand All@@ -6895,10 +6894,12 @@ macro_rules! maybe_create_splice_funding_failed {
interactive_tx_constructor,
..
} => interactive_tx_constructor.$contributed_inputs_and_outputs(),
FundingNegotiation::AwaitingSignatures { .. } => {
debug_assert!(false);
(Vec::new(), Vec::new())
},
FundingNegotiation::AwaitingSignatures { .. } => $funded_channel
.context
.interactive_tx_signing_session
.$get()
.expect("We have a pending splice awaiting signatures")
.$contributed_inputs_and_outputs(),
};

SpliceFundingFailed {
Expand DownExpand Up@@ -6937,7 +6938,7 @@ where

fn maybe_fail_splice_negotiation(&mut self) -> Option<SpliceFundingFailed> {
if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
self.reset_pending_splice_state()
} else {
match self.quiescent_action.take() {
Expand DownExpand Up@@ -7011,19 +7012,54 @@ where

/// Returns a boolean indicating whether we should reset the splice's
/// [`PendingFunding::funding_negotiation`].
fn should_reset_pending_splice_state(&self) -> bool {
fn should_reset_pending_splice_state(&self, counterparty_aborted: bool) -> bool {
self.pending_splice
.as_ref()
.map(|pending_splice| pending_splice.can_abandon_state())
.map(|pending_splice| {
pending_splice
.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
let is_awaiting_signatures = matches!(
funding_negotiation,
FundingNegotiation::AwaitingSignatures { .. }
);
if counterparty_aborted {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we condition on counterparty_aborted? Would it be a problem if always do the new check?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't, then we would always reset the splice state when we haven't received their initial commitment_signed.

!is_awaiting_signatures
|| !self
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
} else {
!is_awaiting_signatures
}
})
.unwrap_or_else(|| {
let has_negotiated_candidates =
!pending_splice.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
})
.unwrap_or(false)
}

fn reset_pending_splice_state(&mut self) -> Option<SpliceFundingFailed> {
debug_assert!(self.should_reset_pending_splice_state());
debug_assert!(self.context.interactive_tx_signing_session.is_none());
self.context.channel_state.clear_quiescent();
debug_assert!(self.should_reset_pending_splice_state(true));
debug_assert!(
self.context.interactive_tx_signing_session.is_none()
|| !self
.context
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
);

let splice_funding_failed = maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_mut(),
take,
into_contributed_inputs_and_outputs
Expand All@@ -7033,15 +7069,19 @@ where
self.pending_splice.take();
}

self.context.channel_state.clear_quiescent();
self.context.interactive_tx_signing_session.take();

splice_funding_failed
}

pub(super) fn maybe_splice_funding_failed(&self) -> Option<SpliceFundingFailed> {
if !self.should_reset_pending_splice_state() {
if !self.should_reset_pending_splice_state(false) {
return None;
}

maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_ref(),
as_ref,
to_contributed_inputs_and_outputs
Expand DownExpand Up@@ -11996,7 +12036,7 @@ where
pub fn abandon_splice(
&mut self,
) -> Result<(msgs::TxAbort, Option<SpliceFundingFailed>), APIError> {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
let tx_abort =
msgs::TxAbort { channel_id: self.context.channel_id(), data: Vec::new() };
let splice_funding_failed = self.reset_pending_splice_state();
Expand DownExpand Up@@ -14361,7 +14401,7 @@ where
}
channel_state.clear_local_stfu_sent();
channel_state.clear_remote_stfu_sent();
if self.should_reset_pending_splice_state()
if self.should_reset_pending_splice_state(false)
|| !self.has_pending_splice_awaiting_signatures()
{
// We shouldn't be quiescent anymore upon reconnecting if:
Expand DownExpand Up@@ -14735,7 +14775,7 @@ where
// We don't have to worry about resetting the pending `FundingNegotiation` because we
// can only read `FundingNegotiation::AwaitingSignatures` variants anyway.
let pending_splice =
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state());
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state(false));

write_tlv_fields!(writer, {
(0, self.context.announcement_sigs, option),
Expand Down
26 changes: 25 additions & 1 deletion lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4996,9 +4996,17 @@ macro_rules! handle_chan_reestablish_msgs {
($src_node: expr, $dst_node: expr) => {{
let msg_events = $src_node.node.get_and_clear_pending_msg_events();
let mut idx = 0;

let mut tx_abort = None;
if let Some(&MessageSendEvent::SendTxAbort { ref node_id, ref msg }) = msg_events.get(idx) {
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
tx_abort = Some(msg.clone());
}

let channel_ready =
if let Some(&MessageSendEvent::SendChannelReady { ref node_id, ref msg }) =
msg_events.get(0)
msg_events.get(idx)
{
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
Expand DownExpand Up@@ -5115,6 +5123,7 @@ macro_rules! handle_chan_reestablish_msgs {
announcement_sigs,
tx_signatures,
stfu,
tx_abort,
)
}};
}
Expand All@@ -5127,6 +5136,7 @@ pub struct ReconnectArgs<'a, 'b, 'c, 'd> {
pub send_stfu: (bool, bool),
pub send_interactive_tx_commit_sig: (bool, bool),
pub send_interactive_tx_sigs: (bool, bool),
pub send_tx_abort: (bool, bool),
pub expect_renegotiated_funding_locked_monitor_update: (bool, bool),
pub pending_responding_commitment_signed: (bool, bool),
/// Indicates that the pending responding commitment signed will be a dup for the recipient,
Expand All@@ -5150,6 +5160,7 @@ impl<'a, 'b, 'c, 'd> ReconnectArgs<'a, 'b, 'c, 'd> {
send_stfu: (false, false),
send_interactive_tx_commit_sig: (false, false),
send_interactive_tx_sigs: (false, false),
send_tx_abort: (false, false),
expect_renegotiated_funding_locked_monitor_update: (false, false),
pending_responding_commitment_signed: (false, false),
pending_responding_commitment_signed_dup_monitor: (false, false),
Expand All@@ -5174,6 +5185,7 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
send_stfu,
send_interactive_tx_commit_sig,
send_interactive_tx_sigs,
send_tx_abort,
expect_renegotiated_funding_locked_monitor_update,
pending_htlc_adds,
pending_htlc_claims,
Expand DownExpand Up@@ -5305,6 +5317,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.0 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.0 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_a.node.handle_tx_signatures(node_b_id, &tx_signatures);
Expand DownExpand Up@@ -5417,6 +5435,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.1 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.1 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_b.node.handle_tx_signatures(node_a_id, &tx_signatures);
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Allow counterparty tx_abort before handling initial commitment signed by wpaulino · Pull Request #4204 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -596,7 +596,7 @@ fn do_test_async_raa_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
assert!(revoke_and_ack.is_none());
Expand All@@ -612,14 +612,14 @@ fn do_test_async_raa_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _) =
let (_, revoke_and_ack, commitment_signed, resend_order, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
assert!(commitment_signed.is_some());
assert!(resend_order == RAACommitmentOrder::RevokeAndACKFirst);
} else {
// Make sure we don't double send the RAA.
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_none());
assert!(commitment_signed.is_none());
Expand DownExpand Up@@ -746,7 +746,7 @@ fn do_test_async_commitment_signature_peer_disconnect(
}

// Expect the RAA
let (_, revoke_and_ack, commitment_signed, _, _, _, _) =
let (_, revoke_and_ack, commitment_signed, _, _, _, _, _) =
handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
Expand All@@ -760,11 +760,11 @@ fn do_test_async_commitment_signature_peer_disconnect(
dst.node.signer_unblocked(Some((src_node_id, chan_id)));

if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_some());
} else {
// Make sure we don't double send the CS.
let (_, _, commitment_signed, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
let (_, _, commitment_signed, _, _, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(commitment_signed.is_none());
}
}
Expand DownExpand Up@@ -882,6 +882,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

if monitor_update_failure {
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
Expand All@@ -904,6 +905,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(as_resp.5.is_none());
assert!(as_resp.6.is_none());
assert!(as_resp.7.is_none());

nodes[0].enable_channel_signer_op(&node_b_id, &chan_id, SignerOp::SignCounterpartyCommitment);
nodes[0].node.signer_unblocked(Some((node_b_id, chan_id)));
Expand All@@ -929,6 +931,9 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.6.is_none());
assert!(bs_resp.6.is_none());

assert!(as_resp.7.is_none());
assert!(bs_resp.7.is_none());

// Now that everything is restored, get the CS + RAA and handle them.
nodes[1]
.node
Expand Down
104 changes: 72 additions & 32 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1654,7 +1654,7 @@ where
}
chan.context.channel_state.clear_local_stfu_sent();
chan.context.channel_state.clear_remote_stfu_sent();
if chan.should_reset_pending_splice_state() {
if chan.should_reset_pending_splice_state(false) {
// If there was a pending splice negotiation that failed due to disconnecting, we
// also take the opportunity to clean up our state.
let splice_funding_failed = chan.reset_pending_splice_state();
Expand DownExpand Up@@ -1775,7 +1775,7 @@ where
None
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(false) {
funded_channel.reset_pending_splice_state()
} else {
debug_assert!(false, "We should never fail an interactive funding negotiation once we're exchanging tx_signatures");
Expand DownExpand Up@@ -1932,12 +1932,24 @@ where
(had_constructor, None)
},
ChannelPhase::Funded(funded_channel) => {
if funded_channel.has_pending_splice_awaiting_signatures() {
if funded_channel.has_pending_splice_awaiting_signatures()
&& funded_channel
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
{
// We only force close once the counterparty tries to abort after committing to

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that this is somehow a limitation in LDK, but it isn't, its a rather fundamental limitation of the protocol - if we have received the CS, the peer cannot know whether we have provided them with funding signatures, at which point cancelling the splice would be unsafe no matter the implementation in LDK.

// the splice via their initial `commitment_signed`. This is because our monitor
// state is updated with the post-splice commitment transaction upon receiving
// their `commitment_signed`, so we would need another monitor update to abandon
// it, which we don't currently support.
return Err(ChannelError::close(
"Received tx_abort while awaiting tx_signatures exchange".to_owned(),
));
}
if funded_channel.should_reset_pending_splice_state() {
if funded_channel.should_reset_pending_splice_state(true) {
let has_funding_negotiation = funded_channel
.pending_splice
.as_ref()
Expand DownExpand Up@@ -2681,19 +2693,6 @@ impl FundingNegotiation {
}

impl PendingFunding {
fn can_abandon_state(&self) -> bool {
self.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
!matches!(funding_negotiation, FundingNegotiation::AwaitingSignatures { .. })
})
.unwrap_or_else(|| {
let has_negotiated_candidates = !self.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
}

fn check_get_splice_locked<SP: Deref>(
&mut self, context: &ChannelContext<SP>, confirmed_funding_index: usize, height: u32,
) -> Option<msgs::SpliceLocked>
Expand DownExpand Up@@ -6873,7 +6872,7 @@ pub struct SpliceFundingFailed {
}

macro_rules! maybe_create_splice_funding_failed {
($pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
($funded_channel: expr, $pending_splice: expr, $get: ident, $contributed_inputs_and_outputs: ident) => {{
$pending_splice
.and_then(|pending_splice| pending_splice.funding_negotiation.$get())
.filter(|funding_negotiation| funding_negotiation.is_initiator())
Expand All@@ -6895,10 +6894,12 @@ macro_rules! maybe_create_splice_funding_failed {
interactive_tx_constructor,
..
} => interactive_tx_constructor.$contributed_inputs_and_outputs(),
FundingNegotiation::AwaitingSignatures { .. } => {
debug_assert!(false);
(Vec::new(), Vec::new())
},
FundingNegotiation::AwaitingSignatures { .. } => $funded_channel
.context
.interactive_tx_signing_session
.$get()
.expect("We have a pending splice awaiting signatures")
.$contributed_inputs_and_outputs(),
};

SpliceFundingFailed {
Expand DownExpand Up@@ -6937,7 +6938,7 @@ where

fn maybe_fail_splice_negotiation(&mut self) -> Option<SpliceFundingFailed> {
if matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
self.reset_pending_splice_state()
} else {
match self.quiescent_action.take() {
Expand DownExpand Up@@ -7011,19 +7012,54 @@ where

/// Returns a boolean indicating whether we should reset the splice's
/// [`PendingFunding::funding_negotiation`].
fn should_reset_pending_splice_state(&self) -> bool {
fn should_reset_pending_splice_state(&self, counterparty_aborted: bool) -> bool {
self.pending_splice
.as_ref()
.map(|pending_splice| pending_splice.can_abandon_state())
.map(|pending_splice| {
pending_splice
.funding_negotiation
.as_ref()
.map(|funding_negotiation| {
let is_awaiting_signatures = matches!(
funding_negotiation,
FundingNegotiation::AwaitingSignatures { .. }
);
if counterparty_aborted {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we condition on counterparty_aborted? Would it be a problem if always do the new check?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't, then we would always reset the splice state when we haven't received their initial commitment_signed.

!is_awaiting_signatures
|| !self
.context()
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
} else {
!is_awaiting_signatures
}
})
.unwrap_or_else(|| {
let has_negotiated_candidates =
!pending_splice.negotiated_candidates.is_empty();
debug_assert!(has_negotiated_candidates);
!has_negotiated_candidates
})
})
.unwrap_or(false)
}

fn reset_pending_splice_state(&mut self) -> Option<SpliceFundingFailed> {
debug_assert!(self.should_reset_pending_splice_state());
debug_assert!(self.context.interactive_tx_signing_session.is_none());
self.context.channel_state.clear_quiescent();
debug_assert!(self.should_reset_pending_splice_state(true));
debug_assert!(
self.context.interactive_tx_signing_session.is_none()
|| !self
.context
.interactive_tx_signing_session
.as_ref()
.expect("We have a pending splice awaiting signatures")
.has_received_commitment_signed()
);

let splice_funding_failed = maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_mut(),
take,
into_contributed_inputs_and_outputs
Expand All@@ -7033,15 +7069,19 @@ where
self.pending_splice.take();
}

self.context.channel_state.clear_quiescent();
self.context.interactive_tx_signing_session.take();

splice_funding_failed
}

pub(super) fn maybe_splice_funding_failed(&self) -> Option<SpliceFundingFailed> {
if !self.should_reset_pending_splice_state() {
if !self.should_reset_pending_splice_state(false) {
return None;
}

maybe_create_splice_funding_failed!(
self,
self.pending_splice.as_ref(),
as_ref,
to_contributed_inputs_and_outputs
Expand DownExpand Up@@ -11996,7 +12036,7 @@ where
pub fn abandon_splice(
&mut self,
) -> Result<(msgs::TxAbort, Option<SpliceFundingFailed>), APIError> {
if self.should_reset_pending_splice_state() {
if self.should_reset_pending_splice_state(false) {
let tx_abort =
msgs::TxAbort { channel_id: self.context.channel_id(), data: Vec::new() };
let splice_funding_failed = self.reset_pending_splice_state();
Expand DownExpand Up@@ -14361,7 +14401,7 @@ where
}
channel_state.clear_local_stfu_sent();
channel_state.clear_remote_stfu_sent();
if self.should_reset_pending_splice_state()
if self.should_reset_pending_splice_state(false)
|| !self.has_pending_splice_awaiting_signatures()
{
// We shouldn't be quiescent anymore upon reconnecting if:
Expand DownExpand Up@@ -14735,7 +14775,7 @@ where
// We don't have to worry about resetting the pending `FundingNegotiation` because we
// can only read `FundingNegotiation::AwaitingSignatures` variants anyway.
let pending_splice =
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state());
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state(false));

write_tlv_fields!(writer, {
(0, self.context.announcement_sigs, option),
Expand Down
26 changes: 25 additions & 1 deletion lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4996,9 +4996,17 @@ macro_rules! handle_chan_reestablish_msgs {
($src_node: expr, $dst_node: expr) => {{
let msg_events = $src_node.node.get_and_clear_pending_msg_events();
let mut idx = 0;

let mut tx_abort = None;
if let Some(&MessageSendEvent::SendTxAbort { ref node_id, ref msg }) = msg_events.get(idx) {
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
tx_abort = Some(msg.clone());
}

let channel_ready =
if let Some(&MessageSendEvent::SendChannelReady { ref node_id, ref msg }) =
msg_events.get(0)
msg_events.get(idx)
{
idx += 1;
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
Expand DownExpand Up@@ -5115,6 +5123,7 @@ macro_rules! handle_chan_reestablish_msgs {
announcement_sigs,
tx_signatures,
stfu,
tx_abort,
)
}};
}
Expand All@@ -5127,6 +5136,7 @@ pub struct ReconnectArgs<'a, 'b, 'c, 'd> {
pub send_stfu: (bool, bool),
pub send_interactive_tx_commit_sig: (bool, bool),
pub send_interactive_tx_sigs: (bool, bool),
pub send_tx_abort: (bool, bool),
pub expect_renegotiated_funding_locked_monitor_update: (bool, bool),
pub pending_responding_commitment_signed: (bool, bool),
/// Indicates that the pending responding commitment signed will be a dup for the recipient,
Expand All@@ -5150,6 +5160,7 @@ impl<'a, 'b, 'c, 'd> ReconnectArgs<'a, 'b, 'c, 'd> {
send_stfu: (false, false),
send_interactive_tx_commit_sig: (false, false),
send_interactive_tx_sigs: (false, false),
send_tx_abort: (false, false),
expect_renegotiated_funding_locked_monitor_update: (false, false),
pending_responding_commitment_signed: (false, false),
pending_responding_commitment_signed_dup_monitor: (false, false),
Expand All@@ -5174,6 +5185,7 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
send_stfu,
send_interactive_tx_commit_sig,
send_interactive_tx_sigs,
send_tx_abort,
expect_renegotiated_funding_locked_monitor_update,
pending_htlc_adds,
pending_htlc_claims,
Expand DownExpand Up@@ -5305,6 +5317,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.0 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.0 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_a.node.handle_tx_signatures(node_b_id, &tx_signatures);
Expand DownExpand Up@@ -5417,6 +5435,12 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
&commitment_update.commitment_signed,
)
}
if send_tx_abort.1 {
let tx_abort = chan_msgs.7.take().unwrap();
node_a.node.handle_tx_abort(node_b_id, &tx_abort);
} else {
assert!(chan_msgs.7.is_none());
}
if send_interactive_tx_sigs.1 {
let tx_signatures = chan_msgs.5.take().unwrap();
node_b.node.handle_tx_signatures(node_a_id, &tx_signatures);
Expand Down
Loading
Loading