Skip to content

Potential lock order inversion in mempool rescan (cs_wallet → cs_main vs cs_main → cs_wallet) #7226

@thepastaclaw

Description

@thepastaclaw

Summary

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.

Lock Order Analysis

Path 1 — Wallet rescan (src/wallet/wallet.cpp:2014):

WITH_LOCK(cs_wallet, chain().requestMempoolTransactions(*this));

requestMempoolTransactions takes LOCK2(cs_main, mempool->cs) and then calls CWallet::transactionAddedToMempool which does LOCK(cs_wallet) (reentrant on same thread).

Lock order: cs_wallet → cs_main

Path 2 — Mempool acceptance (src/validation.cpp:1185):

GetMainSignals().TransactionAddedToMempool(...)

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::transactionAddedToMempoolLOCK(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:

-        WITH_LOCK(cs_wallet, chain().requestMempoolTransactions(*this));
+        chain().requestMempoolTransactions(*this);

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.

Introduced By

/cc @UdjinM6

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions