// Copyright (c) 2018 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 "quorums_debug.h" #include "activemasternode.h" #include "chainparams.h" #include "net.h" #include "net_processing.h" #include "scheduler.h" #include "spork.h" #include "validation.h" #include "evo/deterministicmns.h" #include "quorums_utils.h" namespace llmq { CDKGDebugManager* quorumDKGDebugManager; UniValue CDKGDebugSessionStatus::ToJson(int detailLevel) const { UniValue ret(UniValue::VOBJ); if (!Params().GetConsensus().llmqs.count((Consensus::LLMQType)llmqType) || quorumHash.IsNull()) { return ret; } std::vector dmnMembers; if (detailLevel == 2) { dmnMembers = CLLMQUtils::GetAllQuorumMembers((Consensus::LLMQType) llmqType, quorumHash); } ret.push_back(Pair("llmqType", llmqType)); ret.push_back(Pair("quorumHash", quorumHash.ToString())); ret.push_back(Pair("quorumHeight", (int)quorumHeight)); ret.push_back(Pair("phase", (int)phase)); ret.push_back(Pair("sentContributions", sentContributions)); ret.push_back(Pair("sentComplaint", sentComplaint)); ret.push_back(Pair("sentJustification", sentJustification)); ret.push_back(Pair("sentPrematureCommitment", sentPrematureCommitment)); ret.push_back(Pair("aborted", aborted)); struct ArrOrCount { int count{0}; UniValue arr{UniValue::VARR}; }; ArrOrCount badMembers; ArrOrCount weComplain; ArrOrCount receivedContributions; ArrOrCount receivedComplaints; ArrOrCount receivedJustifications; ArrOrCount receivedPrematureCommitments; ArrOrCount complaintsFromMembers; auto add = [&](ArrOrCount& v, size_t idx, bool flag) { if (flag) { if (detailLevel == 0) { v.count++; } else if (detailLevel == 1) { v.arr.push_back((int)idx); } else if (detailLevel == 2) { UniValue a(UniValue::VOBJ); a.push_back(Pair("memberIndex", (int)idx)); if (idx < dmnMembers.size()) { a.push_back(Pair("proTxHash", dmnMembers[idx]->proTxHash.ToString())); } v.arr.push_back(a); } } }; auto push = [&](ArrOrCount& v, const std::string& name) { if (detailLevel == 0) { ret.push_back(Pair(name, v.count)); } else { ret.push_back(Pair(name, v.arr)); } }; for (size_t i = 0; i < members.size(); i++) { const auto& m = members[i]; add(badMembers, i, m.bad); add(weComplain, i, m.weComplain); add(receivedContributions, i, m.receivedContribution); add(receivedComplaints, i, m.receivedComplaint); add(receivedJustifications, i, m.receivedJustification); add(receivedPrematureCommitments, i, m.receivedPrematureCommitment); } push(badMembers, "badMembers"); push(weComplain, "weComplain"); push(receivedContributions, "receivedContributions"); push(receivedComplaints, "receivedComplaints"); push(receivedJustifications, "receivedJustifications"); push(receivedPrematureCommitments, "receivedPrematureCommitments"); if (detailLevel == 2) { UniValue arr(UniValue::VARR); for (const auto& dmn : dmnMembers) { arr.push_back(dmn->proTxHash.ToString()); } ret.push_back(Pair("allMembers", arr)); } return ret; } CDKGDebugManager::CDKGDebugManager(CScheduler* scheduler) { for (const auto& p : Params().GetConsensus().llmqs) { ResetLocalSessionStatus(p.first, uint256(), 0); } if (scheduler) { scheduler->scheduleEvery([&]() { SendLocalStatus(); }, 10); } } void CDKGDebugManager::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) { if (!sporkManager.IsSporkActive(SPORK_18_QUORUM_DEBUG_ENABLED)) { return; } if (strCommand == NetMsgType::QDEBUGSTATUS) { CDKGDebugStatus status; vRecv >> status; { LOCK(cs_main); connman.RemoveAskFor(::SerializeHash(status)); } ProcessDebugStatusMessage(pfrom->id, status); } } void CDKGDebugManager::ProcessDebugStatusMessage(NodeId nodeId, llmq::CDKGDebugStatus& status) { auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(status.proTxHash); if (!dmn) { if (nodeId != -1) { LOCK(cs_main); Misbehaving(nodeId, 10); } return; } { LOCK(cs); auto it = statusesForMasternodes.find(status.proTxHash); if (it != statusesForMasternodes.end()) { if (statuses[it->second].nTime >= status.nTime) { // we know a more recent status already return; } } } // check if all expected LLMQ types are present and valid std::set llmqTypes; for (const auto& p : status.sessions) { if (!Params().GetConsensus().llmqs.count((Consensus::LLMQType)p.first)) { if (nodeId != -1) { LOCK(cs_main); Misbehaving(nodeId, 10); } return; } const auto& params = Params().GetConsensus().llmqs.at((Consensus::LLMQType)p.first); if (p.second.llmqType != p.first || p.second.members.size() != (size_t)params.size) { if (nodeId != -1) { LOCK(cs_main); Misbehaving(nodeId, 10); } return; } llmqTypes.emplace((Consensus::LLMQType)p.first); } for (const auto& p : Params().GetConsensus().llmqs) { if (!llmqTypes.count(p.first)) { if (nodeId != -1) { LOCK(cs_main); Misbehaving(nodeId, 10); } return; } } // TODO batch verification/processing if (!status.sig.VerifyInsecure(dmn->pdmnState->pubKeyOperator, status.GetSignHash())) { if (nodeId != -1) { LOCK(cs_main); Misbehaving(nodeId, 10); } return; } LOCK(cs); auto it = statusesForMasternodes.find(status.proTxHash); if (it != statusesForMasternodes.end()) { statuses.erase(it->second); statusesForMasternodes.erase(it); } auto hash = ::SerializeHash(status); statuses[hash] = status; statusesForMasternodes[status.proTxHash] = hash; CInv inv(MSG_QUORUM_DEBUG_STATUS, hash); g_connman->RelayInv(inv, DMN_PROTO_VERSION); } UniValue CDKGDebugStatus::ToJson(int detailLevel) const { UniValue ret(UniValue::VOBJ); ret.push_back(Pair("proTxHash", proTxHash.ToString())); ret.push_back(Pair("height", (int)nHeight)); ret.push_back(Pair("time", nTime)); ret.push_back(Pair("timeStr", DateTimeStrFormat("%Y-%m-%d %H:%M:%S", nTime))); UniValue sessionsJson(UniValue::VOBJ); for (const auto& p : sessions) { if (!Params().GetConsensus().llmqs.count((Consensus::LLMQType)p.first)) { continue; } const auto& params = Params().GetConsensus().llmqs.at((Consensus::LLMQType)p.first); sessionsJson.push_back(Pair(params.name, p.second.ToJson(detailLevel))); } ret.push_back(Pair("session", sessionsJson)); return ret; } bool CDKGDebugManager::AlreadyHave(const CInv& inv) { LOCK(cs); if (!sporkManager.IsSporkActive(SPORK_18_QUORUM_DEBUG_ENABLED)) { return true; } return statuses.count(inv.hash) != 0; } bool CDKGDebugManager::GetDebugStatus(const uint256& hash, llmq::CDKGDebugStatus& ret) { LOCK(cs); auto it = statuses.find(hash); if (it == statuses.end()) { return false; } ret = it->second; return true; } bool CDKGDebugManager::GetDebugStatusForMasternode(const uint256& proTxHash, llmq::CDKGDebugStatus& ret) { LOCK(cs); auto it = statusesForMasternodes.find(proTxHash); if (it == statusesForMasternodes.end()) { return false; } ret = statuses.at(it->second); return true; } void CDKGDebugManager::GetLocalDebugStatus(llmq::CDKGDebugStatus& ret) { LOCK(cs); ret = localStatus; ret.proTxHash = activeMasternodeInfo.proTxHash; } void CDKGDebugManager::ResetLocalSessionStatus(Consensus::LLMQType llmqType, const uint256& quorumHash, int quorumHeight) { LOCK(cs); auto& params = Params().GetConsensus().llmqs.at(llmqType); localStatus.nTime = GetAdjustedTime(); auto& session = localStatus.sessions[llmqType]; session.llmqType = llmqType; session.quorumHash = quorumHash; session.quorumHeight = (uint32_t)quorumHeight; session.phase = 0; session.statusBitset = 0; session.members.clear(); session.members.resize((size_t)params.size); } void CDKGDebugManager::UpdateLocalStatus(std::function&& func) { LOCK(cs); if (func(localStatus)) { localStatus.nTime = GetAdjustedTime(); } } void CDKGDebugManager::UpdateLocalSessionStatus(Consensus::LLMQType llmqType, std::function&& func) { LOCK(cs); if (func(localStatus.sessions.at(llmqType))) { localStatus.nTime = GetAdjustedTime(); } } void CDKGDebugManager::UpdateLocalMemberStatus(Consensus::LLMQType llmqType, size_t memberIdx, std::function&& func) { LOCK(cs); if (func(localStatus.sessions.at(llmqType).members.at(memberIdx))) { localStatus.nTime = GetAdjustedTime(); } } void CDKGDebugManager::SendLocalStatus() { if (!fMasternodeMode) { return; } if (activeMasternodeInfo.proTxHash.IsNull()) { return; } if (!sporkManager.IsSporkActive(SPORK_18_QUORUM_DEBUG_ENABLED)) { return; } CDKGDebugStatus status; { LOCK(cs); status = localStatus; } int64_t nTime = status.nTime; status.proTxHash = activeMasternodeInfo.proTxHash; status.nTime = 0; status.sig = CBLSSignature(); uint256 newHash = ::SerializeHash(status); if (newHash == lastStatusHash) { return; } lastStatusHash = newHash; status.nTime = nTime; status.sig = activeMasternodeInfo.blsKeyOperator->Sign(status.GetSignHash()); ProcessDebugStatusMessage(-1, status); } }