LoadChainTip requires cs_main to be locked. Under some conditions, it may call ActivateBestChain. However, ActivateBestChain requires cs_main NOT to be taken when called.
for (const CTxIn& txin : tx.vin) {
        const Coin& coin = view.AccessCoin(txin.prevout);
        
        
        
        
        if (coin.IsSpent()) return false;
        const CTransactionRef& txFrom = pool.get(txin.prevout.hash);
        if (txFrom) {
            assert(txFrom->GetHash() == txin.prevout.hash);
            assert(txFrom->vout.size() > txin.prevout.n);
            assert(txFrom->vout[txin.prevout.n] == coin.out);
        } else {
            const Coin& coinFromDisk = pcoinsTip->AccessCoin(txin.prevout);
            assert(!coinFromDisk.IsSpent());
            assert(coinFromDisk.out == coin.out);
        }
    }
    return CheckInputs(tx, state, view, true, flags, cacheSigStore, true, txdata);
}
/**
 * @param[out] coins_to_uncache   Return any outpoints which were not previously present in the
 *                                coins cache, but were added as a result of validating the tx
 *                                for mempool acceptance. This allows the caller to optionally
 *                                remove the cache additions if the associated transaction ends
 *                                up being rejected by the mempool.
 */
static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx,
                              bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
                              bool bypass_limits, const CAmount& nAbsurdFee, std::vector<COutPoint>& coins_to_uncache, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main)