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 @@ -291,6 +291,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
45 changes: 12 additions & 33 deletions src/llmq/blockprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ CQuorumBlockProcessor::CQuorumBlockProcessor(const ChainstateManager& chainman,
m_evoDb{evoDb},
m_qsnapman{qsnapman}
{
utils::InitQuorumsCache(mapHasMinedCommitmentCache, m_chainman.GetConsensus());
mapHasMinedCommitmentCache.Init(m_chainman.GetConsensus());
LogPrintf("BLS verification uses %d additional threads\n", bls_threads);
m_bls_queue.StartWorkerThreads(bls_threads);
}
Expand Down Expand Up @@ -387,7 +387,7 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH

{
LOCK(minableCommitmentsCs);
mapHasMinedCommitmentCache[qc.llmqType].erase(qc.quorumHash);
mapHasMinedCommitmentCache.erase(qc.llmqType, qc.quorumHash);
minableCommitmentsByQuorum.erase(cacheKey);
minableCommitments.erase(::SerializeHash(qc));
}
Expand All @@ -404,11 +404,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 @@ -424,8 +420,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 @@ -439,15 +435,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 @@ -491,7 +487,7 @@ bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, gsl::not_null<const C
// Only once this commitment's state change is complete; see ProcessCommitment.
DropQcHashesCache();

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

// if a reorg happened, we should allow to mine this commitment later
AddMineableCommitment(qc);
Expand Down Expand Up @@ -586,31 +582,14 @@ uint256 CQuorumBlockProcessor::GetQuorumBlockHash(const Consensus::LLMQParams& l
bool CQuorumBlockProcessor::HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) const
{
bool fExists;
{
// 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 = mapHasMinedCommitmentCache.find(llmqType);
if (it == mapHasMinedCommitmentCache.end()) {
return false;
}
if (it->second.get(quorumHash, fExists)) {
return fExists;
}
if (LOCK(minableCommitmentsCs); mapHasMinedCommitmentCache.get(llmqType, quorumHash, fExists)) {
return fExists;
}

fExists = m_evoDb.Exists(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(llmqType, quorumHash)));

{
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 = mapHasMinedCommitmentCache.find(llmqType); it != mapHasMinedCommitmentCache.end()) {
it->second.insert(quorumHash, fExists);
}
}
LOCK(minableCommitmentsCs);
mapHasMinedCommitmentCache.insert(llmqType, quorumHash, fExists);

return fExists;
}
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 @@ -58,7 +58,7 @@ class CQuorumBlockProcessor
std::map<std::pair<Consensus::LLMQType, uint256>, uint256> minableCommitmentsByQuorum GUARDED_BY(minableCommitmentsCs);
std::map<uint256, CFinalCommitment> minableCommitments GUARDED_BY(minableCommitmentsCs);

mutable std::map<Consensus::LLMQType, Uint256LruHashMap<bool>> mapHasMinedCommitmentCache GUARDED_BY(minableCommitmentsCs);
mutable PerLlmqTypeCache<bool> mapHasMinedCommitmentCache 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 @@ -68,7 +68,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