Skip to content

connectd: swallow channel-scoped error instead of queuing it - #9436

Open
vincenzopalazzo wants to merge 2 commits into
ElementsProject:masterfrom
vincenzopalazzo:connectd-forward-channel-error
Open

connectd: swallow channel-scoped error instead of queuing it#9436
vincenzopalazzo wants to merge 2 commits into
ElementsProject:masterfrom
vincenzopalazzo:connectd-forward-channel-error

Conversation

@vincenzopalazzo

@vincenzopalazzo vincenzopalazzo commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

BOLT #1 says upon receiving a channel-scoped error the node MUST fail that channel. connectd only told lightningd about WIRE_ERROR when find_subd failed; if a subd existed the error was queued to it. channeld abort()s if it ever sees WIRE_ERROR ("swallowed by connectd"), and a dying channeld never reads the queue, so the channel stayed CHANNELD_NORMAL.

Always swallow a channel-scoped WIRE_ERROR in connectd: tell lightningd via CONNECTD_PEER_SPOKE and do not enqueue, whether or not a subd exists. handle_peer_spoke already permanently fails the channel. Do not create a dummy subd just to carry the error.

Two commits, failing test then fix:

  1. tests: reproduce lost channel error while channeld is exiting (@pytest.mark.xfail(strict=True))
  2. connectd: swallow channel-scoped error instead of queuing it (removes the xfail)

Local test_channel_error_not_lost_while_channeld_exits:

  • test commit / unfixed connectd: FAILED in 35.89s (Timeout while waiting for AWAITING_UNILATERAL)
  • fix commit: PASSED in 19.42s

Thanks to Leo Nash (@tankyleo) for the report.

Fixes: #9424
Reported-by: Leo Nash (@tankyleo)
Changelog-Fixed: Protocol: fail the channel on a peer error even if channeld is exiting.

BOLT ElementsProject#1 says upon receiving a channel-scoped error the node MUST
fail that channel.  connectd only told lightningd about WIRE_ERROR
when find_subd failed; if a dying channeld still existed the error
was queued and never read, so the channel stayed CHANNELD_NORMAL.

The test rolls l2's db back after a payment, freezes l1's channeld
after reestablish, then lets l2 send "Awaiting unilateral close".
It fails until the next commit.

Reproduces: ElementsProject#9424
Reported-by: Leo Nash (@tankyleo)
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
@vincenzopalazzo
vincenzopalazzo force-pushed the connectd-forward-channel-error branch from 5e70d0d to cf9378e Compare August 18, 2026 11:14
channeld abort()s on WIRE_ERROR ("swallowed by connectd"), but we
only told lightningd when find_subd failed.  If a subd existed we
enqueued the error; a dying channeld never read it, and a live
one would abort, so the channel stayed up.

Always tell lightningd via CONNECTD_PEER_SPOKE and do not enqueue.
handle_peer_spoke already permanently fails the channel.

Fixes: ElementsProject#9424
Changelog-Fixed: Protocol: fail the channel on a peer error even if channeld is exiting.
Reported-by: Leo Nash (@tankyleo)
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
@vincenzopalazzo
vincenzopalazzo force-pushed the connectd-forward-channel-error branch from cf9378e to 996dc6e Compare August 18, 2026 11:38
@vincenzopalazzo vincenzopalazzo changed the title connectd: forward channel-scoped error even if subd exists connectd: swallow channel-scoped error instead of queuing it Aug 18, 2026
@vincenzopalazzo
vincenzopalazzo requested review from Andezion and nGoline and removed request for Andezion August 18, 2026 11:58
Comment thread tests/test_connection.py
l1.pay(l2, 200000000)


@unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "sqlite3-specific DB rollback")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests/test_closing.py has three pre-existing tests (test_onchain_reestablish_reply at line 4632, and two more around lines 4661 and 4753) that reconnect a peer to an already-closed channel and then call daemon.wait_for_log("peer_in WIRE_ERROR"). That log line is produced by peer_read() in common/peer_io.c at lines 18-27 via status_peer_io(), which only runs inside the subd (channeld/closingd), when it reads the message off its own socketpair to connectd. But with this fix, connectd/multiplex.c new block runs before find_subd() and returns immediately - the message is never written to subd->outq, so the subd peer_read() never sees it, and "peer_in WIRE_ERROR" is never logged again for a channel-scoped error. The new connectd branch also doesnt call status_peer_io() itself, so this specific log line disappears entirely, not just for one path. these three tests use that log line as their synchronization point before asserting the channel state. If the line never appears, wait_for_log will time out and the tests will fail. Maybe we need to run these three tests against this branch to confirm. If they do fail, either add an equivalent debug log call in the new connectd branch (status_peer_io(LOG_IO_IN, &peer->id, decrypted)) before forwarding, so the same observability is kept, and update the tests to match the new log source, or drop the log-based wait in these tests in favor of the state check that already follows it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Channel-scoped error can be lost while channeld exits

2 participants