AI-generated analysisPublished automatically and not human-verified. Validated context appears in community notes below.
← Watch feed
Moderate 63 Bitcoin

Merge bitcoin/bitcoin#34931: validation: abort on DB unreadable coins instead of treating them as missing

Public commit record

What the developer wrote

Authored by merge-script

100/100 · Strong
Merge bitcoin/bitcoin#34931: validation: abort on DB unreadable coins instead of treating them as missing

75f64e50c67dce423efb31fd0a0ac9e1d3320739 test: exercise node abort on UTXO deserialization failure (furszy)
4652cd0d828a14c64b896d1c4d435231bc4d0c50 txdb: detect UTXO deserialization errors via CDBWrapper::TryRead() (furszy)
5dfbb91b6cc5d1e0e3e49dad1fe9dddc4b06dfba dbwrapper: add TryRead() to distinguish errors from valid outcomes (furszy)
f78834fac91698ddbcdc807e422fc1bd733a6454 test: add missing coverage for CDBWrapper::Read() errors (furszy)

Pull request description:

Early note: the majority of this PR consists of test coverage. The changes per se are small.

If a UTXO entry on disk can't be deserialized, the node currently treats it as if the coin
wouldn't exist instead of aborting with an error. A non-existing coin has a very specific
meaning for consensus: any block that spends it would be permanently rejected as invalid
(`BLOCK_FAILED_VALID`), silently forking the node from the rest of the network. This can't
currently be triggered in practice (details below), but it's still the wrong behavior.

The root cause is that `CDBWrapper::Read()` returns `false` for both missing entries and
deserialization failures, so `CCoinsViewDB::GetCoin()` has no way to tell them apart.
`CCoinsViewErrorCatcher` was built to catch database read errors and abort, but it never
fires during deserialization errors because `CDBWrapper::Read()` swallows the exception
before it can propagate. This [comment](https://github.com/bitcoin/bitcoin/blob/8a8edc8d8824464ac7ece958f099f9dfa4cf9d3d/src/coins.cpp#L398-L411) in `ExecuteBackedWrapper()` spells out the code
intent very clearly.

As mentioned initially, this can't happen in practice today. It would require either a bug
in the coin serialization path, or a memory corruption before the data reaches LevelDB
(at which point we have bigger problems). Random disk-level bit flips are caught earlier
by LevelDB's verification (`verify_checksums=true`, enabled by default), which already
propagates correctly as `DB_INTERNAL_ERROR`. Regardless, a db read issue should
never be silently misinterpreted as a consensus violation.

This PR adds `CDBWrapper::TryRead()`, which returns a `ReadStatus` that lets callers
discriminate between all possible outcomes. `CCoinsViewDB::GetCoin()` switches on the
result and throws on any error, letting `ExecuteBackedWrapper()` do what it was designed
to do. `CDBWrapper::Read()` becomes a thin wrapper over `TryRead()`, preserving backward
compatibility for all other callers (so we don't have to change non-consensus code here).
`PeekCoin()` is also covered, as it delegates to `CCoinsViewDB::GetCoin()` at the database
level.

The idea of the PR is to go slowly over the code changes, first commit locks-in the current
`CDBWrapper::Read()` behavior . The second adds `TryRead()` with tests for all four
status codes. The third is the `CCoinsViewDB::GetCoin()` fix. The fourth is a functional
that ensures the node aborts correctly instead of silently diverging.

Testing Notes:
Cherry-picking the functional test commit on master demonstrates the consensus split
when the coin entry fails to deserialize.

Extra Note:
`CDBIterator::GetValue()` has the same silent-swallow pattern. Not consensus-critical.
Should be addressed in a follow-up.

ACKs for top commit:
ajtowns:
reACK 75f64e50c67dce423efb31fd0a0ac9e1d3320739
sedited:
ACK 75f64e50c67dce423efb31fd0a0ac9e1d3320739
mzumsande:
Code Review ACK [75f64e5](https://github.com/bitcoin/bitcoin/commit/75f64e50c67dce423efb31fd0a0ac9e1d3320739)

Tree-SHA512: 51b0114ea443544a2f1fbb8e63be6e1dff94d6f287221d566dbc98d666784a2b4c486acfb87eea5392bc1d092fb6d6dc0ff6782bcdccdcf15939281c895e384d
✓ Specific, descriptive subject✓ Names a concrete action or component✓ Provides detailed explanatory context✓ Explains rationale or failure mode✓ Mentions testing or verification✓ Links an issue, advisory, or supporting reference✓ Names security-relevant behavior explicitly
The short version

What changed, and why it matters

This change fixes a bug where Bitcoin Core could silently treat a corrupted UTXO database entry as if the coin did not exist. Under the old behavior, if a coin on disk could not be deserialized, the node would act like the coin was spent, potentially marking valid blocks as permanently invalid and forking away from the rest of the network. The fix makes the node clearly abort with an error instead, so the operator knows something is wrong and can recover without permanent consensus divergence. The bug is not currently triggerable in practice by an attacker, but the behavior was wrong and dangerous.

Recommended action

This is a defensive correctness fix. Operators and downstream projects should upgrade to a release containing this commit. No immediate emergency response is warranted because the issue is not triggerable by remote attackers in practice, but node maintainers should ensure clean shutdown behavior is monitored and that UTXO database integrity is preserved.

Security signals we found

01

Consensus-critical silent failure: corrupted UTXO read treated as missing coin

02

Potential permanent chain divergence via BLOCK_FAILED_VALID

03

Fix adds explicit error propagation and clean shutdown on DB deserialization failure

04

Functional test demonstrates abort instead of silent divergence

05

Not currently exploitable in practice per PR description

Risk score

Why this scored 63/100

Our methodology →
Potential impact 22/30
Exploitability 5/25
Stealth signal 12/15
Affected reach 10/15
Confidence 9/10
Evidence quality 5/5
Human-validated context

Community notes

Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.

No validated notes yet.

The AI analysis stands alone for now. Submit a note if you can add evidence or important context.