Skip to content

Commit c106074

Browse files
committed
Merge tag 'v0.1.5' into 2025-07-0.1.5-bindings
v0.1.5 - Jul 16, 2025 - "Async Path Reduction" Performance Improvements ======================== * `NetworkGraph`'s expensive internal consistency checks have now been disabled in debug builds in addition to release builds (lightningdevkit#3687). Bug Fixes ========= * Pathfinding which results in a multi-path payment is now substantially smarter, using fewer paths and better optimizing fees and successes (lightningdevkit#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 (lightningdevkit#3923). * Reorgs can no longer cause us to fail to claim HTLCs after a counterparty delayed claiming multiple HTLCs with different expiries (lightningdevkit#3923). * Force-closing a channel while it is blocked on another channel's async `ChannelMonitorUpdate` can no longer lead to a panic (lightningdevkit#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 (lightningdevkit#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 (lightningdevkit#3933).
2 parents 1d12d08 + eb418bc commit c106074

26 files changed

+1572
-353
lines changed

CHANGELOG.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,60 @@
1+
# 0.1.5 - Jul XXX, 2025 - "Async Path Reduction"
2+
3+
## Performance Improvements
4+
* `NetworkGraph`'s expensive internal consistency checks have now been
5+
disabled in debug builds in addition to release builds (#3687).
6+
7+
## Bug Fixes
8+
* Pathfinding which results in a multi-path payment is now substantially
9+
smarter, using fewer paths and better optimizing fees and successes (#3890).
10+
* A counterparty delaying claiming multiple HTLCs with different expiries can
11+
no longer cause our `ChannelMonitor` to continuously rebroadcast invalid
12+
transactions or RBF bump attempts (#3923).
13+
* Reorgs can no longer cause us to fail to claim HTLCs after a counterparty
14+
delayed claiming multiple HTLCs with different expiries (#3923).
15+
* Force-closing a channel while it is blocked on another channel's async
16+
`ChannelMonitorUpdate` can no longer lead to a panic (#3858).
17+
* `ChannelMonitorUpdate`s can no longer be released to storage too early when
18+
doing async updates or on restart. This only impacts async
19+
`ChannelMonitorUpdate` persistence and can lead to loss of funds only in rare
20+
cases with `ChannelMonitorUpdate` persistence order inversions (#3907).
21+
22+
## Security
23+
0.1.5 fixes a vulnerability which could allow a peer to overdraw their reserve
24+
value, potentially cutting into commitment transaction fees on channels with a
25+
low reserve.
26+
* Due to a bug in checking whether an HTLC is dust during acceptance, near-dust
27+
HTLCs were not counted towards the commitment transaction fee, but did
28+
eventually contribute to it when we built a commitment transaction. This can
29+
be used by a counterparty to overdraw their reserve value, or, for channels
30+
with a low reserve value, cut into the commitment transaction fee (#3933).
31+
32+
33+
# 0.1.4 - May 23, 2025 - "Careful Validation of Bogus States"
34+
35+
## Bug Fixes
36+
* In cases where using synchronous persistence with higher latency than the
37+
latency to communicate with peers caused issues fixed in 0.1.2,
38+
`ChannelManager`s may have been left in a state which LDK 0.1.2 and later
39+
would refuse to deserialize. This has been fixed and nodes which experienced
40+
this issue prior to 0.1.2 should now deserialize fine (#3790).
41+
* In some cases, when using synchronous persistence with higher latency than
42+
the latency to communicate with peers, when receiving an MPP payment with
43+
multiple parts received over the same channel, a channel could hang and not
44+
make progress, eventually leading to a force-closure due to timed-out HTLCs.
45+
This has now been fixed (#3680).
46+
47+
## Security
48+
0.1.4 fixes a funds-theft vulnerability in exceedingly rare cases.
49+
* If an LDK-based node funds an anchor channel to a malicious peer, and that
50+
peer sets the channel reserve on the LDK-based node to zero, the LDK-node
51+
could overdraw its total balance upon increasing the feerate of the
52+
commitment transaction. If the malicious peer forwards HTLCs through the
53+
LDK-based node, this could leave the LDK-based node with no valid commitment
54+
transaction to broadcast to claim its part of the forwarded HTLC. The
55+
counterparty would have to forfeit their reserve value (#3796).
56+
57+
158
# 0.1.3 - Apr 30, 2025 - "Routing Unicode in 2025"
259

360
## Bug Fixes

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ members = [
2121

2222
exclude = [
2323
"lightning-transaction-sync",
24+
"lightning-tests",
2425
"no-std-check",
2526
"msrv-no-dev-deps-check",
2627
"bench",

ci/ci-tests.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ for DIR in "${WORKSPACE_MEMBERS[@]}"; do
5757
cargo doc -p "$DIR" --document-private-items
5858
done
5959

60+
echo -e "\n\nTesting upgrade from prior versions of LDK"
61+
pushd lightning-tests
62+
[ "$RUSTC_MINOR_VERSION" -lt 65 ] && cargo update -p regex --precise "1.9.6" --verbose
63+
cargo test
64+
popd
65+
6066
echo -e "\n\nChecking and testing Block Sync Clients with features"
6167

6268
cargo test -p lightning-block-sync --verbose --color always --features rest-client

fuzz/src/chanmon_consistency.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ impl SignerProvider for KeyProvider {
383383
channel_keys_id,
384384
);
385385
let revoked_commitment = self.make_enforcement_state_cell(keys.commitment_seed);
386-
TestChannelSigner::new_with_revoked(keys, revoked_commitment, false)
386+
TestChannelSigner::new_with_revoked(keys, revoked_commitment, false, false)
387387
}
388388

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

395-
Ok(TestChannelSigner::new_with_revoked(inner, state, false))
395+
Ok(TestChannelSigner::new_with_revoked(inner, state, false, false))
396396
}
397397

398398
fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result<ScriptBuf, ()> {

fuzz/src/full_stack.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,14 +521,15 @@ impl SignerProvider for KeyProvider {
521521
},
522522
state,
523523
false,
524+
false,
524525
)
525526
}
526527

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

531-
Ok(TestChannelSigner::new_with_revoked(inner, state, false))
532+
Ok(TestChannelSigner::new_with_revoked(inner, state, false, false))
532533
}
533534

534535
fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result<ScriptBuf, ()> {

lightning-tests/Cargo.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "lightning-tests"
3+
version = "0.0.1"
4+
authors = ["Matt Corallo"]
5+
license = "MIT OR Apache-2.0"
6+
repository = "https://github.com/lightningdevkit/rust-lightning/"
7+
description = "Tests for LDK crates"
8+
edition = "2021"
9+
10+
[features]
11+
12+
[dependencies]
13+
lightning-types = { path = "../lightning-types", features = ["_test_utils"] }
14+
lightning-invoice = { path = "../lightning-invoice", default-features = false }
15+
lightning-macros = { path = "../lightning-macros" }
16+
lightning = { path = "../lightning", features = ["_test_utils"] }
17+
lightning_0_1 = { package = "lightning", version = "0.1.1", features = ["_test_utils"] }
18+
lightning_0_0_125 = { package = "lightning", version = "0.0.125", features = ["_test_utils"] }
19+
20+
bitcoin = { version = "0.32.2", default-features = false }
21+
22+
[dev-dependencies]

lightning-tests/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#[cfg_attr(test, macro_use)]
2+
extern crate lightning;
3+
4+
#[cfg(all(test, not(taproot)))]
5+
pub mod upgrade_downgrade_tests;
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
//! Tests which test upgrading from previous versions of LDK or downgrading to previous versions of
11+
//! LDK.
12+
13+
use lightning_0_1::get_monitor as get_monitor_0_1;
14+
use lightning_0_1::ln::functional_test_utils as lightning_0_1_utils;
15+
use lightning_0_1::util::ser::Writeable as _;
16+
17+
use lightning_0_0_125::chain::ChannelMonitorUpdateStatus as ChannelMonitorUpdateStatus_0_0_125;
18+
use lightning_0_0_125::check_added_monitors as check_added_monitors_0_0_125;
19+
use lightning_0_0_125::events::ClosureReason as ClosureReason_0_0_125;
20+
use lightning_0_0_125::expect_payment_claimed as expect_payment_claimed_0_0_125;
21+
use lightning_0_0_125::get_htlc_update_msgs as get_htlc_update_msgs_0_0_125;
22+
use lightning_0_0_125::get_monitor as get_monitor_0_0_125;
23+
use lightning_0_0_125::get_revoke_commit_msgs as get_revoke_commit_msgs_0_0_125;
24+
use lightning_0_0_125::ln::channelmanager::PaymentId as PaymentId_0_0_125;
25+
use lightning_0_0_125::ln::channelmanager::RecipientOnionFields as RecipientOnionFields_0_0_125;
26+
use lightning_0_0_125::ln::functional_test_utils as lightning_0_0_125_utils;
27+
use lightning_0_0_125::ln::msgs::ChannelMessageHandler as _;
28+
use lightning_0_0_125::routing::router as router_0_0_125;
29+
use lightning_0_0_125::util::ser::Writeable as _;
30+
31+
use lightning::ln::functional_test_utils::*;
32+
33+
use lightning_types::payment::PaymentPreimage;
34+
35+
#[test]
36+
fn simple_upgrade() {
37+
// Tests a simple case of upgrading from LDK 0.1 with a pending payment
38+
let (node_a_ser, node_b_ser, mon_a_ser, mon_b_ser, preimage);
39+
{
40+
let chanmon_cfgs = lightning_0_1_utils::create_chanmon_cfgs(2);
41+
let node_cfgs = lightning_0_1_utils::create_node_cfgs(2, &chanmon_cfgs);
42+
let node_chanmgrs = lightning_0_1_utils::create_node_chanmgrs(2, &node_cfgs, &[None, None]);
43+
let nodes = lightning_0_1_utils::create_network(2, &node_cfgs, &node_chanmgrs);
44+
45+
let chan_id = lightning_0_1_utils::create_announced_chan_between_nodes(&nodes, 0, 1).2;
46+
47+
let payment_preimage =
48+
lightning_0_1_utils::route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
49+
preimage = PaymentPreimage(payment_preimage.0 .0);
50+
51+
node_a_ser = nodes[0].node.encode();
52+
node_b_ser = nodes[1].node.encode();
53+
mon_a_ser = get_monitor_0_1!(nodes[0], chan_id).encode();
54+
mon_b_ser = get_monitor_0_1!(nodes[1], chan_id).encode();
55+
}
56+
57+
// Create a dummy node to reload over with the 0.1 state
58+
59+
let mut chanmon_cfgs = create_chanmon_cfgs(2);
60+
61+
// Our TestChannelSigner will fail as we're jumping ahead, so disable its state-based checks
62+
chanmon_cfgs[0].keys_manager.disable_all_state_policy_checks = true;
63+
chanmon_cfgs[1].keys_manager.disable_all_state_policy_checks = true;
64+
65+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
66+
let (persister_a, persister_b, chain_mon_a, chain_mon_b);
67+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
68+
let (node_a, node_b);
69+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
70+
71+
let config = test_default_channel_config();
72+
let a_mons = &[&mon_a_ser[..]];
73+
reload_node!(nodes[0], config.clone(), &node_a_ser, a_mons, persister_a, chain_mon_a, node_a);
74+
reload_node!(nodes[1], config, &node_b_ser, &[&mon_b_ser], persister_b, chain_mon_b, node_b);
75+
76+
reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
77+
78+
claim_payment(&nodes[0], &[&nodes[1]], preimage);
79+
}
80+
81+
#[test]
82+
fn test_125_dangling_post_update_actions() {
83+
// Tests a failure of upgrading from 0.0.125 to 0.1 when there's a dangling
84+
// `MonitorUpdateCompletionAction` due to the bug fixed in
85+
// 93b4479e472e6767af5df90fecdcdfb79074e260.
86+
let (node_d_ser, mon_ser);
87+
{
88+
// First, we get RAA-source monitor updates held by using async persistence (note that this
89+
// issue was first identified as a consequence of the bug fixed in
90+
// 93b4479e472e6767af5df90fecdcdfb79074e260 but in order to replicate that bug we need a
91+
// complicated multi-threaded race that is not deterministic, thus we "cheat" here by using
92+
// async persistence). We do this by simply claiming an MPP payment and not completing the
93+
// second channel's `ChannelMonitorUpdate`, blocking RAA `ChannelMonitorUpdate`s from the
94+
// first (which is ultimately a very similar bug to the one fixed in 93b4479e472e6767af5df).
95+
//
96+
// Then, we claim a second payment on the channel, which ultimately doesn't have its
97+
// `ChannelMonitorUpdate` completion handled due to the presence of the blocked
98+
// `ChannelMonitorUpdate`. The claim also generates a post-update completion action, but
99+
// the `ChannelMonitorUpdate` isn't queued due to the RAA-update block.
100+
let chanmon_cfgs = lightning_0_0_125_utils::create_chanmon_cfgs(4);
101+
let node_cfgs = lightning_0_0_125_utils::create_node_cfgs(4, &chanmon_cfgs);
102+
let node_chanmgrs =
103+
lightning_0_0_125_utils::create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
104+
let nodes = lightning_0_0_125_utils::create_network(4, &node_cfgs, &node_chanmgrs);
105+
106+
let node_b_id = nodes[1].node.get_our_node_id();
107+
let node_d_id = nodes[3].node.get_our_node_id();
108+
109+
lightning_0_0_125_utils::create_announced_chan_between_nodes_with_value(
110+
&nodes, 0, 1, 100_000, 0,
111+
);
112+
lightning_0_0_125_utils::create_announced_chan_between_nodes_with_value(
113+
&nodes, 0, 2, 100_000, 0,
114+
);
115+
let chan_id_1_3 = lightning_0_0_125_utils::create_announced_chan_between_nodes_with_value(
116+
&nodes, 1, 3, 100_000, 0,
117+
)
118+
.2;
119+
let chan_id_2_3 = lightning_0_0_125_utils::create_announced_chan_between_nodes_with_value(
120+
&nodes, 2, 3, 100_000, 0,
121+
)
122+
.2;
123+
124+
let (preimage, hash, secret) =
125+
lightning_0_0_125_utils::get_payment_preimage_hash(&nodes[3], Some(15_000_000), None);
126+
127+
let pay_params = router_0_0_125::PaymentParameters::from_node_id(
128+
node_d_id,
129+
lightning_0_0_125_utils::TEST_FINAL_CLTV,
130+
)
131+
.with_bolt11_features(nodes[3].node.bolt11_invoice_features())
132+
.unwrap();
133+
134+
let route_params =
135+
router_0_0_125::RouteParameters::from_payment_params_and_value(pay_params, 15_000_000);
136+
let route = lightning_0_0_125_utils::get_route(&nodes[0], &route_params).unwrap();
137+
138+
let onion = RecipientOnionFields_0_0_125::secret_only(secret);
139+
let id = PaymentId_0_0_125(hash.0);
140+
nodes[0].node.send_payment_with_route(route, hash, onion, id).unwrap();
141+
142+
check_added_monitors_0_0_125!(nodes[0], 2);
143+
let paths = &[&[&nodes[1], &nodes[3]][..], &[&nodes[2], &nodes[3]]];
144+
lightning_0_0_125_utils::pass_along_route(&nodes[0], paths, 15_000_000, hash, secret);
145+
146+
let preimage_2 = lightning_0_0_125_utils::route_payment(&nodes[1], &[&nodes[3]], 100_000).0;
147+
148+
chanmon_cfgs[3].persister.set_update_ret(ChannelMonitorUpdateStatus_0_0_125::InProgress);
149+
chanmon_cfgs[3].persister.set_update_ret(ChannelMonitorUpdateStatus_0_0_125::InProgress);
150+
nodes[3].node.claim_funds(preimage);
151+
check_added_monitors_0_0_125!(nodes[3], 2);
152+
153+
let (outpoint, update_id, _) = {
154+
let latest_monitors = nodes[3].chain_monitor.latest_monitor_update_id.lock().unwrap();
155+
latest_monitors.get(&chan_id_1_3).unwrap().clone()
156+
};
157+
nodes[3].chain_monitor.chain_monitor.channel_monitor_updated(outpoint, update_id).unwrap();
158+
expect_payment_claimed_0_0_125!(nodes[3], hash, 15_000_000);
159+
160+
let ds_fulfill = get_htlc_update_msgs_0_0_125!(nodes[3], node_b_id);
161+
// Due to an unrelated test bug in 0.0.125, we have to leave the `ChannelMonitorUpdate` for
162+
// the previous node un-completed or we will panic when dropping the `Node`.
163+
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus_0_0_125::InProgress);
164+
nodes[1].node.handle_update_fulfill_htlc(&node_d_id, &ds_fulfill.update_fulfill_htlcs[0]);
165+
check_added_monitors_0_0_125!(nodes[1], 1);
166+
167+
nodes[1].node.handle_commitment_signed(&node_d_id, &ds_fulfill.commitment_signed);
168+
check_added_monitors_0_0_125!(nodes[1], 1);
169+
170+
// The `ChannelMonitorUpdate` generated by the RAA from node B to node D will be blocked.
171+
let (bs_raa, _) = get_revoke_commit_msgs_0_0_125!(nodes[1], node_d_id);
172+
nodes[3].node.handle_revoke_and_ack(&node_b_id, &bs_raa);
173+
check_added_monitors_0_0_125!(nodes[3], 0);
174+
175+
// Now that there is a blocked update in the B <-> D channel, we can claim the second
176+
// payment across it, which, while it will generate a `ChannelMonitorUpdate`, will not
177+
// complete its post-update actions.
178+
nodes[3].node.claim_funds(preimage_2);
179+
check_added_monitors_0_0_125!(nodes[3], 1);
180+
181+
// Finally, we set up the failure by force-closing the channel in question, ensuring that
182+
// 0.1 will not create a per-peer state for node B.
183+
let err = "Force Closing Channel".to_owned();
184+
nodes[3].node.force_close_without_broadcasting_txn(&chan_id_1_3, &node_b_id, err).unwrap();
185+
let reason =
186+
ClosureReason_0_0_125::HolderForceClosed { broadcasted_latest_txn: Some(false) };
187+
let peers = &[node_b_id];
188+
lightning_0_0_125_utils::check_closed_event(&nodes[3], 1, reason, false, peers, 100_000);
189+
lightning_0_0_125_utils::check_closed_broadcast(&nodes[3], 1, true);
190+
check_added_monitors_0_0_125!(nodes[3], 1);
191+
192+
node_d_ser = nodes[3].node.encode();
193+
mon_ser = get_monitor_0_0_125!(nodes[3], chan_id_2_3).encode();
194+
}
195+
196+
// Create a dummy node to reload over with the 0.0.125 state
197+
198+
let mut chanmon_cfgs = create_chanmon_cfgs(4);
199+
200+
// Our TestChannelSigner will fail as we're jumping ahead, so disable its state-based checks
201+
chanmon_cfgs[0].keys_manager.disable_all_state_policy_checks = true;
202+
chanmon_cfgs[1].keys_manager.disable_all_state_policy_checks = true;
203+
chanmon_cfgs[2].keys_manager.disable_all_state_policy_checks = true;
204+
chanmon_cfgs[3].keys_manager.disable_all_state_policy_checks = true;
205+
206+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
207+
let (persister, chain_mon);
208+
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
209+
let node;
210+
let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);
211+
212+
// Finally, reload the node in the latest LDK. This previously failed.
213+
let config = test_default_channel_config();
214+
reload_node!(nodes[3], config, &node_d_ser, &[&mon_ser], persister, chain_mon, node);
215+
}

lightning/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "lightning"
3-
version = "0.1.3"
3+
version = "0.1.5"
44
authors = ["Matt Corallo"]
55
license = "MIT OR Apache-2.0"
66
repository = "https://github.com/lightningdevkit/rust-lightning/"

0 commit comments

Comments
 (0)