You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Potential lock order inversion in mempool rescan (cs_wallet → cs_main vs cs_main → cs_wallet) #7226
The mempool rescan code introduced by bitcoin#25351 (commit f89f8a1) calls requestMempoolTransactions while holding cs_wallet, creating a cs_wallet → cs_main lock ordering that conflicts with the existing cs_main → cs_wallet ordering in the mempool acceptance path.
requestMempoolTransactions takes LOCK2(cs_main, mempool->cs) and then calls CWallet::transactionAddedToMempool which does LOCK(cs_wallet) (reentrant on same thread).
Called while holding cs_main + mempool->cs. The signal is dispatched asynchronously via ENQUEUE_AND_LOG_EVENT to the scheduler thread. On the scheduler thread, NotificationsProxy::TransactionAddedToMempool calls CWallet::transactionAddedToMempool → LOCK(cs_wallet).
Lock order: cs_main → (async) → cs_wallet
Practical Impact
The actual deadlock risk is very low because the cs_main → cs_wallet path goes through the async validation interface scheduler — cs_main is released before the scheduler thread acquires cs_wallet. A real deadlock would require a synchronous path holding cs_main while trying to acquire cs_wallet, which doesn't currently exist.
However, the lock order debug assertions (-DDEBUG_LOCKORDER) would flag this as an inconsistency.
Note
Bitcoin Core still has the identical pattern on current master (wallet.cpp L2007). The fix would be to remove the outer WITH_LOCK(cs_wallet, ...) wrapper since transactionAddedToMempool acquires cs_wallet internally per-transaction:
Additionally, Dash-specific code in coinjoin/util.cpp has two instances of LOCK2(m_wallet.cs_wallet, ::cs_main) (lines 290, 334), which establishes the same cs_wallet → cs_main ordering and could interact with any future synchronous cs_main → cs_wallet path.
Closing as invalid: after re-reading the callback path and the DEBUG_LOCKORDER implementation, there is no actual cs_main -> cs_wallet vs cs_wallet -> cs_main inversion here. The async validation path drops cs_main before the wallet lock, and the synchronous callback only re-locks cs_wallet recursively on the same thread.
Activity
knst commentedon Apr 28, 2026
(async) in the gap mean that cs_main is not more locked by the time when cs_wallet will be locked.
The order cs_wallet -> cs_main is intentional and designed by.
You can see by
src/wallet/test/util.cpp: LOCK2(wallet->cs_wallet, ::cs_main);@thepastaclaw there's no issue, seems so.
thepastaclaw commentedon Apr 28, 2026
Closing as invalid: after re-reading the callback path and the DEBUG_LOCKORDER implementation, there is no actual
cs_main -> cs_walletvscs_wallet -> cs_maininversion here. The async validation path dropscs_mainbefore the wallet lock, and the synchronous callback only re-lockscs_walletrecursively on the same thread.