Skip to content

Commit

Permalink
Merge #2883: [DMNs] Implement lru cache
Browse files Browse the repository at this point in the history
e3a9721 lru cache for scan quorums and get quorums (Alessandro Rezzi)
0e60d88 rlu cache quorum_hash->has_final_commitment (Alessandro Rezzi)
84cacd8 Add lru cache (Alessandro Rezzi)

Pull request description:

  A LRU cache is an (unordered) map where, once it is full, the least recently used element is removed.
  This PR adds a LRU cache that maps  `quorumHash` -> `bool` where `bool` is `true` iff the quorum has been mined on chain.
  It is a big optimization compared to checking the database each time

ACKs for top commit: e3a9721
  Fuzzbawls:
    ACK e3a9721
  Liquid369:
    tACK e3a9721

Tree-SHA512: a90bf80b5ed6d9ed0caf384614fb55000c34c05088861ffc567a20015389ddd61c2dd8bd4b35110a835a06611da147344e201443b9d31b971313f76443821760
  • Loading branch information
Fuzzbawls committed Sep 20, 2023
2 parents 7b7803c + e3a9721 commit 97551f2
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 24 deletions.
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ BITCOIN_CORE_H = \
guiinterfaceutil.h \
uint256.h \
undo.h \
unordered_lru_cache.h \
util/asmap.h \
util/blockstatecatcher.h \
util/system.h \
Expand Down
70 changes: 57 additions & 13 deletions src/llmq/quorums.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "univalue.h"
#include "validation.h"

#include <cstddef>
#include <iostream>

namespace llmq
Expand Down Expand Up @@ -158,6 +159,8 @@ CQuorumManager::CQuorumManager(CEvoDB& _evoDb, CBLSWorker& _blsWorker, CDKGSessi
blsWorker(_blsWorker),
dkgManager(_dkgManager)
{
utils::InitQuorumsCache(mapQuorumsCache);
utils::InitQuorumsCache(scanQuorumsCache);
}

void CQuorumManager::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fInitialDownload)
Expand Down Expand Up @@ -255,19 +258,61 @@ std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqTyp

std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, const CBlockIndex* pindexStart, size_t maxCount)
{
// TODO: speed up even more by using a cache
auto& params = Params().GetConsensus().llmqs.at(llmqType);
auto quorumIndexes = quorumBlockProcessor->GetMinedCommitmentsUntilBlock(params.type, pindexStart, maxCount);
std::vector<CQuorumCPtr> result;
result.reserve(quorumIndexes.size());
if (pindexStart == nullptr || maxCount == 0) {
return {};
}

bool fCacheExists{false};
void* pIndexScanCommitments{(void*)pindexStart};
size_t nScanCommitments{maxCount};
std::vector<CQuorumCPtr> vecResultQuorums;

{
LOCK(quorumsCacheCs);
auto& cache = scanQuorumsCache.at(llmqType);
fCacheExists = cache.get(pindexStart->GetBlockHash(), vecResultQuorums);
if (fCacheExists) {
// We have exactly what requested so just return it
if (vecResultQuorums.size() == maxCount) {
return vecResultQuorums;
}
// If we have more cached than requested return only a subvector
if (vecResultQuorums.size() > maxCount) {
return {vecResultQuorums.begin(), vecResultQuorums.begin() + maxCount};
}
// If we have cached quorums but not enough, subtract what we have from the count and the set correct index where to start
// scanning for the rests
if (vecResultQuorums.size() > 0) {
nScanCommitments -= vecResultQuorums.size();
pIndexScanCommitments = (void*)vecResultQuorums.back()->pindexQuorum->pprev;
}
} else {
// If there is nothing in cache request at least cache.max_size() because this gets cached then later
nScanCommitments = std::max(maxCount, cache.max_size());
}
}
// Get the block indexes of the mined commitments to build the required quorums from
auto quorumIndexes = quorumBlockProcessor->GetMinedCommitmentsUntilBlock(llmqType, (const CBlockIndex*)pIndexScanCommitments, nScanCommitments);
vecResultQuorums.reserve(vecResultQuorums.size() + quorumIndexes.size());

for (auto& quorumIndex : quorumIndexes) {
assert(quorumIndex);
auto quorum = GetQuorum(params.type, quorumIndex);
auto quorum = GetQuorum(llmqType, quorumIndex);
assert(quorum != nullptr);
result.emplace_back(quorum);
vecResultQuorums.emplace_back(quorum);
}

return result;
size_t nCountResult{vecResultQuorums.size()};
if (nCountResult > 0 && !fCacheExists) {
LOCK(quorumsCacheCs);
// Don't cache more than cache.max_size() elements
auto& cache = scanQuorumsCache.at(llmqType);
size_t nCacheEndIndex = std::min(nCountResult, cache.max_size());
cache.emplace(pindexStart->GetBlockHash(), {vecResultQuorums.begin(), vecResultQuorums.begin() + nCacheEndIndex});
}
// Don't return more than nCountRequested elements
size_t nResultEndIndex = std::min(nCountResult, maxCount);
return {vecResultQuorums.begin(), vecResultQuorums.begin() + nResultEndIndex};
}

CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash)
Expand Down Expand Up @@ -297,10 +342,9 @@ CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const CBlock
}

LOCK(quorumsCacheCs);

auto it = quorumsCache.find(std::make_pair(llmqType, quorumHash));
if (it != quorumsCache.end()) {
return it->second;
CQuorumCPtr pQuorum;
if (mapQuorumsCache.at(llmqType).get(quorumHash, pQuorum)) {
return pQuorum;
}

CFinalCommitment qc;
Expand All @@ -316,7 +360,7 @@ CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const CBlock
return nullptr;
}

quorumsCache.emplace(std::make_pair(llmqType, quorumHash), quorum);
mapQuorumsCache.at(llmqType).emplace(quorumHash, quorum);

return quorum;
}
Expand Down
5 changes: 4 additions & 1 deletion src/llmq/quorums.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include "consensus/params.h"
#include "evo/deterministicmns.h"
#include "evo/evodb.h"
#include "saltedhasher.h"
#include "unordered_lru_cache.h"
#include "validationinterface.h"

namespace llmq
Expand Down Expand Up @@ -84,7 +86,8 @@ class CQuorumManager
CDKGSessionManager& dkgManager;

RecursiveMutex quorumsCacheCs;
std::map<std::pair<Consensus::LLMQType, uint256>, CQuorumPtr> quorumsCache;
mutable std::map<Consensus::LLMQType, unordered_lru_cache<uint256, CQuorumCPtr, StaticSaltedHasher>> mapQuorumsCache;
mutable std::map<Consensus::LLMQType, unordered_lru_cache<uint256, std::vector<CQuorumCPtr>, StaticSaltedHasher>> scanQuorumsCache;

public:
CQuorumManager(CEvoDB& _evoDb, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager);
Expand Down
15 changes: 5 additions & 10 deletions src/llmq/quorums_blockprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ static const std::string DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT = "q_mcih";
CQuorumBlockProcessor::CQuorumBlockProcessor(CEvoDB &_evoDb) :
evoDb(_evoDb)
{
// TODO: add unordered lru cache
//utils::InitQuorumsCache(mapHasMinedCommitmentCache);
utils::InitQuorumsCache(mapHasMinedCommitmentCache);
}

template<typename... Args>
Expand Down Expand Up @@ -178,7 +177,7 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH

{
LOCK(minableCommitmentsCs);
//mapHasMinedCommitmentCache[qc.llmqType].erase(qc.quorumHash);
mapHasMinedCommitmentCache.at((Consensus::LLMQType)qc.llmqType).erase(qc.quorumHash);
minableCommitmentsByQuorum.erase(cacheKey);
minableCommitments.erase(::SerializeHash(qc));
}
Expand Down Expand Up @@ -207,7 +206,7 @@ bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, const CBlockIndex* pi
evoDb.Erase(BuildInversedHeightKey((Consensus::LLMQType)qc.llmqType, pindex->nHeight));
{
LOCK(minableCommitmentsCs);
//mapHasMinedCommitmentCache[qc.llmqType].erase(qc.quorumHash);
mapHasMinedCommitmentCache.at((Consensus::LLMQType)qc.llmqType).erase(qc.quorumHash);
}

// if a reorg happened, we should allow to mine this commitment later
Expand Down Expand Up @@ -281,21 +280,17 @@ uint256 CQuorumBlockProcessor::GetQuorumBlockHash(Consensus::LLMQType llmqType,
bool CQuorumBlockProcessor::HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash)
{
bool fExists;
/*
{
LOCK(minableCommitmentsCs);
if (mapHasMinedCommitmentCache[llmqType].get(quorumHash, fExists)) {
if (mapHasMinedCommitmentCache.at((Consensus::LLMQType)llmqType).get(quorumHash, fExists)) {
return fExists;
}
}
*/

fExists = evoDb.Exists(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(static_cast<uint8_t>(llmqType), quorumHash)));

/*
LOCK(minableCommitmentsCs);
mapHasMinedCommitmentCache[llmqType].insert(quorumHash, fExists);
*/
mapHasMinedCommitmentCache.at((Consensus::LLMQType)llmqType).insert(quorumHash, fExists);

return fExists;
}
Expand Down
4 changes: 4 additions & 0 deletions src/llmq/quorums_blockprocessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
#include "consensus/params.h"
#include "llmq/quorums_commitment.h"
#include "primitives/transaction.h"
#include "saltedhasher.h"
#include "sync.h"
#include "uint256.h"
#include "unordered_lru_cache.h"

#include <map>

Expand All @@ -36,6 +38,8 @@ class CQuorumBlockProcessor
std::map<std::pair<uint8_t, uint256>, uint256> minableCommitmentsByQuorum;
// commitment hash --> final commitment
std::map<uint256, CFinalCommitment> minableCommitments;
// for each llmqtype map quorum_hash --> (bool final_commitment_mined)
mutable std::map<Consensus::LLMQType, unordered_lru_cache<uint256, bool, StaticSaltedHasher>> mapHasMinedCommitmentCache GUARDED_BY(minableCommitmentsCs);

public:
explicit CQuorumBlockProcessor(CEvoDB& _evoDb);
Expand Down
13 changes: 13 additions & 0 deletions src/llmq/quorums_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ bool IsQuorumActive(Consensus::LLMQType llmqType, const uint256& quorumHash)
return false;
}

template <typename CacheType>
void InitQuorumsCache(CacheType& cache)
{
for (auto& llmq : Params().GetConsensus().llmqs) {
cache.emplace(std::piecewise_construct, std::forward_as_tuple(llmq.first),
std::forward_as_tuple(llmq.second.signingActiveQuorumCount + 1));
}
}

template void InitQuorumsCache<std::map<Consensus::LLMQType, unordered_lru_cache<uint256, bool, StaticSaltedHasher>>>(std::map<Consensus::LLMQType, unordered_lru_cache<uint256, bool, StaticSaltedHasher>>& cache);
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);
template void InitQuorumsCache<std::map<Consensus::LLMQType, unordered_lru_cache<uint256, CQuorumCPtr, StaticSaltedHasher>>>(std::map<Consensus::LLMQType, unordered_lru_cache<uint256, CQuorumCPtr, StaticSaltedHasher>>& cache);

} // namespace llmq::utils

} // namespace llmq
4 changes: 4 additions & 0 deletions src/llmq/quorums_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define PIVX_QUORUMS_UTILS_H

#include "consensus/params.h"
#include "unordered_lru_cache.h"

#include <vector>

Expand Down Expand Up @@ -60,6 +61,9 @@ static void IterateNodesRandom(NodesContainer& nodeStates, Continue&& cont, Call
}
}

template <typename CacheType>
void InitQuorumsCache(CacheType& cache);

} // namespace llmq::utils

} // namespace llmq
Expand Down
115 changes: 115 additions & 0 deletions src/unordered_lru_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright (c) 2019-2021 The Dash Core developers
// Copyright (c) 2023 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 PIVX_UNORDERED_LRU_CACHE_H
#define PIVX_UNORDERED_LRU_CACHE_H

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <unordered_map>
#include <vector>

template<typename Key, typename Value, typename Hasher, size_t MaxSize = 0, size_t TruncateThreshold = 0>
class unordered_lru_cache
{
private:
typedef std::unordered_map<Key, std::pair<Value, int64_t>, Hasher> MapType;

MapType cacheMap;
size_t maxSize;
size_t truncateThreshold;
int64_t accessCounter{0};

public:
explicit unordered_lru_cache(size_t _maxSize = MaxSize, size_t _truncateThreshold = TruncateThreshold) : maxSize(_maxSize),
truncateThreshold(_truncateThreshold == 0 ? _maxSize * 2 : _truncateThreshold)
{
// either specify maxSize through template arguments or the constructor and fail otherwise
assert(_maxSize != 0);
}

size_t max_size() const { return maxSize; }

template<typename Value2>
void _emplace(const Key& key, Value2&& v)
{
auto it = cacheMap.find(key);
if (it == cacheMap.end()) {
cacheMap.emplace(key, std::make_pair(std::forward<Value2>(v), accessCounter++));
} else {
it->second.first = std::forward<Value2>(v);
it->second.second = accessCounter++;
}
truncate_if_needed();
}

void emplace(const Key& key, Value&& v)
{
_emplace(key, v);
}

void insert(const Key& key, const Value& v)
{
_emplace(key, v);
}

bool get(const Key& key, Value& value)
{
auto it = cacheMap.find(key);
if (it != cacheMap.end()) {
it->second.second = accessCounter++;
value = it->second.first;
return true;
}
return false;
}

bool exists(const Key& key)
{
auto it = cacheMap.find(key);
if (it != cacheMap.end()) {
it->second.second = accessCounter++;
return true;
}
return false;
}

void erase(const Key& key)
{
cacheMap.erase(key);
}

void clear()
{
cacheMap.clear();
}

private:
void truncate_if_needed()
{
typedef typename MapType::iterator Iterator;

if (cacheMap.size() <= truncateThreshold) {
return;
}

std::vector<Iterator> vec;
vec.reserve(cacheMap.size());
for (auto it = cacheMap.begin(); it != cacheMap.end(); ++it) {
vec.emplace_back(it);
}
// sort by last access time (descending order)
std::sort(vec.begin(), vec.end(), [](const Iterator& it1, const Iterator& it2) {
return it1->second.second > it2->second.second;
});

for (size_t i = maxSize; i < vec.size(); i++) {
cacheMap.erase(vec[i]);
}
}
};

#endif // PIVX_UNORDERED_LRU_CACHE_H

0 comments on commit 97551f2

Please sign in to comment.