Repository navigation
Port the v26.06.10 fixes to master - #9693
Merged
daywalker90 merged 83 commits intoNov 5, 2026
Merged
Conversation
createinvoice only had its label checked for duplicates (inside invoices_create()), but payment_hash is UNIQUE in the invoices table too. Calling it with a preimage whose payment_hash was already stored under another label hit that constraint, and db_exec_prepared_v2() treated the failure as fatal, so any RPC caller (including a commando rune allowing createinvoice) could crash the node. Check for an existing payment_hash before decoding the invoice, as the invoice command already does, and return INVOICE_PREIMAGE_ALREADY_EXISTS (901). This covers both bolt11 and bolt12, and now reports it too. A matching label still takes precedence and returns 900 with the existing invoice's details, which the offers plugin relies on. Changelog-Fixed: JSON-RPC: now returns error 901 for an already-used preimage instead of crashing the node.
A zero-path offer_paths passed decode, then aborted the offers plugin in current_their_path() on fetchinvoice, taking lightningd down with it. Changelog-Fixed: offers: no longer crashes on an offer with an empty offer_paths. Co-authored-by: Cursor <cursoragent@cursor.com>
If the peer answers our splice commitment_signed with tx_signatures, interactive_send_commitments() returns NULL and splice_initiator_user_finalized() dereferenced it. Fail the channel instead, as resume_splice_negotiation() already does. Changelog-Fixed: channeld no longer crashes when a peer sends tx_signatures instead of commitment_signed during splice finalization.
…eer. gossipd passes channel_updates for unknown channels to lightningd if they are signed by the peer which sent them, and lightningd only checked that this was the channel peer for private channels. So any node could overwrite our peer's update for a public channel which wasn't announced yet (fees, cltv, htlc limits end up in listincoming and our routehints). Before 24.02 gossipd told us NULL for updates it checked against the channel_announcement, and we checked the source for all channels. Go back to that. Changelog-Fixed: Protocol: a connected node could overwrite our peer's channel_update for a public channel before it was announced. Co-authored-by: Cursor <cursoragent@cursor.com>
signpsbt handed the caller's PSBT straight to hsmd, which treats any libwally signing error as fatal: a PSBT with no outputs (as fundpsbt and utxopsbt return before the caller adds one), an unknown sighash type, a taproot input next to an input without UTXO info, or our key on someone else's input took down hsmd and therefore lightningd. Refuse PSBTs with no outputs up front, have hsmd return the PSBT unsigned when libwally refuses it on the signpsbt path, and fail the command if an input we were asked to sign is still unsigned. Changelog-Fixed: JSON-RPC: `signpsbt` no longer crashes the node when given a PSBT it cannot sign (e.g. one with no outputs); it returns an error instead.
A peer could send open_channel2 and then go quiet while staying connected: dualopend waited forever and the UTXOs funder contributed stayed reserved for 72 blocks. Give the peer 10 minutes to reach commitment_signed, then fail the open so funder releases them. Changelog-Fixed: dual-funding: a peer stalling a channel open no longer keeps our contributed UTXOs reserved.
Same hole as the initial open: after tx_init_rbf the peer can stay connected and go quiet. dualopend only reads the peer while it is negotiating, so it never sees lightningd's depth_reached and the channel sits in DUALOPEND_AWAITING_LOCKIN even after the original funding tx confirms, for as long as the peer likes. Give the RBF the same negotiation timeout. The channel itself is fine, so just drop the attempt and disconnect: that unblocks dualopend, and on reconnect the channel proceeds as normal. Changelog-Fixed: dual-funding: a peer stalling an RBF can no longer keep our channel from confirming.
time_change and offer_period_start check for value overflows and report back to caller. Changelog-None Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
Changelog-None Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
Instead of return 0 to indicate errors we use a return boolean pattern. Allowing 0 answer as a valid time. Changelog-None Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
Add a CLN policy for recurrent offers. Verify that user input and remote requests comply with the policy. Changelog-Added: offers: create a CLN policy to constraint recurrence parameters to reasonable values, to avoid user footguns and computation overflows. Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
Changelog-Added: docs: offers and fetchinvoice documentation mention the limits set on recurrence variables stablished by CLN's policy. Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
Changelog-Fixed: dualopend: a v2 channel open requesting 'option_zeroconf' is now refused unless we're configured to accept 0-confirmation channels from that peer, as openingd already does.
Hook handler errors and exceptions in plugin frameworks previously triggered fatal errors in lightningd or resulted in hangs when a handler panicked. In cln-plugin: - Separate hook and RPC method dispatch paths. - Add safe fallback mapping across hooks on Err or panic via catch_unwind. - Replace process::exit(1) on typed hook deserialize schema mismatch with returning an Err to allow fallback coercion. In pyln-client: - Complete hook_fallbacks table for all 15 hooks (keeping commitment_revocation and db_write crash-only). - Ensure only registered hooks use fallback responses on exception. - Redact traceback from JSON-RPC error responses on the wire. Changelog-Changed: Plugins: a hook handler that fails or panics (pyln-client, cln-plugin) now gets a safe default reply instead of crashing or hanging lightningd; commitment_revocation and db_write still stop the node.
listconfigs already conceals --recover, --wallet and the other OPT_CONCEAL options, but a value that fails to parse is still echoed back verbatim, e.g. "--recover=<secret>: Not a valid mnemonic, hex, or codex32 string". Print "name=..." instead, both on the command line and in config files.
…ingd through a pipe.
… gossipd will ignore
BOLT ElementsProject#1 requires ignoring an `error` or `warning` whose `channel_id` refers to no existing channel, instead of replying with an error and disconnecting. connectd no longer allocates a subd for such messages, and lightningd now ignores them.
Changelog-Fixed: Avoid closingd crashes on invalid legacy closing fee offers.
A duplicate key, or a name that only differs in punctuation, maps to the same pname. We checked the first one, while the command may use another: plugins take the last duplicate and `plugin` ignores extra names. Now any restriction on such a name fails. Changelog-Fixed: runes: `pname` restrictions can no longer be bypassed with duplicate or punctuation-variant parameter names.
Changelog-Fixed: onchaind: no longer crashes when an unexpected transaction spends a revoked HTLC output with too few outputs
Changelog-Security: gossipd: stale, duplicate and unknown-node gossip is now rejected before signature verification, so peers can no longer force needless ECDSA work
Changelog-Fixed: Protocol: onion messages with an empty (0-length) per-hop payload are now accepted, as BOLT 4 allows, instead of being silently dropped
Grow the pending replies array once per message rather than once per scid, use size_t for the counts, and drop the query if the accumulated replies exceed a sane limit. Changelog-Fixed: gossipd: more efficient handling of large reply_channel_range replies.
bkpr-dumpincomecsv passed csv_file straight to fopen(.., "w"), so it could name, and truncate, any file the node can write. The file goes in the node's network directory, next to the node's own files, so restricting it to a name alone isn't enough either. Accept only a plain filename ending in .csv (no directory, not hidden), and open it without following a symlink. Rewriting an existing .csv keeps working. Changelog-Changed: bookkeeper: `bkpr-dumpincomecsv` `csv_file` must now be a plain filename ending in `.csv`; it is written in the node's network directory, and never through a symlink. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
peer_got_revoke() applied the HTLC changes which a revoke_and_ack locks in -- forwarding the final accepts, settling updates and failing fails -- before it had checked the revocation secret. If wallet_shachain_add_hash() rejects that secret we fail the channel anyway, so it must not act on them first. Run the shachain and revokenum checks before acting on the HTLCs. Nothing in between touches HTLC state, and crash-consistency is unchanged: sd_msg_read() wraps every channeld message in a single DB transaction, so a crash replays the whole message or nothing. Reported-by: Erick C (@erickcestari)
…hannel
After an RBF the channel has more than one candidate funding tx. After
a mutual close we watch the ones we didn't close on, in case they
confirm. To build that watch we need the peer's funding key, and we
were reading it from a field that only splices fill in. For RBF it is
NULL, and we crashed on it:
lightningd: FATAL SIGNAL 6
bitcoin/pubkey.c:88 (pubkey_cmp)
bitcoin/script.c:155 (bitcoin_redeem_2of2)
lightningd/peer_control.c:530 (drop_to_chain)
lightningd/closing_control.c (peer_closing_complete)
RBF never changes the funding keys, so use the channel's own copy of
the peer's key instead.
Changelog-Fixed: lightningd: fix a crash on cooperative close of an unconfirmed dual-funded channel.
A channel_announcement, channel_update or node_announcement is a few hundred bytes. Add generous upper bounds (1024, 1024 and 2048 bytes) and ignore anything larger, before we do any work on it. Changelog-Fixed: gossipd: ignore gossip messages far larger than any valid one. Signed-off-by: Sangbida Chaudhuri <101164840+sangbida@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
We compact the gossip_store when dead records outnumber live ones 4:1. That only works if records are similar in size, and they are not: a node_announcement can be 2048 bytes, a channel_update ~140. So remember the store size after the last compaction (at startup, file size minus deleted records) and also compact once the store is 5x that. A failed compaction resets the baseline so we don't retry on every message. Changelog-Fixed: gossipd: the gossip_store is also compacted based on its size, not just its record count. Signed-off-by: Sangbida Chaudhuri <101164840+sangbida@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
gossipd ignores gossip messages far larger than any valid one, but connectd was still queueing them for gossipd. Apply the same bounds in connectd, before the message is queued. Changelog-Fixed: connectd: gossip messages far larger than any valid one are dropped before they are queued for gossipd. Signed-off-by: Sangbida Chaudhuri <101164840+sangbida@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
setup_peer() re-arms the chain watches for every channel on startup. For a channel in CHANNELD_AWAITING_SPLICE it only watched the splice inflights, so after a restart nothing was watching the channel's current funding output. That output stays live until a splice confirms, and if it is spent by a commitment instead we never noticed: onchaind was not started and the channel was stuck in CHANNELD_AWAITING_SPLICE. Watch the funding as we do in every other state, so the channel has the same watches it had before the restart. Changelog-Fixed: lightningd: a channel with an unconfirmed splice now notices if its funding output gets spent after a restart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolving a DNS address announced in a node_announcement used to call getaddrinfo() directly, on connectd's single io thread. A slow or unresponsive nameserver held up every peer on the machine for the duration of the lookup: ~10s per query with glibc defaults, enough to trip the 60s ping-liveness check and disconnect all peers. getaddrinfo() now runs in a forked child which writes the results back over a pipe; connectd reads the pipe from its io loop and resumes the connect attempt from a loop callback, so the io thread never waits on resolution. Keeping libc for the actual lookup preserves /etc/hosts, search domains, systemd-resolved and AI_ADDRCONFIG ordering exactly, and keeps DNS packet parsing out of connectd itself. A 30-second cap covers each lookup end to end (a wedged child is killed), and at most ten resolvers run at once: a gossip-driven reconnect storm queues the excess FIFO rather than forking without bound. Before resolving, the child closes every fd it inherited: a wedged child must not keep peer connections alive, nor pin EOF on another lookup's pipe with its copy of that write end. The connect RPC resolved a host name the same way, with a blocking getaddrinfo() in lightningd's main loop, and ignored --disable-dns. It now hands the unresolved name to connectd as a DNS address, which goes through the same asynchronous resolver and honours --disable-dns (unless a proxy resolves names for us). Reported-by: Vincenzo Palazzo Changelog-Fixed: connectd: a stalling DNS name in a peer's announced address no longer freezes all peer traffic; resolution is now done asynchronously. Changelog-Changed: `connect` with a host name now resolves it asynchronously in connectd, and fails with `--disable-dns`.
Upon timing out an upstream incoming HTLC, we were setting the resolution to be ignored, since the funds will presumably be returned to the peer, not us. However, since the HTLC timeout and the internal resolution do not line up perfectly, due to the security margin, there is a chance for a late preimage coming in, resolving the HTLC in the other direction, i.e., we now own it. If that resolution comes in after we have internally marked the output as ignored, we would not clean up our prior decision, leaving the HTLC stranded, but recoverable. This just ensures that we clear our internal resolution too, when the downstream preimage comes in, so that we can use the preimage to grab the funds in the HTLC. Changelog-Fixed: onchaind: Fixed a race between preimage return from downstream to upstream
The non-revoked THEIR_HTLC arm of output_spent() only annotates: it has always relied on the "no witness elements" fallback in tx_new_depth() to resolve the output once we reach the timeout depth. But handle_preimage() replaces that ignore-proposal with a real spend, which fills in the witness elements, and kills the fallback for that output. So on the losing side of the same race the previous commit fixes -- their HTLC-timeout confirms after we decided to claim it -- nothing ever resolves the output: wait_for_resolved() spins forever, we never send all_irrevocably_resolved, and the channel is never forgotten. There's a second ordering with the same outcome: we see their spend first (and skip it, as the output is resolved), then the preimage arrives and clears that resolution. Resolve it in the spend arm, and don't try to claim an output we watched them spend: there is nothing left to grab, and proposing a spend of it is exactly what strands us. That's the *only* case where we decline a preimage: an HTLC we merely gave up on is always still claimable, which is what the enum makes explicit. Changelog-Fixed: onchaind: Don't wait forever if the peer takes back an HTLC we were trying to claim with a late preimage Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
When an incoming HTLC timed out we told the bookkeeper immediately that it had gone back to the peer. If a late preimage then let us claim it after all, we booked it *again*, this time to ourselves: the bookkeeper never reverses an event, so the commitment output ends up with two deposits. find_txo_set() then builds two pairs for one output, the pairs == closed_count check in account_onchain_closeheight() fails, and the channel is never marked resolved. We can't record it any earlier than we know it, so record it later instead: when we see their spend confirm, or, for an output nobody ever spent, on our way out of wait_for_resolved(), where no preimage can reach us any more. Note we can't record it in the spend arm alone: propose_ignore() uses cltv_expiry while their spend can only confirm after it, so we always give up on the output before they take it, and the deposit would go unrecorded (which leaves the channel unresolved just the same). As a side-effect this also covers propose_ignore()'s immediate path, taken when the commitment confirms at or after the expiry, which never recorded anything at all. Changelog-Fixed: bookkeeper: An incoming HTLC we claim with a late preimage after it timed out is no longer booked both to the peer and to us Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three bare 100s (the BOLT ElementsProject#5 quote above the first one explains what they are). Taken from Rusty's branch. Changelog-None Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019XZZWMuBhXrq41u9HhxVBM
If lightningd can't make our claim on a THEIR_HTLC pay (a small HTLC, or a fee spike since the commitment was signed) it says so in the spend_created reply, and handle_spend_created() ignores the output. That's giving up just as a timeout is, but it left the fate as THEIR_HTLC_UNDECIDED, so neither the spend arm nor the exit recorded the htlc_timeout deposit: bkpr never saw the HTLC leave, and the channel was never resolved. master booked it exactly once here, so this was a regression of "onchaind: only record an HTLC as theirs once that's final". The lowballed spend can also confirm anyway. We used to skip it at the already-resolved check and book nothing (and now would have booked it as theirs). Record it as ours, and re-anchor the resolution on the spending tx: that's what actually resolved the output, so the 100-block clock for irrevocable resolution runs from its confirmation. A lowball tx can mine long after the commitment itself is 100 deep, and counting from the commitment would let us forget the channel while the spend is a block deep and could still be reorged out. This delays forgetting a channel we gave up on until any spend we'd written off matures; test_onchain_all_dust checks that. Only claims lightningd builds itself can be lowballed: pre-signed HTLC-success/-timeout txs always come back as worthwhile. So the test has l1 close, and forces the lowball with a fee spike after the commitment confirms. One variant then has l1 take the HTLC back, the other mines the lowballed claim directly (bitcoind won't relay it, since lightningd's weight estimate for a fulfill spend leaves out the preimage and so undershoots the relay minimum at the floor; that's a separate bug). The tests above restart the node that wrote the claim off, after that claim's confirmation: propose_resolution_to_master() waits for the spend_created reply before anything else is processed, so replay re-derives the same ignore decision, re-matches the mined tx against our proposal, and re-books through this branch -- the tests assert bookkeeper sees it exactly once and depth counts from the confirmation all the same. Changelog-None Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019XZZWMuBhXrq41u9HhxVBM
…g key The peer may use a new funding_pubkey for a splice, so the splice output is a 2-of-2 of our key and that one. We signed an inflight's commitment with the channel's current remote funding key, which doesn't match it. Use the inflight's splice_remote_funding, the key we already watch the splice output with, in drop_to_chain() and dev-sign-last-tx. Changelog-Fixed: lightningd: unilateral close of a channel with a pending splice now works when the peer used a new funding key for it.
With a splice pending, channeld sends splice_init as tx_init_rbf, and BOLT 2 requires that feerate to be at least 25/24 of the previous one. We didn't check, so we proposed RBFs a peer such as Eclair rejects. Refuse a lower feerate at the RPC, and raise the default up to the floor. test_splice_rbf_duplicate_inflight's RBF now has to clear it. Changelog-Fixed: splice_init: RBF of a pending splice now uses at least 25/24 of its feerate, which peers such as Eclair require. Co-authored-by: Cursor <cursoragent@cursor.com>
splice_feerate() is already capped at our_feerate_max(), so raising it to 25/24 of a splice that is pending near that cap goes over. channeld refuses that too, but only after setting peer->splicing, which it does not clear on that error: every later splice_init then failed with "Can't start two splices on the same channel at once" until channeld restarted. An explicit feerate above the cap did the same. Refuse it in json_splice_init instead, as openchannel_bump does. Changelog-Fixed: splice_init: a feerate above the most we'll pay no longer leaves the channel unable to splice until channeld restarts. Co-authored-by: Cursor <cursoragent@cursor.com>
abort_channels passed the last inflight to splice_abort() even once we had sent tx_signatures for it, which failed the channel. If a negotiation is in progress, that inflight is the splice it is RBFing: abort the negotiation and keep the inflight. If we're quiescent (after stfu_channels), tx_abort is still how we leave, and the inflight is kept too. Otherwise there is nothing to abort, so don't send tx_abort at all: BOLT 2 forbids it after tx_signatures, we're no longer quiescent, and splice_abort() would throw away whatever the peer sends while it waits for the ack. Changelog-Fixed: abort_channels no longer fails the channel when a splice we already signed is pending. Co-authored-by: Cursor <cursoragent@cursor.com>
Changelog-Fixed: Protocol: channeld now rejects a 'start_batch' with 'batch_size' above 20, as the spec requires, so a peer can no longer stall the channel or use up memory
… theirs. onchaind works this out when it starts (it compares the txid to the one lightningd told it we broadcast). lightningd only gets the commitment number back, which doesn't say whose commitment it is. Add a flag to init_reply so it knows. lightningd reads the flag but doesn't use it yet. Co-authored-by: Cursor <cursoragent@cursor.com>
When a channel closes, lightningd gives onchaind a list of HTLCs that might be in the commitment tx. The list is a bit too generous: an HTLC removed just before the close is still on it. That's harmless until two of those HTLCs share a payment hash, amount and cltv: onchaind can't tell them apart, and the commitment tx may contain only one of them. We still track each live HTLC's state, which says whether it's in the latest commitment on each side, or in the peer's previous commitment while that one is not yet revoked. Send that as a `present` flag with each HTLC. onchaind stores the flag but doesn't act on it yet. Co-authored-by: Cursor <cursoragent@cursor.com>
When two HTLCs on the list matched the same output, onchaind took the first one in cltv order. If a removed HTLC had the same payment hash, amount and cltv as a live one, it could win, and the live HTLC was reported missing. lightningd then failed it upstream right away, while the peer could still claim the output onchain with the preimage. A forwarding node loses the whole HTLC. Same bug as 027e97e (trimmed HTLC as the look-alike), different decoy. Fix: when HTLCs tie on cltv, sort the ones lightningd says are present first. Matching is still one output to one HTLC, so a commitment that really does have two identical HTLCs works as before. Changelog-Fixed: onchaind: an HTLC removed just before a unilateral close, with the same payment hash, amount and cltv as a live one, could be mistaken for it onchain, causing us to fail the live HTLC upstream while the peer could still claim it. Co-authored-by: Cursor <cursoragent@cursor.com>
Changelog-Fixed: commando and rune RPCs reject oversized runes instead of spending quadratic time decoding them.
The test elementsd runs without -txindex, so getrawtransaction on the
mined HTLC spend failed on liquid:
No such mempool transaction. Use -txindex or provide a block hash
to enable blockchain transaction queries.
Pass the hash of the block that mined it.
Changelog-None
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hbJ3AeummizNQSXoHKhYo
check-source-bolt reads any "BOLT ElementsProject#4" comment as a spec quote, and this one isn't. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 893ec0f55d77825def0b805536e5693d65bff2f4)
Changelog-None Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019hbJ3AeummizNQSXoHKhYo
daywalker90
approved these changes
Nov 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This brings the fixes from the v26.06.10 point release forward to master: 79 commits from the branches the fixes were written on, which were already based on master, plus a unit-test mocks update, the
test_onchain_all_dustfollow-up the release also carries, a comment fix forcheck-source-bolt, and one commit adding the 26.06.10 entry toCHANGELOG.md. Authorship is preserved throughout.These are the same fixes the release ships, source only. As announced in the release notes, the tests for the security fixes are temporarily withheld and will follow in a later PR. Fixes in the release that came from master in the first place (#9575, #9626, #9656 and the other picks) are not repeated here.
What is in it
Channel opens, dual funding and closing
option_zeroconfis refused unless zero-conf is allowed for that peer, as for v1 opensdust_limit_satoshisabove 5000sat (v1 and v2) or above our reserve (v2) is refusedcloseon a dual-funded open we haven't saved yet, and mutual close of an unconfirmed dual-funded channel, no longer crashchannel_ready, and a channel with HTLCs is failed instead of forgottenfeerangewith min above max are rejectedopenchannel2policy checks, queues RBF funding with its inputs reserved, and keeps the saved UTXOs when an RBF failsprevtx_voutSplicing
commitment_signedon splice finalizesplice_initmust meet the 25/24 feerate floor, and a feerate above our maximum is refused instead of wedging the channelabort_channelsno longer fails a channel with a signed splicestart_batchwith abatch_sizeabove 20 is rejectedonchaind
steal_htlc_txTHEIR_HTLCis resolved when the peer takes it back, and recorded as theirs only once that's finalSIGHASH_ALLGossip, connectd and peers
channel_updates are only accepted from the channel peer, and a peer's updates are no longer written to the db one by onereply_channel_rangereplies are resized once per query;gossip_storeis also compacted by sizechannel_updates for dying channels are ignored, and libplugin-pay disables such channels on matching errorspeer_spokeaccept()on a websocket port no longer crashestlvstream_set_rawinitializesmetaRPC, runes, plugins and wallet
createinvoicereturns error 901 on a duplicate payment hash instead of crashing;signpsbtno longer kills hsmdpayment_hash;getlog/checkno longer crash after the client hung uppnamerestrictions fail on ambiguous parameters, and runes over 64KiB are rejected before decoding--recoveris wiped from argv and passed to the re-exec'd lightningd through a pipe; concealed option values aren't printed in option errorsoffer_pathsare rejected; recurrence values are bounded and overflow-checked.csvfileNot a straight cherry-pick
test_onchain_all_dustandtest_addgossip.wallet: update run-wallet mocks for channeld_got_revokesits right afterchanneld: build a penalty tx for each splice candidate, the commit that needs it, so every commit builds.pytest: don't rely on -txindex in test_onchain_all_dustis the follow-up the release carries for that test on Liquid.common/test: don't start a non-quote comment with BOLTfixes a comment from the onion-message fix thatcheck-source-boltreads as a spec quote.Testing
Builds, unit tests and source checks pass on this branch. The same fixes were tested on the v26.06.10 release tree (full unit and integration runs: vanilla, dual-fund, Liquid, postgres, valgrind and sanitizers).
🤖 Generated with Claude Code