Bug 103

An IPC client's notification handler returning an error aborts the node

Status
Open, fix under review
Symptom
Crash; worst effect seen: crash
Where seen
Branch-build
First reported
2026-10-06 (first report)
Fixed
Not yet
Fix
bitcoin#36520
In released binaries
No
Defect ever on master
Yes

Summary

A program that subscribes to chain notifications over the Chain IPC interface could crash bitcoin-node by returning an error from a notification handler such as updatedBlockTip. The error comes back to the node as an ipc::Exception on the thread that delivers validation notifications, nothing catches it there, and the process terminates. A disconnect while a notification is being delivered has the same effect. Only the open PR bitcoin#29409, which exposes Chain over IPC, makes this reachable, so master and releases are not affected. The fix is in bitcoin#36520, not yet merged.

What happened

Root cause

NotificationsProxy in src/node/interfaces.cpp adapts a Chain::Notifications object to the node's CValidationInterface. Its methods run on the validation interface scheduler thread and call the handler directly. When the handler lives in another process, a remote error or a disconnect during the call is thrown on the node side as an ipc::Exception. Neither NotificationsProxy nor the scheduler catches it, so it escapes the thread and the node terminates. The code was written for the wallet's in-process handlers, which are not expected to throw.

Fix

No fix is merged yet. In bitcoin#36520, the commit b67d871f95fa, by ryanofsky and written with Claude, sends every notification through a helper that catches ipc::Exception and logs "Chain notification ... failed". Other exceptions are still not caught, since an in-process handler throwing indicates a bug. The handler stays registered after an error: as ryanofsky replied, a client that disconnects loses its handler along with the connection, and a client that fails one notification may still want the others. A new throwingNotifications unit test checks that later notifications are still delivered after a handler throws.

Next steps

Timeline

Lessons

What would have prevented this bug, found it sooner, or made it easier to debug. The patterns page groups these lessons across all bugs.

Pattern Error paths that skip cleanup or completion: Assuming a remote call cannot fail

  • Prevented by a design rule
  • Earliest detector a test with a non-C++ client (did not exist then)
  • Diagnosis easy
  • Fix design moderate
  • Debugging aids logging
  • Rust no

Would Rust have prevented it? No. An error from a remote handler propagating out of a scheduler thread is error handling, not memory or thread safety; Rust would make the Result visible at the call site, but a .unwrap() or ? into a thread that panics on error ends the same way. More on Rust

Could it have been prevented?

Yes, with the rule that every call into another process can fail, and that the code making it must decide what a failure means. NotificationsProxy calls Chain::Notifications methods from the validation scheduler thread with no error handling, because the wallet's in-process handlers do not throw. Once bitcoin#29409 lets another process implement those handlers, each call can throw ipc::Exception on a remote error or a disconnect. The fix in bitcoin#36520 applies the rule at the one place all notifications pass through. The same assumption caused earlier crashes on the same branch, in a destroy call made from a destructor and in ChainClient::stop() at shutdown, so the rule was known; it had not been applied to the notification path.

Could it have been found sooner?

Yes, by a test with a notification handler that fails, which the fix adds as a unit test and the report supplied as a functional test. It was found by xyzconstant while writing a Rust client that returns errors from handlers, a behaviour the wallet never has.

What made it hard to debug?

Little: the log shows the remote error text, the exception type and the thread name ("bitcoin in scheduler") before the abort, which points straight at the notification path.

Lesson

Code that delivers callbacks to plug-ins in other processes must treat each callback as a call that can fail, and contain that failure on the delivering thread, because an error from one subscriber should never stop the publisher.

Notes

Classification

The survey's classification of this bug. The explorer charts these fields across all bugs; the method page explains them.

Field Value
Symptom crash
Worst effect observed crash
Seen in branch-build
Scope production
Trigger other (other)
Phase steady-state
Side server
Component bitcoin-ipc
Root cause kind unhandled-exception
Fix kind local-fix
Detection manual-test
Needs a peer that is not libmultiprocess C++ no
Needed a sanitizer to see no
Defect ever on master yes
Released no
Confidence high