Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
146d48f
askrene: reject maxparts=0 instead of dividing by zero
daywalker90 Sep 21, 2026
25727a9
common: don't allocate caller-sized hex buffers on the stack
daywalker90 Sep 21, 2026
ef4fd5a
onchaind: don't die on outputs we can't resolve in their unilateral c…
nGoline Oct 2, 2026
565b362
currencyrate: reject non-finite prices from sources
daywalker90 Sep 7, 2026
936d7c7
clnrest/wss-proxy: write TLS private keys with 0600 permissions
daywalker90 Sep 9, 2026
02229d4
wss-proxy: cap message size and concurrent connections
daywalker90 Sep 9, 2026
f737cd9
wss-proxy: time out handshakes so the accept loop can't be stalled
daywalker90 Sep 10, 2026
9282308
plugin-bip353: bound fetchbip353 with HTTP and overall timeouts
daywalker90 Sep 25, 2026
e4e0d56
lightningd: fail HTLCs whose onion we couldn't parse as malformed
nGoline Oct 2, 2026
dfb8bf5
channeld: fail the channel if asked to resend a commitment they revok…
nGoline Oct 2, 2026
4438d74
lightningd: time out offered HTLCs on a channel that is shutting down
nGoline Sep 28, 2026
0d19031
internal API: proper use of peer_billboard
Lagrang3 Sep 25, 2026
663f0f0
lightningd: return after failing the channel on a missing inflight
nGoline Oct 5, 2026
16681c3
lightningd: fix use-after-free of checkrune param cache keys
Andezion Oct 5, 2026
e86a7ce
runes: treat invokerune/destroyrune as their real commands in checkrune
Andezion Oct 5, 2026
a1bbb45
runes: only unrestricted runes may relist blacklisted runes
Andezion Oct 5, 2026
ea1ab9c
connectd: fix reply to query_channel_range
Lagrang3 Sep 29, 2026
254ea5f
channeld: fail the channel instead of crashing if we can't build a co…
jaonoctus Oct 5, 2026
33fe202
channeld: bound HTLC additions by our own splice-out amount
jaonoctus Oct 5, 2026
91bff31
channeld: tell lightningd the HTLC sigs for a splice's first commitment
jaonoctus Oct 5, 2026
29fd43e
lightningd: use a splice's HTLC sigs once we move onto its commitment
jaonoctus Oct 5, 2026
25df150
runes: only unrestricted runes may create new runes
Andezion Oct 5, 2026
a3b9b84
runes: record valid runes missing from the db on first use
Andezion Oct 5, 2026
0afb8d7
lightningd: conceal secret config values in listconfigs
Andezion Oct 5, 2026
9b47cb4
lightningd: don't abort when a splice RBF reuses a funding txid
sangbida Sep 28, 2026
2a3366b
channeld: tx_abort a splice whose txid matches an existing inflight
sangbida Sep 28, 2026
45c896c
lightningd: reject newlines in persistent setconfig values
sangbida Sep 29, 2026
0ccbe9f
channeld: fix splice accepter never recording negotiated feerate
Andezion Sep 22, 2026
b9a0899
runes: start deprecating invokerune/destroyrune in v26.12
nGoline Oct 6, 2026
a003365
CHANGELOG: add the 26.06.9 release entry
nGoline Oct 7, 2026
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
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,56 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [26.06.9] - 2026-10-06: "Quantum-Resistant Lightning Channel VI"

This point release is recommended for all users.

### Added

- Releases now include signed reproducible arm64 and armv7 binaries for Ubuntu 22.04, 24.04 and 26.04.

### Changed

- JSON-RPC: `blacklistrune` with `relist` needs a rune without restrictions when called through a rune, and refuses an `end` before `start`.
- JSON-RPC: `createrune` without a `rune` needs a rune without restrictions when called through a rune; any rune can still add restrictions to a rune it supplies.
- JSON-RPC: `listconfigs` shows `...` instead of the value of `wallet`, `recover`, `tor-service-password`, `bitcoin-rpcpassword` and the old `bookkeeper-db`, for every caller; unset ones are omitted.

### Fixed

- JSON-RPC: `getroutes` with `maxparts=0` no longer crashes the askrene solver.
- JSON-RPC: a very large `datastore` value no longer crashes lightningd.
- onchaind: a channel closed by one of our own old commitments no longer crashes onchaind on every restart, and now resolves.
- currencyrate: reject non-finite prices from sources instead of hanging the RPC
- clnrest and wss-proxy: plugins now create TLS private keys with restrictive (0600) permissions regardless of umask.
- wss-proxy: now rejects messages larger than a BOLT8 wire message (65535 bytes) and caps concurrent connections.
- wss-proxy: an idle client no longer blocks the accept loop and prevents all new connections
- cln-bip353: fetchbip353 (and xpay payments to a human readable name) no longer hang when a remote endpoint stops responding
- lightningd: an incoming HTLC whose onion can't be parsed is now always failed as malformed, instead of crashing the node in some cases.
- channeld: a peer's `channel_reestablish` asking for a commitment it has already acknowledged now fails the channel instead of getting it re-signed.
- lightningd: an offered HTLC that reaches its deadline while the channel is shutting down now force-closes the channel, so forwarded funds cannot be lost to a late fulfill.
- askrene-getroutes: don't crash on invalid user input, source==destination
- lightningd: don't spuriously try to re-broadcast funding txs of already-confirmed channels on startup.
- Fix: SIGINT was ignored by the cln docker container
- Crash when trying to pay and you have recovered channel stubs in your peers list
- lightningd: a node recovering from a static channel backup no longer hangs up on the peer when one of the recovered channels has already closed, so the peer can still close the channels which are still open.
- invoice could omit warning_offline if created right after a peer disconnect
- macOS: channeld no longer dies silently with status 0 right after a channel open under load.
- macOS: fundchannel no longer fails or hangs with "Peer connection lost" when opening channels under load.
- plugins: a command whose id or method name needs JSON escaping no longer hangs.
- lightningd: a splice message for an inflight we don't know about now fails that channel instead of crashing the node.
- lightningd: `checkrune` no longer uses freed memory when a rune has several restrictions on the same parameter.
- protocol: reply to query_channel_range with channels ranges in correct block order
- channeld: no longer crash-loops when an HTLC that only the pre-splice commitment can carry is added during a splice-out.
- splicing: closing with a splice's commitment before any later commitment update can now time out or claim the HTLCs on it.
- lightningd: a valid rune missing from the database (e.g. after restoring an older backup) is recorded on first use, so its `per` and `rate` limits apply and its id isn't reused.
- connectd: ordinary gossip, pings and onion messages no longer count against the gossip query CPU budget, so busy nodes no longer throttle their peers.
- A channel peer can no longer crash lightningd by RBF-ing a splice into a transaction the node has already recorded.
- `setconfig` no longer permits config-line injection through persistent option values.
- channeld: when accepting a splice, we now enforce the negotiated feerate on the splice transaction, so a peer can no longer get us to sign a splice paying (almost) no fees
- 'checkrune': 'method/createrune' and 'method/blacklistrune' restrictions can no longer be bypassed with the 'invokerune'/'destroyrune' aliases
- runes: a rune with restrictions can no longer relist blacklisted runes.
- runes: a rune with restrictions can no longer create a new rune without them.

## [26.06.8] - 2026-09-19: "Quantum-Resistant Lightning Channel V"

This point release is recommended for all users.
Expand Down
78 changes: 67 additions & 11 deletions channeld/channeld.c
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ static void billboard_update(const struct peer *peer)
peer->depth_togo,
num_channel_htlcs(peer->channel));

peer_billboard(false, update);
peer_billboard(false, "%s", update);
}

const u8 *hsm_req(const tal_t *ctx, const u8 *req TAKES)
Expand Down Expand Up @@ -1299,6 +1299,12 @@ static u8 *send_commit_part(const tal_t *ctx,
remote_index, REMOTE,
splice_amnt, remote_splice_amnt, &local_anchor_outnum,
funding_pubkeys);
if (!txs)
peer_failed_err(peer->pps, &peer->channel_id,
"Could not create commitment %"PRIu64
" for funding %s",
remote_index,
fmt_bitcoin_outpoint(tmpctx, funding));
htlc_sigs =
calc_commitsigs(tmpctx, peer, txs, funding_wscript, htlc_map,
remote_index, remote_per_commit, &commit_sig,
Expand Down Expand Up @@ -2051,6 +2057,23 @@ static NORETURN void splice_abort(struct peer *peer, struct inflight *inflight,
exit(0);
}

/* lightningd keys inflights by funding txid, so a splice tx which
* reuses the txid of one we already have (e.g. an RBF that rebuilt an
* identical tx) cannot be recorded. Abort the splice rather than hand
* lightningd a duplicate. */
static void check_duplicate_inflight(struct peer *peer,
const struct bitcoin_txid *txid)
{
for (size_t i = 0; i < tal_count(peer->splice_state->inflights); i++) {
if (!bitcoin_txid_eq(&peer->splice_state->inflights[i]->outpoint.txid,
txid))
continue;
splice_abort(peer, NULL,
"Splice tx %s duplicates an existing inflight",
fmt_bitcoin_txid(tmpctx, txid));
}
}

struct commitsig_info {
struct commitsig *commitsig;
struct secret *old_secret;
Expand Down Expand Up @@ -2215,6 +2238,12 @@ static struct commitsig_info *handle_peer_commit_sig(struct peer *peer,
local_index, LOCAL, splice_amnt,
remote_splice_amnt, &remote_anchor_outnum,
funding_pubkeys);
if (!txs)
peer_failed_err(peer->pps, &peer->channel_id,
"Could not create commitment %"PRIu64
" for funding %s",
local_index,
fmt_bitcoin_outpoint(tmpctx, &outpoint));

/* Set the commit_sig on the commitment tx psbt */
if (!psbt_input_set_signature(txs[0]->psbt, 0,
Expand Down Expand Up @@ -3674,6 +3703,13 @@ static struct amount_sat check_balances(struct peer *peer,
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"amount_sat_less / amount_sat_sub mismtach");

/* feerate_per_kw must have been negotiated (accepter) or supplied by
* the user (initiator) by this point: 0 would silently collapse the
* fee floors below to zero and let a near-zero-fee splice through. */
if (peer->splicing->feerate_per_kw == 0)
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"check_balances called with unset splicing feerate_per_kw");

min_initiator_fee = amount_tx_fee(peer->splicing->feerate_per_kw,
calc_weight(TX_INITIATOR, psbt, false));
min_accepter_fee = amount_tx_fee(peer->splicing->feerate_per_kw,
Expand Down Expand Up @@ -3815,20 +3851,23 @@ static void update_view_from_inflights(struct peer *peer)
struct inflight **inflights = peer->splice_state->inflights;

for (size_t i = 0; i < tal_count(inflights); i++) {
s64 splice_amnt = inflights[i]->amnt.satoshis; /* Raw: splicing */
/* Each side's balance change on this inflight's commitment,
* relative to the current funding: the same amounts
* channel_txs() applies when it builds that commitment. */
s64 splice_amnt = inflights[i]->splice_amnt;
s64 funding_diff = sats_diff(inflights[i]->amnt, peer->channel->funding_sats);
s64 remote_splice_amnt = funding_diff - inflights[i]->splice_amnt;
s64 remote_splice_amnt = funding_diff - splice_amnt;

if (splice_amnt < peer->channel->view[LOCAL].lowest_splice_amnt[LOCAL])
peer->channel->view[LOCAL].lowest_splice_amnt[LOCAL] = splice_amnt;

if (splice_amnt < peer->channel->view[REMOTE].lowest_splice_amnt[REMOTE])
if (splice_amnt < peer->channel->view[REMOTE].lowest_splice_amnt[LOCAL])
peer->channel->view[REMOTE].lowest_splice_amnt[LOCAL] = splice_amnt;

if (remote_splice_amnt < peer->channel->view[LOCAL].lowest_splice_amnt[REMOTE])
peer->channel->view[LOCAL].lowest_splice_amnt[REMOTE] = remote_splice_amnt;

if (remote_splice_amnt < peer->channel->view[REMOTE].lowest_splice_amnt[LOCAL])
if (remote_splice_amnt < peer->channel->view[REMOTE].lowest_splice_amnt[REMOTE])
peer->channel->view[REMOTE].lowest_splice_amnt[REMOTE] = remote_splice_amnt;
}
}
Expand Down Expand Up @@ -3935,6 +3974,7 @@ static void resume_splice_negotiation(struct peer *peer,
msg = towire_channeld_update_inflight(NULL, current_psbt,
their_commit->tx,
&their_commit->commit_signature,
their_commit->htlc_signatures,
inflight->locked_scid,
inflight->i_sent_sigs);
wire_sync_write(MASTER_FD, take(msg));
Expand Down Expand Up @@ -4007,7 +4047,7 @@ static void resume_splice_negotiation(struct peer *peer,
inflight->force_sign_first)
&& send_signature) {
msg = towire_channeld_update_inflight(NULL, current_psbt,
NULL, NULL,
NULL, NULL, NULL,
inflight->locked_scid,
inflight->i_sent_sigs);
wire_sync_write(MASTER_FD, take(msg));
Expand Down Expand Up @@ -4186,7 +4226,7 @@ static void resume_splice_negotiation(struct peer *peer,
if (recv_signature || send_signature) {
/* We let core validate our peer's signatures are correct. */
msg = towire_channeld_update_inflight(NULL, current_psbt, NULL,
NULL,
NULL, NULL,
inflight->locked_scid,
inflight->i_sent_sigs);
wire_sync_write(MASTER_FD, take(msg));
Expand Down Expand Up @@ -4392,6 +4432,8 @@ static void splice_accepter(struct peer *peer, const u8 *inmsg)
funding_feerate_perkw,
accepted_feerate_max(peer));

peer->splicing->feerate_per_kw = funding_feerate_perkw;

/* TODO: Add plugin hook for user to adjust accepter amount */
peer->splicing->accepter_relative = 0;

Expand Down Expand Up @@ -4463,6 +4505,7 @@ static void splice_accepter(struct peer *peer, const u8 *inmsg)
psbt_elements_normalize_fees(ictx->current_psbt);

psbt_txid(tmpctx, ictx->current_psbt, &outpoint.txid, NULL);
check_duplicate_inflight(peer, &outpoint.txid);

psbt_finalize(ictx->current_psbt);

Expand Down Expand Up @@ -4769,6 +4812,7 @@ static void splice_initiator_user_finalized(struct peer *peer)
fmt_wally_psbt(tmpctx, ictx->current_psbt));

psbt_txid(tmpctx, ictx->current_psbt, &current_psbt_txid, NULL);
check_duplicate_inflight(peer, &current_psbt_txid);

outmsg = towire_channeld_add_inflight(tmpctx,
&peer->splicing->remote_funding_pubkey,
Expand Down Expand Up @@ -4829,6 +4873,7 @@ static void splice_initiator_user_finalized(struct peer *peer)
outmsg = towire_channeld_update_inflight(NULL, new_inflight->psbt,
their_commit->tx,
&their_commit->commit_signature,
their_commit->htlc_signatures,
new_inflight->locked_scid,
new_inflight->i_sent_sigs);
wire_sync_write(MASTER_FD, take(outmsg));
Expand Down Expand Up @@ -5025,8 +5070,7 @@ static void splice_initiator_user_signed(struct peer *peer, const u8 *inmsg)
/* Save the user provided signatures to DB incase we have to
* restart and reestablish later. */
outmsg = towire_channeld_update_inflight(NULL, inflight->psbt,
inflight->last_tx,
&inflight->last_sig,
NULL, NULL, NULL,
inflight->locked_scid,
inflight->i_sent_sigs);

Expand Down Expand Up @@ -6246,9 +6290,20 @@ static void peer_reconnect(struct peer *peer,
* `commitment_signed`.
*/
if (next_commitment_number == peer->next_index[REMOTE] - 1) {
if (!recv_tlvs || !recv_tlvs->next_funding)
if (!recv_tlvs || !recv_tlvs->next_funding) {
/* They only revoke the previous commitment once they
* have our last commitment_signed, so they can't need
* it again. And re-signing it now would use the
* per-commitment point of the one after. */
if (peer->revocations_received != peer->next_index[REMOTE] - 2)
peer_failed_err(peer->pps,
&peer->channel_id,
"bad reestablish commitment_number: %"
PRIu64" but you already revoked %"PRIu64,
next_commitment_number,
peer->revocations_received - 1);
retransmit_commitment_signed = true;
else
} else
retransmit_commitment_signed = false;

/* BOLT #2:
Expand Down Expand Up @@ -6417,6 +6472,7 @@ static void handle_funding_depth(struct peer *peer, const u8 *msg)
inflight->psbt,
NULL,
NULL,
NULL,
inflight->locked_scid,
inflight->i_sent_sigs);
wire_sync_write(MASTER_FD, take(msg));
Expand Down
3 changes: 3 additions & 0 deletions channeld/channeld_wire.csv
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ msgtype,channeld_update_inflight,7219
msgdata,channeld_update_inflight,psbt,wally_psbt,
msgdata,channeld_update_inflight,last_tx,?bitcoin_tx,
msgdata,channeld_update_inflight,last_sig,?bitcoin_signature,
# If last_tx is set, their HTLC signatures for it.
msgdata,channeld_update_inflight,num_htlc_sigs,u16,
msgdata,channeld_update_inflight,htlc_sigs,bitcoin_signature,num_htlc_sigs
msgdata,channeld_update_inflight,locked_scid,?short_channel_id,
msgdata,channeld_update_inflight,i_sent_sigs,bool,

Expand Down
4 changes: 2 additions & 2 deletions common/json_stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -317,12 +317,12 @@ void json_add_hex(struct json_stream *js, const char *fieldname,
{
/* Size without NUL term */
size_t hexlen = hex_str_size(len);
char str[hexlen];
char *str = tal_arr(js, char, hexlen);

if (!hex_encode(data, len, str, hexlen))
abort();

json_add_string(js, fieldname, str);
json_add_string(js, fieldname, take(str));
}

void json_add_hex_talarr(struct json_stream *result,
Expand Down
2 changes: 1 addition & 1 deletion common/peer_failed.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ peer_failed(struct per_peer_state *pps,
desc,
warn,
msg);
peer_billboard(true, desc);
peer_billboard(true, "%s", desc);
peer_fatal_continue(take(msg), pps);
}

Expand Down
Loading