Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ BITCOIN_CORE_H = \
key_io.h \
limitedmap.h \
llmq/blockprocessor.h \
llmq/cache.h \
llmq/commitment.h \
llmq/context.h \
llmq/debug.h \
Expand Down
36 changes: 10 additions & 26 deletions src/llmq/blockprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ CQuorumBlockProcessor::CQuorumBlockProcessor(ChainstateManager& chainman, CDeter
m_evoDb{evoDb},
m_qsnapman{qsnapman}
{
utils::InitQuorumsCache(mapMinedCommitmentBlockCache, m_chainman.GetConsensus());
mapMinedCommitmentBlockCache.Init(m_chainman.GetConsensus());
LogPrintf("BLS verification uses %d additional threads\n", bls_threads);
m_bls_queue.StartWorkerThreads(bls_threads);
}
Expand Down Expand Up @@ -427,7 +427,7 @@ bool CQuorumBlockProcessor::ProcessCommitment(Chainstate& chainstate, int nHeigh

{
LOCK(minableCommitmentsCs);
mapMinedCommitmentBlockCache[qc.llmqType].erase(qc.quorumHash);
mapMinedCommitmentBlockCache.erase(qc.llmqType, qc.quorumHash);
minableCommitmentsByQuorum.erase(cacheKey);
minableCommitments.erase(::SerializeHash(qc));
}
Expand All @@ -444,11 +444,7 @@ void CQuorumBlockProcessor::DropQcHashesCache()
m_quorums_cached.clear();
m_qc_hashes_cached.clear();
m_qc_indexed_hashes_cached.clear();
// Clear per-type LRU contents but keep the map entries so InitQuorumsCache is not
// required on every subsequent miss.
for (auto& [_, cache] : m_qc_hashes_lru) {
cache.clear();
}
m_qc_hashes_lru.clear();
}

std::optional<std::pair<QcHashMap, QcIndexedHashMap>> CQuorumBlockProcessor::GetQcHashes(const CBlockIndex* pindexPrev) const
Expand All @@ -464,8 +460,8 @@ std::optional<std::pair<QcHashMap, QcIndexedHashMap>> CQuorumBlockProcessor::Get
m_quorums_cached.clear();
m_qc_hashes_cached.clear();
m_qc_indexed_hashes_cached.clear();
if (m_qc_hashes_lru.empty()) {
utils::InitQuorumsCache(m_qc_hashes_lru, Params().GetConsensus());
if (!m_qc_hashes_lru.IsInitialized()) {
m_qc_hashes_lru.Init(Params().GetConsensus());
}

for (const auto& [llmqType, vecBlockIndexes] : quorums) {
Expand All @@ -479,15 +475,15 @@ std::optional<std::pair<QcHashMap, QcIndexedHashMap>> CQuorumBlockProcessor::Get
uint256 block_hash{blockIndex->GetBlockHash()};

std::pair<uint256, int> qc_hash;
if (!m_qc_hashes_lru[llmqType].get(block_hash, qc_hash)) {
if (!m_qc_hashes_lru.get(llmqType, block_hash, qc_hash)) {
auto [pqc, dummy_hash] = GetMinedCommitment(llmqType, block_hash);
if (dummy_hash == uint256::ZERO) {
// this should never happen
return std::nullopt;
}
qc_hash.first = ::SerializeHash(pqc);
qc_hash.second = rotation_enabled ? pqc.quorumIndex : 0;
m_qc_hashes_lru[llmqType].insert(block_hash, qc_hash);
m_qc_hashes_lru.insert(llmqType, block_hash, qc_hash);
}
if (rotation_enabled) {
map_indexed_hashes[qc_hash.second] = qc_hash.first;
Expand Down Expand Up @@ -534,7 +530,7 @@ bool CQuorumBlockProcessor::UndoBlock(const Chainstate& chainstate, const CBlock
// Only once this commitment's state change is complete; see ProcessCommitment.
DropQcHashesCache();

WITH_LOCK(minableCommitmentsCs, mapMinedCommitmentBlockCache[qc.llmqType].erase(qc.quorumHash));
WITH_LOCK(minableCommitmentsCs, mapMinedCommitmentBlockCache.erase(qc.llmqType, qc.quorumHash));

// if a reorg happened, we should allow to mine this commitment later
AddMineableCommitment(qc);
Expand Down Expand Up @@ -639,28 +635,16 @@ bool CQuorumBlockProcessor::HasMinedCommitment(Consensus::LLMQType llmqType, con
uint256 mined_block_hash;
bool cached;
{
// Defence-in-depth: this map is only pre-seeded by InitQuorumsCache() with the LLMQ types
// from the chain's consensus params. operator[] with any other type would insert a
// default-constructed, zero-capacity cache and abort in its constructor, so treat an
// unregistered type as "no mined commitment" rather than indexing the map.
LOCK(minableCommitmentsCs);
auto it = mapMinedCommitmentBlockCache.find(llmqType);
if (it == mapMinedCommitmentBlockCache.end()) {
return false;
}
cached = it->second.get(quorumHash, mined_block_hash);
cached = mapMinedCommitmentBlockCache.get(llmqType, quorumHash, mined_block_hash);
}
if (!cached) {
mined_block_hash = GetMinedCommitment(llmqType, quorumHash).second;
// Do not negatively cache. Snapshot activation seeds EvoDB directly,
// outside ProcessCommitment's normal cache-invalidation path.
if (!mined_block_hash.IsNull()) {
LOCK(minableCommitmentsCs);
// The key set is fixed at construction, so this can only miss if the type was
// unregistered, which the check above already returned on.
if (auto it = mapMinedCommitmentBlockCache.find(llmqType); it != mapMinedCommitmentBlockCache.end()) {
it->second.insert(quorumHash, mined_block_hash);
}
mapMinedCommitmentBlockCache.insert(llmqType, quorumHash, mined_block_hash);
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/llmq/blockprocessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
#define BITCOIN_LLMQ_BLOCKPROCESSOR_H

#include <bls/bls.h>
#include <llmq/cache.h>
#include <llmq/params.h>
#include <llmq/utils.h>
#include <msg_result.h>
#include <unordered_lru_cache.h>

#include <checkqueue.h>
#include <protocol.h>
Expand Down Expand Up @@ -67,7 +67,7 @@ class CQuorumBlockProcessor

// Cache the block in which a commitment was mined. Membership in a
// particular chain is checked on every call so reorgs need no cache flush.
mutable std::map<Consensus::LLMQType, Uint256LruHashMap<uint256>> mapMinedCommitmentBlockCache GUARDED_BY(minableCommitmentsCs);
mutable PerLlmqTypeCache<uint256> mapMinedCommitmentBlockCache GUARDED_BY(minableCommitmentsCs);

// Memoizes GetQcHashes(). The whole-result cache is keyed on the set of active
// quorum base blocks, the LRU on those base-block hashes; neither key identifies
Expand All @@ -77,7 +77,7 @@ class CQuorumBlockProcessor
// block index whose CBlockIndex* the outer cache stores.
mutable Mutex m_qc_hashes_cache_mutex;
mutable std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> m_quorums_cached GUARDED_BY(m_qc_hashes_cache_mutex);
mutable std::map<Consensus::LLMQType, Uint256LruHashMap<std::pair<uint256, int>>> m_qc_hashes_lru GUARDED_BY(m_qc_hashes_cache_mutex);
mutable PerLlmqTypeCache<std::pair<uint256, int>> m_qc_hashes_lru GUARDED_BY(m_qc_hashes_cache_mutex);
mutable QcHashMap m_qc_hashes_cached GUARDED_BY(m_qc_hashes_cache_mutex);
mutable QcIndexedHashMap m_qc_indexed_hashes_cached GUARDED_BY(m_qc_hashes_cache_mutex);

Expand Down
106 changes: 106 additions & 0 deletions src/llmq/cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (c) 2026 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_LLMQ_CACHE_H
#define BITCOIN_LLMQ_CACHE_H

#include <consensus/params.h>
#include <llmq/params.h>
#include <saltedhasher.h>
#include <uint256.h>
#include <unordered_lru_cache.h>

#include <map>
#include <utility>

namespace llmq {

//! A separate LRU cache per LLMQ type, sized from that type's consensus parameters.
//!
//! Only the types registered for the active chain get a cache. Consensus::LLMQType is a
//! uint8_t enum that arrives over the wire, so callers may pass a type this chain does not
//! use: those lookups miss and those writes are dropped, which is the same answer a cache
//! that has never held such an entry would give.
template <typename Value, typename Key = uint256>
class PerLlmqTypeCache
{
private:
using CacheType = unordered_lru_cache<Key, Value, StaticSaltedHasher>;

std::map<Consensus::LLMQType, CacheType> m_caches;

public:
//! Creates a cache per registered type, sized by size_fn. Must be called before use;
//! until then every type reads as absent.
template <typename SizeFn>
void Init(const Consensus::Params& consensus_params, SizeFn size_fn)
{
for (const auto& llmq : consensus_params.llmqs) {
m_caches.emplace(std::piecewise_construct, std::forward_as_tuple(llmq.type),
std::forward_as_tuple(size_fn(llmq)));
}
}

void Init(const Consensus::Params& consensus_params, bool limit_by_connections = true)
{
Init(consensus_params, [limit_by_connections](const Consensus::LLMQParams& llmq) {
return limit_by_connections ? llmq.keepOldConnections : llmq.keepOldKeys;
});
}

bool IsInitialized() const { return !m_caches.empty(); }

bool get(Consensus::LLMQType llmqType, const Key& key, Value& value)
{
auto it = m_caches.find(llmqType);
return it != m_caches.end() && it->second.get(key, value);
}

void insert(Consensus::LLMQType llmqType, const Key& key, const Value& value)
{
if (auto it = m_caches.find(llmqType); it != m_caches.end()) {
it->second.insert(key, value);
}
}

void emplace(Consensus::LLMQType llmqType, const Key& key, Value&& value)
{
if (auto it = m_caches.find(llmqType); it != m_caches.end()) {
it->second.emplace(key, std::move(value));
}
}

void erase(Consensus::LLMQType llmqType, const Key& key)
{
if (auto it = m_caches.find(llmqType); it != m_caches.end()) {
it->second.erase(key);
}
}

//! Drops cached entries but keeps the per-type caches, so Init is not needed again.
void clear()
{
for (auto& [_, cache] : m_caches) {
cache.clear();
}
}

void clear(Consensus::LLMQType llmqType)
{
if (auto it = m_caches.find(llmqType); it != m_caches.end()) {
it->second.clear();
}
}

//! Capacity of a type's cache, or 0 if this chain does not use it.
size_t max_size(Consensus::LLMQType llmqType) const
{
auto it = m_caches.find(llmqType);
return it != m_caches.end() ? it->second.max_size() : 0;
}
};

} // namespace llmq

#endif // BITCOIN_LLMQ_CACHE_H
8 changes: 4 additions & 4 deletions src/llmq/net_dkg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -420,10 +420,10 @@ void NetDKG::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStre
int quorumIndex{-1};
{
LOCK(cs_indexed_quorums_cache);
if (indexed_quorums_cache.empty()) {
utils::InitQuorumsCache(indexed_quorums_cache, m_chainman.GetConsensus());
if (!indexed_quorums_cache.IsInitialized()) {
indexed_quorums_cache.Init(m_chainman.GetConsensus());
}
indexed_quorums_cache[llmqType].get(quorumHash, quorumIndex);
indexed_quorums_cache.get(llmqType, quorumHash, quorumIndex);
}

if (quorumIndex == -1) {
Expand Down Expand Up @@ -532,7 +532,7 @@ void NetDKG::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStre
return;
}

WITH_LOCK(cs_indexed_quorums_cache, indexed_quorums_cache[llmqType].insert(quorumHash, quorumIndex));
WITH_LOCK(cs_indexed_quorums_cache, indexed_quorums_cache.insert(llmqType, quorumHash, quorumIndex));
}

bool NetDKG::AlreadyHave(const CInv& inv)
Expand Down
4 changes: 2 additions & 2 deletions src/llmq/net_dkg.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
#define BITCOIN_LLMQ_NET_DKG_H

#include <consensus/params.h>
#include <llmq/cache.h>
#include <net_processing.h>
#include <sync.h>
#include <uint256.h>
#include <unordered_lru_cache.h>

#include <map>
#include <memory>
Expand Down Expand Up @@ -100,7 +100,7 @@ class NetDKG final : public NetHandler

/** Cache: quorum hash → quorum index, populated lazily by ProcessMessage. */
mutable Mutex cs_indexed_quorums_cache;
mutable std::map<Consensus::LLMQType, Uint256LruHashMap<int>> indexed_quorums_cache GUARDED_BY(cs_indexed_quorums_cache);
mutable PerLlmqTypeCache<int> indexed_quorums_cache GUARDED_BY(cs_indexed_quorums_cache);

std::vector<std::thread> m_phase_threads;
};
Expand Down
10 changes: 5 additions & 5 deletions src/llmq/net_quorum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -688,20 +688,19 @@ void NetQuorum::StartCleanupOldQuorumDataThread(gsl::not_null<const CBlockIndex*
workerPool.push([pIndex, t, this](int threadId) {
Uint256HashSet dbKeysToSkip;

if (LOCK(cs_cleanup); cleanupQuorumsCache.empty()) {
utils::InitQuorumsCache(cleanupQuorumsCache, m_chainman.GetConsensus(), /*limit_by_connections=*/false);
if (LOCK(cs_cleanup); !cleanupQuorumsCache.IsInitialized()) {
cleanupQuorumsCache.Init(m_chainman.GetConsensus(), /*limit_by_connections=*/false);
}
for (const auto& params : Params().GetConsensus().llmqs) {
if (quorumThreadInterrupt) {
break;
}
LOCK(cs_cleanup);
auto& cache = cleanupQuorumsCache[params.type];
const CBlockIndex* pindex_loop{pIndex};
Uint256HashSet quorum_keys;
while (pindex_loop != nullptr && pIndex->nHeight - pindex_loop->nHeight < params.max_store_depth()) {
uint256 quorum_key;
if (cache.get(pindex_loop->GetBlockHash(), quorum_key)) {
if (cleanupQuorumsCache.get(params.type, pindex_loop->GetBlockHash(), quorum_key)) {
quorum_keys.insert(quorum_key);
if (quorum_keys.size() >= static_cast<size_t>(params.keepOldKeys)) break; // extra safety belt
}
Expand All @@ -710,7 +709,8 @@ void NetQuorum::StartCleanupOldQuorumDataThread(gsl::not_null<const CBlockIndex*
for (const auto& pQuorum : m_qman.ScanQuorums(params.type, pIndex, params.keepOldKeys - quorum_keys.size())) {
const uint256 quorum_key = MakeQuorumKey(*pQuorum);
quorum_keys.insert(quorum_key);
cache.insert(pQuorum->m_quorum_base_block_index->GetBlockHash(), quorum_key);
cleanupQuorumsCache.insert(params.type, pQuorum->m_quorum_base_block_index->GetBlockHash(),
quorum_key);
}
dbKeysToSkip.merge(quorum_keys);
}
Expand Down
4 changes: 2 additions & 2 deletions src/llmq/net_quorum.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
#ifndef BITCOIN_LLMQ_NET_QUORUM_H
#define BITCOIN_LLMQ_NET_QUORUM_H

#include <llmq/cache.h>
#include <llmq/options.h>
#include <llmq/quorums.h>
#include <net_processing.h>
#include <sync.h>
#include <unordered_lru_cache.h>
#include <util/threadinterrupt.h>
#include <validationinterface.h>

Expand Down Expand Up @@ -117,7 +117,7 @@ class NetQuorum final : public NetHandler, public CValidationInterface
const bool m_quorums_recovery;

mutable Mutex cs_cleanup;
mutable std::map<Consensus::LLMQType, Uint256LruHashMap<uint256>> cleanupQuorumsCache
mutable PerLlmqTypeCache<uint256> cleanupQuorumsCache
GUARDED_BY(cs_cleanup);

mutable ctpl::thread_pool workerPool;
Expand Down
Loading
Loading