Test channel reestablish during splice lifecycle
What changed, and why it matters
This commit only adds new test code for the Lightning channel re-establishment flow during a splice. It does not change production logic, so it introduces no direct security vulnerability. The tests verify that nodes correctly retransmit messages after a disconnect while splicing a channel.
No security action required; review as normal test coverage improvement.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff is entirely test additions and test helper updates in rust-lightning. It adds a test_splice_reestablish test plus helper negotiate_splice_tx, and extends ReconnectArgs/reconnect_nodes and handle_chan_reestablish_msgs! macros to handle interactive-tx commitment_signed and tx_signatures retransmission during reestablish. No production channel state machine or cryptographic code is modified.
Changed components
lightning/src/ln/splicing_tests.rslightning/src/ln/functional_test_utils.rslightning/src/ln/async_signer_tests.rsInspect captured patch +376 / −46
diff --git a/lightning/src/ln/async_signer_tests.rs b/lightning/src/ln/async_signer_tests.rs
index ff4ef50..de5103a 100644
--- a/lightning/src/ln/async_signer_tests.rs
+++ b/lightning/src/ln/async_signer_tests.rs
@@ -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());
@@ -612,14 +612,15 @@ 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, _, _) = handle_chan_reestablish_msgs!(dst, src);
+ let (_, revoke_and_ack, commitment_signed, _, _, _) =
+ handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_none());
assert!(commitment_signed.is_none());
}
@@ -745,7 +746,7 @@ fn do_test_async_commitment_signature_peer_disconnect(
}
// Expect the RAA
- let (_, revoke_and_ack, commitment_signed, _, _) = handle_chan_reestablish_msgs!(dst, src);
+ let (_, revoke_and_ack, commitment_signed, _, _, _) = handle_chan_reestablish_msgs!(dst, src);
assert!(revoke_and_ack.is_some());
if test_case == UnblockSignerAcrossDisconnectCase::AtEnd {
assert!(commitment_signed.is_none());
@@ -758,11 +759,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());
}
}
@@ -878,6 +879,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.1.is_none());
assert!(as_resp.2.is_none());
assert!(as_resp.4.is_none());
+ assert!(as_resp.5.is_none());
if monitor_update_failure {
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
@@ -898,6 +900,7 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.1.is_none());
assert!(as_resp.2.is_none());
assert!(as_resp.4.is_none());
+ assert!(as_resp.5.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)));
@@ -917,6 +920,9 @@ fn do_test_async_commitment_signature_ordering(monitor_update_failure: bool) {
assert!(as_resp.4.is_none());
assert!(bs_resp.4.is_none());
+ assert!(as_resp.5.is_none());
+ assert!(bs_resp.5.is_none());
+
// Now that everything is restored, get the CS + RAA and handle them.
nodes[1]
.node
diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs
index aa2c45c..fbfe320 100644
--- a/lightning/src/ln/functional_test_utils.rs
+++ b/lightning/src/ln/functional_test_utils.rs
@@ -4862,6 +4862,15 @@ macro_rules! handle_chan_reestablish_msgs {
}
}
+ let mut tx_signatures = None;
+ if let Some(&MessageSendEvent::SendTxSignatures { ref node_id, ref msg }) =
+ msg_events.get(idx)
+ {
+ assert_eq!(*node_id, $dst_node.node.get_our_node_id());
+ tx_signatures = Some(msg.clone());
+ idx += 1;
+ }
+
if let Some(&MessageSendEvent::SendAnnouncementSignatures { ref node_id, ref msg }) =
msg_events.get(idx)
{
@@ -4880,7 +4889,7 @@ macro_rules! handle_chan_reestablish_msgs {
assert_eq!(msg_events.len(), idx, "{msg_events:?}");
- (channel_ready, revoke_and_ack, commitment_update, order, announcement_sigs)
+ (channel_ready, revoke_and_ack, commitment_update, order, announcement_sigs, tx_signatures)
}};
}
@@ -4889,6 +4898,9 @@ pub struct ReconnectArgs<'a, 'b, 'c, 'd> {
pub node_b: &'a Node<'b, 'c, 'd>,
pub send_channel_ready: (bool, bool),
pub send_announcement_sigs: (bool, bool),
+ pub send_interactive_tx_commit_sig: (bool, bool),
+ pub send_interactive_tx_sigs: (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,
/// and no monitor update is expected
@@ -4908,6 +4920,9 @@ impl<'a, 'b, 'c, 'd> ReconnectArgs<'a, 'b, 'c, 'd> {
node_b,
send_channel_ready: (false, false),
send_announcement_sigs: (false, false),
+ send_interactive_tx_commit_sig: (false, false),
+ send_interactive_tx_sigs: (false, false),
+ expect_renegotiated_funding_locked_monitor_update: (false, false),
pending_responding_commitment_signed: (false, false),
pending_responding_commitment_signed_dup_monitor: (false, false),
pending_htlc_adds: (0, 0),
@@ -4928,6 +4943,9 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
node_b,
send_channel_ready,
send_announcement_sigs,
+ send_interactive_tx_commit_sig,
+ send_interactive_tx_sigs,
+ expect_renegotiated_funding_locked_monitor_update,
pending_htlc_adds,
pending_htlc_claims,
pending_htlc_fails,
@@ -4978,7 +4996,11 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
node_b.node.handle_channel_reestablish(node_a_id, &msg);
resp_1.push(handle_chan_reestablish_msgs!(node_b, node_a));
}
- if pending_cell_htlc_claims.0 != 0 || pending_cell_htlc_fails.0 != 0 {
+
+ if pending_cell_htlc_claims.0 != 0
+ || pending_cell_htlc_fails.0 != 0
+ || expect_renegotiated_funding_locked_monitor_update.1
+ {
check_added_monitors!(node_b, 1);
} else {
check_added_monitors!(node_b, 0);
@@ -4989,7 +5011,10 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
node_a.node.handle_channel_reestablish(node_b_id, &msg);
resp_2.push(handle_chan_reestablish_msgs!(node_a, node_b));
}
- if pending_cell_htlc_claims.1 != 0 || pending_cell_htlc_fails.1 != 0 {
+ if pending_cell_htlc_claims.1 != 0
+ || pending_cell_htlc_fails.1 != 0
+ || expect_renegotiated_funding_locked_monitor_update.0
+ {
check_added_monitors!(node_a, 1);
} else {
check_added_monitors!(node_a, 0);
@@ -5036,6 +5061,21 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
} else {
assert!(chan_msgs.4.is_none());
}
+ if send_interactive_tx_commit_sig.0 {
+ assert!(chan_msgs.1.is_none());
+ let commitment_update = chan_msgs.2.take().unwrap();
+ assert_eq!(commitment_update.commitment_signed.len(), 1);
+ node_a.node.handle_commitment_signed_batch_test(
+ node_b_id,
+ &commitment_update.commitment_signed,
+ )
+ }
+ 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);
+ } else {
+ assert!(chan_msgs.5.is_none());
+ }
if pending_raa.0 {
assert!(chan_msgs.3 == RAACommitmentOrder::RevokeAndACKFirst);
node_a.node.handle_revoke_and_ack(node_b_id, &chan_msgs.1.unwrap());
@@ -5127,6 +5167,21 @@ pub fn reconnect_nodes<'a, 'b, 'c, 'd>(args: ReconnectArgs<'a, 'b, 'c, 'd>) {
} else {
assert!(chan_msgs.4.is_none());
}
+ if send_interactive_tx_commit_sig.1 {
+ assert!(chan_msgs.1.is_none());
+ let commitment_update = chan_msgs.2.take().unwrap();
+ assert_eq!(commitment_update.commitment_signed.len(), 1);
+ node_b.node.handle_commitment_signed_batch_test(
+ node_a_id,
+ &commitment_update.commitment_signed,
+ )
+ }
+ 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);
+ } else {
+ assert!(chan_msgs.5.is_none());
+ }
if pending_raa.1 {
assert!(chan_msgs.3 == RAACommitmentOrder::RevokeAndACKFirst);
node_b.node.handle_revoke_and_ack(node_a_id, &chan_msgs.1.unwrap());
diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs
index fc062d6..62e1064 100644
--- a/lightning/src/ln/splicing_tests.rs
+++ b/lightning/src/ln/splicing_tests.rs
@@ -10,6 +10,7 @@
use crate::chain::chaininterface::FEERATE_FLOOR_SATS_PER_KW;
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS};
use crate::chain::transaction::OutPoint;
+use crate::chain::ChannelMonitorUpdateStatus;
use crate::events::bump_transaction::sync::WalletSourceSync;
use crate::events::{ClosureReason, Event, FundingInfo, HTLCHandlingFailureType};
use crate::ln::chan_utils;
@@ -63,6 +64,49 @@ fn test_v1_splice_in_negative_insufficient_inputs() {
}
}
+fn negotiate_splice_tx<'a, 'b, 'c, 'd>(
+ initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
+ initiator_contribution: SpliceContribution,
+) -> msgs::CommitmentSigned {
+ let node_id_initiator = initiator.node.get_our_node_id();
+ let node_id_acceptor = acceptor.node.get_our_node_id();
+
+ initiator
+ .node
+ .splice_channel(
+ &channel_id,
+ &node_id_acceptor,
+ initiator_contribution.clone(),
+ FEERATE_FLOOR_SATS_PER_KW,
+ None,
+ )
+ .unwrap();
+
+ let stfu_init = get_event_msg!(initiator, MessageSendEvent::SendStfu, node_id_acceptor);
+ acceptor.node.handle_stfu(node_id_initiator, &stfu_init);
+ let stfu_ack = get_event_msg!(acceptor, MessageSendEvent::SendStfu, node_id_initiator);
+ initiator.node.handle_stfu(node_id_acceptor, &stfu_ack);
+
+ let splice_init = get_event_msg!(initiator, MessageSendEvent::SendSpliceInit, node_id_acceptor);
+ acceptor.node.handle_splice_init(node_id_initiator, &splice_init);
+ let splice_ack = get_event_msg!(acceptor, MessageSendEvent::SendSpliceAck, node_id_initiator);
+ initiator.node.handle_splice_ack(node_id_acceptor, &splice_ack);
+
+ let new_funding_script = chan_utils::make_funding_redeemscript(
+ &splice_init.funding_pubkey,
+ &splice_ack.funding_pubkey,
+ )
+ .to_p2wsh();
+
+ complete_interactive_funding_negotiation(
+ initiator,
+ acceptor,
+ channel_id,
+ initiator_contribution,
+ new_funding_script,
+ )
+}
+
fn complete_interactive_funding_negotiation<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
initiator_contribution: SpliceContribution, new_funding_script: ScriptBuf,
@@ -202,43 +246,8 @@ fn splice_channel<'a, 'b, 'c, 'd>(
initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
initiator_contribution: SpliceContribution,
) -> Transaction {
- let node_id_initiator = initiator.node.get_our_node_id();
- let node_id_acceptor = acceptor.node.get_our_node_id();
-
- initiator
- .node
- .splice_channel(
- &channel_id,
- &node_id_acceptor,
- initiator_contribution.clone(),
- FEERATE_FLOOR_SATS_PER_KW,
- None,
- )
- .unwrap();
-
- let stfu_init = get_event_msg!(initiator, MessageSendEvent::SendStfu, node_id_acceptor);
- acceptor.node.handle_stfu(node_id_initiator, &stfu_init);
- let stfu_ack = get_event_msg!(acceptor, MessageSendEvent::SendStfu, node_id_initiator);
- initiator.node.handle_stfu(node_id_acceptor, &stfu_ack);
-
- let splice_init = get_event_msg!(initiator, MessageSendEvent::SendSpliceInit, node_id_acceptor);
- acceptor.node.handle_splice_init(node_id_initiator, &splice_init);
- let splice_ack = get_event_msg!(acceptor, MessageSendEvent::SendSpliceAck, node_id_initiator);
- initiator.node.handle_splice_ack(node_id_acceptor, &splice_ack);
-
- let new_funding_script = chan_utils::make_funding_redeemscript(
- &splice_init.funding_pubkey,
- &splice_ack.funding_pubkey,
- )
- .to_p2wsh();
-
- let initial_commit_sig_for_acceptor = complete_interactive_funding_negotiation(
- initiator,
- acceptor,
- channel_id,
- initiator_contribution,
- new_funding_script,
- );
+ let initial_commit_sig_for_acceptor =
+ negotiate_splice_tx(initiator, acceptor, channel_id, initiator_contribution);
sign_interactive_funding_transaction(initiator, acceptor, initial_commit_sig_for_acceptor);
let splice_tx = {
@@ -785,3 +794,263 @@ fn do_test_splice_commitment_broadcast(splice_status: SpliceStatus, claim_htlcs:
}
}
}
+
+#[test]
+fn test_splice_reestablish() {
+ do_test_splice_reestablish(false, false);
+ do_test_splice_reestablish(false, true);
+ do_test_splice_reestablish(true, false);
+ do_test_splice_reestablish(true, true);
+}
+
+fn do_test_splice_reestablish(reload: bool, async_monitor_update: bool) {
+ // Test that we're able to reestablish the channel succesfully throughout the lifecycle of a splice.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let (persister_0a, persister_0b, persister_1a, persister_1b);
+ let (chain_monitor_0a, chain_monitor_0b, chain_monitor_1a, chain_monitor_1b);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let (node_0a, node_0b, node_1a, node_1b);
+ let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let node_id_0 = nodes[0].node.get_our_node_id();
+ let node_id_1 = nodes[1].node.get_our_node_id();
+
+ let initial_channel_value_sat = 100_000;
+ let (_, _, channel_id, _) =
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_value_sat, 0);
+
+ let prev_funding_outpoint = get_monitor!(nodes[0], channel_id).get_funding_txo();
+ let prev_funding_script = get_monitor!(nodes[0], channel_id).get_funding_script();
+
+ // Keep a pending HTLC throughout the reestablish flow to make sure we can handle them.
+ route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+
+ // Negotiate the splice up until the nodes exchange `tx_complete`.
+ let initiator_contribution = SpliceContribution::SpliceOut {
+ outputs: vec![
+ TxOut {
+ value: Amount::from_sat(initial_channel_value_sat / 4),
+ script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
+ },
+ TxOut {
+ value: Amount::from_sat(initial_channel_value_sat / 4),
+ script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
+ },
+ ],
+ };
+ let initial_commit_sig_for_acceptor =
+ negotiate_splice_tx(&nodes[0], &nodes[1], channel_id, initiator_contribution);
+ assert_eq!(initial_commit_sig_for_acceptor.htlc_signatures.len(), 1);
+ let initial_commit_sig_for_initiator = get_htlc_update_msgs!(&nodes[1], node_id_0);
+ assert_eq!(initial_commit_sig_for_initiator.commitment_signed.len(), 1);
+ assert_eq!(initial_commit_sig_for_initiator.commitment_signed[0].htlc_signatures.len(), 1);
+
+ macro_rules! reconnect_nodes {
+ ($f: expr) => {
+ nodes[0].node.peer_disconnected(node_id_1);
+ nodes[1].node.peer_disconnected(node_id_0);
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ $f(&mut reconnect_args);
+ reconnect_nodes(reconnect_args);
+ };
+ }
+
+ // Reestablishing now should force both nodes to retransmit their initial `commitment_signed`
+ // message as they were never delivered.
+ if reload {
+ let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
+ reload_node!(
+ nodes[0],
+ nodes[0].node.encode(),
+ &[&encoded_monitor_0],
+ persister_0a,
+ chain_monitor_0a,
+ node_0a
+ );
+ let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
+ reload_node!(
+ nodes[1],
+ nodes[1].node.encode(),
+ &[&encoded_monitor_1],
+ persister_1a,
+ chain_monitor_1a,
+ node_1a
+ );
+ if async_monitor_update {
+ persister_0a.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ persister_1a.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ }
+ } else {
+ nodes[0].node.peer_disconnected(node_id_1);
+ nodes[1].node.peer_disconnected(node_id_0);
+ if async_monitor_update {
+ chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ }
+ }
+
+ let mut reconnect_args = ReconnectArgs::new(&nodes[0], &nodes[1]);
+ reconnect_args.send_interactive_tx_commit_sig = (true, true);
+ reconnect_nodes(reconnect_args);
+
+ // The `commitment_signed` messages were delivered in the reestablishment, so we should expect
+ // to see a `RenegotiatedFunding` monitor update on both nodes.
+ check_added_monitors(&nodes[0], 1);
+ check_added_monitors(&nodes[1], 1);
+
+ if async_monitor_update {
+ // Reconnecting again should result in no messages/events being generated as the monitor
+ // update is pending.
+ reconnect_nodes!(|_| {});
+ assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+ assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+ nodes[0].chain_monitor.complete_sole_pending_chan_update(&channel_id);
+ nodes[1].chain_monitor.complete_sole_pending_chan_update(&channel_id);
+ chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
+ chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
+ }
+
+ // Node 0 should have a signing event to handle since they had a contribution in the splice.
+ // Node 1 won't and will immediately send `tx_signatures`.
+ let _ = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
+ assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+ let _ = get_event_msg!(nodes[1], MessageSendEvent::SendTxSignatures, node_id_0);
+
+ // Reconnecting now should force node 1 to retransmit their `tx_signatures` since it was never
+ // delivered. Node 0 still hasn't called back with `funding_transaction_signed`, so its
+ // `tx_signatures` is not ready yet.
+ reconnect_nodes!(|reconnect_args: &mut ReconnectArgs| {
+ reconnect_args.send_interactive_tx_sigs = (true, false);
+ });
+ let _ = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
+
+ // Reconnect again to make sure node 1 doesn't retransmit `tx_signatures` unnecessarily as it
+ // was delivered in the previous reestablishment.
+ reconnect_nodes!(|_| {});
+
+ // Have node 0 sign, we should see its `tx_signatures` go out.
+ let event = get_event!(nodes[0], Event::FundingTransactionReadyForSigning);
+ if let Event::FundingTransactionReadyForSigning { unsigned_transaction, .. } = event {
+ let tx = nodes[0].wallet_source.sign_tx(unsigned_transaction).unwrap();
+ nodes[0].node.funding_transaction_signed(&channel_id, &node_id_1, tx).unwrap();
+ }
+ let _ = get_event_msg!(nodes[0], MessageSendEvent::SendTxSignatures, node_id_1);
+
+ // Reconnect to make sure node 0 retransmits its `tx_signatures` as it was never delivered.
+ reconnect_nodes!(|reconnect_args: &mut ReconnectArgs| {
+ reconnect_args.send_interactive_tx_sigs = (false, true);
+ });
+
+ // Reestablish the channel again to make sure node 0 doesn't retransmit `tx_signatures`
+ // unnecessarily as it was delivered in the previous reestablishment.
+ if reload {
+ let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
+ reload_node!(
+ nodes[0],
+ nodes[0].node.encode(),
+ &[&encoded_monitor_0],
+ persister_0b,
+ chain_monitor_0b,
+ node_0b
+ );
+ let encoded_monitor_1 = get_monitor!(nodes[1], channel_id).encode();
+ reload_node!(
+ nodes[1],
+ nodes[1].node.encode(),
+ &[&encoded_monitor_1],
+ persister_1b,
+ chain_monitor_1b,
+ node_1b
+ );
+ } else {
+ nodes[0].node.peer_disconnected(node_id_1);
+ nodes[1].node.peer_disconnected(node_id_0);
+ }
+ reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
+
+ // The channel should no longer be quiescent with `tx_signatures` exchanged. We should expect to
+ // see the splice transaction broadcast.
+ let splice_tx = {
+ let mut txn_0 = nodes[0].tx_broadcaster.txn_broadcast();
+ assert_eq!(txn_0.len(), 1);
+ let txn_1 = nodes[1].tx_broadcaster.txn_broadcast();
+ assert_eq!(txn_0, txn_1);
+ txn_0.remove(0)
+ };
+
+ // Make sure we can still send payments.
+ send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+
+ // Lock in the splice on node 0. We should see its `splice_locked` sent.
+ confirm_transaction(&nodes[0], &splice_tx);
+ let _ = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_id_1);
+
+ // Confirm the splice but with one less confirmation than required on node 1. Its
+ // `splice_locked` should no be sent yet.
+ mine_transaction(&nodes[1], &splice_tx);
+ connect_blocks(&nodes[1], ANTI_REORG_DELAY - 2);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+
+ // Reconnect the nodes. Node 1 should assume node 0's `splice_locked` via
+ // `ChannelReestablish::my_current_funding_locked`.
+ reconnect_nodes!(|_| {});
+
+ if async_monitor_update {
+ chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
+ }
+
+ // Mine the remaining block on node 1 for the splice to be locked. Since `splice_locked` has now
+ // been exchanged on node 1, we should see its `announcement_signatures` sent as well, and the
+ // `RenegotiatedFundingLocked` monitor update.
+ connect_blocks(&nodes[1], 1);
+ check_added_monitors(&nodes[1], 1);
+ let mut msg_events = nodes[1].node.get_and_clear_pending_msg_events();
+ assert_eq!(msg_events.len(), 2, "{msg_events:?}");
+ if let MessageSendEvent::SendSpliceLocked { .. } = msg_events.remove(0) {
+ } else {
+ panic!()
+ }
+ if let MessageSendEvent::SendAnnouncementSignatures { .. } = msg_events.remove(0) {
+ } else {
+ panic!()
+ }
+ expect_channel_ready_event(&nodes[1], &node_id_0);
+
+ // Reconnect the nodes to ensure node 1 retransmits its `splice_locked` (implicitly via
+ // `my_current_funding_locked`) and `announcement_signatures` to node 0.
+ reconnect_nodes!(|reconnect_args: &mut ReconnectArgs| {
+ reconnect_args.expect_renegotiated_funding_locked_monitor_update = (true, false);
+ reconnect_args.send_announcement_sigs = (true, true);
+ });
+ expect_channel_ready_event(&nodes[0], &node_id_1);
+
+ if async_monitor_update {
+ nodes[0].chain_monitor.complete_sole_pending_chan_update(&channel_id);
+ nodes[1].chain_monitor.complete_sole_pending_chan_update(&channel_id);
+ chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
+ chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
+ }
+
+ // We shouldn't have any further events or messages to process.
+ assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+ assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+
+ // Make sure we can still send payments.
+ send_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+
+ // Remove the previous funding info the chain source was watching to avoid failing the
+ // end-of-test sanity checks.
+ nodes[0]
+ .chain_source
+ .remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script.clone());
+ nodes[1]
+ .chain_source
+ .remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script);
+}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.