#34864 coins: tighten cache entry state invariants
| Reviewability | Reviews | Agreement | Size | ||||
|---|---|---|---|---|---|---|---|
| #34864 coins: tighten cache entry state invariants | Ready | 0 (+1) | Strong | 97 + 60 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
This PR refactors `CCoinsCacheEntry` to derive an entry's dirty status directly from its presence in the dirty linked list rather than maintaining a redundant `DIRTY` bitflag. It eliminates standalone `FRESH` and spent `FRESH` states, asserts that all cursor entries during `BatchWrite()` are dirty, and updates `SpendCoin()` to return false without mutating caller output or emitting tracepoints on already-spent entries while marking it `[[nodiscard]]`.
Problem: `CCoinsCacheEntry` tracked dirty status both through linked-list pointers and an independent `DIRTY` bitflag, allowing invalid or unreachable states (such as fresh but not dirty, or spent fresh entries) to be represented in memory and test harnesses. In addition, `SpendCoin()` allowed repeated spends against already-spent cached entries without returning an error or leaving caller-provided `Coin` output buffers untouched.
Evidence of importance:
- PR description: 'The coins cache still exposed and tested states that production code should not create: standalone FRESH entries, non-dirty BatchWrite() cursor entries, and spent FRESH entries. That makes the representation harder to reason about, because CCoinsCacheEntry stores a separate DIRTY bit even though dirty entries are already tracked in the linked list.'
- Revives and splits earlier work from closed PRs #30673 and #33018, with commits co-authored by Andrew Toth.
Scope: Modifies core UTXO cache entry state representations in `src/coins.h` and validation callers in `src/txdb.cpp`, `src/validation.cpp`, and `src/txmempool.cpp`, alongside corresponding updates to coin cache unit and fuzz tests.
Risk of a mistake: high (consensus, validation). A logic error in UTXO cache dirty tracking, freshness handling, or spending invariants could lead to state desynchronization between memory and the coins database, causing consensus validation failures or chain forks.
Changed since the previous assessment: No substantial architectural changes; commit structure and approach remain the same as the previous version.
Reviewability
Ready
Nothing known that would invalidate a review now.
Waiting on (from the thread read):
- reviewer: review of rebased branch with SpendCoin tightening and cleanup 2026-09-30
- reviewer: review of rebase and recent SpendCoin/AI cleanup updates 2026-09-30
Agreement
Strong
substantive support, no open objection (optout21, ptrinh, optout21)
Thread notes: Added the missing question from andrewtoth (rc:3105652703), which was answered by l0rinc. The PR is waiting on reviewer follow-up after the author rebased and applied cleanups. / Added the previously unrecorded question rc:3105652703 from andrewtoth. Updated waiting_on to reviewer following the author's rebase and cleanup in c:5920884279.
Objections, suggestions, and questions:
| Reviewer | Type | Harm, request, or question | Status | Must be | Author replied | Quote |
|---|---|---|---|---|---|---|
| andrewtoth (member) | objection, maintenance | removing a relevant comment loses valuable context | fixed · fix pushed · author restored the comment above the new SetDirty call | answered | yes | 2026-04-18: 'Why remove this comment? It's still relevant and provides valuable context.' Status from: 2026-04-19: l0rinc: 'Restored above the new `SetDirty` call' |
| andrewtoth (member) | objection, interface | removing 'changed' from the log message conflates committing deleted outputs with committing only new outputs | fixed · fix pushed · author added 'changed' back to the log message | answered | yes | 2026-04-18: 'The `changed` adjective could be left here. Existing transaction outputs that are deleted are `changed`. The log now can be conflated with committing only new transactions outputs.' Status from: 2026-04-19: l0rinc: 'Added `changed` back' |
| andrewtoth (member) | question, scope | Why was this code touched here? | answered · author explained it was for symmetry with SetDirty | answered | yes | 2026-04-18: 'Why is this touched here?' Status from: 2026-04-19: l0rinc: 'Just for symmetry with `SetDirty`' |
| andrewtoth (member) | objection, correctness | SetDirty could unexpectedly clear freshness on an already fresh entry | fixed · fix pushed · author added an Assume check to prevent silently clearing FRESH | changed | yes | 2026-04-18: 'Why do we want to set a fresh entry to not-fresh? If an entry is fresh, it should stay fresh until cleaned or erased as before.' Status from: 2026-04-19: l0rinc: 'Added `Assume(!pair.second.m_fresh || fresh)` so the API can't silently clear FRESH, thanks' |
Support:
- andrewtoth (member) (Concept ACK, 2026-04-18): Thanks for picking this up. [not substantive]
- optout21 (contributor) (ACK, 2026-04-23) [both: AddFlags, BatchWrite, CCoinsCacheEntry, SetDirty, SetFresh]: Post-change the invalid states cannot be represented. This change is a code maintainability benefit, because any code change could potentially break the conventions. It also results in slightly simpler code.
- ptrinh (none) (2026-06-24) [both: BatchWrite, IsDirty, SetDirty, coins fuzz targets, coins_view, coinscache_sim]: Tested the coins fuzz targets on this branch with no crashes, OOMs, sanitizer errors, or hits on the new BatchWrite logic_error; confirmed invariant consistency.
- optout21 (contributor) (ACK, 2026-09-07) [read: coins view flags]: No issues. A net reduction in complexity around coins view flags
Participants:
- andrewtoth (member, objection, 5 statements 2026-04-18 to 2026-04-18): Concept ACKed, but raised objections on comment removal, log phrasing, and SetDirty freshness semantics, plus a question on scope; all addressed by author
- optout21 (contributor, support, 2 statements 2026-04-23 to 2026-09-07): ACKed and reACKed citing code maintainability benefits and elimination of invalid cache states
- ptrinh (none, support, 1 statements 2026-06-24 to 2026-06-24): tested coins fuzz targets and unit suites on the branch without issues and confirmed invariant consistency
Files
145 lines under test/bench/ci.
- src/coins.h +59/-57
- src/test/coins_tests.cpp +31/-49
- src/coins.cpp +29/-32
- src/test/fuzz/coinscache_sim.cpp +11/-22
- src/test/coinscachepair_tests.cpp +10/-7
- src/txdb.cpp +7/-10
- src/test/fuzz/coins_view.cpp +8/-7
- src/txmempool.cpp +1/-1
- src/validation.cpp +1/-1
Card
The short text the ranking pass compares across PRs.
This PR simplifies the consensus-critical UTXO cache by deriving dirty status directly from linked-list membership rather than a separate flag, making fresh-without-dirty states impossible to represent. It eliminates unreachable states in cache tests, asserts that all BatchWrite cursor entries are dirty, and throws on spent fresh entries. It also tightens SpendCoin to return false on already-spent coins without mutating caller buffers and enforces [[nodiscard]] across callers.
Data
thread read through 2026-09-30 (15 statements), head 6fd59bf706
labels: UTXO Db and Indexes
Categories
Validation
P3 · cleanup (ranking pass: Moved down: UTXO cache state hygiene with no active bug, grouped with the other invariant-tightening refactors)
Tightens UTXO cache invariants by making invalid internal states unrepresentable and preventing mutating re-spends. As reviewers noted, eliminating standalone FRESH entries and deriving dirty state from linked-list tracking yields 'a net reduction in complexity around coins view flags' and prevents potential convention breaks, but addresses internal invariant hygiene rather than an active crash or consensus failure.
Membership: Has the 'UTXO Db and Indexes' label and modifies core UTXO cache entry state management in `src/coins.h`, `src/coins.cpp`, and `src/txdb.cpp`.
Factors: security/stability 1, bug 0, performance 0, user value 0, leverage 1