Skip to content

Commit ee4c27d

Browse files
UdjinM6PastaPastaPasta
authored andcommitted
fix: Improve quorum caching (again) (dashpay#5761)
## Issue being fixed or feature implemented 1. `scanQuorumsCache` is a special one and we use it incorrectly. 2. Platform doesn't really use anything that calls `ScanQuorums()` directly, they specify the exact quorum hash in RPCs so it's `GetQuorum()` that is used instead. The only place `ScanQuorums()` is used for Platform related stuff is `StartCleanupOldQuorumDataThread()` because we want to preserve quorum data used by `GetQuorum()`. But this can be optimised with its own (much more compact) cache. 3. RPCs that use `ScanQuorums()` should in most cases be ok with smaller cache, for other use cases there is a note in help text now. ## What was done? pls see individual commits ## How Has This Been Tested? run tests, run a node (~in progress~ looks stable) ## Breaking Changes n/a ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone
1 parent f0feb36 commit ee4c27d

File tree

6 files changed

+85
-21
lines changed

6 files changed

+85
-21
lines changed

src/llmq/dkgsessionmgr.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -465,10 +465,6 @@ void CDKGSessionManager::CleanupOldContributions() const
465465
const auto prefixes = {DB_VVEC, DB_SKCONTRIB, DB_ENC_CONTRIB};
466466

467467
for (const auto& params : Params().GetConsensus().llmqs) {
468-
// For how many blocks recent DKG info should be kept
469-
const int MAX_CYCLES = params.useRotation ? params.keepOldKeys / params.signingActiveQuorumCount : params.keepOldKeys;
470-
const int MAX_STORE_DEPTH = MAX_CYCLES * params.dkgInterval;
471-
472468
LogPrint(BCLog::LLMQ, "CDKGSessionManager::%s -- looking for old entries for llmq type %d\n", __func__, ToUnderlying(params.type));
473469

474470
CDBBatch batch(*db);
@@ -486,7 +482,7 @@ void CDKGSessionManager::CleanupOldContributions() const
486482
}
487483
cnt_all++;
488484
const CBlockIndex* pindexQuorum = m_chainstate.m_blockman.LookupBlockIndex(std::get<2>(k));
489-
if (pindexQuorum == nullptr || m_chainstate.m_chain.Tip()->nHeight - pindexQuorum->nHeight > MAX_STORE_DEPTH) {
485+
if (pindexQuorum == nullptr || m_chainstate.m_chain.Tip()->nHeight - pindexQuorum->nHeight > utils::max_store_depth(params)) {
490486
// not found or too old
491487
batch.Erase(k);
492488
cnt_old++;

src/llmq/quorums.cpp

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,6 @@ CQuorumManager::CQuorumManager(CBLSWorker& _blsWorker, CChainState& chainstate,
201201
m_peerman(peerman)
202202
{
203203
utils::InitQuorumsCache(mapQuorumsCache, false);
204-
utils::InitQuorumsCache(scanQuorumsCache, false);
205-
206204
quorumThreadInterrupt.reset();
207205
}
208206

@@ -503,14 +501,45 @@ std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqTyp
503501
return {};
504502
}
505503

506-
const CBlockIndex* pIndexScanCommitments{pindexStart};
504+
gsl::not_null<const CBlockIndex*> pindexStore{pindexStart};
505+
const auto& llmq_params_opt = GetLLMQParams(llmqType);
506+
assert(llmq_params_opt.has_value());
507+
508+
// Quorum sets can only change during the mining phase of DKG.
509+
// Find the closest known block index.
510+
const int quorumCycleStartHeight = pindexStart->nHeight - (pindexStart->nHeight % llmq_params_opt->dkgInterval);
511+
const int quorumCycleMiningStartHeight = quorumCycleStartHeight + llmq_params_opt->dkgMiningWindowStart;
512+
const int quorumCycleMiningEndHeight = quorumCycleStartHeight + llmq_params_opt->dkgMiningWindowEnd;
513+
514+
if (pindexStart->nHeight < quorumCycleMiningStartHeight) {
515+
// too early for this cycle, use the previous one
516+
// bail out if it's below genesis block
517+
if (quorumCycleMiningEndHeight < llmq_params_opt->dkgInterval) return {};
518+
pindexStore = pindexStart->GetAncestor(quorumCycleMiningEndHeight - llmq_params_opt->dkgInterval);
519+
} else if (pindexStart->nHeight > quorumCycleMiningEndHeight) {
520+
// we are past the mining phase of this cycle, use it
521+
pindexStore = pindexStart->GetAncestor(quorumCycleMiningEndHeight);
522+
}
523+
// everything else is inside the mining phase of this cycle, no pindexStore adjustment needed
524+
525+
gsl::not_null<const CBlockIndex*> pIndexScanCommitments{pindexStore};
507526
size_t nScanCommitments{nCountRequested};
508527
std::vector<CQuorumCPtr> vecResultQuorums;
509528

510529
{
511530
LOCK(cs_scan_quorums);
531+
if (scanQuorumsCache.empty()) {
532+
for (const auto& llmq : Params().GetConsensus().llmqs) {
533+
// NOTE: We store it for each block hash in the DKG mining phase here
534+
// and not for a single quorum hash per quorum like we do for other caches.
535+
// And we only do this for max_cycles() of the most recent quorums
536+
// because signing by old quorums requires the exact quorum hash to be specified
537+
// and quorum scanning isn't needed there.
538+
scanQuorumsCache.try_emplace(llmq.type, utils::max_cycles(llmq, llmq.keepOldConnections) * (llmq.dkgMiningWindowEnd - llmq.dkgMiningWindowStart));
539+
}
540+
}
512541
auto& cache = scanQuorumsCache[llmqType];
513-
bool fCacheExists = cache.get(pindexStart->GetBlockHash(), vecResultQuorums);
542+
bool fCacheExists = cache.get(pindexStore->GetBlockHash(), vecResultQuorums);
514543
if (fCacheExists) {
515544
// We have exactly what requested so just return it
516545
if (vecResultQuorums.size() == nCountRequested) {
@@ -524,17 +553,17 @@ std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqTyp
524553
// scanning for the rests
525554
if (!vecResultQuorums.empty()) {
526555
nScanCommitments -= vecResultQuorums.size();
556+
// bail out if it's below genesis block
557+
if (vecResultQuorums.back()->m_quorum_base_block_index->pprev == nullptr) return {};
527558
pIndexScanCommitments = vecResultQuorums.back()->m_quorum_base_block_index->pprev;
528559
}
529560
} else {
530-
// If there is nothing in cache request at least cache.max_size() because this gets cached then later
531-
nScanCommitments = std::max(nCountRequested, cache.max_size());
561+
// If there is nothing in cache request at least keepOldConnections because this gets cached then later
562+
nScanCommitments = std::max(nCountRequested, static_cast<size_t>(llmq_params_opt->keepOldConnections));
532563
}
533564
}
534565

535566
// Get the block indexes of the mined commitments to build the required quorums from
536-
const auto& llmq_params_opt = GetLLMQParams(llmqType);
537-
assert(llmq_params_opt.has_value());
538567
std::vector<const CBlockIndex*> pQuorumBaseBlockIndexes{ llmq_params_opt->useRotation ?
539568
quorumBlockProcessor.GetMinedCommitmentsIndexedUntilBlock(llmqType, pIndexScanCommitments, nScanCommitments) :
540569
quorumBlockProcessor.GetMinedCommitmentsUntilBlock(llmqType, pIndexScanCommitments, nScanCommitments)
@@ -551,10 +580,12 @@ std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqTyp
551580
const size_t nCountResult{vecResultQuorums.size()};
552581
if (nCountResult > 0) {
553582
LOCK(cs_scan_quorums);
554-
// Don't cache more than cache.max_size() elements
583+
// Don't cache more than keepOldConnections elements
584+
// because signing by old quorums requires the exact quorum hash
585+
// to be specified and quorum scanning isn't needed there.
555586
auto& cache = scanQuorumsCache[llmqType];
556-
const size_t nCacheEndIndex = std::min(nCountResult, cache.max_size());
557-
cache.emplace(pindexStart->GetBlockHash(), {vecResultQuorums.begin(), vecResultQuorums.begin() + nCacheEndIndex});
587+
const size_t nCacheEndIndex = std::min(nCountResult, static_cast<size_t>(llmq_params_opt->keepOldConnections));
588+
cache.emplace(pindexStore->GetBlockHash(), {vecResultQuorums.begin(), vecResultQuorums.begin() + nCacheEndIndex});
558589
}
559590
// Don't return more than nCountRequested elements
560591
const size_t nResultEndIndex = std::min(nCountResult, nCountRequested);
@@ -1023,13 +1054,31 @@ void CQuorumManager::StartCleanupOldQuorumDataThread(const CBlockIndex* pIndex)
10231054
workerPool.push([pIndex, t, this](int threadId) {
10241055
std::set<uint256> dbKeysToSkip;
10251056

1057+
if (LOCK(cs_cleanup); cleanupQuorumsCache.empty()) {
1058+
utils::InitQuorumsCache(cleanupQuorumsCache, false);
1059+
}
10261060
for (const auto& params : Params().GetConsensus().llmqs) {
10271061
if (quorumThreadInterrupt) {
10281062
break;
10291063
}
1030-
for (const auto& pQuorum : ScanQuorums(params.type, pIndex, params.keepOldKeys)) {
1031-
dbKeysToSkip.insert(MakeQuorumKey(*pQuorum));
1064+
LOCK(cs_cleanup);
1065+
auto& cache = cleanupQuorumsCache[params.type];
1066+
const CBlockIndex* pindex_loop{pIndex};
1067+
std::set<uint256> quorum_keys;
1068+
while (pindex_loop != nullptr && pIndex->nHeight - pindex_loop->nHeight < utils::max_store_depth(params)) {
1069+
uint256 quorum_key;
1070+
if (cache.get(pindex_loop->GetBlockHash(), quorum_key)) {
1071+
quorum_keys.insert(quorum_key);
1072+
if (quorum_keys.size() >= params.keepOldKeys) break; // extra safety belt
1073+
}
1074+
pindex_loop = pindex_loop->pprev;
1075+
}
1076+
for (const auto& pQuorum : ScanQuorums(params.type, pIndex, params.keepOldKeys - quorum_keys.size())) {
1077+
const uint256 quorum_key = MakeQuorumKey(*pQuorum);
1078+
quorum_keys.insert(quorum_key);
1079+
cache.insert(pQuorum->m_quorum_base_block_index->GetBlockHash(), quorum_key);
10321080
}
1081+
dbKeysToSkip.merge(quorum_keys);
10331082
}
10341083

10351084
if (!quorumThreadInterrupt) {

src/llmq/quorums.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ class CQuorumManager
231231
mutable std::map<Consensus::LLMQType, unordered_lru_cache<uint256, CQuorumPtr, StaticSaltedHasher>> mapQuorumsCache GUARDED_BY(cs_map_quorums);
232232
mutable RecursiveMutex cs_scan_quorums;
233233
mutable std::map<Consensus::LLMQType, unordered_lru_cache<uint256, std::vector<CQuorumCPtr>, StaticSaltedHasher>> scanQuorumsCache GUARDED_BY(cs_scan_quorums);
234+
mutable Mutex cs_cleanup;
235+
mutable std::map<Consensus::LLMQType, unordered_lru_cache<uint256, uint256, StaticSaltedHasher>> cleanupQuorumsCache GUARDED_BY(cs_cleanup);
234236

235237
mutable ctpl::thread_pool workerPool;
236238
mutable CThreadInterrupt quorumThreadInterrupt;

src/llmq/utils.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,7 @@ template void InitQuorumsCache<std::map<Consensus::LLMQType, unordered_lru_cache
11151115
template void InitQuorumsCache<std::map<Consensus::LLMQType, unordered_lru_cache<uint256, std::vector<CQuorumCPtr>, StaticSaltedHasher>>>(std::map<Consensus::LLMQType, unordered_lru_cache<uint256, std::vector<CQuorumCPtr>, StaticSaltedHasher>>& cache, bool limit_by_connections);
11161116
template void InitQuorumsCache<std::map<Consensus::LLMQType, unordered_lru_cache<uint256, std::shared_ptr<llmq::CQuorum>, StaticSaltedHasher, 0ul, 0ul>, std::less<Consensus::LLMQType>, std::allocator<std::pair<Consensus::LLMQType const, unordered_lru_cache<uint256, std::shared_ptr<llmq::CQuorum>, StaticSaltedHasher, 0ul, 0ul>>>>>(std::map<Consensus::LLMQType, unordered_lru_cache<uint256, std::shared_ptr<llmq::CQuorum>, StaticSaltedHasher, 0ul, 0ul>, std::less<Consensus::LLMQType>, std::allocator<std::pair<Consensus::LLMQType const, unordered_lru_cache<uint256, std::shared_ptr<llmq::CQuorum>, StaticSaltedHasher, 0ul, 0ul>>>>&cache, bool limit_by_connections);
11171117
template void InitQuorumsCache<std::map<Consensus::LLMQType, unordered_lru_cache<uint256, int, StaticSaltedHasher>>>(std::map<Consensus::LLMQType, unordered_lru_cache<uint256, int, StaticSaltedHasher>>& cache, bool limit_by_connections);
1118+
template void InitQuorumsCache<std::map<Consensus::LLMQType, unordered_lru_cache<uint256, uint256, StaticSaltedHasher>>>(std::map<Consensus::LLMQType, unordered_lru_cache<uint256, uint256, StaticSaltedHasher>>& cache, bool limit_by_connections);
11181119

11191120
} // namespace utils
11201121

src/llmq/utils.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,17 @@ void IterateNodesRandom(NodesContainer& nodeStates, Continue&& cont, Callback&&
122122
template <typename CacheType>
123123
void InitQuorumsCache(CacheType& cache, bool limit_by_connections = true);
124124

125+
[[ nodiscard ]] static constexpr int max_cycles(const Consensus::LLMQParams& llmqParams, int quorums_count)
126+
{
127+
return llmqParams.useRotation ? quorums_count / llmqParams.signingActiveQuorumCount : quorums_count;
128+
}
129+
130+
[[ nodiscard ]] static constexpr int max_store_depth(const Consensus::LLMQParams& llmqParams)
131+
{
132+
// For how many blocks recent DKG info should be kept
133+
return max_cycles(llmqParams, llmqParams.keepOldKeys) * llmqParams.dkgInterval;
134+
}
135+
125136
} // namespace utils
126137

127138
[[ nodiscard ]] const std::optional<Consensus::LLMQParams> GetLLMQParams(Consensus::LLMQType llmqType);

src/rpc/quorums.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ static void quorum_list_help(const JSONRPCRequest& request)
3636
RPCHelpMan{"quorum list",
3737
"List of on-chain quorums\n",
3838
{
39-
{"count", RPCArg::Type::NUM, /* default */ "", "Number of quorums to list. Will list active quorums if \"count\" is not specified."},
39+
{"count", RPCArg::Type::NUM, /* default */ "",
40+
"Number of quorums to list. Will list active quorums if \"count\" is not specified.\n"
41+
"Can be CPU/disk heavy when the value is larger than the number of active quorums."
42+
},
4043
},
4144
RPCResult{
4245
RPCResult::Type::OBJ, "", "",
@@ -365,8 +368,10 @@ static void quorum_memberof_help(const JSONRPCRequest& request)
365368
{
366369
{"proTxHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "ProTxHash of the masternode."},
367370
{"scanQuorumsCount", RPCArg::Type::NUM, /* default */ "",
368-
"Number of quorums to scan for. If not specified,\n"
369-
"the active quorum count for each specific quorum type is used."},
371+
"Number of quorums to scan for.\n"
372+
"If not specified, the active quorum count for each specific quorum type is used.\n"
373+
"Can be CPU/disk heavy when the value is larger than the number of active quorums."
374+
},
370375
},
371376
RPCResults{},
372377
RPCExamples{""},

0 commit comments

Comments
 (0)