The test censors l2's sendrawtransaction with an rpcproxy mock while
the node RBFs its penalty tx three times, then removes the mock so the
next RBF version reaches bitcoind for real and gets mined. But each
'RBF onchain txid' log line precedes the corresponding broadcast, so
wait_for_log can return -- and the test un-mock -- while the third
replacement is still in flight. That older version then lands in
bitcoind's real mempool, the next version's broadcast is rejected as a
conflict, and the block mines a version the node no longer tracks, so
RBF-ing continues after confirmation and the final assertion fails.
Seen under valgrind, where the window between log line and broadcast
is wide.
Count the broadcasts the censoring mock swallows and wait for all four
(initial penalty tx plus three replacements) to have reached the proxy
before un-mocking, so no broadcast can be in flight when censoring
stops.
Fixes: #9347
Changelog-None
funding_satoshis values above the total bitcoin supply were not rejected
during open_channel/accept_channel negotiation and would later cause
libwally to fail and openingd to crash during commitment transaction
construction.
Reject such funding_satoshis values immediately so that the negotiation
terminates gracefully.
Fixes: #9225
Changelog-Fixed: `openingd` no longer crashes when a peer opens a channel with a `funding_satoshis` value greater than the total bitcoin supply.
If a payment's HTLC failure completes before waitsendpay is called,
wait_payment() rebuilds the error from the database, which did not
persist the raw BOLT4 failure message: a 2019 FIXME in that path set
fail->msg = NULL, silently dropping raw_message from the error data.
This is the cause of the test_error_returns_blockheight CI flake -
the test loses the race occasionally, calls waitsendpay after the
failure has landed, and KeyErrors on the missing raw_message.
Add a failmsg column to the payments table (with a downgrade drop),
persist fail->msg when recording the failure, and read it back in
wait_payment. Local and self-payment failures store NULL as before,
since no onion failure message exists for them; failed payments
recorded before this migration also return NULL, matching the old
behavior.
The flaky test now also calls waitsendpay a second time, which
deterministically takes the database-replay path, so the regression
is covered without any timing dependence.
Changelog-Fixed: JSON-RPC: `waitsendpay` error data now includes `raw_message` even when the payment already failed before the command was called.
Fixes: #9341
You can't access a local variable from a point before it was initialized and
expect it to have the initialized value. Move the setjmp() call to after
run_ctx is initialized so that the tal_free() call at cleanup will see the
correct address and not crash.
Fixes: https://github.com/ElementsProject/lightning/issues/9131
Changelog-None
The plugin start command only accepted plugin options flattened onto
the RPC call (e.g. via -k/--keyword), but the plugin RPC schema
documents an explicit 'options' array. This mismatch meant that callers
using named parameters against generated RPC bindings (cln-rpc, grpc,
protobuf), which cannot flatten arbitrary options, failed with
'unknown parameter options'.
Options without a value are treated as boolean flags, matching the
flattened form.
Changelog-Fixed: JSON-RPC: `plugin start` now accepts plugin options as an `options` array of `keyword=value` strings, as documented in the `plugin` schema.
CLN is too permissive for serde_json when validating json:
`lightning-cli -k myplugin-cmd channels='[123456x1x0]'`
is valid for lightning-cli but is actually invalid json (bare token in array).
The cln-plugin decoder would error and end the FramedRead stream, causing the
PluginDriver loop to exit, and therefore exiting the plugin itself.
We need to recover the id from the invalid json with a separate parser to return a
json rpc error to CLN with the correct id so the rpc command does not hang.
Changelog-None
```
def send_many_payments():
passes = 0
fails = 0
# Make sure we try many times, and get at least one pass and fail.
while passes == 0 or fails == 0 or passes + fails < 30:
inv = l3.rpc.invoice(100, "label-" + str(passes + fails), "desc")
l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret'])
time.sleep(0.05)
try:
l1.rpc.waitsendpay(inv['payment_hash'])
passes += 1
except RpcError:
fails += 1
pass
# Send a heap of payments, while reconnecting...
fut = executor.submit(send_many_payments)
for _ in range(30):
l3.rpc.disconnect(l2.info['id'], force=True)
time.sleep(0.1)
l3.rpc.connect(l2.info['id'], 'localhost', l2.port)
> fut.result(TIMEOUT)
```
The while loop would somtimes go infinitely long if no payments fail.
Changelog-None
We need to wait for bwatch to see the deposit block or it won't see a
reorg.
```
assert l1.db_query('SELECT COUNT(*) AS c FROM outputs')[0]['c'] == 1
# Reorg the deposit block away. Deprioritize the returned mempool tx
# (same trick as simple_reorg) so the replacement blocks don't just
# re-confirm it.
bitcoind.rpc.invalidateblock(bitcoind.rpc.getblockhash(deposit_height))
memp = bitcoind.rpc.getrawmempool()
assert txid in memp
for t in memp:
bitcoind.rpc.prioritisetransaction(t, None, -1000000)
bitcoind.generate_block(2)
> l1.daemon.wait_for_log(r'Reorg detected', timeout=60)
```
Changelog-None
When an important plugin dies, lightningd shuts down while other plugins
may still be mid-sync-RPC during init. If rpc_open() fails because the
RPC socket is already gone (logging "Could not connect ... Connection
refused"), sync_req() proceeds with sync_fd == -1, and the subsequent
read(-1) fails with EBADF, producing the BROKEN message:
Reading sync lightningd: Bad file descriptor
This is the same intentional shutdown that already produces the
whitelisted "Reading sync lightningd: Connection reset by peer" (or a
clean EOF exit); the errno merely differs by the connection race. Add
"Bad file descriptor" to the broken_log whitelist so the test no longer
fails at teardown on this race.
Changelog-None
Shellcheck 0.11 (SC2268) rejects the old x"$1" comparison idiom.
Prevents: "tests/plugins/compacter-slow.sh:5:6: note: Avoid x-prefix in comparisons as it no longer serves a purpose. [SC2268]"
Shellcheck 0.11 (SC2268) rejects the old x"$1" comparison idiom.
Prevents: "tests/plugins/compacter-slow.sh:5:6: note: Avoid x-prefix in comparisons as it no longer serves a purpose. [SC2268]"
Fix test_explain_source_dest_failure by increasing the amounts by a
factor of ten, which reduces the relative significance of on-chain fees
on the commitment transaction.
This used to fail on liquid-regtest due to insufficient liquidity on
l2->l4 to prevent channel starvation.
At line
```
l1.rpc.xpay(l4.rpc.invoice('30000sat', 'test_explain_simple_failures2', 'test_explain_simple_failures2')['bolt11'])
```
we would get
```
lightningd-2 2026-07-06T11:29:09.973Z DEBUG 02287bfac8b99b35477ebe9334eede1e32b189e24644eb701c079614712331cec0-channeld-chan#3: Adding HTLC would leave us only 19454000msat: we need 25281sat for another HTLC if fees increase from 7500perkw to 13906perkw
lightningd-2 2026-07-06T11:29:09.973Z DEBUG 02287bfac8b99b35477ebe9334eede1e32b189e24644eb701c079614712331cec0-channeld-chan#3: Adding HTLC 0 amount=30000000msat cltv=130 gave CHANNEL_ERR_CHANNEL_CAPACITY_EXCEEDED
```
Changelog-None
Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
This is an undocumented interface, so we can just change it.
Rename "recurrence_label" to the more general "label", now we don't
require it to find previous payments.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
We don't actually need to enforce this check here: we can make that
the users' responsibility. This simplifies our work quite a lot,
since createinvoicerequest won't have to do a lookup any more.
This can be done by the repeatpay plugin itself.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This mirrors the previous commit, where we did it for recurring offers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Fixed: Offers: we set a 10 minute expiry when we create invoices for offers in other currencies.
Add PAY_INSUFFICIENT_FUNDS and PAY_ROUTE_NOT_FOUND, and give nice
detailed errors for those.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Changed: JSON-RPC: `getroutes` can now return PAY_INSUFFICIENT_FUNDS (215) and PAY_DESTINATION_INSUFFICIENT_CAPACITY (220) error codes.
It diagnoses if the *total capacity* of the source/dest are
insufficient, but not if the *known capacity* is.
So we get:
The shortest path is 103x1x0->105x1x0, but 103x1x0/1 layer auto.localchans says max is 77704899msat
Whereas it would be better to do:
We know from auto.localchans that source has maximum capacity xxx msat (in 1 channels)
Similarly for the destination, we get:
The shortest path is 103x1x0->105x1x0, but 103x1x0/1 layer auto.localchans says max is 77704899msat
Pure "constraints" don't care about order (they simply clamp max and
min), but "impressions" are relative, so they do. Change the
hashtable to keep them timestamp sorted.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
channeld_fakenet sudbdaemon purposedly crafted for this test needs
updating to account for funds moving after every success payment.
I've tried to fix that but in doing so I also triggered an xpay bug not
related to this PR. Therefore, for the moment we skip this test.
Changelog-None
Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
Normal constraints are clamps on min/max caused by failed payments:
min for the channels that succeeded, max for the channel which failed.
Impressions are the results of successful payments, which alter both
min and max (negatively in the forward direction, positively in the
reverse).
impression: n
1. An effect, feeling, or image retained as a consequence of experience.
2. A vague notion, remembrance, or belief.
3. A mark produced on a surface by pressure.
Unlike constraints, this is the result of our own effect on the network: they're related
but different enough to get their own API and terminology.
The name conveys both we made an impression on the channel, and that
the results are a bit vague (due to other changes since then, which we
won't know about).
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Added: JSON-RPC: `askrene` layers now contain "impressions" representing the effects of successful payments we made through channels.
In particular, "struct jsonrpc_request"'s id is always a string.
cmd->id isn't, though.
We can also remove the now-unused json_get_id.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
We used to handle it being a literal, but this was removed in
73fc9b0c2a (v25.05) so we don't need to handle that at all.
Not using the raw JSON means we handle weird methodnames by replacement: otherwise we would
not match the responses. Only an issue for commando, where the command would time out
rather than report "Unknown method".
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
It's not okay to call free() on the pointer to a truncated log message
that was allocated by tal_fmt() in cap_header(). Let's call tal_free()
instead, and rather than calling vasprintf() to malloc the log message in
the first place, let's call tal_vfmt().
Also, since we're now always using a tallocated string for the log message,
let's have cap_header() take ownership of it and either free it if it
truncated the message (and is returning a different pointer to the truncated
message) or else return the taken original pointer without freeing it.
Also, log_io()'s str and data parameters are marked TAKES, but the function
was not actually taking them, so fix that up too.
Also, don't call strlen() on a string returned by tal_fmt(). The returned
pointer is guaranteed to have tal_count() == strlen() + 1, so there's no
sense in scanning through the string to find its length.
Suggested-by: Lagrang3 <lagrang3@protonmail.com>
See: https://github.com/ElementsProject/lightning/pull/9331#discussion_r3628600315
Changelog-Fixed: log: don't crash when truncating large log messages
We were using vasprintf to generate the log line and then using free to
deallocate the string. However, in the case of a very long log line
a new pointer was created with tal_fmt and then tried to use free on it.
This was introduced in commit: 4d8f923a9a
```
free(): invalid pointer
lightningd: FATAL SIGNAL 6 (version v26.06-21-gebc5dc2)
0x563dab20be43 send_backtrace
common/daemon.c:38
0x563dab20becd crashdump
common/daemon.c:83
0x7f0cf0c96def ???
./signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0
0x7f0cf0ceb95c __pthread_kill_implementation
./nptl/pthread_kill.c:44
0x7f0cf0c96cc1 __GI_raise
../sysdeps/posix/raise.c:26
0x7f0cf0c7f4ab __GI_abort
./stdlib/abort.c:77
0x7f0cf0c80290 __libc_message_impl
../sysdeps/posix/libc_fatal.c:134
0x7f0cf0cf5464 malloc_printerr
./malloc/malloc.c:5832
0x7f0cf0cfa41b _int_free_check
./malloc/malloc.c:4560
0x7f0cf0cfa41b _int_free
./malloc/malloc.c:4692
0x7f0cf0cfa41b __GI___libc_free
./malloc/malloc.c:3476
0x563dab19be60 logv
lightningd/log.c:688
0x563dab19c0bc log_
lightningd/log.c:728
0x563dab1c1047 plugin_log_handle
lightningd/plugin.c:530
0x563dab1c5801 plugin_notification_handle
lightningd/plugin.c:609
0x563dab1c5a9a plugin_read_json
lightningd/plugin.c:753
0x563dab2388a5 next_plan
ccan/ccan/io/io.c:60
0x563dab238c83 do_plan
ccan/ccan/io/io.c:422
0x563dab238d3c io_ready
ccan/ccan/io/io.c:439
0x563dab239e5b io_loop
ccan/ccan/io/poll.c:470
0x563dab194399 io_loop_with_timers
lightningd/io_loop_with_timers.c:22
0x563dab199c29 main
lightningd/lightningd.c:1480
0x7f0cf0c80ca7 __libc_start_call_main
../sysdeps/nptl/libc_start_call_main.h:58
0x7f0cf0c80d64 __libc_start_main_impl
../csu/libc-start.c:360
0x563dab169020 ???
_start+0x20:0
0xffffffffffffffff ???
???:0
```
Changelog-None
Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
The graceful command notifies watchers about the closest-expiry HTLC,
including its state, whenever that message changes. But nothing
re-evaluated the message when an HTLC changed state: only HTLC
removal, peer disconnect and another graceful invocation re-ran the
check. If graceful was invoked while a commitment dance was in
flight, the initial notification named a transient state (e.g.
RCVD_ADD_REVOCATION) and no follow-up ever announced the settled
state.
test_graceful_htlc waits for exactly that follow-up (since 6994681ae
"flake: Fix test_graceful_htlc to be flexible for notifs"), so it
times out whenever graceful catches the dance mid-flight, which
valgrind CI runs make likely. In one CI failure graceful caught the
outgoing HTLC in RCVD_ADD_REVOCATION, the dance completed 600ms
later, and no notification followed for the remaining 180 seconds.
Re-check graceful progress in the handlers that advance HTLC states
(peer_sending_commitsig, peer_got_commitsig, peer_got_revoke). The
check is a no-op unless a graceful command is outstanding, and
identical messages are already deduplicated.
The dance now generates transient-state notifications, so rewrite the
test to match expected notifications as an ordered subsequence
instead of by exact index. That also removes two accidents the old
indexing depended on: the RCVD_ADD_REVOCATION special case (the
settled state now always notifies), and the wait for a notification
after l1's disconnect, which was really satisfied by the graceful(1)
call's own initial notification arriving on the shared rpc socket --
the disconnect itself never notifies, since the message text
describes the still-connected peer l3 and does not change.
Fixes: https://github.com/ElementsProject/lightning/issues/9219
Changelog-Fixed: JSON-RPC: `graceful` notifications now update when a pending HTLC changes state.
This got fixed in libwally v1.5.2: 7e483c049b
```
def test_sign_signed_psbt(node_factory, bitcoind, chainparams):
l1 = node_factory.get_node()
l1.fundwallet(10**6)
psbt = l1.rpc.txprepare([{l1.rpc.newaddr('bech32')['bech32']: 10000}])['psbt']
signed_psbt = l1.rpc.signpsbt(psbt)['signed_psbt']
if TEST_NETWORK != 'liquid-regtest':
# FIXME: ideally this would succeed, as a noop. But it shouldn't crash
> with pytest.raises(RpcError):
^^^^^^^^^^^^^^^^^^^^^^^
E Failed: DID NOT RAISE <class 'pyln.client.lightning.RpcError'>
```
Changelog-Changed: Update libwally to v1.5.6
Changelog-Fixed: JSON-RPC: `signpsbt` on an already-signed PSBT with taproot paths now succeeds as a no-op instead of failing.
When RLIMIT_NOFILE hard limit is RLIM_INFINITY (macOS default) or
larger than UINT32_MAX, passing limits[1] and limits[1]+1 directly as
--dev-fd-limit-multiplier (a u32 option) causes lightningd to reject
the argument as out-of-range and exit(1), making both nodes fail to
start with "Unable to find Server started with public key" timeout.
Cap to TEST_CEILING=65536 when the hard limit is RLIM_INFINITY or
exceeds the ceiling, keeping the existing soft==hard halving path for
normal bounded limits.
Also add the test to the macOS CI list, so the platform it was broken
on now covers it.
Changelog-None
An inline plugin's Plugin() object lives in the test process, and
Plugin() installs a PluginLogHandler on the root logger so that a
plugin author's logging calls reach lightningd. In the test process
that handler also forwards pyln's own machinery logs into the node's
log whenever the test-side logger emits at DEBUG. In particular
wait_for_logs() announces 'Waiting for [pattern]' with the pattern
embedded verbatim, so the announcement lands in the very log being
scanned and matches itself, silently reducing the wait to a no-op.
That let test_sendpay_notifications_nowaiter race its channel close
against the first payment (both payments then fail and the success
assertion sees an empty list); any literal-pattern wait_for_log on an
inline-plugin node is similarly voided under DEBUG logging.
Attach a filter to the inline plugin's log handler that only forwards
records originating outside the pyln packages, so author logging still
reaches the node log but pyln internals never do.
The new test covers both directions: an author log line is found by
wait_for_log, and a never-logged sentinel genuinely times out (it
matched instantly via the forwarded announcement before this fix).
The node directory embeds the test name, and this test's long name
pushes inline-plugin.sock past the AF_UNIX bind cap on some runs:
Linux's 108 bytes under liquid-regtest's longer network dir, and
Darwin's 104 with even the default path. Teach _inline_plugin() to
fall back to binding through a short symlink alias to the socket's
directory -- the same technique UnixSocket.connect already uses on
Darwin, aliasing the directory rather than the socket since bind
can't traverse a dangling final-component symlink. The socket file
still lands where the shim's cwd-relative connect expects it. The
test runs in the macOS smoke set too, so CI exercises the fallback on
the platform with the tighter cap.
Fixes: #9343
Changelog-None
The wallet registers its own scriptpubkey watches at node startup
since the bwatch series landed. The test anticipates them by
recording a total-count baseline first, but on a slow machine the
registration can land after the baseline is taken: an ASan CI run
caught a baseline of 0 and a final count of 105. A total-count
assertion races background registration no matter when the baseline
is read.
Count only the watches this test adds (their identifiers are
distinctive), which no background registration can perturb. The
test's per-watch assertions already work that way.
Fixes: #9360
Changelog-None
create_onionpacket returns NULL when the route's per-hop payloads
exceed the 1300-byte onion; send_payment passed the packet to
send_onion unchecked, and serialize_onionpacket dereferenced it,
killing lightningd with SIGSEGV. Observed in production on a
25-hop route submitted by a rebalancing plugin.
The sendonion path already checks this call and fails the command;
mirror it, and add a test.
Changelog-Fixed: JSON-RPC: `sendpay` with a route too long to fit the onion packet now fails cleanly instead of crashing lightningd.
The wallet no longer writes UTXO state to the legacy outputs table's
status/spend_height columns, so tests that peek at the database
directly must read our_outputs instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
Upgrade a pre-bwatch snapshot db and check the migration mirrored
outputs/transactions into our_outputs/our_txs, listfunds still reports
the old UTXOs, and the wallet can receive and withdraw new funds.
Co-authored-by: Cursor <cursoragent@cursor.com>
The wallet's live UTXO state now lives in our_outputs; point the raw-SQL
assertions at it (spent means spendheight IS NOT NULL, reserved means
reserved_til > 0).
Co-authored-by: Cursor <cursoragent@cursor.com>
d09d0112f ("pytest: handle v fast disconnect during
test_funding_external_wallet_corners()") wrapped the reconnect in
try/except to tolerate the "disconnected during connection" race, but
the assert checks the substring against err.error, which is the whole
error dict ({'code': 402, 'message': ...}). `in` on a dict tests its
keys, so the assert fails exactly when the race it is meant to
tolerate occurs.
Seen in CI, where connect raised code 402 and the handler itself then
asserted:
E assert 'disconnected during connection' in {'code': 402,
'message': 'disconnected during connection'}
Match against err.error['message'] instead, as the equivalent handlers
in test_plugin.py and test_misc.py already do.
Changelog-None
The test snapshots getrawmempool() and then calls getrawtransaction()
on each txid. In simple close both peers broadcast conflicting closer
txs: here l2's tx pays only 400sat fee (its entire 400sat balance goes
to fees since its own output is dust) while l1's pays 3375sat, so l1's
tx can RBF-replace l2's between the snapshot and the fetch. The
getrawtransaction() call on the replaced txid then fails with error -5
(No such mempool or blockchain transaction). A CI failure showed
exactly this ordering: l2 broadcast its closer tx, l1's higher-fee tx
replaced it 14ms later, and the test's per-txid fetch raced the
replacement.
Retry the whole snapshot-and-check loop when a txid vanishes
mid-iteration, keeping the single-output assertion hard. This is the
same class of mempool race fixed for test_simple_close_delay_broadcast
in 5ae0705f2 ("tests: fix flaky test_simple_close_delay_broadcast
mempool race").
Changelog-None
test_no_delay compares mean round-trip times with a 3-standard-error
margin. On loaded CI runners the trip-time distribution is heavy-tailed
enough that the margin can exceed the entire effect being measured: in
the #9329 failure the margin came to 45.3ms while the true Linux effect
is one delayed-ACK quantum (40ms - the docstring's ~200ms figure is
other platforms), so the assertion failed with the effect cleanly
present (saving 44.6ms).
The stall shifts the whole distribution by the quantum, so the median
difference detects it regardless of the noise tail. Compare medians,
requiring half the quantum on Linux; on platforms without a measurable
stall keep the not-slower sanity check with the same slack.
Fixes: #9329
Changelog-None
The test mines ~106 blocks (channel close, replacement channel
funding, then generate_block(99)) immediately before making three
payments, without waiting for the nodes to process those blocks.
Under valgrind the paying node can lag far behind bitcoind when the
payments start, so it builds onions whose cltv expiries the caught-up
peers reject. Two CI failures showed both shapes of the race:
In one run l1 was ~40 blocks behind (l2/l3 at block 210, l1 still
catching up), so l2 failed the forward: "Expiry cltv 181 too close to
current 210" (expiry_too_soon). Since the error came from the
invoice's route-hint channel, xpay disabled it and the payment failed
hard: "All 1 channels to the destination are disabled."
In the other run l1 was only a few blocks behind, so l3 failed the
final hop: "Expiry cltv too soon 211 < 210 + 5". xpay's
waitblockheight retry then succeeded, but the failed first attempt
left status 'failed' in the forwards row the test later asserts is
'settled'.
The test paid with pay until 7f6a33394 ("pytest: use xpay, not pay in
misc tests."); pay took its start height from bitcoind's headercount
and tracked the node's chainlag, so a lagging node still built onions
valid at the true chain tip. xpay uses its own notification-fed
blockheight plus one block of slack, which is why this only started
flaking recently.
Sync the nodes' blockheights before paying, as other tests do after
mining bursts.
Changelog-None
The Update examples in doc schemas CI job intermittently fails with the
l3->l4 direction of their channel flipping between disabled and enabled
in the regenerated listchannels example (channel_flags 3 vs 1, active
false vs true).
l4's database is deleted to generate the recoverchannel example, after
which l3 disables its side of the l3-l4 channel. That disabling
channel_update is emitted lazily (via an error reply or the gossip
refresh timer), so whether it lands in l3's gossip store before the
listchannels snapshot is a race; the committed example captured the
post-disable state. Wait for the disable explicitly, as the script
already does for the post-close listchannels races.
Fixes: #9327
Changelog-None
Changelog-Fixed: renepay: fix the computation of the CLTV for the first hop, it was double counting the current blockheight leading to too 900k blocks into the future.
Signed-off-by: Lagrang3 <lagrang3@protonmail.com>