#35744 coins: prevent DB resize from invalidating cursors

full analysis

https://github.com/bitcoin/bitcoin/pull/35744 · l0rinc · +194/-49 in 9 files, 8 commits · labels: UTXO Db and Indexes

Goal

  • Prevent node crashes during concurrent UTXO scans and AssumeUTXO cache rebalancing
  • Ensure UTXO database iterators remain valid when resizing the cache

This pull request prevents `CCoinsViewDB::ResizeCache()` from destroying active LevelDB iterators and causing an abort during AssumeUTXO cache rebalancing. It introduces a shared lock (`m_db_mutex`) held by active cursors and compaction while requiring exclusive locking for cache resizing. To support this safely, it extends Bitcoin Core's synchronization infrastructure with checked `SharedMutex` and `SharedLock` wrappers and updates `DEBUG_LOCKORDER` to handle non-LIFO lock release.

Problem: RPCs like `gettxoutsetinfo`, `scantxoutset`, and `dumptxoutset` retain LevelDB cursors after releasing `cs_main`. If AssumeUTXO cache rebalancing calls `ResizeCache()` concurrently, it replaces `m_db` and triggers a LevelDB assertion failure or abort. Furthermore, existing shared mutex usage bypassed lock order debugging and static thread safety analysis.

Category: Validation (#7 of 48)

P2 · bug fix

  • P2 because it prevents node aborts during concurrent UTXO scans and AssumeUTXO rebalancing
  • Keeps database iterators valid until destroyed so cache resizing cannot trigger crashes

Prevents a node abort in LevelDB when AssumeUTXO cache resizing races with active UTXO set scans. A crash during node operation due to concurrent RPC execution and chainstate sync is a clear stability issue.

Membership: Modifies CCoinsViewDB cursor management and cache resizing in src/txdb.cpp and src/txdb.h.

Factors: security/stability 2, bug 2, performance 0, user value 1, leverage 0

Category: Utilities (logging, arguments, libraries) (#6 of 66)

P2 · new feature

  • P2 because it adds shared mutex primitives with debug lockorder tracking to the codebase
  • Enables non-LIFO lock releases without corrupting debug lock tracking state

Brings shared locking into the project's lock-order tracking and Clang thread-safety analysis frameworks. It also fixes lock tracking corruption when shared locks outlive enclosing critical sections.

Membership: Extends synchronization primitives in src/sync.cpp and src/sync.h with SharedMutex, SharedLock, and non-LIFO lock release detection.

Factors: security/stability 2, bug 1, performance 0, user value 0, leverage 2

Reviewability: Ready

  • Ready for review
  • Cleanly addresses reviewer feedback with passing CI and unit tests

The code incorporates reviewer suggestions, cleanly passes CI, and has no outstanding questions or blocking conflicts.

Author status: active; addressed all reviewer design suggestions in updated commits

Resolved concerns:

  • Replacing an atomic counter approach with a shared mutex to avoid custom synchronization logic
  • Handling out-of-order (non-LIFO) lock releases in DEBUG_LOCKORDER when long-lived shared locks outlive outer locks
  • Ensuring AssertLockHeld(cs_main) is checked during ResizeCache

Agreement: Strong

  • Strong collaboration shaping the shared mutex and sync framework design (andrewtoth)
  • Suggested integrating shared locks into lockorder tracking with non-LIFO releases (andrewtoth)
  • Concept approval without stated reasons (sedited)

Strong: andrewtoth collaboratively guided the shared-locking design and co-authored commits, accompanied by a Concept ACK from sedited.

Reviewers have substantively shaped and supported the PR, with design suggestions integrated and no open objections remaining.

  • andrewtoth proposed the shared mutex design on 2026-07-18 and provided lock-checking diffs on 2026-07-26
  • sedited left a Concept ACK on 2026-09-08

Objections:

ReviewerKindHarmStatusBlockingAuthor repliedQuote
andrewtothapproachAtomic counter approach was non-standard compared to using a shared mutex for read/write synchronizationresolvednoyes2026-07-18: "I'm not sure about this approach with an atomic counter though. Would it make sense to instead have a shared mutex..."
Settled: 2026-07-25: l0rinc: "Thanks, took your suggestions, @andrewtoth. I reworked the fix around it..."

Support:

  • andrewtoth: Favored the shared mutex approach and provided code snippets to extend sync.cpp for non-LIFO lock checking
  • sedited: Concept ACK [not substantive]

Participants: andrewtoth (objection), sedited (support)

State derived from the lists: substantive support, no open objection (andrewtoth)

Review verdicts (DrahtBot): 0

Files

114 lines under test/bench/ci.

  • src/sync.h +51/-15
  • src/test/coins_tests.cpp +52/-3
  • src/test/sync_tests.cpp +48/-0
  • src/sync.cpp +17/-5
  • src/txdb.cpp +9/-7
  • src/txdb.h +7/-6
  • src/test/cuckoocache_tests.cpp +5/-6
  • src/script/sigcache.cpp +2/-4
  • src/script/sigcache.h +3/-3

Card

This PR fixes a bug where AssumeUTXO cache rebalancing via ResizeCache() can invalidate live LevelDB iterators held by gettxoutsetinfo or scantxoutset, causing an abort. It ensures cursors and compaction hold a shared lock while cache resizing requires exclusive access to the DB wrapper. To enable this safely, it adds checked SharedMutex wrappers and non-LIFO lock tracking to Bitcoin Core's synchronization utilities. Review is in a ready state with strong collaboration from andrewtoth and a Concept ACK from sedited.

Data

dossier JSON · extract JSON · model openrouter/google/gemini-3.8-flash, generated 2026-09-17T21:39, confidence high, input hash 44515057d5dabe6c