Problem
The coins cache implementation still carries logic and representation for states that production code does not create, especially standalone fresh entries and BatchWrite() inputs that are not dirty.
The unit tests and fuzz harnesses were also constructing those states directly.
That reduces confidence in the results: instead of checking realistic cache behavior, parts of the test suite are checking behavior around impossible states. It also becomes a recurring source of test-only failures whenever production invariants are tightened.
Fix
This series first makes the production invariant explicit by setting freshness through the same method that marks entries dirty. That makes the dependency clear at the call sites instead of relying on a separate freshness step.
With that in place, the series updates the tests so they stop constructing fresh-only states before the stricter BatchWrite() checks land.
It then asserts that every cache entry reaching BatchWrite() is dirty, rejects spent-and-fresh BatchWrite() entries as logic errors, removes the bare freshness helper, and simplifies the internal representation so dirtiness is derived from linked-list membership instead of a stored flag bit.
The final step makes the dirty-marking API take the resulting freshness explicitly. That avoids callers accidentally preserving an old fresh state when they mean to clear it.
Notes
This revives Andrew’s earlier work in #30673 and #33018.
A spent-and-fresh entry is not just unusual, it is inconsistent with how the cache works.
If a fresh entry is spent, it should be erased locally because the parent view never knew it existed.
Writing it upward as spent is therefore a logic error, and the series makes that boundary explicit in BatchWrite().
Another subtle point is that making freshness explicit changes the contract of the dirty-marking helper. The caller must pass the resulting freshness of the entry, not just whether the current operation introduced freshness. That matters in paths where an entry may already be fresh before the current update.
Finally, once fresh-only states are removed from tests, dirtiness can be derived from linked-list membership without losing information. That lets the cache entry store only whether an entry is fresh, while the linked list itself becomes the source of truth for whether an entry is dirty.