Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
50e1d15
Do not fail to load `ChannelManager` when we see claiming payments
TheBlueMatt May 12, 2025
63f5d77
Optionally disable all state-based policy checks in test signer
TheBlueMatt Feb 2, 2025
87bb72d
Add a simple test of upgrading from LDK 0.1
TheBlueMatt Feb 2, 2025
bc9ffe4
Read `ChannelManager` even if we have no-peer post-update actions
TheBlueMatt May 21, 2025
edcd376
Do not dip into the funder's reserve to cover the two anchors
tankyleo May 23, 2025
24d89ab
Merge pull request #3794 from TheBlueMatt/2025-05-0.1.4-backports
TheBlueMatt May 23, 2025
96e0f34
Bump the `lightning` crate version to 0.1.4
TheBlueMatt May 23, 2025
1fcaca2
Add release notes for LDK 0.1.4
TheBlueMatt May 23, 2025
63f41fd
Merge pull request #3798 from TheBlueMatt/2025-05-0.1.4-relnotes
TheBlueMatt May 24, 2025
502b9b5
Only run aggressive `test_node_counter_consistency` in tests
TheBlueMatt Mar 27, 2025
b1e9921
Fix `update_id` gap during `force_shutdown`
whfuyn Jun 13, 2025
d2f3d1f
Skip storing an explicit `node_id` in `RouteGraphNode`
TheBlueMatt Jun 25, 2025
6577c4a
Reduce `total_cltv_delta` size in `RouteGraphNode`
TheBlueMatt Jun 25, 2025
e7c1f8f
Use `cost / path amt limit` as the pathfinding score, not `cost`
TheBlueMatt Jun 25, 2025
2fdd07e
Only mark all mon updates complete if there are no blocked updates
TheBlueMatt Jul 2, 2025
4455505
Add a test utility to provide nodes with anchor reserves
TheBlueMatt Jul 10, 2025
56a9bf5
Prune locktimed packages when inputs are spent
whfuyn Jul 10, 2025
41c2b51
Track outpoint creation height in `PackageSolvingData`
TheBlueMatt Jul 10, 2025
3463a0c
Use outpoint creation height when restoring locktimed packages
TheBlueMatt Jul 10, 2025
a9597aa
Add a test case for the issues fixed in the previous few commits
TheBlueMatt Jul 10, 2025
382e71b
Correct non-dust HTLC accounting in `next_remote_commit_tx_fee_msat`
tankyleo Jul 15, 2025
b6a8fbc
Add CHANGELOG entry for 0.1.5
TheBlueMatt Jul 15, 2025
843a69f
Bump the `lightning` crate version to 0.1.5
TheBlueMatt Jul 15, 2025
6e6e4dc
Merge pull request #3932 from TheBlueMatt/2025-07-0.1.4-backports
TheBlueMatt Jul 16, 2025
eb418bc
Merge pull request #3935 from TheBlueMatt/2025-07-0.1.5-relnotes
TheBlueMatt Jul 16, 2025
c106074
Merge tag 'v0.1.5' into 2025-07-0.1.5-bindings
TheBlueMatt Jul 23, 2025
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
57 changes: 57 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,60 @@
# 0.1.5 - Jul XXX, 2025 - "Async Path Reduction"

## Performance Improvements
* `NetworkGraph`'s expensive internal consistency checks have now been
disabled in debug builds in addition to release builds (#3687).

## Bug Fixes
* Pathfinding which results in a multi-path payment is now substantially
smarter, using fewer paths and better optimizing fees and successes (#3890).
* A counterparty delaying claiming multiple HTLCs with different expiries can
no longer cause our `ChannelMonitor` to continuously rebroadcast invalid
transactions or RBF bump attempts (#3923).
* Reorgs can no longer cause us to fail to claim HTLCs after a counterparty
delayed claiming multiple HTLCs with different expiries (#3923).
* Force-closing a channel while it is blocked on another channel's async
`ChannelMonitorUpdate` can no longer lead to a panic (#3858).
* `ChannelMonitorUpdate`s can no longer be released to storage too early when
doing async updates or on restart. This only impacts async
`ChannelMonitorUpdate` persistence and can lead to loss of funds only in rare
cases with `ChannelMonitorUpdate` persistence order inversions (#3907).

## Security
0.1.5 fixes a vulnerability which could allow a peer to overdraw their reserve
value, potentially cutting into commitment transaction fees on channels with a
low reserve.
* Due to a bug in checking whether an HTLC is dust during acceptance, near-dust
HTLCs were not counted towards the commitment transaction fee, but did
eventually contribute to it when we built a commitment transaction. This can
be used by a counterparty to overdraw their reserve value, or, for channels
with a low reserve value, cut into the commitment transaction fee (#3933).


# 0.1.4 - May 23, 2025 - "Careful Validation of Bogus States"

## Bug Fixes
* In cases where using synchronous persistence with higher latency than the
latency to communicate with peers caused issues fixed in 0.1.2,
`ChannelManager`s may have been left in a state which LDK 0.1.2 and later
would refuse to deserialize. This has been fixed and nodes which experienced
this issue prior to 0.1.2 should now deserialize fine (#3790).
* In some cases, when using synchronous persistence with higher latency than
the latency to communicate with peers, when receiving an MPP payment with
multiple parts received over the same channel, a channel could hang and not
make progress, eventually leading to a force-closure due to timed-out HTLCs.
This has now been fixed (#3680).

## Security
0.1.4 fixes a funds-theft vulnerability in exceedingly rare cases.
* If an LDK-based node funds an anchor channel to a malicious peer, and that
peer sets the channel reserve on the LDK-based node to zero, the LDK-node
could overdraw its total balance upon increasing the feerate of the
commitment transaction. If the malicious peer forwards HTLCs through the
LDK-based node, this could leave the LDK-based node with no valid commitment
transaction to broadcast to claim its part of the forwarded HTLC. The
counterparty would have to forfeit their reserve value (#3796).


# 0.1.3 - Apr 30, 2025 - "Routing Unicode in 2025"

## Bug Fixes
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ members = [

exclude = [
"lightning-transaction-sync",
"lightning-tests",
"no-std-check",
"msrv-no-dev-deps-check",
"bench",
Expand Down
6 changes: 6 additions & 0 deletions ci/ci-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ for DIR in "${WORKSPACE_MEMBERS[@]}"; do
cargo doc -p "$DIR" --document-private-items
done

echo -e "\n\nTesting upgrade from prior versions of LDK"
pushd lightning-tests
[ "$RUSTC_MINOR_VERSION" -lt 65 ] && cargo update -p regex --precise "1.9.6" --verbose
cargo test
popd

echo -e "\n\nChecking and testing Block Sync Clients with features"

cargo test -p lightning-block-sync --verbose --color always --features rest-client
Expand Down
4 changes: 2 additions & 2 deletions fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ impl SignerProvider for KeyProvider {
channel_keys_id,
);
let revoked_commitment = self.make_enforcement_state_cell(keys.commitment_seed);
TestChannelSigner::new_with_revoked(keys, revoked_commitment, false)
TestChannelSigner::new_with_revoked(keys, revoked_commitment, false, false)
}

fn read_chan_signer(&self, buffer: &[u8]) -> Result<Self::EcdsaSigner, DecodeError> {
Expand All @@ -392,7 +392,7 @@ impl SignerProvider for KeyProvider {
let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);

Ok(TestChannelSigner::new_with_revoked(inner, state, false))
Ok(TestChannelSigner::new_with_revoked(inner, state, false, false))
}

fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result<ScriptBuf, ()> {
Expand Down
3 changes: 2 additions & 1 deletion fuzz/src/full_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,14 +521,15 @@ impl SignerProvider for KeyProvider {
},
state,
false,
false,
)
}

fn read_chan_signer(&self, mut data: &[u8]) -> Result<TestChannelSigner, DecodeError> {
let inner: InMemorySigner = ReadableArgs::read(&mut data, self)?;
let state = Arc::new(Mutex::new(EnforcementState::new()));

Ok(TestChannelSigner::new_with_revoked(inner, state, false))
Ok(TestChannelSigner::new_with_revoked(inner, state, false, false))
}

fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result<ScriptBuf, ()> {
Expand Down
22 changes: 22 additions & 0 deletions lightning-tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "lightning-tests"
version = "0.0.1"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
description = "Tests for LDK crates"
edition = "2021"

[features]

[dependencies]
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_0_125 = { package = "lightning", version = "0.0.125", features = ["_test_utils"] }

bitcoin = { version = "0.32.2", default-features = false }

[dev-dependencies]
5 changes: 5 additions & 0 deletions lightning-tests/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[cfg_attr(test, macro_use)]
extern crate lightning;

#[cfg(all(test, not(taproot)))]
pub mod upgrade_downgrade_tests;
215 changes: 215 additions & 0 deletions lightning-tests/src/upgrade_downgrade_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// This file is Copyright its original authors, visible in version control
// history.
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.

//! Tests which test upgrading from previous versions of LDK or downgrading to previous versions of
//! LDK.

use lightning_0_1::get_monitor as get_monitor_0_1;
use lightning_0_1::ln::functional_test_utils as lightning_0_1_utils;
use lightning_0_1::util::ser::Writeable as _;

use lightning_0_0_125::chain::ChannelMonitorUpdateStatus as ChannelMonitorUpdateStatus_0_0_125;
use lightning_0_0_125::check_added_monitors as check_added_monitors_0_0_125;
use lightning_0_0_125::events::ClosureReason as ClosureReason_0_0_125;
use lightning_0_0_125::expect_payment_claimed as expect_payment_claimed_0_0_125;
use lightning_0_0_125::get_htlc_update_msgs as get_htlc_update_msgs_0_0_125;
use lightning_0_0_125::get_monitor as get_monitor_0_0_125;
use lightning_0_0_125::get_revoke_commit_msgs as get_revoke_commit_msgs_0_0_125;
use lightning_0_0_125::ln::channelmanager::PaymentId as PaymentId_0_0_125;
use lightning_0_0_125::ln::channelmanager::RecipientOnionFields as RecipientOnionFields_0_0_125;
use lightning_0_0_125::ln::functional_test_utils as lightning_0_0_125_utils;
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::ln::functional_test_utils::*;

use lightning_types::payment::PaymentPreimage;

#[test]
fn simple_upgrade() {
// Tests a simple case of upgrading from LDK 0.1 with a pending payment
let (node_a_ser, node_b_ser, mon_a_ser, mon_b_ser, preimage);
{
let chanmon_cfgs = lightning_0_1_utils::create_chanmon_cfgs(2);
let node_cfgs = lightning_0_1_utils::create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = lightning_0_1_utils::create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = lightning_0_1_utils::create_network(2, &node_cfgs, &node_chanmgrs);

let chan_id = lightning_0_1_utils::create_announced_chan_between_nodes(&nodes, 0, 1).2;

let payment_preimage =
lightning_0_1_utils::route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
preimage = PaymentPreimage(payment_preimage.0 .0);

node_a_ser = nodes[0].node.encode();
node_b_ser = nodes[1].node.encode();
mon_a_ser = get_monitor_0_1!(nodes[0], chan_id).encode();
mon_b_ser = get_monitor_0_1!(nodes[1], chan_id).encode();
}

// Create a dummy node to reload over with the 0.1 state

let mut chanmon_cfgs = create_chanmon_cfgs(2);

// 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;

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

let config = test_default_channel_config();
let a_mons = &[&mon_a_ser[..]];
reload_node!(nodes[0], config.clone(), &node_a_ser, a_mons, persister_a, chain_mon_a, node_a);
reload_node!(nodes[1], config, &node_b_ser, &[&mon_b_ser], persister_b, chain_mon_b, node_b);

reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));

claim_payment(&nodes[0], &[&nodes[1]], preimage);
}

#[test]
fn test_125_dangling_post_update_actions() {
// Tests a failure of upgrading from 0.0.125 to 0.1 when there's a dangling
// `MonitorUpdateCompletionAction` due to the bug fixed in
// 93b4479e472e6767af5df90fecdcdfb79074e260.
let (node_d_ser, mon_ser);
{
// First, we get RAA-source monitor updates held by using async persistence (note that this
// issue was first identified as a consequence of the bug fixed in
// 93b4479e472e6767af5df90fecdcdfb79074e260 but in order to replicate that bug we need a
// complicated multi-threaded race that is not deterministic, thus we "cheat" here by using
// async persistence). We do this by simply claiming an MPP payment and not completing the
// second channel's `ChannelMonitorUpdate`, blocking RAA `ChannelMonitorUpdate`s from the
// first (which is ultimately a very similar bug to the one fixed in 93b4479e472e6767af5df).
//
// Then, we claim a second payment on the channel, which ultimately doesn't have its
// `ChannelMonitorUpdate` completion handled due to the presence of the blocked
// `ChannelMonitorUpdate`. The claim also generates a post-update completion action, but
// the `ChannelMonitorUpdate` isn't queued due to the RAA-update block.
let chanmon_cfgs = lightning_0_0_125_utils::create_chanmon_cfgs(4);
let node_cfgs = lightning_0_0_125_utils::create_node_cfgs(4, &chanmon_cfgs);
let node_chanmgrs =
lightning_0_0_125_utils::create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
let nodes = lightning_0_0_125_utils::create_network(4, &node_cfgs, &node_chanmgrs);

let node_b_id = nodes[1].node.get_our_node_id();
let node_d_id = nodes[3].node.get_our_node_id();

lightning_0_0_125_utils::create_announced_chan_between_nodes_with_value(
&nodes, 0, 1, 100_000, 0,
);
lightning_0_0_125_utils::create_announced_chan_between_nodes_with_value(
&nodes, 0, 2, 100_000, 0,
);
let chan_id_1_3 = lightning_0_0_125_utils::create_announced_chan_between_nodes_with_value(
&nodes, 1, 3, 100_000, 0,
)
.2;
let chan_id_2_3 = lightning_0_0_125_utils::create_announced_chan_between_nodes_with_value(
&nodes, 2, 3, 100_000, 0,
)
.2;

let (preimage, hash, secret) =
lightning_0_0_125_utils::get_payment_preimage_hash(&nodes[3], Some(15_000_000), None);

let pay_params = router_0_0_125::PaymentParameters::from_node_id(
node_d_id,
lightning_0_0_125_utils::TEST_FINAL_CLTV,
)
.with_bolt11_features(nodes[3].node.bolt11_invoice_features())
.unwrap();

let route_params =
router_0_0_125::RouteParameters::from_payment_params_and_value(pay_params, 15_000_000);
let route = lightning_0_0_125_utils::get_route(&nodes[0], &route_params).unwrap();

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

check_added_monitors_0_0_125!(nodes[0], 2);
let paths = &[&[&nodes[1], &nodes[3]][..], &[&nodes[2], &nodes[3]]];
lightning_0_0_125_utils::pass_along_route(&nodes[0], paths, 15_000_000, hash, secret);

let preimage_2 = lightning_0_0_125_utils::route_payment(&nodes[1], &[&nodes[3]], 100_000).0;

chanmon_cfgs[3].persister.set_update_ret(ChannelMonitorUpdateStatus_0_0_125::InProgress);
chanmon_cfgs[3].persister.set_update_ret(ChannelMonitorUpdateStatus_0_0_125::InProgress);
nodes[3].node.claim_funds(preimage);
check_added_monitors_0_0_125!(nodes[3], 2);

let (outpoint, update_id, _) = {
let latest_monitors = nodes[3].chain_monitor.latest_monitor_update_id.lock().unwrap();
latest_monitors.get(&chan_id_1_3).unwrap().clone()
};
nodes[3].chain_monitor.chain_monitor.channel_monitor_updated(outpoint, update_id).unwrap();
expect_payment_claimed_0_0_125!(nodes[3], hash, 15_000_000);

let ds_fulfill = get_htlc_update_msgs_0_0_125!(nodes[3], node_b_id);
// Due to an unrelated test bug in 0.0.125, we have to leave the `ChannelMonitorUpdate` for
// the previous node un-completed or we will panic when dropping the `Node`.
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus_0_0_125::InProgress);
nodes[1].node.handle_update_fulfill_htlc(&node_d_id, &ds_fulfill.update_fulfill_htlcs[0]);
check_added_monitors_0_0_125!(nodes[1], 1);

nodes[1].node.handle_commitment_signed(&node_d_id, &ds_fulfill.commitment_signed);
check_added_monitors_0_0_125!(nodes[1], 1);

// The `ChannelMonitorUpdate` generated by the RAA from node B to node D will be blocked.
let (bs_raa, _) = get_revoke_commit_msgs_0_0_125!(nodes[1], node_d_id);
nodes[3].node.handle_revoke_and_ack(&node_b_id, &bs_raa);
check_added_monitors_0_0_125!(nodes[3], 0);

// Now that there is a blocked update in the B <-> D channel, we can claim the second
// payment across it, which, while it will generate a `ChannelMonitorUpdate`, will not
// complete its post-update actions.
nodes[3].node.claim_funds(preimage_2);
check_added_monitors_0_0_125!(nodes[3], 1);

// Finally, we set up the failure by force-closing the channel in question, ensuring that
// 0.1 will not create a per-peer state for node B.
let err = "Force Closing Channel".to_owned();
nodes[3].node.force_close_without_broadcasting_txn(&chan_id_1_3, &node_b_id, err).unwrap();
let reason =
ClosureReason_0_0_125::HolderForceClosed { broadcasted_latest_txn: Some(false) };
let peers = &[node_b_id];
lightning_0_0_125_utils::check_closed_event(&nodes[3], 1, reason, false, peers, 100_000);
lightning_0_0_125_utils::check_closed_broadcast(&nodes[3], 1, true);
check_added_monitors_0_0_125!(nodes[3], 1);

node_d_ser = nodes[3].node.encode();
mon_ser = get_monitor_0_0_125!(nodes[3], chan_id_2_3).encode();
}

// Create a dummy node to reload over with the 0.0.125 state

let mut chanmon_cfgs = create_chanmon_cfgs(4);

// 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[3].keys_manager.disable_all_state_policy_checks = true;

let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
let (persister, chain_mon);
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
let node;
let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);

// Finally, reload the node in the latest LDK. This previously failed.
let config = test_default_channel_config();
reload_node!(nodes[3], config, &node_d_ser, &[&mon_ser], persister, chain_mon, node);
}
2 changes: 1 addition & 1 deletion lightning/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning"
version = "0.1.3"
version = "0.1.5"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
Loading
Loading