Skip to content

Commit 3e21ba3

Browse files
authored
Merge pull request #4068 from wpaulino/test-splice-commitment-broadcast
Test commitment broadcast during different stages of a splice
2 parents 5a84daf + aa912ce commit 3e21ba3

File tree

4 files changed

+237
-9
lines changed

4 files changed

+237
-9
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5571,6 +5571,17 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
55715571
OnchainEvent::FundingSpendConfirmation { commitment_tx_to_counterparty_output, .. } => {
55725572
self.funding_spend_confirmed = Some(entry.txid);
55735573
self.confirmed_commitment_tx_counterparty_output = commitment_tx_to_counterparty_output;
5574+
if self.alternative_funding_confirmed.is_none() {
5575+
for funding in self.pending_funding.drain(..) {
5576+
self.outputs_to_watch.remove(&funding.funding_txid());
5577+
self.pending_events.push(Event::DiscardFunding {
5578+
channel_id: self.channel_id,
5579+
funding_info: crate::events::FundingInfo::OutPoint {
5580+
outpoint: funding.funding_outpoint(),
5581+
},
5582+
});
5583+
}
5584+
}
55745585
},
55755586
OnchainEvent::AlternativeFundingConfirmation {} => {
55765587
// An alternative funding transaction has irrevocably confirmed and we're no

lightning/src/ln/channel.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6115,9 +6115,9 @@ where
61156115
}
61166116
}
61176117

6118-
fn get_initial_counterparty_commitment_signature<L: Deref>(
6118+
fn get_initial_counterparty_commitment_signatures<L: Deref>(
61196119
&self, funding: &FundingScope, logger: &L,
6120-
) -> Option<Signature>
6120+
) -> Option<(Signature, Vec<Signature>)>
61216121
where
61226122
SP::Target: SignerProvider,
61236123
L::Target: Logger,
@@ -6152,7 +6152,6 @@ where
61526152
Vec::new(),
61536153
&self.secp_ctx,
61546154
)
6155-
.map(|(signature, _)| signature)
61566155
.ok()
61576156
},
61586157
// TODO (taproot|arik)
@@ -6170,16 +6169,20 @@ where
61706169
{
61716170
debug_assert!(self.interactive_tx_signing_session.is_some());
61726171

6173-
let signature = self.get_initial_counterparty_commitment_signature(funding, logger);
6174-
if let Some(signature) = signature {
6172+
let signatures = self.get_initial_counterparty_commitment_signatures(funding, logger);
6173+
if let Some((signature, htlc_signatures)) = signatures {
61756174
log_info!(
61766175
logger,
61776176
"Generated commitment_signed for peer for channel {}",
61786177
&self.channel_id()
61796178
);
6179+
if matches!(self.channel_state, ChannelState::FundingNegotiated(_)) {
6180+
// We shouldn't expect any HTLCs before `ChannelReady`.
6181+
debug_assert!(htlc_signatures.is_empty());
6182+
}
61806183
Some(msgs::CommitmentSigned {
61816184
channel_id: self.channel_id,
6182-
htlc_signatures: vec![],
6185+
htlc_signatures,
61836186
signature,
61846187
funding_txid: funding.get_funding_txo().map(|funding_txo| funding_txo.txid),
61856188
#[cfg(taproot)]

lightning/src/ln/functional_test_utils.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4309,6 +4309,13 @@ pub fn test_default_channel_config() -> UserConfig {
43094309
default_config
43104310
}
43114311

4312+
pub fn test_default_anchors_channel_config() -> UserConfig {
4313+
let mut config = test_default_channel_config();
4314+
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
4315+
config.manually_accept_inbound_channels = true;
4316+
config
4317+
}
4318+
43124319
pub fn create_node_chanmgrs<'a, 'b>(
43134320
node_count: usize, cfgs: &'a Vec<NodeCfg<'b>>, node_config: &[Option<UserConfig>],
43144321
) -> Vec<

lightning/src/ln/splicing_tests.rs

Lines changed: 210 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
// licenses.
99

1010
use crate::chain::chaininterface::FEERATE_FLOOR_SATS_PER_KW;
11-
use crate::chain::channelmonitor::ANTI_REORG_DELAY;
11+
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS};
12+
use crate::chain::transaction::OutPoint;
1213
use crate::events::bump_transaction::sync::WalletSourceSync;
13-
use crate::events::Event;
14+
use crate::events::{ClosureReason, Event, FundingInfo, HTLCHandlingFailureType};
1415
use crate::ln::chan_utils;
16+
use crate::ln::channelmanager::BREAKDOWN_TIMEOUT;
1517
use crate::ln::functional_test_utils::*;
1618
use crate::ln::funding::{FundingTxInput, SpliceContribution};
1719
use crate::ln::msgs::{self, BaseMessageHandler, ChannelMessageHandler, MessageSendEvent};
@@ -305,7 +307,8 @@ fn lock_splice_after_blocks<'a, 'b, 'c, 'd>(
305307
panic!();
306308
}
307309

308-
// Remove the corresponding outputs and transactions the chain source is watching.
310+
// Remove the corresponding outputs and transactions the chain source is watching for the
311+
// old funding as it is no longer being tracked.
309312
node_a
310313
.chain_source
311314
.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script.clone());
@@ -395,3 +398,207 @@ fn test_splice_out() {
395398
assert!(htlc_limit_msat < initial_channel_value_sat / 2 * 1000);
396399
let _ = send_payment(&nodes[0], &[&nodes[1]], htlc_limit_msat);
397400
}
401+
402+
#[derive(PartialEq)]
403+
enum SpliceStatus {
404+
Unconfirmed,
405+
Confirmed,
406+
Locked,
407+
}
408+
409+
#[test]
410+
fn test_splice_commitment_broadcast() {
411+
do_test_splice_commitment_broadcast(SpliceStatus::Unconfirmed, false);
412+
do_test_splice_commitment_broadcast(SpliceStatus::Unconfirmed, true);
413+
do_test_splice_commitment_broadcast(SpliceStatus::Confirmed, false);
414+
do_test_splice_commitment_broadcast(SpliceStatus::Confirmed, true);
415+
do_test_splice_commitment_broadcast(SpliceStatus::Locked, false);
416+
do_test_splice_commitment_broadcast(SpliceStatus::Locked, true);
417+
}
418+
419+
fn do_test_splice_commitment_broadcast(splice_status: SpliceStatus, claim_htlcs: bool) {
420+
// Tests that we're able to enforce HTLCs onchain during the different stages of a splice.
421+
let chanmon_cfgs = create_chanmon_cfgs(2);
422+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
423+
let config = test_default_anchors_channel_config();
424+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
425+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
426+
427+
let node_id_0 = nodes[0].node.get_our_node_id();
428+
let node_id_1 = nodes[1].node.get_our_node_id();
429+
430+
let initial_channel_capacity = 100_000;
431+
let (_, _, channel_id, initial_funding_tx) =
432+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_capacity, 0);
433+
434+
let coinbase_tx = provide_anchor_reserves(&nodes);
435+
436+
// We want to have two HTLCs pending to make sure we can claim those sent before and after a
437+
// splice negotiation.
438+
let payment_amount = 1_000_000;
439+
let (preimage1, payment_hash1, ..) = route_payment(&nodes[0], &[&nodes[1]], payment_amount);
440+
let splice_in_amount = initial_channel_capacity / 2;
441+
let initiator_contribution = SpliceContribution::SpliceIn {
442+
value: Amount::from_sat(splice_in_amount),
443+
inputs: vec![FundingTxInput::new_p2wpkh(coinbase_tx.clone(), 0).unwrap()],
444+
change_script: Some(nodes[0].wallet_source.get_change_script().unwrap()),
445+
};
446+
let splice_tx = splice_channel(&nodes[0], &nodes[1], channel_id, initiator_contribution);
447+
let (preimage2, payment_hash2, ..) = route_payment(&nodes[0], &[&nodes[1]], payment_amount);
448+
let htlc_expiry = nodes[0].best_block_info().1 + TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS;
449+
450+
if splice_status == SpliceStatus::Confirmed || splice_status == SpliceStatus::Locked {
451+
mine_transaction(&nodes[0], &splice_tx);
452+
mine_transaction(&nodes[1], &splice_tx);
453+
}
454+
if splice_status == SpliceStatus::Locked {
455+
lock_splice_after_blocks(&nodes[0], &nodes[1], channel_id, ANTI_REORG_DELAY - 1);
456+
}
457+
458+
if claim_htlcs {
459+
// Claim both HTLCs, but don't do anything with the update message sent since we want to
460+
// resolve the HTLCs onchain instead with a single transaction (thanks to anchors).
461+
nodes[1].node.claim_funds(preimage1);
462+
expect_payment_claimed!(&nodes[1], payment_hash1, payment_amount);
463+
nodes[1].node.claim_funds(preimage2);
464+
expect_payment_claimed!(&nodes[1], payment_hash2, payment_amount);
465+
check_added_monitors(&nodes[1], 2);
466+
let _ = get_htlc_update_msgs(&nodes[1], &node_id_0);
467+
}
468+
469+
// Force close the channel. This should broadcast the appropriate commitment transaction based
470+
// on the currently confirmed funding.
471+
nodes[0]
472+
.node
473+
.force_close_broadcasting_latest_txn(&channel_id, &node_id_1, "test".to_owned())
474+
.unwrap();
475+
handle_bump_events(&nodes[0], true, 0);
476+
let commitment_tx = {
477+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
478+
assert_eq!(txn.len(), 1);
479+
let commitment_tx = txn.remove(0);
480+
match splice_status {
481+
SpliceStatus::Unconfirmed => check_spends!(&commitment_tx, &initial_funding_tx),
482+
SpliceStatus::Confirmed | SpliceStatus::Locked => {
483+
check_spends!(&commitment_tx, &splice_tx)
484+
},
485+
}
486+
commitment_tx
487+
};
488+
489+
mine_transaction(&nodes[0], &commitment_tx);
490+
mine_transaction(&nodes[1], &commitment_tx);
491+
492+
let closure_reason = ClosureReason::HolderForceClosed {
493+
broadcasted_latest_txn: Some(true),
494+
message: "test".to_owned(),
495+
};
496+
let closed_channel_capacity = if splice_status == SpliceStatus::Locked {
497+
initial_channel_capacity + splice_in_amount
498+
} else {
499+
initial_channel_capacity
500+
};
501+
check_closed_event(&nodes[0], 1, closure_reason, false, &[node_id_1], closed_channel_capacity);
502+
check_closed_broadcast(&nodes[0], 1, true);
503+
check_added_monitors(&nodes[0], 1);
504+
505+
let closure_reason = ClosureReason::CommitmentTxConfirmed;
506+
check_closed_event(&nodes[1], 1, closure_reason, false, &[node_id_0], closed_channel_capacity);
507+
check_closed_broadcast(&nodes[1], 1, true);
508+
check_added_monitors(&nodes[1], 1);
509+
510+
if !claim_htlcs {
511+
// If we're supposed to time out the HTLCs, mine enough blocks until the expiration.
512+
connect_blocks(&nodes[0], htlc_expiry - nodes[0].best_block_info().1);
513+
connect_blocks(&nodes[1], htlc_expiry - nodes[1].best_block_info().1);
514+
expect_htlc_handling_failed_destinations!(
515+
nodes[1].node.get_and_clear_pending_events(),
516+
&[
517+
HTLCHandlingFailureType::Receive { payment_hash: payment_hash1 },
518+
HTLCHandlingFailureType::Receive { payment_hash: payment_hash2 }
519+
]
520+
);
521+
}
522+
523+
// We should see either an aggregated HTLC timeout or success transaction spending the valid
524+
// commitment transaction we mined earlier.
525+
let htlc_claim_tx = if claim_htlcs {
526+
let mut txn = nodes[1].tx_broadcaster.txn_broadcast();
527+
assert_eq!(txn.len(), 1);
528+
let htlc_success_tx = txn.remove(0);
529+
assert_eq!(htlc_success_tx.input.len(), 2);
530+
check_spends!(&htlc_success_tx, &commitment_tx);
531+
htlc_success_tx
532+
} else {
533+
handle_bump_htlc_event(&nodes[0], 1);
534+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
535+
assert_eq!(txn.len(), 1);
536+
let htlc_timeout_tx = txn.remove(0);
537+
// The inputs spent correspond to the fee bump input and the two HTLCs from the commitment
538+
// transaction.
539+
assert_eq!(htlc_timeout_tx.input.len(), 3);
540+
let tx_with_fee_bump_utxo =
541+
if splice_status == SpliceStatus::Unconfirmed { &coinbase_tx } else { &splice_tx };
542+
check_spends!(&htlc_timeout_tx, &commitment_tx, tx_with_fee_bump_utxo);
543+
htlc_timeout_tx
544+
};
545+
546+
mine_transaction(&nodes[0], &htlc_claim_tx);
547+
mine_transaction(&nodes[1], &htlc_claim_tx);
548+
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
549+
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
550+
551+
let events = nodes[0].node.get_and_clear_pending_events();
552+
if claim_htlcs {
553+
assert_eq!(events.iter().filter(|e| matches!(e, Event::PaymentSent { .. })).count(), 2);
554+
assert_eq!(
555+
events.iter().filter(|e| matches!(e, Event::PaymentPathSuccessful { .. })).count(),
556+
2
557+
);
558+
} else {
559+
assert_eq!(events.iter().filter(|e| matches!(e, Event::PaymentFailed { .. })).count(), 2,);
560+
assert_eq!(
561+
events.iter().filter(|e| matches!(e, Event::PaymentPathFailed { .. })).count(),
562+
2
563+
);
564+
}
565+
check_added_monitors(&nodes[0], 2); // Two `ReleasePaymentComplete` monitor updates
566+
567+
// When the splice never confirms and we see a commitment transaction broadcast and confirm for
568+
// the current funding instead, we should expect to see an `Event::DiscardFunding` for the
569+
// splice transaction.
570+
if splice_status == SpliceStatus::Unconfirmed {
571+
// Remove the corresponding outputs and transactions the chain source is watching for the
572+
// splice as it is no longer being tracked.
573+
connect_blocks(&nodes[0], BREAKDOWN_TIMEOUT as u32);
574+
let (vout, txout) = splice_tx
575+
.output
576+
.iter()
577+
.enumerate()
578+
.find(|(_, output)| output.script_pubkey.is_p2wsh())
579+
.unwrap();
580+
let funding_outpoint = OutPoint { txid: splice_tx.compute_txid(), index: vout as u16 };
581+
nodes[0]
582+
.chain_source
583+
.remove_watched_txn_and_outputs(funding_outpoint, txout.script_pubkey.clone());
584+
nodes[1]
585+
.chain_source
586+
.remove_watched_txn_and_outputs(funding_outpoint, txout.script_pubkey.clone());
587+
588+
// `SpendableOutputs` events are also included here, but we don't care for them.
589+
let events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
590+
assert_eq!(events.len(), if claim_htlcs { 2 } else { 4 }, "{events:?}");
591+
if let Event::DiscardFunding { funding_info, .. } = &events[0] {
592+
assert_eq!(*funding_info, FundingInfo::OutPoint { outpoint: funding_outpoint });
593+
} else {
594+
panic!();
595+
}
596+
let events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events();
597+
assert_eq!(events.len(), if claim_htlcs { 2 } else { 1 }, "{events:?}");
598+
if let Event::DiscardFunding { funding_info, .. } = &events[0] {
599+
assert_eq!(*funding_info, FundingInfo::OutPoint { outpoint: funding_outpoint });
600+
} else {
601+
panic!();
602+
}
603+
}
604+
}

0 commit comments

Comments
 (0)