mirror of
https://github.com/dashpay/dash.git
synced 2024-12-29 05:49:11 +01:00
7f1f1d12f5
Instead of only doing it the first time. This is a preparation for the new intra-quorum connection system (which connects all members to all other members)
392 lines
12 KiB
C++
392 lines
12 KiB
C++
// Copyright (c) 2018-2019 The Dash Core developers
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <llmq/quorums.h>
|
|
#include <llmq/quorums_blockprocessor.h>
|
|
#include <llmq/quorums_commitment.h>
|
|
#include <llmq/quorums_dkgsession.h>
|
|
#include <llmq/quorums_dkgsessionmgr.h>
|
|
#include <llmq/quorums_init.h>
|
|
#include <llmq/quorums_utils.h>
|
|
|
|
#include <evo/specialtx.h>
|
|
|
|
#include <masternode/activemasternode.h>
|
|
#include <chainparams.h>
|
|
#include <init.h>
|
|
#include <masternode/masternode-sync.h>
|
|
#include <univalue.h>
|
|
#include <validation.h>
|
|
|
|
#include <cxxtimer.hpp>
|
|
|
|
namespace llmq
|
|
{
|
|
|
|
static const std::string DB_QUORUM_SK_SHARE = "q_Qsk";
|
|
static const std::string DB_QUORUM_QUORUM_VVEC = "q_Qqvvec";
|
|
|
|
CQuorumManager* quorumManager;
|
|
|
|
static uint256 MakeQuorumKey(const CQuorum& q)
|
|
{
|
|
CHashWriter hw(SER_NETWORK, 0);
|
|
hw << q.params.type;
|
|
hw << q.qc.quorumHash;
|
|
for (const auto& dmn : q.members) {
|
|
hw << dmn->proTxHash;
|
|
}
|
|
return hw.GetHash();
|
|
}
|
|
|
|
CQuorum::~CQuorum()
|
|
{
|
|
// most likely the thread is already done
|
|
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)
|
|
{
|
|
qc = _qc;
|
|
pindexQuorum = _pindexQuorum;
|
|
members = _members;
|
|
minedBlockHash = _minedBlockHash;
|
|
}
|
|
|
|
bool CQuorum::IsMember(const uint256& proTxHash) const
|
|
{
|
|
for (auto& dmn : members) {
|
|
if (dmn->proTxHash == proTxHash) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CQuorum::IsValidMember(const uint256& proTxHash) const
|
|
{
|
|
for (size_t i = 0; i < members.size(); i++) {
|
|
if (members[i]->proTxHash == proTxHash) {
|
|
return qc.validMembers[i];
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
CBLSPublicKey CQuorum::GetPubKeyShare(size_t memberIdx) const
|
|
{
|
|
if (quorumVvec == nullptr || memberIdx >= members.size() || !qc.validMembers[memberIdx]) {
|
|
return CBLSPublicKey();
|
|
}
|
|
auto& m = members[memberIdx];
|
|
return blsCache.BuildPubKeyShare(m->proTxHash, quorumVvec, CBLSId::FromHash(m->proTxHash));
|
|
}
|
|
|
|
CBLSSecretKey CQuorum::GetSkShare() const
|
|
{
|
|
return skShare;
|
|
}
|
|
|
|
int CQuorum::GetMemberIndex(const uint256& proTxHash) const
|
|
{
|
|
for (size_t i = 0; i < members.size(); i++) {
|
|
if (members[i]->proTxHash == proTxHash) {
|
|
return (int)i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void CQuorum::WriteContributions(CEvoDB& evoDb)
|
|
{
|
|
uint256 dbKey = MakeQuorumKey(*this);
|
|
|
|
if (quorumVvec != nullptr) {
|
|
evoDb.GetRawDB().Write(std::make_pair(DB_QUORUM_QUORUM_VVEC, dbKey), *quorumVvec);
|
|
}
|
|
if (skShare.IsValid()) {
|
|
evoDb.GetRawDB().Write(std::make_pair(DB_QUORUM_SK_SHARE, dbKey), skShare);
|
|
}
|
|
}
|
|
|
|
bool CQuorum::ReadContributions(CEvoDB& evoDb)
|
|
{
|
|
uint256 dbKey = MakeQuorumKey(*this);
|
|
|
|
BLSVerificationVector qv;
|
|
if (evoDb.Read(std::make_pair(DB_QUORUM_QUORUM_VVEC, dbKey), qv)) {
|
|
quorumVvec = std::make_shared<BLSVerificationVector>(std::move(qv));
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
// We ignore the return value here as it is ok if this fails. If it fails, it usually means that we are not a
|
|
// member of the quorum but observed the whole DKG process to have the quorum verification vector.
|
|
evoDb.Read(std::make_pair(DB_QUORUM_SK_SHARE, dbKey), skShare);
|
|
|
|
return true;
|
|
}
|
|
|
|
void CQuorum::StartCachePopulatorThread(std::shared_ptr<CQuorum> _this)
|
|
{
|
|
if (_this->quorumVvec == nullptr) {
|
|
return;
|
|
}
|
|
|
|
cxxtimer::Timer t(true);
|
|
LogPrint(BCLog::LLMQ, "CQuorum::StartCachePopulatorThread -- start\n");
|
|
|
|
// this thread will exit after some time
|
|
// when then later some other thread tries to get keys, it will be much faster
|
|
_this->cachePopulatorThread = std::thread([_this, t]() {
|
|
RenameThread("dash-q-cachepop");
|
|
for (size_t i = 0; i < _this->members.size() && !_this->stopCachePopulatorThread && !ShutdownRequested(); i++) {
|
|
if (_this->qc.validMembers[i]) {
|
|
_this->GetPubKeyShare(i);
|
|
}
|
|
}
|
|
LogPrint(BCLog::LLMQ, "CQuorum::StartCachePopulatorThread -- done. time=%d\n", t.count());
|
|
});
|
|
}
|
|
|
|
CQuorumManager::CQuorumManager(CEvoDB& _evoDb, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager) :
|
|
evoDb(_evoDb),
|
|
blsWorker(_blsWorker),
|
|
dkgManager(_dkgManager)
|
|
{
|
|
}
|
|
|
|
void CQuorumManager::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fInitialDownload)
|
|
{
|
|
if (!masternodeSync.IsBlockchainSynced()) {
|
|
return;
|
|
}
|
|
|
|
for (auto& p : Params().GetConsensus().llmqs) {
|
|
EnsureQuorumConnections(p.first, pindexNew);
|
|
}
|
|
}
|
|
|
|
void CQuorumManager::EnsureQuorumConnections(Consensus::LLMQType llmqType, const CBlockIndex* pindexNew)
|
|
{
|
|
const auto& params = Params().GetConsensus().llmqs.at(llmqType);
|
|
|
|
auto myProTxHash = activeMasternodeInfo.proTxHash;
|
|
auto lastQuorums = ScanQuorums(llmqType, pindexNew, (size_t)params.keepOldConnections);
|
|
|
|
auto connmanQuorumsToDelete = g_connman->GetMasternodeQuorums(llmqType);
|
|
|
|
// don't remove connections for the currently in-progress DKG round
|
|
int curDkgHeight = pindexNew->nHeight - (pindexNew->nHeight % params.dkgInterval);
|
|
auto curDkgBlock = pindexNew->GetAncestor(curDkgHeight)->GetBlockHash();
|
|
connmanQuorumsToDelete.erase(curDkgBlock);
|
|
|
|
bool allowWatch = gArgs.GetBoolArg("-watchquorums", DEFAULT_WATCH_QUORUMS);
|
|
for (auto& quorum : lastQuorums) {
|
|
if (!quorum->IsMember(myProTxHash) && !allowWatch) {
|
|
continue;
|
|
}
|
|
|
|
CLLMQUtils::EnsureQuorumConnections(llmqType, quorum->pindexQuorum, myProTxHash, allowWatch);
|
|
|
|
connmanQuorumsToDelete.erase(quorum->qc.quorumHash);
|
|
}
|
|
|
|
for (auto& qh : connmanQuorumsToDelete) {
|
|
LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- removing masternodes quorum connections for quorum %s:\n", __func__, qh.ToString());
|
|
g_connman->RemoveMasternodeQuorumNodes(llmqType, qh);
|
|
}
|
|
}
|
|
|
|
bool CQuorumManager::BuildQuorumFromCommitment(const CFinalCommitment& qc, const CBlockIndex* pindexQuorum, const uint256& minedBlockHash, std::shared_ptr<CQuorum>& quorum) const
|
|
{
|
|
assert(pindexQuorum);
|
|
assert(qc.quorumHash == pindexQuorum->GetBlockHash());
|
|
|
|
auto members = CLLMQUtils::GetAllQuorumMembers((Consensus::LLMQType)qc.llmqType, pindexQuorum);
|
|
|
|
quorum->Init(qc, pindexQuorum, minedBlockHash, members);
|
|
|
|
bool hasValidVvec = false;
|
|
if (quorum->ReadContributions(evoDb)) {
|
|
hasValidVvec = true;
|
|
} else {
|
|
if (BuildQuorumContributions(qc, quorum)) {
|
|
quorum->WriteContributions(evoDb);
|
|
hasValidVvec = true;
|
|
} else {
|
|
LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- quorum.ReadContributions and BuildQuorumContributions for block %s failed\n", __func__, qc.quorumHash.ToString());
|
|
}
|
|
}
|
|
|
|
if (hasValidVvec) {
|
|
// pre-populate caches in the background
|
|
// recovering public key shares is quite expensive and would result in serious lags for the first few signing
|
|
// sessions if the shares would be calculated on-demand
|
|
CQuorum::StartCachePopulatorThread(quorum);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CQuorumManager::BuildQuorumContributions(const CFinalCommitment& fqc, std::shared_ptr<CQuorum>& quorum) const
|
|
{
|
|
std::vector<uint16_t> memberIndexes;
|
|
std::vector<BLSVerificationVectorPtr> vvecs;
|
|
BLSSecretKeyVector skContributions;
|
|
if (!dkgManager.GetVerifiedContributions((Consensus::LLMQType)fqc.llmqType, quorum->pindexQuorum, fqc.validMembers, memberIndexes, vvecs, skContributions)) {
|
|
return false;
|
|
}
|
|
|
|
BLSVerificationVectorPtr quorumVvec;
|
|
CBLSSecretKey skShare;
|
|
|
|
cxxtimer::Timer t2(true);
|
|
quorumVvec = blsWorker.BuildQuorumVerificationVector(vvecs);
|
|
if (quorumVvec == nullptr) {
|
|
LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- failed to build quorumVvec\n", __func__);
|
|
// without the quorum vvec, there can't be a skShare, so we fail here. Failure is not fatal here, as it still
|
|
// allows to use the quorum as a non-member (verification through the quorum pub key)
|
|
return false;
|
|
}
|
|
skShare = blsWorker.AggregateSecretKeys(skContributions);
|
|
if (!skShare.IsValid()) {
|
|
LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- failed to build skShare\n", __func__);
|
|
// We don't bail out here as this is not a fatal error and still allows us to recover public key shares (as we
|
|
// have a valid quorum vvec at this point)
|
|
}
|
|
t2.stop();
|
|
|
|
LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- built quorum vvec and skShare. time=%d\n", __func__, t2.count());
|
|
|
|
quorum->quorumVvec = quorumVvec;
|
|
quorum->skShare = skShare;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CQuorumManager::HasQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash)
|
|
{
|
|
return quorumBlockProcessor->HasMinedCommitment(llmqType, quorumHash);
|
|
}
|
|
|
|
std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, size_t maxCount)
|
|
{
|
|
const CBlockIndex* pindex;
|
|
{
|
|
LOCK(cs_main);
|
|
pindex = chainActive.Tip();
|
|
}
|
|
return ScanQuorums(llmqType, pindex, maxCount);
|
|
}
|
|
|
|
std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, const CBlockIndex* pindexStart, size_t maxCount)
|
|
{
|
|
auto& params = Params().GetConsensus().llmqs.at(llmqType);
|
|
|
|
auto cacheKey = std::make_pair(llmqType, pindexStart->GetBlockHash());
|
|
const size_t cacheMaxSize = params.signingActiveQuorumCount + 1;
|
|
|
|
std::vector<CQuorumCPtr> result;
|
|
|
|
if (maxCount <= cacheMaxSize) {
|
|
LOCK(quorumsCacheCs);
|
|
if (scanQuorumsCache.get(cacheKey, result)) {
|
|
if (result.size() > maxCount) {
|
|
result.resize(maxCount);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
bool storeCache = false;
|
|
size_t maxCount2 = maxCount;
|
|
if (maxCount2 <= cacheMaxSize) {
|
|
maxCount2 = cacheMaxSize;
|
|
storeCache = true;
|
|
}
|
|
|
|
auto quorumIndexes = quorumBlockProcessor->GetMinedCommitmentsUntilBlock(params.type, pindexStart, maxCount2);
|
|
result.reserve(quorumIndexes.size());
|
|
|
|
for (auto& quorumIndex : quorumIndexes) {
|
|
assert(quorumIndex);
|
|
auto quorum = GetQuorum(params.type, quorumIndex);
|
|
assert(quorum != nullptr);
|
|
result.emplace_back(quorum);
|
|
}
|
|
|
|
if (storeCache) {
|
|
LOCK(quorumsCacheCs);
|
|
scanQuorumsCache.insert(cacheKey, result);
|
|
}
|
|
|
|
if (result.size() > maxCount) {
|
|
result.resize(maxCount);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash)
|
|
{
|
|
CBlockIndex* pindexQuorum;
|
|
{
|
|
LOCK(cs_main);
|
|
auto quorumIt = mapBlockIndex.find(quorumHash);
|
|
|
|
if (quorumIt == mapBlockIndex.end()) {
|
|
LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- block %s not found", __func__, quorumHash.ToString());
|
|
return nullptr;
|
|
}
|
|
pindexQuorum = quorumIt->second;
|
|
}
|
|
return GetQuorum(llmqType, pindexQuorum);
|
|
}
|
|
|
|
CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const CBlockIndex* pindexQuorum)
|
|
{
|
|
assert(pindexQuorum);
|
|
|
|
auto quorumHash = pindexQuorum->GetBlockHash();
|
|
|
|
// we must check this before we look into the cache. Reorgs might have happened which would mean we might have
|
|
// cached quorums which are not in the active chain anymore
|
|
if (!HasQuorum(llmqType, quorumHash)) {
|
|
return nullptr;
|
|
}
|
|
|
|
LOCK(quorumsCacheCs);
|
|
|
|
auto it = quorumsCache.find(std::make_pair(llmqType, quorumHash));
|
|
if (it != quorumsCache.end()) {
|
|
return it->second;
|
|
}
|
|
|
|
CFinalCommitment qc;
|
|
uint256 minedBlockHash;
|
|
if (!quorumBlockProcessor->GetMinedCommitment(llmqType, quorumHash, qc, minedBlockHash)) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto& params = Params().GetConsensus().llmqs.at(llmqType);
|
|
|
|
auto quorum = std::make_shared<CQuorum>(params, blsWorker);
|
|
|
|
if (!BuildQuorumFromCommitment(qc, pindexQuorum, minedBlockHash, quorum)) {
|
|
return nullptr;
|
|
}
|
|
|
|
quorumsCache.emplace(std::make_pair(llmqType, quorumHash), quorum);
|
|
|
|
return quorum;
|
|
}
|
|
|
|
} // namespace llmq
|