Skip to content

Conversation

ajtowns
Copy link
Contributor

@ajtowns ajtowns commented May 11, 2022

This changes AssertLockNotHeld so that it is annotated with the negative capability for the mutex it refers to. clang applies negative capabilities recursively, so this helps avoid forgetting to annotate functions.

Note that this can't reasonably be used for globals, because clang would require every function to be annotated with EXCLUSIVE_LOCKS_REQUIRED(!g_mutex) for each global mutex. At present, the only global mutexes that use AssertLockNotHeld are RecursiveMutex so we treat that as an exception in order to avoid having to add an excessive number of negative annotations.

@ajtowns
Copy link
Contributor Author

ajtowns commented May 11, 2022

cc @vasild @w0xlt @jonatack @hebasto

This is the first two commits from #24931 so we can hopefully make some progress..

@DrahtBot
Copy link
Contributor

DrahtBot commented May 12, 2022

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #24970 (net processing: Move cleanSubVer, fPreferredDownload and nLocalHostNonce to Peer by jnewbery)
  • #24474 (Additional thread safety annotations for CNode/Peer by ajtowns)
  • #24356 (refactor: replace CConnman::SocketEvents() with mockable Sock::WaitMany() by vasild)
  • #24230 (indexes: Stop using node internal types and locking cs_main, improve sync logic by ryanofsky)
  • #24170 (p2p, rpc: Manual block-relay-only connections with addnode by mzumsande)
  • #24122 (refactor: replace RecursiveMutex cs_vProcessMsg with Mutex (and rename) by theStack)
  • #21878 (Make all networking code mockable by vasild)
  • #21527 (net_processing: lock clean up by ajtowns)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

Copy link
Contributor

@vasild vasild left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 436ce02

Looks like replacing LOCKS_EXCLUDED(::cs_main) with EXCLUSIVE_LOCKS_REQUIRED(!::cs_main) or adding the latter where missing is not too many changes and is in line with the direction of this PR. Consider the diff below, then we don't need to distinguish between Mutex and RecursiveMutex in AssertLockNotHeld().

diff
diff --git i/src/index/base.h w/src/index/base.h
index a8f6a18c8d..989cbcf824 100644
--- i/src/index/base.h
+++ w/src/index/base.h
@@ -115,13 +115,13 @@ public:
 
     /// Blocks the current thread until the index is caught up to the current
     /// state of the block chain. This only blocks if the index has gotten in
     /// sync once and only needs to process blocks in the ValidationInterface
     /// queue. If the index is catching up from far behind, this method does
     /// not block and immediately returns false.
-    bool BlockUntilSyncedToCurrentChain() const LOCKS_EXCLUDED(::cs_main);
+    bool BlockUntilSyncedToCurrentChain() const EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
 
     void Interrupt();
 
     /// Start initializes the sync state and registers the instance as a
     /// ValidationInterface so that it stays in sync with blockchain updates.
     [[nodiscard]] bool Start(CChainState& active_chainstate);
diff --git i/src/net_processing.cpp w/src/net_processing.cpp
index 46a5e54e32..d436b63d9b 100644
--- i/src/net_processing.cpp
+++ w/src/net_processing.cpp
@@ -451,13 +451,13 @@ public:
     void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) override;
 
     /** Implement NetEventsInterface */
     void InitializeNode(CNode* pnode) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
     void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
     bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override
-        EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex);
+        EXCLUSIVE_LOCKS_REQUIRED(!::cs_main, !m_peer_mutex, !m_recent_confirmed_transactions_mutex);
     bool SendMessages(CNode* pto) override EXCLUSIVE_LOCKS_REQUIRED(pto->cs_sendProcessing)
         EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex);
 
     /** Implement PeerManager */
     void StartScheduledTasks(CScheduler& scheduler) override;
     void CheckForStaleTipAndEvictPeers() override;
@@ -467,13 +467,13 @@ public:
     void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
     void RelayTransaction(const uint256& txid, const uint256& wtxid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
     void SetBestHeight(int height) override { m_best_height = height; };
     void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
     void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv,
                         const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override
-        EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex);
+        EXCLUSIVE_LOCKS_REQUIRED(!::cs_main, !m_peer_mutex, !m_recent_confirmed_transactions_mutex);
     void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) override;
 
 private:
     /** Consider evicting an outbound peer based on the amount of time they've been behind our tip */
     void ConsiderEviction(CNode& pto, std::chrono::seconds time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
@@ -732,16 +732,16 @@ private:
     /** When our tip was last updated. */
     std::atomic<std::chrono::seconds> m_last_tip_update{0s};
 
     /** Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed). */
     CTransactionRef FindTxForGetData(const CNode& peer, const GenTxid& gtxid, const std::chrono::seconds mempool_req, const std::chrono::seconds now) LOCKS_EXCLUDED(cs_main);
 
-    void ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic<bool>& interruptMsgProc) EXCLUSIVE_LOCKS_REQUIRED(peer.m_getdata_requests_mutex) LOCKS_EXCLUDED(::cs_main);
+    void ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic<bool>& interruptMsgProc) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main, peer.m_getdata_requests_mutex);
 
     /** Process a new block. Perform any post-processing housekeeping */
-    void ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing);
+    void ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
 
     /** Relay map (txid or wtxid -> CTransactionRef) */
     typedef std::map<uint256, CTransactionRef> MapRelay;
     MapRelay mapRelay GUARDED_BY(cs_main);
     /** Expiration-time ordered list of (expire time, relay map entry) pairs. */
     std::deque<std::pair<std::chrono::microseconds, MapRelay::iterator>> g_relay_expiration GUARDED_BY(cs_main);
diff --git i/src/qt/test/wallettests.cpp w/src/qt/test/wallettests.cpp
index c4cd0f4cd1..5624125121 100644
--- i/src/qt/test/wallettests.cpp
+++ w/src/qt/test/wallettests.cpp
@@ -139,13 +139,13 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st
 //
 // This also requires overriding the default minimal Qt platform:
 //
 //     QT_QPA_PLATFORM=xcb     src/qt/test/test_bitcoin-qt  # Linux
 //     QT_QPA_PLATFORM=windows src/qt/test/test_bitcoin-qt  # Windows
 //     QT_QPA_PLATFORM=cocoa   src/qt/test/test_bitcoin-qt  # macOS
-void TestGUI(interfaces::Node& node)
+void TestGUI(interfaces::Node& node) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     // Set up wallet and chain with 105 blocks (5 mature blocks for spending).
     TestChain100Setup test;
     for (int i = 0; i < 5; ++i) {
         test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
     }
diff --git i/src/qt/test/wallettests.h w/src/qt/test/wallettests.h
index 6044bedb1d..a819ad5127 100644
--- i/src/qt/test/wallettests.h
+++ w/src/qt/test/wallettests.h
@@ -2,12 +2,15 @@
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 #ifndef BITCOIN_QT_TEST_WALLETTESTS_H
 #define BITCOIN_QT_TEST_WALLETTESTS_H
 
+#include <chain.h>
+#include <sync.h>
+
 #include <QObject>
 #include <QTest>
 
 namespace interfaces {
 class Node;
 } // namespace interfaces
@@ -18,10 +21,10 @@ class WalletTests : public QObject
     explicit WalletTests(interfaces::Node& node) : m_node(node) {}
     interfaces::Node& m_node;
 
     Q_OBJECT
 
 private Q_SLOTS:
-    void walletTests();
+    void walletTests() EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
 };
 
 #endif // BITCOIN_QT_TEST_WALLETTESTS_H
diff --git i/src/rest.cpp w/src/rest.cpp
index 22b5d2e074..f26d4e1d3b 100644
--- i/src/rest.cpp
+++ w/src/rest.cpp
@@ -635,12 +635,13 @@ static bool rest_mempool_contents(const std::any& context, HTTPRequest* req, con
         return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
     }
     }
 }
 
 static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
+    EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     if (!CheckWarmup(req))
         return false;
     std::string hashStr;
     const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart);
 
diff --git i/src/rpc/blockchain.cpp w/src/rpc/blockchain.cpp
index 50bf764e53..354a391da7 100644
--- i/src/rpc/blockchain.cpp
+++ w/src/rpc/blockchain.cpp
@@ -130,12 +130,13 @@ static const CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateMan
 
         return pindex;
     }
 }
 
 UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex)
+    EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     // Serialize passed information without accessing chain state of the active chain!
     AssertLockNotHeld(cs_main); // For performance reasons
 
     UniValue result(UniValue::VOBJ);
     result.pushKV("hash", blockindex->GetBlockHash().GetHex());
@@ -158,13 +159,13 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex
         result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex());
     if (pnext)
         result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
     return result;
 }
 
-UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity)
+UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     UniValue result = blockheaderToJSON(tip, blockindex);
 
     result.pushKV("strippedsize", (int)::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS));
     result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION));
     result.pushKV("weight", (int)::GetBlockWeight(block));
diff --git i/src/rpc/mempool.cpp w/src/rpc/mempool.cpp
index 27080d3881..d6f897b0ca 100644
--- i/src/rpc/mempool.cpp
+++ w/src/rpc/mempool.cpp
@@ -45,13 +45,13 @@ static RPCHelpMan sendrawtransaction()
             + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
             "\nSend the transaction (signed hex)\n"
             + HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
             "\nAs a JSON-RPC call\n"
             + HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
                 },
-        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+        [&](const RPCHelpMan& self, const JSONRPCRequest& request) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main) -> UniValue
         {
             RPCTypeCheck(request.params, {
                 UniValue::VSTR,
                 UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
             });
 
diff --git i/src/rpc/mining.cpp w/src/rpc/mining.cpp
index b552528951..fddfb04be6 100644
--- i/src/rpc/mining.cpp
+++ w/src/rpc/mining.cpp
@@ -111,13 +111,13 @@ static RPCHelpMan getnetworkhashps()
     LOCK(cs_main);
     return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].get_int() : 120, !request.params[1].isNull() ? request.params[1].get_int() : -1, chainman.ActiveChain());
 },
     };
 }
 
-static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, uint256& block_hash)
+static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, uint256& block_hash) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     block_hash.SetNull();
     block.hashMerkleRoot = BlockMerkleRoot(block);
 
     CChainParams chainparams(Params());
 
@@ -138,13 +138,13 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t&
     }
 
     block_hash = block.GetHash();
     return true;
 }
 
-static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& mempool, const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries)
+static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& mempool, const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     UniValue blockHashes(UniValue::VARR);
     while (nGenerate > 0 && !ShutdownRequested()) {
         std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(chainman.ActiveChainstate(), mempool, Params()).CreateNewBlock(coinbase_script));
         if (!pblocktemplate.get())
             throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
@@ -212,13 +212,13 @@ static RPCHelpMan generatetodescriptor()
             {
                 {RPCResult::Type::STR_HEX, "", "blockhash"},
             }
         },
         RPCExamples{
             "\nGenerate 11 blocks to mydesc\n" + HelpExampleCli("generatetodescriptor", "11 \"mydesc\"")},
-        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+        [&](const RPCHelpMan& self, const JSONRPCRequest& request) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main) -> UniValue
 {
     const int num_blocks{request.params[0].get_int()};
     const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()};
 
     CScript coinbase_script;
     std::string error;
@@ -259,13 +259,13 @@ static RPCHelpMan generatetoaddress()
          RPCExamples{
             "\nGenerate 11 blocks to myaddress\n"
             + HelpExampleCli("generatetoaddress", "11 \"myaddress\"")
             + "If you are using the " PACKAGE_NAME " wallet, you can get a new address to send the newly generated bitcoin to with:\n"
             + HelpExampleCli("getnewaddress", "")
                 },
-        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+        [&](const RPCHelpMan& self, const JSONRPCRequest& request) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main) -> UniValue
 {
     const int num_blocks{request.params[0].get_int()};
     const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()};
 
     CTxDestination destination = DecodeDestination(request.params[1].get_str());
     if (!IsValidDestination(destination)) {
diff --git i/src/rpc/rawtransaction.cpp w/src/rpc/rawtransaction.cpp
index e8713fbd2e..1b82795952 100644
--- i/src/rpc/rawtransaction.cpp
+++ w/src/rpc/rawtransaction.cpp
@@ -205,13 +205,13 @@ static RPCHelpMan getrawtransaction()
                     HelpExampleCli("getrawtransaction", "\"mytxid\"")
             + HelpExampleCli("getrawtransaction", "\"mytxid\" true")
             + HelpExampleRpc("getrawtransaction", "\"mytxid\", true")
             + HelpExampleCli("getrawtransaction", "\"mytxid\" false \"myblockhash\"")
             + HelpExampleCli("getrawtransaction", "\"mytxid\" true \"myblockhash\"")
                 },
-        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+        [&](const RPCHelpMan& self, const JSONRPCRequest& request) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main) -> UniValue
 {
     const NodeContext& node = EnsureAnyNodeContext(request.context);
     ChainstateManager& chainman = EnsureChainman(node);
 
     bool in_active_chain = true;
     uint256 hash = ParseHashV(request.params[0], "parameter 1");
diff --git i/src/test/blockfilter_index_tests.cpp w/src/test/blockfilter_index_tests.cpp
index 82b9617384..785ef6f852 100644
--- i/src/test/blockfilter_index_tests.cpp
+++ w/src/test/blockfilter_index_tests.cpp
@@ -21,13 +21,13 @@ using node::BlockAssembler;
 using node::CBlockTemplate;
 
 BOOST_AUTO_TEST_SUITE(blockfilter_index_tests)
 
 struct BuildChainTestingSetup : public TestChain100Setup {
     CBlock CreateBlock(const CBlockIndex* prev, const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey);
-    bool BuildChain(const CBlockIndex* pindex, const CScript& coinbase_script_pub_key, size_t length, std::vector<std::shared_ptr<CBlock>>& chain);
+    bool BuildChain(const CBlockIndex* pindex, const CScript& coinbase_script_pub_key, size_t length, std::vector<std::shared_ptr<CBlock>>& chain) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
 };
 
 static bool CheckFilterLookups(BlockFilterIndex& filter_index, const CBlockIndex* block_index,
                                uint256& last_header)
 {
     BlockFilter expected_filter;
diff --git i/src/test/coinstatsindex_tests.cpp w/src/test/coinstatsindex_tests.cpp
index 5b73481bc1..59caa7cdde 100644
--- i/src/test/coinstatsindex_tests.cpp
+++ w/src/test/coinstatsindex_tests.cpp
@@ -15,13 +15,13 @@
 
 using node::CCoinsStats;
 using node::CoinStatsHashType;
 
 BOOST_AUTO_TEST_SUITE(coinstatsindex_tests)
 
-static void IndexWaitSynced(BaseIndex& index)
+static void IndexWaitSynced(BaseIndex& index) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     // Allow the CoinStatsIndex to catch up with the block index that is syncing
     // in a background thread.
     const auto timeout = GetTime<std::chrono::seconds>() + 120s;
     while (!index.BlockUntilSyncedToCurrentChain()) {
         BOOST_REQUIRE(timeout > GetTime<std::chrono::milliseconds>());
@@ -84,13 +84,13 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup)
 
     // Rest of shutdown sequence and destructors happen in ~TestingSetup()
 }
 
 // Test shutdown between BlockConnected and ChainStateFlushed notifications,
 // make sure index is not corrupted and is able to reload.
-BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup)
+BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     CChainState& chainstate = Assert(m_node.chainman)->ActiveChainstate();
     const CChainParams& params = Params();
     {
         CoinStatsIndex index{1 << 20};
         BOOST_REQUIRE(index.Start(chainstate));
diff --git i/src/test/fuzz/utxo_snapshot.cpp w/src/test/fuzz/utxo_snapshot.cpp
index e513f1883c..3c9b2fb76c 100644
--- i/src/test/fuzz/utxo_snapshot.cpp
+++ w/src/test/fuzz/utxo_snapshot.cpp
@@ -24,13 +24,13 @@ void initialize_chain()
 {
     const auto params{CreateChainParams(ArgsManager{}, CBaseChainParams::REGTEST)};
     static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
     g_chain = &chain;
 }
 
-FUZZ_TARGET_INIT(utxo_snapshot, initialize_chain)
+FUZZ_TARGET_INIT(utxo_snapshot, initialize_chain) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
     std::unique_ptr<const TestingSetup> setup{MakeNoLogFileContext<const TestingSetup>()};
     const auto& node = setup->m_node;
     auto& chainman{*node.chainman};
 
diff --git i/src/test/interfaces_tests.cpp w/src/test/interfaces_tests.cpp
index 49b7d2003b..a6288d8ee6 100644
--- i/src/test/interfaces_tests.cpp
+++ w/src/test/interfaces_tests.cpp
@@ -88,13 +88,13 @@ BOOST_AUTO_TEST_CASE(findAncestorByHash)
     int height = -1;
     BOOST_CHECK(chain->findAncestorByHash(active[20]->GetBlockHash(), active[10]->GetBlockHash(), FoundBlock().height(height)));
     BOOST_CHECK_EQUAL(height, 10);
     BOOST_CHECK(!chain->findAncestorByHash(active[10]->GetBlockHash(), active[20]->GetBlockHash()));
 }
 
-BOOST_AUTO_TEST_CASE(findCommonAncestor)
+BOOST_AUTO_TEST_CASE(findCommonAncestor) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     auto& chain = m_node.chain;
     const CChain& active = Assert(m_node.chainman)->ActiveChain();
     auto* orig_tip = active.Tip();
     for (int i = 0; i < 10; ++i) {
         BlockValidationState state;
diff --git i/src/test/txindex_tests.cpp w/src/test/txindex_tests.cpp
index 15213f826b..bce6d8b297 100644
--- i/src/test/txindex_tests.cpp
+++ w/src/test/txindex_tests.cpp
@@ -10,13 +10,13 @@
 #include <validation.h>
 
 #include <boost/test/unit_test.hpp>
 
 BOOST_AUTO_TEST_SUITE(txindex_tests)
 
-BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup)
+BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     TxIndex txindex(1 << 20, true);
 
     CTransactionRef tx_disk;
     uint256 block_hash;
 
diff --git i/src/test/txpackage_tests.cpp w/src/test/txpackage_tests.cpp
index 079b753304..b08aec63f3 100644
--- i/src/test/txpackage_tests.cpp
+++ w/src/test/txpackage_tests.cpp
@@ -339,13 +339,13 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
         BOOST_CHECK(submit_deduped.m_package_feerate == std::nullopt);
     }
 }
 
 // Tests for packages containing transactions that have same-txid-different-witness equivalents in
 // the mempool.
-BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
+BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     // Mine blocks to mature coinbases.
     mineBlocks(5);
     LOCK(cs_main);
 
     // Transactions with a same-txid-different-witness transaction in the mempool should be ignored,
@@ -595,13 +595,13 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
         BOOST_CHECK_MESSAGE(mixed_result.m_package_feerate.value() == expected_feerate,
                             strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(),
                                       mixed_result.m_package_feerate.value().ToString()));
     }
 }
 
-BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
+BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     mineBlocks(5);
     LOCK(::cs_main);
     size_t expected_pool_size = m_node.mempool->size();
     CKey child_key;
     child_key.MakeNewKey(true);
diff --git i/src/test/txvalidationcache_tests.cpp w/src/test/txvalidationcache_tests.cpp
index d41b54af20..d8bd91cace 100644
--- i/src/test/txvalidationcache_tests.cpp
+++ w/src/test/txvalidationcache_tests.cpp
@@ -22,13 +22,13 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
                        const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
                        bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
                        std::vector<CScriptCheck>* pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
 BOOST_AUTO_TEST_SUITE(txvalidationcache_tests)
 
-BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, Dersig100Setup)
+BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, Dersig100Setup) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     // Make sure skipping validation of transactions that were
     // validated going into the memory pool does not allow
     // double-spends in blocks to pass validation when they should not.
 
     CScript scriptPubKey = CScript() <<  ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
diff --git i/src/test/util/mining.cpp w/src/test/util/mining.cpp
index 5ed8598e8e..8699c19622 100644
--- i/src/test/util/mining.cpp
+++ w/src/test/util/mining.cpp
@@ -57,12 +57,13 @@ std::vector<std::shared_ptr<CBlock>> CreateBlockChain(size_t total_height, const
         }
     }
     return ret;
 }
 
 CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)
+    EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     auto block = PrepareBlock(node, coinbase_scriptPubKey);
 
     while (!CheckProofOfWork(block->GetHash(), block->nBits, Params().GetConsensus())) {
         ++block->nNonce;
         assert(block->nNonce);
diff --git i/src/test/util/setup_common.h w/src/test/util/setup_common.h
index a1b7525cf4..69abd9437f 100644
--- i/src/test/util/setup_common.h
+++ w/src/test/util/setup_common.h
@@ -128,25 +128,26 @@ struct TestChain100Setup : public TestingSetup {
      * Create a new block with just given transactions, coinbase paying to
      * scriptPubKey, and try to add it to the current chain.
      * If no chainstate is specified, default to the active.
      */
     CBlock CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns,
                                  const CScript& scriptPubKey,
-                                 CChainState* chainstate = nullptr);
+                                 CChainState* chainstate = nullptr)
+        EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
 
     /**
      * Create a new block with just given transactions, coinbase paying to
      * scriptPubKey.
      */
     CBlock CreateBlock(
         const std::vector<CMutableTransaction>& txns,
         const CScript& scriptPubKey,
         CChainState& chainstate);
 
     //! Mine a series of new blocks on the active chain.
-    void mineBlocks(int num_blocks);
+    void mineBlocks(int num_blocks) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
 
     /**
      * Create a transaction and submit to the mempool.
      *
      * @param input_transaction  The transaction to spend
      * @param input_vout         The vout to spend from the input_transaction
diff --git i/src/test/validation_block_tests.cpp w/src/test/validation_block_tests.cpp
index c5b1dabcb7..7fd265b74c 100644
--- i/src/test/validation_block_tests.cpp
+++ w/src/test/validation_block_tests.cpp
@@ -21,16 +21,20 @@
 
 using node::BlockAssembler;
 
 namespace validation_block_tests {
 struct MinerTestingSetup : public RegTestingSetup {
     std::shared_ptr<CBlock> Block(const uint256& prev_hash);
-    std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash);
-    std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash);
-    std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock);
-    void BuildChain(const uint256& root, int height, const unsigned int invalid_rate, const unsigned int branch_rate, const unsigned int max_size, std::vector<std::shared_ptr<const CBlock>>& blocks);
+    std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash)
+        EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
+    std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash)
+        EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
+    std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock)
+        EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
+    void BuildChain(const uint256& root, int height, const unsigned int invalid_rate, const unsigned int branch_rate, const unsigned int max_size, std::vector<std::shared_ptr<const CBlock>>& blocks)
+        EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
 };
 } // namespace validation_block_tests
 
 BOOST_FIXTURE_TEST_SUITE(validation_block_tests, MinerTestingSetup)
 
 struct TestSubscriber final : public CValidationInterface {
@@ -143,13 +147,13 @@ void MinerTestingSetup::BuildChain(const uint256& root, int height, const unsign
     if (gen_fork) {
         blocks.push_back(GoodBlock(root));
         BuildChain(blocks.back()->GetHash(), height - 1, invalid_rate, branch_rate, max_size, blocks);
     }
 }
 
-BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
+BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     // build a large-ish chain that's likely to have some forks
     std::vector<std::shared_ptr<const CBlock>> blocks;
     while (blocks.size() < 50) {
         blocks.clear();
         BuildChain(Params().GenesisBlock().GetHash(), 100, 15, 10, 500, blocks);
@@ -171,13 +175,13 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
 
     // create a bunch of threads that repeatedly process a block generated above at random
     // this will create parallelism and randomness inside validation - the ValidationInterface
     // will subscribe to events generated during block validation and assert on ordering invariance
     std::vector<std::thread> threads;
     for (int i = 0; i < 10; i++) {
-        threads.emplace_back([&]() {
+        threads.emplace_back([&]() EXCLUSIVE_LOCKS_REQUIRED(!::cs_main) {
             bool ignored;
             FastRandomContext insecure;
             for (int i = 0; i < 1000; i++) {
                 auto block = blocks[insecure.randrange(blocks.size() - 1)];
                 Assert(m_node.chainman)->ProcessNewBlock(Params(), block, true, &ignored);
             }
@@ -217,16 +221,16 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
  * from another thread during the reorg and checking that its size only changes
  * once. The size changing exactly once indicates that the polling thread's
  * view of the mempool is either consistent with the chain state before reorg,
  * or consistent with the chain state after the reorg, and not just consistent
  * with some intermediate state during the reorg.
  */
-BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
+BOOST_AUTO_TEST_CASE(mempool_locks_reorg) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     bool ignored;
-    auto ProcessBlock = [&](std::shared_ptr<const CBlock> block) -> bool {
+    auto ProcessBlock = [&](std::shared_ptr<const CBlock> block) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main) -> bool {
         return Assert(m_node.chainman)->ProcessNewBlock(Params(), block, /*force_processing=*/true, /*new_block=*/&ignored);
     };
 
     // Process all mined blocks
     BOOST_REQUIRE(ProcessBlock(std::make_shared<CBlock>(Params().GenesisBlock())));
     auto last_mined = GoodBlock(Params().GenesisBlock().GetHash());
diff --git i/src/test/validation_chainstate_tests.cpp w/src/test/validation_chainstate_tests.cpp
index 2a3990bb7c..12ad0fd485 100644
--- i/src/test/validation_chainstate_tests.cpp
+++ w/src/test/validation_chainstate_tests.cpp
@@ -75,13 +75,13 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches)
 }
 
 //! Test UpdateTip behavior for both active and background chainstates.
 //!
 //! When run on the background chainstate, UpdateTip should do a subset
 //! of what it does for the active chainstate.
-BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
+BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     ChainstateManager& chainman = *Assert(m_node.chainman);
     uint256 curr_tip = ::g_best_block;
 
     // Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can
     // be found.
diff --git i/src/validation.cpp w/src/validation.cpp
index b5d6a66088..6c8cf79649 100644
--- i/src/validation.cpp
+++ w/src/validation.cpp
@@ -2951,21 +2951,22 @@ static bool NotifyHeaderTip(CChainState& chainstate) LOCKS_EXCLUDED(cs_main) {
     if (fNotify) {
         uiInterface.NotifyHeaderTip(GetSynchronizationState(fInitialBlockDownload), pindexHeader);
     }
     return fNotify;
 }
 
-static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) {
+static void LimitValidationInterfaceQueue() EXCLUSIVE_LOCKS_REQUIRED(!::cs_main) {
     AssertLockNotHeld(cs_main);
 
     if (GetMainSignals().CallbacksPending() > 10) {
         SyncWithValidationInterfaceQueue();
     }
 }
 
 bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr<const CBlock> pblock)
+    EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     AssertLockNotHeld(m_chainstate_mutex);
 
     // Note that while we're often called here from ProcessNewBlock, this is
     // far from a guarantee. Things in the P2P/RPC will often end up calling
     // us in the middle of ProcessNewBlock - do not assume pblock is set
diff --git i/src/validation.h w/src/validation.h
index 42e41502f9..fa1cfd778f 100644
--- i/src/validation.h
+++ w/src/validation.h
@@ -660,19 +660,17 @@ public:
     // Manual block validity manipulation:
     /** Mark a block as precious and reorganize.
      *
      * May not be called in a validationinterface callback.
      */
     bool PreciousBlock(BlockValidationState& state, CBlockIndex* pindex)
-        EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex)
-        LOCKS_EXCLUDED(::cs_main);
+        EXCLUSIVE_LOCKS_REQUIRED(!::cs_main, !m_chainstate_mutex);
 
     /** Mark a block as invalid. */
     bool InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex)
-        EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex)
-        LOCKS_EXCLUDED(::cs_main);
+        EXCLUSIVE_LOCKS_REQUIRED(!::cs_main, !m_chainstate_mutex);
 
     /** Remove invalidity status from a block and its descendants. */
     void ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
     /** Replay blocks that aren't fully applied to the database. */
     bool ReplayBlocks();
@@ -957,26 +955,26 @@ public:
      *
      * @param[in]   block The block we want to process.
      * @param[in]   force_processing Process this block even if unrequested; used for non-network block sources.
      * @param[out]  new_block A boolean which is set to indicate if the block was first received via this call
      * @returns     If the block was processed, independently of block validity
      */
-    bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock>& block, bool force_processing, bool* new_block) LOCKS_EXCLUDED(cs_main);
+    bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock>& block, bool force_processing, bool* new_block) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
 
     /**
      * Process incoming block headers.
      *
      * May not be called in a
      * validationinterface callback.
      *
      * @param[in]  block The block headers themselves
      * @param[out] state This may be set to an Error state if any error occurred processing them
      * @param[in]  chainparams The params for the chain we want to connect to
      * @param[out] ppindex If set, the pointer will be set to point to the last new block index object for the given headers
      */
-    bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main);
+    bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
 
     /**
      * Try to add a transaction to the memory pool.
      *
      * @param[in]  tx              The transaction to submit for mempool acceptance.
      * @param[in]  test_accept     When true, run validation checks but don't submit to mempool.
diff --git i/src/validationinterface.cpp w/src/validationinterface.cpp
index 3f7fad3f87..a1d094164e 100644
--- i/src/validationinterface.cpp
+++ w/src/validationinterface.cpp
@@ -154,13 +154,13 @@ void UnregisterAllValidationInterfaces()
 
 void CallFunctionInValidationInterfaceQueue(std::function<void()> func)
 {
     g_signals.m_internals->m_schedulerClient.AddToProcessQueue(std::move(func));
 }
 
-void SyncWithValidationInterfaceQueue()
+void SyncWithValidationInterfaceQueue() EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     AssertLockNotHeld(cs_main);
     // Block until the validation queue drains
     std::promise<void> promise;
     CallFunctionInValidationInterfaceQueue([&promise] {
         promise.set_value();
diff --git i/src/wallet/bdb.cpp w/src/wallet/bdb.cpp
index 1aa0339445..459c64aeab 100644
--- i/src/wallet/bdb.cpp
+++ w/src/wallet/bdb.cpp
@@ -421,13 +421,13 @@ void BerkeleyEnvironment::CloseDb(const fs::path& filename)
             database.m_db->close(0);
             database.m_db.reset();
         }
     }
 }
 
-void BerkeleyEnvironment::ReloadDbEnv()
+void BerkeleyEnvironment::ReloadDbEnv() EXCLUSIVE_LOCKS_REQUIRED(!cs_db)
 {
     // Make sure that no Db's are in use
     AssertLockNotHeld(cs_db);
     std::unique_lock<RecursiveMutex> lock(cs_db);
     m_db_in_use.wait(lock, [this](){
         for (auto& db : m_databases) {
@@ -653,13 +653,13 @@ void BerkeleyDatabase::Flush()
 
 void BerkeleyDatabase::Close()
 {
     env->Flush(true);
 }
 
-void BerkeleyDatabase::ReloadDbEnv()
+void BerkeleyDatabase::ReloadDbEnv() EXCLUSIVE_LOCKS_REQUIRED(!cs_db)
 {
     env->ReloadDbEnv();
 }
 
 bool BerkeleyBatch::StartCursor()
 {
diff --git i/src/wallet/test/spend_tests.cpp w/src/wallet/test/spend_tests.cpp
index 334bd5b8bc..d10c4f1644 100644
--- i/src/wallet/test/spend_tests.cpp
+++ w/src/wallet/test/spend_tests.cpp
@@ -12,13 +12,13 @@
 
 #include <boost/test/unit_test.hpp>
 
 namespace wallet {
 BOOST_FIXTURE_TEST_SUITE(spend_tests, WalletTestingSetup)
 
-BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
+BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
     auto wallet = CreateSyncedWallet(*m_node.chain, m_node.chainman->ActiveChain(), m_args, coinbaseKey);
 
     // Check that a subtract-from-recipient transaction slightly less than the
     // coinbase input amount does not create a change output (because it would
diff --git i/src/wallet/test/wallet_tests.cpp w/src/wallet/test/wallet_tests.cpp
index 683f0eb327..06eefaf2b7 100644
--- i/src/wallet/test/wallet_tests.cpp
+++ w/src/wallet/test/wallet_tests.cpp
@@ -90,13 +90,13 @@ static void AddKey(CWallet& wallet, const CKey& key)
     std::unique_ptr<Descriptor> desc = Parse("combo(" + EncodeSecret(key) + ")", provider, error, /* require_checksum=*/ false);
     assert(desc);
     WalletDescriptor w_desc(std::move(desc), 0, 0, 1, 1);
     if (!wallet.AddWalletDescriptor(w_desc, provider, "", false)) assert(false);
 }
 
-BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
+BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     // Cap last block file size, and mine new block in a new block file.
     CBlockIndex* oldTip = m_node.chainman->ActiveChain().Tip();
     WITH_LOCK(::cs_main, m_node.chainman->m_blockman.GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE);
     CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
     CBlockIndex* newTip = m_node.chainman->ActiveChain().Tip();
@@ -194,13 +194,13 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
         BOOST_CHECK(result.last_scanned_block.IsNull());
         BOOST_CHECK(!result.last_scanned_height);
         BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 0);
     }
 }
 
-BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
+BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     // Cap last block file size, and mine new block in a new block file.
     CBlockIndex* oldTip = m_node.chainman->ActiveChain().Tip();
     WITH_LOCK(::cs_main, m_node.chainman->m_blockman.GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE);
     CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
     CBlockIndex* newTip = m_node.chainman->ActiveChain().Tip();
@@ -260,13 +260,13 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
 }
 
 // Verify importwallet RPC starts rescan at earliest block with timestamp
 // greater or equal than key birthday. Previously there was a bug where
 // importwallet RPC would start the scan at the latest block with timestamp less
 // than or equal to key birthday.
-BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
+BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     // Create two blocks with same timestamp to verify that importwallet rescan
     // will pick up both blocks, not just the first.
     const int64_t BLOCK_TIME = m_node.chainman->ActiveChain().Tip()->GetBlockTimeMax() + 5;
     SetMockTime(BLOCK_TIME);
     m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
@@ -515,13 +515,13 @@ public:
 
     ~ListCoinsTestingSetup()
     {
         wallet.reset();
     }
 
-    CWalletTx& AddTx(CRecipient recipient)
+    CWalletTx& AddTx(CRecipient recipient) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
     {
         CTransactionRef tx;
         CAmount fee;
         int changePos = -1;
         bilingual_str error;
         CCoinControl dummy;
@@ -545,13 +545,13 @@ public:
         return it->second;
     }
 
     std::unique_ptr<CWallet> wallet;
 };
 
-BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
+BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString();
 
     // Confirm ListCoins initially returns 1 coin grouped under coinbaseKey
     // address.
     std::map<CTxDestination, std::vector<COutput>> list;
@@ -712,13 +712,13 @@ BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup)
 //! the test verifies the transactions are detected before they arrive.
 //!
 //! In the second case, block and mempool transactions are created after the
 //! wallet rescan and notifications are immediately synced, to verify the wallet
 //! must already have a handler in place for them, and there's no gap after
 //! rescanning where new transactions in new blocks could be lost.
-BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
+BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     m_args.ForceSetArg("-unsafesqlitesync", "1");
     // Create new wallet with known key and unload it.
     WalletContext context;
     context.args = &m_args;
     context.chain = m_node.chain.get();
@@ -786,13 +786,13 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
     // paying to the wallet as the wallet finishes loading and syncing the
     // queue so the events have to be handled immediately. Releasing the wallet
     // lock during the sync is a little artificial but is needed to avoid a
     // deadlock during the sync and simulates a new block notification happening
     // as soon as possible.
     addtx_count = 0;
-    auto handler = HandleLoadWallet(context, [&](std::unique_ptr<interfaces::Wallet> wallet) {
+    auto handler = HandleLoadWallet(context, [&](std::unique_ptr<interfaces::Wallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main) {
             BOOST_CHECK(rescan_completed);
             m_coinbase_txns.push_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
             block_tx = TestSimpleSpend(*m_coinbase_txns[2], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
             m_coinbase_txns.push_back(CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
             mempool_tx = TestSimpleSpend(*m_coinbase_txns[3], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
             BOOST_CHECK(m_node.chain->broadcastTransaction(MakeTransactionRef(mempool_tx), DEFAULT_TRANSACTION_MAXFEE, false, error));
@@ -816,13 +816,13 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
     context.args = &m_args;
     auto wallet = TestLoadWallet(context);
     BOOST_CHECK(wallet);
     UnloadWallet(std::move(wallet));
 }
 
-BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
+BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main)
 {
     m_args.ForceSetArg("-unsafesqlitesync", "1");
     WalletContext context;
     context.args = &m_args;
     context.chain = m_node.chain.get();
     auto wallet = TestLoadWallet(context);

@maflcko
Copy link
Member

maflcko commented May 13, 2022

diff ...

I don't think it is meaningful or useful to annotate lambdas, as annotations are stripped as soon as they are assigned to a std::function<...> type.

@vasild
Copy link
Contributor

vasild commented May 13, 2022

annotations are stripped

Hmm:

void f()
{
    auto g = []() EXCLUSIVE_LOCKS_REQUIRED(!::cs_main) { AssertLockNotHeld(::cs_main); };
    g(); // error: calling function 'operator()' requires negative capability '!cs_main' [-Werror,-Wthread-safety-analysis]
}

(clang 15)

@ajtowns
Copy link
Contributor Author

ajtowns commented May 13, 2022

diff ...

I think that's large enough and independent enough that it warrants its own PR...

I don't think it is meaningful or useful to annotate lambdas, as annotations are stripped as soon as they are assigned to a std::function<...> type.

That means they're still useful when passed around via auto or templated types though. It gets pretty cumbersome anytime the lambda is passed to some generic function that wants a callback though.

@maflcko
Copy link
Member

maflcko commented May 13, 2022

Hmm auto

auto in this context is not a std::function, so it may still have the annotations attached.

That means they're still useful when passed around via auto or templated types though.

Eh, isn't this what we want to avoid? For example, annotating an rpc method lambda with lock annotations will either:

  • get them stripped away (as they are assigned to a std::function)
  • pollute the rpc server code (if they are somehow templated) with global lock annotations
  • might result in a compile failure if the calling function can't be annotated (for example it is from an external library)

None of this sounds useful or like an improvement. The diff that @vasild posted above looks similar to the diff that I NACKed two years ago #20272 (review)

@vasild
Copy link
Contributor

vasild commented May 13, 2022

Well, that diff above is required if we want to avoid the two AssertLockNotHeldInline() introduced in this PR and use just the first one:

inline void AssertLockNotHeldInline(const char* name, const char* file, int line, Mutex* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) { AssertLockNotHeldInternal(name, file, line, cs); }
inline void AssertLockNotHeldInline(const char* name, const char* file, int line, RecursiveMutex* cs) LOCKS_EXCLUDED(cs) { AssertLockNotHeldInternal(name, file, line, cs); }

Or, in other words, to avoid this (from this PR description):

Note that this can't reasonably be used for globals, because clang would require every function to be annotated with EXCLUSIVE_LOCKS_REQUIRED(!g_mutex) for each global mutex.

Anyway - I am ok with this PR as it is. It's an improvement from the current situation IMO, even with the two AssertLockNotHeldInline()s.

@ajtowns
Copy link
Contributor Author

ajtowns commented May 13, 2022

That means they're still useful when passed around via auto or templated types though. It gets pretty cumbersome anytime the lambda is passed to some generic function that wants a callback though.

Eh, isn't this what we want to avoid? For example, annotating an rpc method lambda with lock annotations will ...

Yeah, that's what I meant by cumbersome. Could be worthwhile in some cases maybe though (I know I've added annotations to some lambdas which didn't seem too bad).

Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. I didn't get the !m_added_nodes_mutex thing in Start().

Also left some unrelated comments.

review ACK 436ce02 🌺

Show signature

Signature:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

review ACK 436ce0233c276e263dcb441255dc0b881cb39cfb 🌺
-----BEGIN PGP SIGNATURE-----

iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
pUj/3gv+LfVwadPBlDSmCSWLuam73ROet6lfw7CgJRFosylwqX2ePcHTQLbzDlmG
qaSsa3tHEuWD4wjbUbq6cNF8st78YS4W/7geDhn8yeos9US0JeuXv9XaEWxGXmjU
O9oDIQgJRB4bsc8lUBZ1W6do7zaQXsFepA0avd+DvJJ50EHeIMsnJ3e1TabGk2EM
X53qeT50vMwK0SH3UCaVVF+yEH5Hcrt1TDddzy8QlP2kKoKX80AhIlCdxa+L1nAy
0zzzDCFNuxopDECXzBv0XizGRC2EoklCi1ajnjzmVWctLygeEWOzYAx5izL2C8qC
OCBjmVSRzgW4kTy5pkYO8z5KZDHb8zCoDOwicBSbaFz5n0RYaQKrFSxouhuNlkmE
p59l2jJN829m+jI6kI3wOWsmreRNwnm5NYUWJS2gty7J86PVHQ+Jsds63mKBdMcz
ZOoQ+HffljeKFIkqvp2jmTqUjxvgrPdRBpWg9kGswHVtHoRXwb9Zm8iG/GDenx5T
O3C5661p
=YVtl
-----END PGP SIGNATURE-----

@maflcko maflcko merged commit aa3200d into bitcoin:master May 16, 2022
Copy link
Member

@hebasto hebasto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post-merge ACK 436ce02.

Copy link
Member

@jonatack jonatack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post-merge Approach ACK

LGTM (modulo run-time assertions may be missing for the added annotations)

@w0xlt
Copy link
Contributor

w0xlt commented May 16, 2022

Post-merge ACK 436ce02

sidhujag pushed a commit to syscoin/syscoin that referenced this pull request May 28, 2022
@bitcoin bitcoin locked and limited conversation to collaborators May 16, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants