Skip to content
Open
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
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