Skip to content
2 changes: 1 addition & 1 deletion lightning-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ lightning-types = { path = "../lightning-types", features = ["_test_utils"] }
lightning-invoice = { path = "../lightning-invoice", default-features = false }
lightning-macros = { path = "../lightning-macros" }
lightning = { path = "../lightning", features = ["_test_utils"] }
lightning_0_1 = { package = "lightning", version = "0.1.1", features = ["_test_utils"] }
lightning_0_1 = { package = "lightning", version = "0.1.7", features = ["_test_utils"] }
lightning_0_0_125 = { package = "lightning", version = "0.0.125", features = ["_test_utils"] }

bitcoin = { version = "0.32.2", default-features = false }
Expand Down
206 changes: 202 additions & 4 deletions lightning-tests/src/upgrade_downgrade_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@
//! Tests which test upgrading from previous versions of LDK or downgrading to previous versions of
//! LDK.

use lightning_0_1::commitment_signed_dance as commitment_signed_dance_0_1;
use lightning_0_1::events::ClosureReason as ClosureReason_0_1;
use lightning_0_1::expect_pending_htlcs_forwardable_ignore as expect_pending_htlcs_forwardable_ignore_0_1;
use lightning_0_1::get_monitor as get_monitor_0_1;
use lightning_0_1::ln::channelmanager::PaymentId as PaymentId_0_1;
use lightning_0_1::ln::channelmanager::RecipientOnionFields as RecipientOnionFields_0_1;
use lightning_0_1::ln::functional_test_utils as lightning_0_1_utils;
use lightning_0_1::ln::msgs::ChannelMessageHandler as _;
use lightning_0_1::routing::router as router_0_1;
use lightning_0_1::util::ser::Writeable as _;

use lightning_0_0_125::chain::ChannelMonitorUpdateStatus as ChannelMonitorUpdateStatus_0_0_125;
Expand All @@ -29,16 +35,23 @@ use lightning_0_0_125::ln::msgs::ChannelMessageHandler as _;
use lightning_0_0_125::routing::router as router_0_0_125;
use lightning_0_0_125::util::ser::Writeable as _;

use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
use lightning::events::{ClosureReason, Event};
use lightning::chain::channelmonitor::{ANTI_REORG_DELAY, HTLC_FAIL_BACK_BUFFER};
use lightning::events::bump_transaction::sync::WalletSourceSync;
use lightning::events::{ClosureReason, Event, HTLCHandlingFailureType};
use lightning::ln::functional_test_utils::*;
use lightning::ln::funding::SpliceContribution;
use lightning::ln::msgs::BaseMessageHandler as _;
use lightning::ln::msgs::ChannelMessageHandler as _;
use lightning::ln::msgs::MessageSendEvent;
use lightning::ln::splicing_tests::*;
use lightning::ln::types::ChannelId;
use lightning::sign::OutputSpender;

use lightning_types::payment::PaymentPreimage;
use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};

use bitcoin::opcodes;
use bitcoin::script::Builder;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::{opcodes, Amount, TxOut};

use std::sync::Arc;

Expand Down Expand Up @@ -299,3 +312,188 @@ fn test_0_1_legacy_remote_key_derivation() {
panic!("Wrong event");
}
}

fn do_test_0_1_htlc_forward_after_splice(fail_htlc: bool) {
// Test what happens if an HTLC set to be forwarded in 0.1 is forwarded after the inbound
// channel is spliced. In the initial splice code, this could have led to a dangling HTLC if
// the HTLC is failed as the backwards-failure would use the channel's original SCID which is
// no longer valid.
// In some later splice code, this also failed because the `KeysManager` would have tried to
// rotate the `to_remote` key, which we aren't able to do in the splicing protocol.
let (node_a_ser, node_b_ser, node_c_ser, mon_a_1_ser, mon_b_1_ser, mon_b_2_ser, mon_c_1_ser);
let (node_a_id, node_b_id, node_c_id);
let (chan_id_bytes_a, chan_id_bytes_b);
let (payment_secret_bytes, payment_hash_bytes, payment_preimage_bytes);
let (node_a_blocks, node_b_blocks, node_c_blocks);

const EXTRA_BLOCKS_BEFORE_FAIL: u32 = 145;

{
let chanmon_cfgs = lightning_0_1_utils::create_chanmon_cfgs(3);
let node_cfgs = lightning_0_1_utils::create_node_cfgs(3, &chanmon_cfgs);
let node_chanmgrs =
lightning_0_1_utils::create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
let nodes = lightning_0_1_utils::create_network(3, &node_cfgs, &node_chanmgrs);

node_a_id = nodes[0].node.get_our_node_id();
node_b_id = nodes[1].node.get_our_node_id();
node_c_id = nodes[2].node.get_our_node_id();
let chan_id_a = lightning_0_1_utils::create_announced_chan_between_nodes_with_value(
&nodes, 0, 1, 10_000_000, 0,
)
.2;
chan_id_bytes_a = chan_id_a.0;

let chan_id_b = lightning_0_1_utils::create_announced_chan_between_nodes_with_value(
&nodes, 1, 2, 50_000, 0,
)
.2;
chan_id_bytes_b = chan_id_b.0;

// Ensure all nodes are at the same initial height.
let node_max_height = nodes.iter().map(|node| node.best_block_info().1).max().unwrap();
for node in &nodes {
let blocks_to_mine = node_max_height - node.best_block_info().1;
if blocks_to_mine > 0 {
lightning_0_1_utils::connect_blocks(node, blocks_to_mine);
}
}

let (preimage, hash, secret) =
lightning_0_1_utils::get_payment_preimage_hash(&nodes[2], Some(1_000_000), None);
payment_preimage_bytes = preimage.0;
payment_hash_bytes = hash.0;
payment_secret_bytes = secret.0;

let pay_params = router_0_1::PaymentParameters::from_node_id(
node_c_id,
lightning_0_1_utils::TEST_FINAL_CLTV,
)
.with_bolt11_features(nodes[2].node.bolt11_invoice_features())
.unwrap();

let route_params =
router_0_1::RouteParameters::from_payment_params_and_value(pay_params, 1_000_000);
let mut route = lightning_0_1_utils::get_route(&nodes[0], &route_params).unwrap();
route.paths[0].hops[1].cltv_expiry_delta =
EXTRA_BLOCKS_BEFORE_FAIL + HTLC_FAIL_BACK_BUFFER + 1;
if fail_htlc {
// Pay more than the channel's value (and probably not enough fee)
route.paths[0].hops[1].fee_msat = 50_000_000;
}

let onion = RecipientOnionFields_0_1::secret_only(secret);
let id = PaymentId_0_1(hash.0);
nodes[0].node.send_payment_with_route(route, hash, onion, id).unwrap();

lightning_0_1_utils::check_added_monitors(&nodes[0], 1);
let send_event = lightning_0_1_utils::SendEvent::from_node(&nodes[0]);

nodes[1].node.handle_update_add_htlc(node_a_id, &send_event.msgs[0]);
commitment_signed_dance_0_1!(nodes[1], nodes[0], send_event.commitment_msg, false);
expect_pending_htlcs_forwardable_ignore_0_1!(nodes[1]);

// We now have an HTLC pending in node B's forwarding queue with the original channel's
// SCID as the source.
// We now upgrade to 0.2 and splice before forwarding that HTLC...
node_a_ser = nodes[0].node.encode();
node_b_ser = nodes[1].node.encode();
node_c_ser = nodes[2].node.encode();
mon_a_1_ser = get_monitor_0_1!(nodes[0], chan_id_a).encode();
mon_b_1_ser = get_monitor_0_1!(nodes[1], chan_id_a).encode();
mon_b_2_ser = get_monitor_0_1!(nodes[1], chan_id_b).encode();
mon_c_1_ser = get_monitor_0_1!(nodes[2], chan_id_b).encode();

node_a_blocks = Arc::clone(&nodes[0].blocks);
node_b_blocks = Arc::clone(&nodes[1].blocks);
node_c_blocks = Arc::clone(&nodes[2].blocks);
}

// Create a dummy node to reload over with the 0.1 state
let mut chanmon_cfgs = create_chanmon_cfgs(3);

// Our TestChannelSigner will fail as we're jumping ahead, so disable its state-based checks
chanmon_cfgs[0].keys_manager.disable_all_state_policy_checks = true;
chanmon_cfgs[1].keys_manager.disable_all_state_policy_checks = true;
chanmon_cfgs[2].keys_manager.disable_all_state_policy_checks = true;

chanmon_cfgs[0].tx_broadcaster.blocks = node_a_blocks;
chanmon_cfgs[1].tx_broadcaster.blocks = node_b_blocks;
chanmon_cfgs[2].tx_broadcaster.blocks = node_c_blocks;

let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let (persister_a, persister_b, persister_c, chain_mon_a, chain_mon_b, chain_mon_c);
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
let (node_a, node_b, node_c);
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);

let config = test_default_channel_config();
let a_mons = &[&mon_a_1_ser[..]];
reload_node!(nodes[0], config.clone(), &node_a_ser, a_mons, persister_a, chain_mon_a, node_a);
let b_mons = &[&mon_b_1_ser[..], &mon_b_2_ser[..]];
reload_node!(nodes[1], config.clone(), &node_b_ser, b_mons, persister_b, chain_mon_b, node_b);
let c_mons = &[&mon_c_1_ser[..]];
reload_node!(nodes[2], config, &node_c_ser, c_mons, persister_c, chain_mon_c, node_c);

reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
let mut reconnect_b_c_args = ReconnectArgs::new(&nodes[1], &nodes[2]);
reconnect_b_c_args.send_channel_ready = (true, true);
reconnect_b_c_args.send_announcement_sigs = (true, true);
reconnect_nodes(reconnect_b_c_args);

let contribution = SpliceContribution::SpliceOut {
outputs: vec![TxOut {
value: Amount::from_sat(1_000),
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
}],
};
let splice_tx = splice_channel(&nodes[0], &nodes[1], ChannelId(chan_id_bytes_a), contribution);
for node in nodes.iter() {
mine_transaction(node, &splice_tx);
connect_blocks(node, ANTI_REORG_DELAY - 1);
}

let splice_locked = get_event_msg!(nodes[0], MessageSendEvent::SendSpliceLocked, node_b_id);
lock_splice(&nodes[0], &nodes[1], &splice_locked, false);

for node in nodes.iter() {
connect_blocks(node, EXTRA_BLOCKS_BEFORE_FAIL - ANTI_REORG_DELAY);
}

// Now release the HTLC to be failed back to node A
nodes[1].node.process_pending_htlc_forwards();

let pay_secret = PaymentSecret(payment_secret_bytes);
let pay_hash = PaymentHash(payment_hash_bytes);
let pay_preimage = PaymentPreimage(payment_preimage_bytes);

if fail_htlc {
let failure = HTLCHandlingFailureType::Forward {
node_id: Some(node_c_id),
channel_id: ChannelId(chan_id_bytes_b),
};
expect_and_process_pending_htlcs_and_htlc_handling_failed(&nodes[1], &[failure]);
check_added_monitors(&nodes[1], 1);

let updates = get_htlc_update_msgs(&nodes[1], &node_a_id);
nodes[0].node.handle_update_fail_htlc(node_b_id, &updates.update_fail_htlcs[0]);
commitment_signed_dance!(nodes[0], nodes[1], updates.commitment_signed, false);
let conditions = PaymentFailedConditions::new();
expect_payment_failed_conditions(&nodes[0], pay_hash, false, conditions);
} else {
check_added_monitors(&nodes[1], 1);
let forward_event = SendEvent::from_node(&nodes[1]);
nodes[2].node.handle_update_add_htlc(node_b_id, &forward_event.msgs[0]);
commitment_signed_dance!(nodes[2], nodes[1], forward_event.commitment_msg, false);

expect_and_process_pending_htlcs(&nodes[2], false);
expect_payment_claimable!(nodes[2], pay_hash, pay_secret, 1_000_000);
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], pay_preimage);
}
}

#[test]
fn test_0_1_htlc_forward_after_splice() {
do_test_0_1_htlc_forward_after_splice(true);
do_test_0_1_htlc_forward_after_splice(false);
}
14 changes: 11 additions & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ pub const ARCHIVAL_DELAY_BLOCKS: u32 = 4032;
/// (2) is the same, but with an additional buffer to avoid accepting an HTLC which is immediately
/// in a race condition between the user connecting a block (which would fail it) and the user
/// providing us the preimage (which would claim it).
pub(crate) const HTLC_FAIL_BACK_BUFFER: u32 = CLTV_CLAIM_BUFFER + LATENCY_GRACE_PERIOD_BLOCKS;
pub const HTLC_FAIL_BACK_BUFFER: u32 = CLTV_CLAIM_BUFFER + LATENCY_GRACE_PERIOD_BLOCKS;

// Deprecated, use [`HolderCommitment`] or [`HolderCommitmentTransaction`].
#[derive(Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -4118,6 +4118,14 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
self.funding.prev_holder_commitment_tx.clone(),
);

// It's possible that no commitment updates happened during the lifecycle of the pending
// splice's `FundingScope` that was promoted. If so, our `prev_holder_htlc_data` is
// now irrelevant, since there's no valid previous commitment that exists for the current
// funding transaction that could be broadcast.
if self.funding.prev_holder_commitment_tx.is_none() {
self.prev_holder_htlc_data.take();
}

let no_further_updates_allowed = self.no_further_updates_allowed();

// The swap above places the previous `FundingScope` into `pending_funding`.
Expand Down Expand Up @@ -7001,7 +7009,7 @@ mod tests {
let funding_outpoint = OutPoint { txid: Txid::all_zeros(), index: u16::MAX };
let channel_id = ChannelId::v1_from_funding_outpoint(funding_outpoint);
let channel_parameters = ChannelTransactionParameters {
holder_pubkeys: keys.new_pubkeys(None, &secp_ctx),
holder_pubkeys: keys.pubkeys(&secp_ctx),
holder_selected_contest_delay: 66,
is_outbound_from_holder: true,
counterparty_parameters: Some(CounterpartyChannelTransactionParameters {
Expand Down Expand Up @@ -7264,7 +7272,7 @@ mod tests {
let funding_outpoint = OutPoint { txid: Txid::all_zeros(), index: u16::MAX };
let channel_id = ChannelId::v1_from_funding_outpoint(funding_outpoint);
let channel_parameters = ChannelTransactionParameters {
holder_pubkeys: keys.new_pubkeys(None, &secp_ctx),
holder_pubkeys: keys.pubkeys(&secp_ctx),
holder_selected_contest_delay: 66,
is_outbound_from_holder: true,
counterparty_parameters: Some(CounterpartyChannelTransactionParameters {
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/chain/onchaintx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1339,7 +1339,7 @@ mod tests {
// Use non-anchor channels so that HTLC-Timeouts are broadcast immediately instead of sent
// to the user for external funding.
let chan_params = ChannelTransactionParameters {
holder_pubkeys: signer.new_pubkeys(None, &secp_ctx),
holder_pubkeys: signer.pubkeys(&secp_ctx),
holder_selected_contest_delay: 66,
is_outbound_from_holder: true,
counterparty_parameters: Some(CounterpartyChannelTransactionParameters {
Expand Down
8 changes: 4 additions & 4 deletions lightning/src/ln/chan_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1018,11 +1018,11 @@ pub struct ChannelTransactionParameters {
/// If a channel was funded with transaction A, and later spliced with transaction B, this field
/// tracks the txid of transaction A.
///
/// See [`compute_funding_key_tweak`] and [`ChannelSigner::new_pubkeys`] for more context on how
/// See [`compute_funding_key_tweak`] and [`ChannelSigner::pubkeys`] for more context on how
/// this may be used.
///
/// [`compute_funding_key_tweak`]: crate::sign::compute_funding_key_tweak
/// [`ChannelSigner::new_pubkeys`]: crate::sign::ChannelSigner::new_pubkeys
/// [`ChannelSigner::pubkeys`]: crate::sign::ChannelSigner::pubkeys
pub splice_parent_funding_txid: Option<Txid>,
/// This channel's type, as negotiated during channel open. For old objects where this field
/// wasn't serialized, it will default to static_remote_key at deserialization.
Expand Down Expand Up @@ -2245,8 +2245,8 @@ mod tests {
let counterparty_signer = keys_provider.derive_channel_signer(keys_provider.generate_channel_keys_id(true, 1));
let per_commitment_secret = SecretKey::from_slice(&<Vec<u8>>::from_hex("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap();
let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret);
let holder_pubkeys = signer.new_pubkeys(None, &secp_ctx);
let counterparty_pubkeys = counterparty_signer.new_pubkeys(None, &secp_ctx).clone();
let holder_pubkeys = signer.pubkeys(&secp_ctx);
let counterparty_pubkeys = counterparty_signer.pubkeys(&secp_ctx).clone();
let channel_parameters = ChannelTransactionParameters {
holder_pubkeys: holder_pubkeys.clone(),
holder_selected_contest_delay: 0,
Expand Down
Loading
Loading