llmq: Some fixes/improvements (#3943)

* llmq: Detach dash-q-cachepop from caller

There should be no reason to keep this tread attached
to its parent, if so, let me know.

* llmq: Avoid nullptr access for pindexStart in ScanQuorums

* llmq: Add cacheKey in ProcessCommitment

* llmq: Erase minable commitments if they have been processed

* llmq: Add CLLMQUtils::InitQuorumsCache

* llmq: Use unordered_lru_cache for quorumsCache and rename it

* llmq: Use unordered_lru_cache for hasMinedCommitmentCache and rename it

* llmq: Drop redundant check

* llmq: Rename nMaxCount2 -> nScanCommitments

* llmq: Refactor storeCache -> fCacheExists

* llmq: Rename maxCount -> nCountRequested

* llmq: Rename result -> vecResultQuorums

* llmq: Return an empty vector if the are zero elements requested

* unordered_lru_cache: Add max_size()

* llmq: Partially reuse existing cache if more than max is requested

* llmq: std::map<LLMQType, unordered_lru_cache<...>> for scanQuoumsCache

* llmq: Drop params

* llmq: Only emplace to cache if there is something available
This commit is contained in:
dustinface 2021-01-25 10:22:28 +01:00 committed by GitHub
parent f69d0f4d64
commit dc2473cd04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 86 additions and 60 deletions

View File

@ -43,11 +43,6 @@ CQuorum::~CQuorum()
{ {
// most likely the thread is already done // most likely the thread is already done
stopCachePopulatorThread = true; stopCachePopulatorThread = true;
// watch out to not join the thread when we're called from inside the thread, which might happen on shutdown. This
// is because on shutdown the thread is the last owner of the shared CQuorum instance and thus the destroyer of it.
if (cachePopulatorThread.joinable() && cachePopulatorThread.get_id() != std::this_thread::get_id()) {
cachePopulatorThread.join();
}
} }
void CQuorum::Init(const CFinalCommitment& _qc, const CBlockIndex* _pindexQuorum, const uint256& _minedBlockHash, const std::vector<CDeterministicMNCPtr>& _members) void CQuorum::Init(const CFinalCommitment& _qc, const CBlockIndex* _pindexQuorum, const uint256& _minedBlockHash, const std::vector<CDeterministicMNCPtr>& _members)
@ -152,6 +147,7 @@ void CQuorum::StartCachePopulatorThread(std::shared_ptr<CQuorum> _this)
} }
LogPrint(BCLog::LLMQ, "CQuorum::StartCachePopulatorThread -- done. time=%d\n", t.count()); LogPrint(BCLog::LLMQ, "CQuorum::StartCachePopulatorThread -- done. time=%d\n", t.count());
}); });
_this->cachePopulatorThread.detach();
} }
CQuorumManager::CQuorumManager(CEvoDB& _evoDb, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager) : CQuorumManager::CQuorumManager(CEvoDB& _evoDb, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager) :
@ -159,6 +155,8 @@ CQuorumManager::CQuorumManager(CEvoDB& _evoDb, CBLSWorker& _blsWorker, CDKGSessi
blsWorker(_blsWorker), blsWorker(_blsWorker),
dkgManager(_dkgManager) dkgManager(_dkgManager)
{ {
CLLMQUtils::InitQuorumsCache(mapQuorumsCache);
CLLMQUtils::InitQuorumsCache(scanQuorumsCache);
} }
void CQuorumManager::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fInitialDownload) const void CQuorumManager::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fInitialDownload) const
@ -240,7 +238,7 @@ CQuorumPtr CQuorumManager::BuildQuorumFromCommitment(const Consensus::LLMQType l
CQuorum::StartCachePopulatorThread(quorum); CQuorum::StartCachePopulatorThread(quorum);
} }
quorumsCache.emplace(std::make_pair(llmqType, quorumHash), quorum); mapQuorumsCache[llmqType].emplace(quorumHash, quorum);
return quorum; return quorum;
} }
@ -286,62 +284,73 @@ bool CQuorumManager::HasQuorum(Consensus::LLMQType llmqType, const uint256& quor
return quorumBlockProcessor->HasMinedCommitment(llmqType, quorumHash); return quorumBlockProcessor->HasMinedCommitment(llmqType, quorumHash);
} }
std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, size_t maxCount) const std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, size_t nCountRequested) const
{ {
const CBlockIndex* pindex; const CBlockIndex* pindex;
{ {
LOCK(cs_main); LOCK(cs_main);
pindex = chainActive.Tip(); pindex = chainActive.Tip();
} }
return ScanQuorums(llmqType, pindex, maxCount); return ScanQuorums(llmqType, pindex, nCountRequested);
} }
std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, const CBlockIndex* pindexStart, size_t maxCount) const std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, const CBlockIndex* pindexStart, size_t nCountRequested) const
{ {
auto& params = Params().GetConsensus().llmqs.at(llmqType); if (pindexStart == nullptr || nCountRequested == 0) {
return {};
}
auto cacheKey = std::make_pair(llmqType, pindexStart->GetBlockHash()); bool fCacheExists{false};
const size_t cacheMaxSize = params.signingActiveQuorumCount + 1; void* pIndexScanCommitments{(void*)pindexStart};
size_t nScanCommitments{nCountRequested};
std::vector<CQuorumCPtr> vecResultQuorums;
std::vector<CQuorumCPtr> result; {
if (maxCount <= cacheMaxSize) {
LOCK(quorumsCacheCs); LOCK(quorumsCacheCs);
if (scanQuorumsCache.get(cacheKey, result)) { auto& cache = scanQuorumsCache[llmqType];
if (result.size() > maxCount) { fCacheExists = cache.get(pindexStart->GetBlockHash(), vecResultQuorums);
result.resize(maxCount); if (fCacheExists) {
// We have exactly what requested so just return it
if (vecResultQuorums.size() == nCountRequested) {
return vecResultQuorums;
} }
return result; // If we have more cached than requested return only a subvector
if (vecResultQuorums.size() > nCountRequested) {
return {vecResultQuorums.begin(), vecResultQuorums.begin() + nCountRequested};
}
// 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(nCountRequested, cache.max_size());
} }
} }
// Get the block indexes of the mined commitments to build the required quorums from
bool storeCache = false; auto quorumIndexes = quorumBlockProcessor->GetMinedCommitmentsUntilBlock(llmqType, (const CBlockIndex*)pIndexScanCommitments, nScanCommitments);
size_t maxCount2 = maxCount; vecResultQuorums.reserve(vecResultQuorums.size() + quorumIndexes.size());
if (maxCount2 <= cacheMaxSize) {
maxCount2 = cacheMaxSize;
storeCache = true;
}
auto quorumIndexes = quorumBlockProcessor->GetMinedCommitmentsUntilBlock(params.type, pindexStart, maxCount2);
result.reserve(quorumIndexes.size());
for (auto& quorumIndex : quorumIndexes) { for (auto& quorumIndex : quorumIndexes) {
assert(quorumIndex); assert(quorumIndex);
auto quorum = GetQuorum(params.type, quorumIndex); auto quorum = GetQuorum(llmqType, quorumIndex);
assert(quorum != nullptr); assert(quorum != nullptr);
result.emplace_back(quorum); vecResultQuorums.emplace_back(quorum);
} }
if (storeCache) { size_t nCountResult{vecResultQuorums.size()};
if (nCountResult > 0 && !fCacheExists) {
LOCK(quorumsCacheCs); LOCK(quorumsCacheCs);
scanQuorumsCache.insert(cacheKey, result); // Don't cache more than cache.max_size() elements
auto& cache = scanQuorumsCache[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
if (result.size() > maxCount) { size_t nResultEndIndex = std::min(nCountResult, nCountRequested);
result.resize(maxCount); return {vecResultQuorums.begin(), vecResultQuorums.begin() + nResultEndIndex};
}
return result;
} }
CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash) const CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash) const
@ -372,10 +381,9 @@ CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const CBlock
} }
LOCK(quorumsCacheCs); LOCK(quorumsCacheCs);
CQuorumCPtr pQuorum;
auto it = quorumsCache.find(std::make_pair(llmqType, quorumHash)); if (mapQuorumsCache[llmqType].get(quorumHash, pQuorum)) {
if (it != quorumsCache.end()) { return pQuorum;
return it->second;
} }
return BuildQuorumFromCommitment(llmqType, pindexQuorum); return BuildQuorumFromCommitment(llmqType, pindexQuorum);

View File

@ -86,8 +86,8 @@ private:
CDKGSessionManager& dkgManager; CDKGSessionManager& dkgManager;
mutable CCriticalSection quorumsCacheCs; mutable CCriticalSection quorumsCacheCs;
mutable std::map<std::pair<Consensus::LLMQType, uint256>, CQuorumPtr> quorumsCache; mutable std::map<Consensus::LLMQType, unordered_lru_cache<uint256, CQuorumCPtr, StaticSaltedHasher>> mapQuorumsCache;
mutable unordered_lru_cache<std::pair<Consensus::LLMQType, uint256>, std::vector<CQuorumCPtr>, StaticSaltedHasher, 32> scanQuorumsCache; mutable std::map<Consensus::LLMQType, unordered_lru_cache<uint256, std::vector<CQuorumCPtr>, StaticSaltedHasher>> scanQuorumsCache;
public: public:
CQuorumManager(CEvoDB& _evoDb, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager); CQuorumManager(CEvoDB& _evoDb, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager);
@ -98,10 +98,10 @@ public:
// all these methods will lock cs_main for a short period of time // all these methods will lock cs_main for a short period of time
CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash) const; CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash) const;
std::vector<CQuorumCPtr> ScanQuorums(Consensus::LLMQType llmqType, size_t maxCount) const; std::vector<CQuorumCPtr> ScanQuorums(Consensus::LLMQType llmqType, size_t nCountRequested) const;
// this one is cs_main-free // this one is cs_main-free
std::vector<CQuorumCPtr> ScanQuorums(Consensus::LLMQType llmqType, const CBlockIndex* pindexStart, size_t maxCount) const; std::vector<CQuorumCPtr> ScanQuorums(Consensus::LLMQType llmqType, const CBlockIndex* pindexStart, size_t nCountRequested) const;
private: private:
// all private methods here are cs_main-free // all private methods here are cs_main-free

View File

@ -25,6 +25,12 @@ static const std::string DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT = "q_mcih";
static const std::string DB_BEST_BLOCK_UPGRADE = "q_bbu2"; static const std::string DB_BEST_BLOCK_UPGRADE = "q_bbu2";
CQuorumBlockProcessor::CQuorumBlockProcessor(CEvoDB &_evoDb) :
evoDb(_evoDb)
{
CLLMQUtils::InitQuorumsCache(mapHasMinedCommitmentCache);
}
void CQuorumBlockProcessor::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv) void CQuorumBlockProcessor::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv)
{ {
if (strCommand == NetMsgType::QFCOMMITMENT) { if (strCommand == NetMsgType::QFCOMMITMENT) {
@ -220,12 +226,15 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH
} }
// Store commitment in DB // Store commitment in DB
evoDb.Write(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(params.type, quorumHash)), std::make_pair(qc, blockHash)); auto cacheKey = std::make_pair(params.type, quorumHash);
evoDb.Write(std::make_pair(DB_MINED_COMMITMENT, cacheKey), std::make_pair(qc, blockHash));
evoDb.Write(BuildInversedHeightKey(params.type, nHeight), quorumIndex->nHeight); evoDb.Write(BuildInversedHeightKey(params.type, nHeight), quorumIndex->nHeight);
{ {
LOCK(minableCommitmentsCs); LOCK(minableCommitmentsCs);
hasMinedCommitmentCache.erase(std::make_pair(params.type, quorumHash)); mapHasMinedCommitmentCache[qc.llmqType].erase(qc.quorumHash);
minableCommitmentsByQuorum.erase(cacheKey);
minableCommitments.erase(::SerializeHash(qc));
} }
LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- processed commitment from block. type=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s\n", __func__, LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- processed commitment from block. type=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s\n", __func__,
@ -254,7 +263,7 @@ bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, const CBlockIndex* pi
evoDb.Erase(BuildInversedHeightKey((Consensus::LLMQType)qc.llmqType, pindex->nHeight)); evoDb.Erase(BuildInversedHeightKey((Consensus::LLMQType)qc.llmqType, pindex->nHeight));
{ {
LOCK(minableCommitmentsCs); LOCK(minableCommitmentsCs);
hasMinedCommitmentCache.erase(std::make_pair((Consensus::LLMQType)qc.llmqType, qc.quorumHash)); mapHasMinedCommitmentCache[qc.llmqType].erase(qc.quorumHash);
} }
// if a reorg happened, we should allow to mine this commitment later // if a reorg happened, we should allow to mine this commitment later
@ -392,21 +401,20 @@ uint256 CQuorumBlockProcessor::GetQuorumBlockHash(Consensus::LLMQType llmqType,
bool CQuorumBlockProcessor::HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) bool CQuorumBlockProcessor::HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash)
{ {
auto cacheKey = std::make_pair(llmqType, quorumHash); bool fExists;
{ {
LOCK(minableCommitmentsCs); LOCK(minableCommitmentsCs);
auto cacheIt = hasMinedCommitmentCache.find(cacheKey); if (mapHasMinedCommitmentCache[llmqType].get(quorumHash, fExists)) {
if (cacheIt != hasMinedCommitmentCache.end()) { return fExists;
return cacheIt->second;
} }
} }
auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair(llmqType, quorumHash)); fExists = evoDb.Exists(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(llmqType, quorumHash)));
bool ret = evoDb.Exists(key);
LOCK(minableCommitmentsCs); LOCK(minableCommitmentsCs);
hasMinedCommitmentCache.emplace(cacheKey, ret); mapHasMinedCommitmentCache[llmqType].insert(quorumHash, fExists);
return ret;
return fExists;
} }
bool CQuorumBlockProcessor::GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, CFinalCommitment& retQc, uint256& retMinedBlockHash) bool CQuorumBlockProcessor::GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, CFinalCommitment& retQc, uint256& retMinedBlockHash)

View File

@ -15,6 +15,7 @@
#include <map> #include <map>
#include <unordered_map> #include <unordered_map>
#include <unordered_lru_cache.h>
class CNode; class CNode;
class CConnman; class CConnman;
@ -32,10 +33,10 @@ private:
std::map<std::pair<Consensus::LLMQType, uint256>, uint256> minableCommitmentsByQuorum; std::map<std::pair<Consensus::LLMQType, uint256>, uint256> minableCommitmentsByQuorum;
std::map<uint256, CFinalCommitment> minableCommitments; std::map<uint256, CFinalCommitment> minableCommitments;
std::unordered_map<std::pair<Consensus::LLMQType, uint256>, bool, StaticSaltedHasher> hasMinedCommitmentCache; std::map<Consensus::LLMQType, unordered_lru_cache<uint256, bool, StaticSaltedHasher>> mapHasMinedCommitmentCache;
public: public:
explicit CQuorumBlockProcessor(CEvoDB& _evoDb) : evoDb(_evoDb) {} explicit CQuorumBlockProcessor(CEvoDB& _evoDb);
bool UpgradeDB(); bool UpgradeDB();

View File

@ -89,6 +89,14 @@ public:
} }
return HexStr(vBytes); return HexStr(vBytes);
} }
template <typename CacheType>
static 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));
}
}
}; };
const Consensus::LLMQParams& GetLLMQParams(const Consensus::LLMQType llmqType); const Consensus::LLMQParams& GetLLMQParams(const Consensus::LLMQType llmqType);

View File

@ -27,6 +27,7 @@ public:
assert(_maxSize != 0); assert(_maxSize != 0);
} }
size_t max_size() const { return maxSize; }
template<typename Value2> template<typename Value2>
void _emplace(const Key& key, Value2&& v) void _emplace(const Key& key, Value2&& v)