#34132 coins, dbwrapper: remove error catcher, make point-read failures fatal
| Reviewability | Reviews | Agreement | Size | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| #34132 coins, dbwrapper: remove error catcher, make point-read failures fatal | Ready | 0 (+2) | Blocked | 139 + 153 tests | ||||||
|
|
|
|
| ||||||
Reviewability: ReadyStalePaused Agreement: StrongPositivePositive w/ caveatsNeutralMildDisputedBlockedCrickets Reviews: current code-review ACKs, then (+stale ACKs) and -NACKs; greener = more ACKs. Size: lines added or modified outside tests, then in tests; greener = smaller. | ||||||||||
Goal
Centralizes LevelDB point-read and deserialization error handling directly in CDBWrapper by logging errors, invoking a registered fatal-error notification callback, and aborting. Removes the CCoinsViewErrorCatcher view wrapper layer, wiring CCoinsViewCache directly to CCoinsViewDB. Confines HaveCoin() to CCoinsViewCache and marks coin view lookup methods noexcept.
Problem: CDBWrapper::Read() previously returned false on deserialization failures—making corrupted data appear as non-existent keys—while LevelDB point-read failures threw exceptions without invoking fatal error notifications. Consequently, corrupted index records could fail individual RPC calls while allowing the node to continue running with damaged state, and chainstate required a dedicated CCoinsViewErrorCatcher wrapper layer solely to intercept read errors.
Evidence of importance:
- PR description: 'A corrupt txindex point read can consequently fail one RPC while the node continues running.'
- Commit c916a71961: 'When a txindex point read encounters a corrupt LevelDB table, the RPC reports the error and the node continues running. CDBWrapper::Read() throws on a LevelDB failure without running the read-error callback and reports a corrupt value as a missing key.'
Scope: Changes error-handling semantics across CDBWrapper, chainstate UTXO database lookups, and all secondary indexes (txindex, coinstatsindex, blockfilterindex, and txospenderindex).
Risk of a mistake: medium (validation). A flaw in database read error classification could trigger unexpected process aborts during normal coin or index lookups, halting the node.
Reviewability
Ready
Nothing known that would invalidate a review now.
Waiting on (from the thread read):
- author: address furszy's objection that aborting in db read breaks startup recovery and kernel library usage 2026-10-29
Agreement
Blocked
objection that needs a change, nobody has replied (ajtowns, furszy)
Thread notes: Added furszy to participants with an objection regarding fatal DB read aborts breaking startup recovery and killing kernel library hosts. Ignored the inline style nit rc:4401858796.
Objections, suggestions, and questions:
| Reviewer | Type | Harm, request, or question | Status | Must be | Author replied | Quote |
|---|---|---|---|---|---|---|
| andrewtoth (member) | question, correctness | Why does the coinsdb read error test fail without IPC on 32-bit systems? | answered · author investigated and explained LevelDB disables mmap on 32-bit architectures, causing stale block cache reads | answered | yes | 2026-01-04: 'Do we know why?' Status from: 2026-01-11: l0rinc: 'The trick was that on 64-bit systems, LevelDB uses mmap which reflects file changes immediately - this is what we we're ' |
| andrewtoth (member) | suggestion, correctness | Add verification, such as asserting a debug log, that the read error callback is executed in the functional test. | accepted · andrewtoth withdrew the suggestion | answered | yes | 2026-01-11: 'Is there a way to verify that this callback is still being executed in the functional test? Maybe add a debug log that we can assert?' Status from: 2026-01-11: andrewtoth: 'Right nvm, LGTM.' |
| maflcko (member) | suggestion, approach | Keep ExecuteBackedWrapper and move it to txdb.cpp instead of removing it. | answered · author declined, preferring simple local try-catch and noting HaveCoin removal | answered | yes | 2026-01-13: 'I think the ExecuteBackedWrapper can be kept. It seems applicable if it is moved to txdb.cpp as well?' Status from: 2026-01-24: l0rinc: 'It's a simple try-catch, I don't see the point in extending the call stack even more, we don't do that in other cases an' |
| andrewtoth (member) | suggestion, approach | Keep an optimized existence check on the database backing view that avoids copying, caching, or deserialization. | answered · author explained Exists still performs a DB Get and folded #34320 keeping HaveCoin only on CCoinsViewCache | answered | yes | 2026-01-24: 'I think having an optimized existence check that doesn't make copies, cache the value or deserialize after disk lookup makes sense to keep.' Status from: 2026-08-11: l0rinc: 'following @optout21's suggestion there, HaveCoin() now remains only on CCoinsViewCache' |
| maflcko (member) | objection, safety | Swallowing deserialization exceptions or catching them without aborting leaves the program continuing despite storage corruption or severe logic bugs. | fixed · fix pushed · CDBWrapper::Read() was changed to treat deserialization failures as fatal via FatalReadError() | changed | yes | 2026-02-02: 'The program continues, even though there is a clear storage corruption or severe logic bug' Status from: 2026-02-11: l0rinc: 'CDBWrapper::Read() now catches deserialization exceptions and calls FatalReadError() which does LogError -> m_read_error' |
| ajtowns (member) | objection, approach | Aborting the entire node on optional index corruption takes down operating nodes for non-essential failures. | contested · ajtowns pushed back against taking down operating nodes on optional index corruption; author maintained fatal behavior in c:5643408398 and proposed IRC discussion | changed | yes | 2026-08-24: 'doing that on a corrupt index seems a bit more questionable -- if the txindex or blockfilter or coinstats indexes fail, I could see it being better' Status from: 2026-08-24: ajtowns: 'If I'm running a node in order to receive funds via lightning or btcpayserver and enabled an index because I thought it ' |
| ajtowns (member) | objection, maintenance | The callback chain to the GUI message box before abort is convoluted and difficult to trace thread safety for across threads and locks. | open | changed | yes | 2026-08-24: 'Tracing through callback logic safety ("It must be safe to call from any thread, with or without locks held") isn't very straightforward, so it would be nice to not have to do that.' |
| furszy (member) | objection, correctness | Aborting abruptly inside DB read functions breaks startup automatic recovery paths and kills host applications using the kernel library during init. | open | changed | no | 2026-10-29: 'Where a chainstate read failure triggers an automatic recovery process (which should be broken on this branch).' |
Support:
- andrewtoth (member) (Concept ACK, 2026-01-04) [read]: I think getting rid of this indirection is great. It reduces a lot of complexity when thinking about the stack of views.
- sedited (member) (Concept ACK, 2026-01-05): Concept ACK [not substantive]
- andrewtoth (member) (ACK, 2026-01-11): ACK 4ae1f793d6718edf7a94fb032a2a7423105e85bd [not substantive]
- optout21 (contributor) (ACK, 2026-09-04) [both]: Code review, local unit tests OK! [not substantive]
- optout21 (contributor) (ACK, 2026-09-14): reACK 6c07101f28ade1a3023d4fe15042ce6b8c97ffa8 [not substantive]
Participants:
- andrewtoth (member, support, 12 statements 2026-01-04 to 2026-01-29): Concept ACK and ACKed an earlier commit; discussed HaveCoin implementation details and functional test verification.
- sedited (member, support, 1 statements 2026-01-05 to 2026-01-05): Concept ACK.
- maflcko (member, objection, 17 statements 2026-01-06 to 2026-02-10): Objected to continuing execution on storage corruption or deserialization errors, advocating wholesale fatal aborts.
- ajtowns (member, objection, 3 statements 2026-08-24 to 2026-08-25): Objected to aborting the entire node on optional index corruption and questioned the convoluted callback architecture.
- optout21 (contributor, support, 2 statements 2026-09-04 to 2026-09-14): ACK and reACK after testing and code review.
- furszy (member, objection, 2 statements 2026-10-28 to 2026-10-29): Objected to aborting inside DB read functions, noting it breaks startup recovery paths and abruptly terminates kernel library hosts.
Files
255 lines under test/bench/ci.
- src/dbwrapper.h +26/-73
- src/test/dbwrapper_tests.cpp +13/-65
- test/functional/feature_coinsdb_read_error.py +77/-0
- src/test/coins_tests.cpp +50/-12
- src/coins.h +16/-45
- src/coins.cpp +9/-39
- src/txdb.cpp +6/-25
- src/init.cpp +15/-12
- src/dbwrapper.cpp +10/-15
- src/index/txindex.cpp +12/-9
- src/index/blockfilterindex.cpp +6/-13
- src/index/coinstatsindex.cpp +4/-12
- src/validation.h +2/-13
- src/test/fuzz/coins_view.cpp +3/-11
- src/test/fuzz/dbwrapper.cpp +1/-11
- src/index/blockfilterindex.h +4/-2
- src/validation.cpp +3/-3
- src/index/base.h +4/-1
- src/index/txospenderindex.cpp +3/-2
- src/txdb.h +2/-3
- test/functional/feature_init.py +5/-0
- src/index/base.cpp +3/-1
- src/index/coinstatsindex.h +3/-1
- src/index/txindex.h +3/-1
- src/index/txospenderindex.h +3/-1
- src/node/chainstate.cpp +0/-4
- test/functional/feature_utxo_abort_on_error.py +2/-2
- src/kernel/chainstatemanager_opts.h +3/-0
- src/node/chainstate.h +0/-2
- src/test/fuzz/coinscache_sim.cpp +1/-1
- src/txmempool.cpp +1/-1
- src/txmempool.h +1/-1
- test/functional/test_runner.py +1/-0
Card
The short text the ranking pass compares across PRs.
Centralizes LevelDB point-read and deserialization error handling in CDBWrapper, logging the error, invoking a fatal shutdown callback, and aborting rather than returning false or propagating exceptions. This eliminates the CCoinsViewErrorCatcher view layer, restricts HaveCoin() to the coins cache view, and marks coin view lookups noexcept. It prevents corrupted index reads from failing isolated RPCs while leaving a damaged node running, and stops corrupted entries from masquerading as missing keys.
Data
thread read through 2026-10-29 (75 statements), head 846b0e83e1
labels: UTXO Db and Indexes
Categories
Indexes
P2 · bug fix
Prevents secondary indexes from silently treating corrupted database records as non-existent keys. As the author notes, previously 'a corrupt txindex point read can consequently fail one RPC while the node continues running' with damaged state.
Membership: Touches src/index/ base, txindex, blockfilterindex, coinstatsindex, and txospenderindex files to alter error handling on corrupt reads.
Factors: security/stability 2, bug 2, performance 0, user value 0, leverage 1
Validation
P2 · bug fix
Fixes a chainstate integrity hazard where LevelDB deserialization failures were reported as missing keys instead of fatal errors. It also removes the CCoinsViewErrorCatcher wrapper layer, eliminating unnecessary indirection across the coins view stack.
Membership: Has the 'UTXO Db and Indexes' label and modifies src/coins.*, src/txdb.*, src/dbwrapper.*, and src/node/chainstate.*.
Factors: security/stability 2, bug 2, performance 0, user value 0, leverage 1