| 
8 | 8 | // licenses.  | 
9 | 9 | 
 
  | 
10 | 10 | 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;  | 
12 | 13 | use crate::events::bump_transaction::sync::WalletSourceSync;  | 
13 |  | -use crate::events::Event;  | 
 | 14 | +use crate::events::{ClosureReason, Event, FundingInfo, HTLCHandlingFailureType};  | 
14 | 15 | use crate::ln::chan_utils;  | 
 | 16 | +use crate::ln::channelmanager::BREAKDOWN_TIMEOUT;  | 
15 | 17 | use crate::ln::functional_test_utils::*;  | 
16 | 18 | use crate::ln::funding::{FundingTxInput, SpliceContribution};  | 
17 | 19 | use crate::ln::msgs::{self, BaseMessageHandler, ChannelMessageHandler, MessageSendEvent};  | 
@@ -305,7 +307,8 @@ fn lock_splice_after_blocks<'a, 'b, 'c, 'd>(  | 
305 | 307 | 		panic!();  | 
306 | 308 | 	}  | 
307 | 309 | 
 
  | 
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.  | 
309 | 312 | 	node_a  | 
310 | 313 | 		.chain_source  | 
311 | 314 | 		.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script.clone());  | 
@@ -395,3 +398,207 @@ fn test_splice_out() {  | 
395 | 398 | 	assert!(htlc_limit_msat < initial_channel_value_sat / 2 * 1000);  | 
396 | 399 | 	let _ = send_payment(&nodes[0], &[&nodes[1]], htlc_limit_msat);  | 
397 | 400 | }  | 
 | 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