From 18950f923e1d7c26c8ef2c1eba300df0ed33bc30 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 1 Feb 2019 08:49:01 +0100 Subject: [PATCH] Optimize DKG debug message processing for performance and lower bandwidth (#2672) * Allow sub-batch verification in CBLSInsecureBatchVerifier * Implement batch verification of CDKGDebugStatus messages * Use uint8_t for statusBitset in CDKGDebugMemberStatus and CDKGDebugSessionStatus No need to waste one byte per member and per LLMQ type. * Reserve 4k of buffer for CSerializedNetMsg buffer Profiling has shown that a lot of time is spent in resizing the data vector when large messages are involved. * Remove nHeight from CDKGDebugStatus This field changes every block and causes all masternodes to propagate its status for every block, even if nothing DKG related has changed. * Leave out session statuses when we're not a member of that session Otherwise MNs which are not members of DKG sessions will spam the network * Remove receivedFinalCommitment from CDKGDebugSessionStatus This is not bound to a session and thus is prone to spam the network when final commitments are propagated in the finalization phase. * Add "minableCommitments" to "quorum dkgstatus" * Hold cs_main while calling GetMinableCommitment * Abort processing of pending debug messages when spork18 gets disabled * Don't ask for debug messages when we've already seen them "statuses" only contains the current messages but none of the old messages, so nodes kept re-requesting old messages. --- qa/rpc-tests/test_framework/test_framework.py | 14 +- src/bls/bls_batchverifier.h | 22 ++- src/llmq/quorums_blockprocessor.cpp | 8 - src/llmq/quorums_debug.cpp | 184 +++++++++++++----- src/llmq/quorums_debug.h | 24 ++- src/llmq/quorums_dkgsession.cpp | 4 + src/llmq/quorums_dkgsessionhandler.cpp | 25 +-- src/llmq/quorums_signing.cpp | 4 +- src/llmq/quorums_signing_shares.cpp | 4 +- src/netmessagemaker.h | 1 + src/rpc/rpcquorums.cpp | 21 +- 11 files changed, 222 insertions(+), 89 deletions(-) diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index 9673503527a2a..5984b858263e0 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -446,7 +446,11 @@ def wait_for_quorum_phase(self, phase, check_received_messages, check_received_m while time() - t < timeout: all_ok = True for mn in self.mninfo: - s = mn.node.quorum("dkgstatus")["session"]["llmq_10"] + s = mn.node.quorum("dkgstatus")["session"] + if "llmq_10" not in s: + all_ok = False + break + s = s["llmq_10"] if "phase" not in s: all_ok = False break @@ -467,8 +471,12 @@ def wait_for_quorum_commitment(self, timeout = 15): while time() - t < timeout: all_ok = True for node in self.nodes: - s = node.quorum("dkgstatus")["session"]["llmq_10"] - if "receivedFinalCommitment" not in s or not s["receivedFinalCommitment"]: + s = node.quorum("dkgstatus") + if "minableCommitments" not in s: + all_ok = False + break + s = s["minableCommitments"] + if "llmq_10" not in s: all_ok = False break if all_ok: diff --git a/src/bls/bls_batchverifier.h b/src/bls/bls_batchverifier.h index 4282cb61ca2b3..86df3c2d42ed9 100644 --- a/src/bls/bls_batchverifier.h +++ b/src/bls/bls_batchverifier.h @@ -25,6 +25,9 @@ class CBLSInsecureBatchVerifier typedef typename MessageMap::iterator MessageMapIterator; typedef std::map> MessagesBySourceMap; + bool perMessageFallback; + size_t subBatchSize; + MessageMap messages; MessagesBySourceMap messagesBySource; @@ -33,15 +36,32 @@ class CBLSInsecureBatchVerifier std::set badMessages; public: + CBLSInsecureBatchVerifier(bool _perMessageFallback, size_t _subBatchSize = 0) : + perMessageFallback(_perMessageFallback), + subBatchSize(_subBatchSize) + { + } + void PushMessage(const SourceId& sourceId, const MessageId& msgId, const uint256& msgHash, const CBLSSignature& sig, const CBLSPublicKey& pubKey) { assert(sig.IsValid() && pubKey.IsValid()); auto it = messages.emplace(msgId, Message{msgId, msgHash, sig, pubKey}).first; messagesBySource[sourceId].emplace_back(it); + + if (subBatchSize != 0 && messages.size() >= subBatchSize) { + Verify(); + ClearMessages(); + } + } + + void ClearMessages() + { + messages.clear(); + messagesBySource.clear(); } - void Verify(bool perMessageFallback) + void Verify() { std::map> byMessageHash; diff --git a/src/llmq/quorums_blockprocessor.cpp b/src/llmq/quorums_blockprocessor.cpp index 93563db732ef6..2c54f2560f55e 100644 --- a/src/llmq/quorums_blockprocessor.cpp +++ b/src/llmq/quorums_blockprocessor.cpp @@ -337,14 +337,6 @@ void CQuorumBlockProcessor::AddMinableCommitment(const CFinalCommitment& fqc) } } - quorumDKGDebugManager->UpdateLocalSessionStatus((Consensus::LLMQType)fqc.llmqType, [&](CDKGDebugSessionStatus& status) { - if (status.quorumHash != fqc.quorumHash || status.receivedFinalCommitment) { - return false; - } - status.receivedFinalCommitment = true; - return true; - }); - // We only relay the new commitment if it's new or better then the old one if (relay) { CInv inv(MSG_QUORUM_FINAL_COMMITMENT, commitmentHash); diff --git a/src/llmq/quorums_debug.cpp b/src/llmq/quorums_debug.cpp index e4c02d293e572..ea79cfef07ba2 100644 --- a/src/llmq/quorums_debug.cpp +++ b/src/llmq/quorums_debug.cpp @@ -5,6 +5,7 @@ #include "quorums_debug.h" #include "activemasternode.h" +#include "bls/bls_batchverifier.h" #include "chainparams.h" #include "net.h" #include "net_processing.h" @@ -95,7 +96,6 @@ UniValue CDKGDebugSessionStatus::ToJson(int detailLevel) const push(receivedComplaints, "receivedComplaints"); push(receivedJustifications, "receivedJustifications"); push(receivedPrematureCommitments, "receivedPrematureCommitments"); - ret.push_back(Pair("receivedFinalCommitment", receivedFinalCommitment)); if (detailLevel == 2) { UniValue arr(UniValue::VARR); @@ -108,12 +108,9 @@ UniValue CDKGDebugSessionStatus::ToJson(int detailLevel) const return ret; } -CDKGDebugManager::CDKGDebugManager(CScheduler* scheduler) +CDKGDebugManager::CDKGDebugManager(CScheduler* _scheduler) : + scheduler(_scheduler) { - for (const auto& p : Params().GetConsensus().llmqs) { - ResetLocalSessionStatus(p.first, uint256(), 0); - } - if (scheduler) { scheduler->scheduleEvery([&]() { SendLocalStatus(); @@ -131,73 +128,136 @@ void CDKGDebugManager::ProcessMessage(CNode* pfrom, const std::string& strComman CDKGDebugStatus status; vRecv >> status; + uint256 hash = ::SerializeHash(status); + { LOCK(cs_main); - connman.RemoveAskFor(::SerializeHash(status)); + connman.RemoveAskFor(hash); + } + + bool ban = false; + if (!PreVerifyDebugStatusMessage(hash, status, ban)) { + if (ban) { + LOCK(cs_main); + Misbehaving(pfrom->id, 10); + return; + } } - ProcessDebugStatusMessage(pfrom->id, status); + LOCK(cs); + + pendingIncomingStatuses.emplace(hash, std::make_pair(std::move(status), pfrom->id)); + + ScheduleProcessPending(); } } -void CDKGDebugManager::ProcessDebugStatusMessage(NodeId nodeId, llmq::CDKGDebugStatus& status) +bool CDKGDebugManager::PreVerifyDebugStatusMessage(const uint256& hash, llmq::CDKGDebugStatus& status, bool& retBan) { + retBan = false; + auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(status.proTxHash); if (!dmn) { - if (nodeId != -1) { - LOCK(cs_main); - Misbehaving(nodeId, 10); - } - return; + retBan = true; + return false; } { LOCK(cs); + + if (!seenStatuses.emplace(hash, GetTimeMillis()).second) { + return false; + } + 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; + return false; } } } - // check if all expected LLMQ types are present and valid - std::set llmqTypes; + // check if all present LLMQ types are valid 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; + retBan = true; + return false; } 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; + retBan = true; + return false; } - 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; + + return true; +} + +void CDKGDebugManager::ScheduleProcessPending() +{ + AssertLockHeld(cs); + + if (hasScheduledProcessPending) { + return; + } + + scheduler->schedule([&] { + ProcessPending(); + }, boost::chrono::system_clock::now() + boost::chrono::milliseconds(100)); +} + +void CDKGDebugManager::ProcessPending() +{ + decltype(pendingIncomingStatuses) pend; + + { + LOCK(cs); + hasScheduledProcessPending = false; + pend = std::move(pendingIncomingStatuses); + } + + if (!sporkManager.IsSporkActive(SPORK_18_QUORUM_DEBUG_ENABLED)) { + return; + } + + CBLSInsecureBatchVerifier batchVerifier(true, 8); + for (const auto& p : pend) { + const auto& hash = p.first; + const auto& status = p.second.first; + auto nodeId = p.second.second; + auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(status.proTxHash); + if (!dmn) { + continue; } + batchVerifier.PushMessage(nodeId, hash, status.GetSignHash(), status.sig, dmn->pdmnState->pubKeyOperator); } - // TODO batch verification/processing - if (!status.sig.VerifyInsecure(dmn->pdmnState->pubKeyOperator, status.GetSignHash())) { - if (nodeId != -1) { - LOCK(cs_main); - Misbehaving(nodeId, 10); + batchVerifier.Verify(); + + if (!batchVerifier.badSources.empty()) { + LOCK(cs_main); + for (auto& nodeId : batchVerifier.badSources) { + Misbehaving(nodeId, 100); } + } + for (const auto& p : pend) { + const auto& hash = p.first; + const auto& status = p.second.first; + auto nodeId = p.second.second; + if (batchVerifier.badMessages.count(p.first)) { + continue; + } + + ProcessDebugStatusMessage(hash, status); + } +} + +// status must have a validated signature +void CDKGDebugManager::ProcessDebugStatusMessage(const uint256& hash, const llmq::CDKGDebugStatus& status) +{ + auto dmn = deterministicMNManager->GetListAtChainTip().GetMN(status.proTxHash); + if (!dmn) { return; } @@ -208,8 +268,6 @@ void CDKGDebugManager::ProcessDebugStatusMessage(NodeId nodeId, llmq::CDKGDebugS statusesForMasternodes.erase(it); } - auto hash = ::SerializeHash(status); - statuses[hash] = status; statusesForMasternodes[status.proTxHash] = hash; @@ -222,7 +280,6 @@ 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))); @@ -248,7 +305,7 @@ bool CDKGDebugManager::AlreadyHave(const CInv& inv) return true; } - return statuses.count(inv.hash) != 0; + return statuses.count(inv.hash) != 0 || seenStatuses.count(inv.hash) != 0; } bool CDKGDebugManager::GetDebugStatus(const uint256& hash, llmq::CDKGDebugStatus& ret) @@ -280,16 +337,30 @@ void CDKGDebugManager::GetLocalDebugStatus(llmq::CDKGDebugStatus& ret) ret.proTxHash = activeMasternodeInfo.proTxHash; } -void CDKGDebugManager::ResetLocalSessionStatus(Consensus::LLMQType llmqType, const uint256& quorumHash, int quorumHeight) +void CDKGDebugManager::ResetLocalSessionStatus(Consensus::LLMQType llmqType) { LOCK(cs); - auto& params = Params().GetConsensus().llmqs.at(llmqType); + auto it = localStatus.sessions.find(llmqType); + if (it == localStatus.sessions.end()) { + return; + } + localStatus.sessions.erase(it); localStatus.nTime = GetAdjustedTime(); +} - auto& session = localStatus.sessions[llmqType]; +void CDKGDebugManager::InitLocalSessionStatus(Consensus::LLMQType llmqType, const uint256& quorumHash, int quorumHeight) +{ + LOCK(cs); + + auto it = localStatus.sessions.find(llmqType); + if (it == localStatus.sessions.end()) { + it = localStatus.sessions.emplace((uint8_t)llmqType, CDKGDebugSessionStatus()).first; + } + auto& params = Params().GetConsensus().llmqs.at(llmqType); + auto& session = it->second; session.llmqType = llmqType; session.quorumHash = quorumHash; session.quorumHeight = (uint32_t)quorumHeight; @@ -297,7 +368,6 @@ void CDKGDebugManager::ResetLocalSessionStatus(Consensus::LLMQType llmqType, con session.statusBitset = 0; session.members.clear(); session.members.resize((size_t)params.size); - session.receivedFinalCommitment = false; } void CDKGDebugManager::UpdateLocalStatus(std::function&& func) @@ -311,7 +381,13 @@ void CDKGDebugManager::UpdateLocalStatus(std::function&& func) { LOCK(cs); - if (func(localStatus.sessions.at(llmqType))) { + + auto it = localStatus.sessions.find(llmqType); + if (it == localStatus.sessions.end()) { + return; + } + + if (func(it->second)) { localStatus.nTime = GetAdjustedTime(); } } @@ -319,7 +395,13 @@ void CDKGDebugManager::UpdateLocalSessionStatus(Consensus::LLMQType llmqType, st void CDKGDebugManager::UpdateLocalMemberStatus(Consensus::LLMQType llmqType, size_t memberIdx, std::function&& func) { LOCK(cs); - if (func(localStatus.sessions.at(llmqType).members.at(memberIdx))) { + + auto it = localStatus.sessions.find(llmqType); + if (it == localStatus.sessions.end()) { + return; + } + + if (func(it->second.members.at(memberIdx))) { localStatus.nTime = GetAdjustedTime(); } } @@ -356,7 +438,7 @@ void CDKGDebugManager::SendLocalStatus() status.nTime = nTime; status.sig = activeMasternodeInfo.blsKeyOperator->Sign(status.GetSignHash()); - ProcessDebugStatusMessage(-1, status); + ProcessDebugStatusMessage(newHash, status); } } diff --git a/src/llmq/quorums_debug.h b/src/llmq/quorums_debug.h index 30c2cf741b436..136d0ab3d7940 100644 --- a/src/llmq/quorums_debug.h +++ b/src/llmq/quorums_debug.h @@ -36,7 +36,7 @@ class CDKGDebugMemberStatus bool receivedJustification : 1; bool receivedPrematureCommitment : 1; }; - uint16_t statusBitset; + uint8_t statusBitset; }; std::set complaintsFromMembers; @@ -74,11 +74,10 @@ class CDKGDebugSessionStatus bool aborted : 1; }; - uint16_t statusBitset; + uint8_t statusBitset; }; std::vector members; - bool receivedFinalCommitment{false}; public: CDKGDebugSessionStatus() : statusBitset(0) {} @@ -95,7 +94,6 @@ class CDKGDebugSessionStatus READWRITE(phase); READWRITE(statusBitset); READWRITE(members); - READWRITE(receivedFinalCommitment); } UniValue ToJson(int detailLevel) const; @@ -106,7 +104,6 @@ class CDKGDebugStatus public: uint256 proTxHash; int64_t nTime{0}; - uint32_t nHeight{0}; std::map sessions; @@ -120,7 +117,6 @@ class CDKGDebugStatus { READWRITE(proTxHash); READWRITE(nTime); - READWRITE(nHeight); READWRITE(sessions); } @@ -145,8 +141,14 @@ class CDKGDebugStatus class CDKGDebugManager { private: + CScheduler* scheduler; + CCriticalSection cs; + std::map seenStatuses; + std::multimap> pendingIncomingStatuses; + bool hasScheduledProcessPending{false}; + std::map statuses; std::map statusesForMasternodes; @@ -154,17 +156,21 @@ class CDKGDebugManager uint256 lastStatusHash; public: - CDKGDebugManager(CScheduler* scheduler); + CDKGDebugManager(CScheduler* _scheduler); void ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman); - void ProcessDebugStatusMessage(NodeId nodeId, CDKGDebugStatus& status); + bool PreVerifyDebugStatusMessage(const uint256& hash, CDKGDebugStatus& status, bool& retBan); + void ScheduleProcessPending(); + void ProcessPending(); + void ProcessDebugStatusMessage(const uint256& hash, const CDKGDebugStatus& status); bool AlreadyHave(const CInv& inv); bool GetDebugStatus(const uint256& hash, CDKGDebugStatus& ret); bool GetDebugStatusForMasternode(const uint256& proTxHash, CDKGDebugStatus& ret); void GetLocalDebugStatus(CDKGDebugStatus& ret); - void ResetLocalSessionStatus(Consensus::LLMQType llmqType, const uint256& quorumHash, int quorumHeight); + void ResetLocalSessionStatus(Consensus::LLMQType llmqType); + void InitLocalSessionStatus(Consensus::LLMQType llmqType, const uint256& quorumHash, int quorumHeight); void UpdateLocalStatus(std::function&& func); void UpdateLocalSessionStatus(Consensus::LLMQType llmqType, std::function&& func); diff --git a/src/llmq/quorums_dkgsession.cpp b/src/llmq/quorums_dkgsession.cpp index 52ee98f2b6870..aff821bf06c59 100644 --- a/src/llmq/quorums_dkgsession.cpp +++ b/src/llmq/quorums_dkgsession.cpp @@ -94,6 +94,10 @@ bool CDKGSession::Init(int _height, const uint256& _quorumHash, const std::vecto } } + if (!myProTxHash.IsNull()) { + quorumDKGDebugManager->InitLocalSessionStatus(params.type, quorumHash, height); + } + CDKGLogger logger(*this, __func__); if (myProTxHash.IsNull()) { diff --git a/src/llmq/quorums_dkgsessionhandler.cpp b/src/llmq/quorums_dkgsessionhandler.cpp index f2cf23c707f61..d25e1ce9c6397 100644 --- a/src/llmq/quorums_dkgsessionhandler.cpp +++ b/src/llmq/quorums_dkgsessionhandler.cpp @@ -124,12 +124,6 @@ void CDKGSessionHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBl if (fNewPhase && phaseInt >= QuorumPhase_Initialized && phaseInt <= QuorumPhase_Idle) { phase = static_cast(phaseInt); } - - quorumDKGDebugManager->UpdateLocalStatus([&](CDKGDebugStatus& status) { - bool changed = status.nHeight != pindexNew->nHeight; - status.nHeight = (uint32_t)pindexNew->nHeight; - return changed; - }); } void CDKGSessionHandler::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) @@ -202,13 +196,14 @@ void CDKGSessionHandler::WaitForNextPhase(QuorumPhase curPhase, } if (nextPhase == QuorumPhase_Initialized) { - quorumDKGDebugManager->ResetLocalSessionStatus(params.type, quorumHash, quorumHeight); + quorumDKGDebugManager->ResetLocalSessionStatus(params.type); + } else { + quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) { + bool changed = status.phase != (uint8_t) nextPhase; + status.phase = (uint8_t) nextPhase; + return changed; + }); } - quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) { - bool changed = status.phase != (uint8_t)nextPhase; - status.phase = (uint8_t)nextPhase; - return changed; - }); } void CDKGSessionHandler::WaitForNewQuorum(const uint256& oldQuorumHash) @@ -475,6 +470,12 @@ void CDKGSessionHandler::HandleDKGRound() throw AbortPhaseException(); } + quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) { + bool changed = status.phase != (uint8_t) QuorumPhase_Initialized; + status.phase = (uint8_t) QuorumPhase_Initialized; + return changed; + }); + if (curSession->AreWeMember() || GetBoolArg("-watchquorums", DEFAULT_WATCH_QUORUMS)) { std::set connections; if (curSession->AreWeMember()) { diff --git a/src/llmq/quorums_signing.cpp b/src/llmq/quorums_signing.cpp index 081b5070e17c5..239978356da39 100644 --- a/src/llmq/quorums_signing.cpp +++ b/src/llmq/quorums_signing.cpp @@ -361,7 +361,7 @@ void CSigningManager::ProcessPendingRecoveredSigs(CConnman& connman) return; } - CBLSInsecureBatchVerifier batchVerifier; + CBLSInsecureBatchVerifier batchVerifier(false); size_t verifyCount = 0; for (auto& p : recSigsByNode) { @@ -376,7 +376,7 @@ void CSigningManager::ProcessPendingRecoveredSigs(CConnman& connman) } cxxtimer::Timer verifyTimer; - batchVerifier.Verify(false); + batchVerifier.Verify(); verifyTimer.stop(); LogPrint("llmq", "CSigningManager::%s -- verified recovered sig(s). count=%d, vt=%d, nodes=%d\n", __func__, verifyCount, verifyTimer.count(), recSigsByNode.size()); diff --git a/src/llmq/quorums_signing_shares.cpp b/src/llmq/quorums_signing_shares.cpp index 79189b2684ad3..d895e020ed39a 100644 --- a/src/llmq/quorums_signing_shares.cpp +++ b/src/llmq/quorums_signing_shares.cpp @@ -474,7 +474,7 @@ void CSigSharesManager::ProcessPendingSigShares(CConnman& connman) return; } - CBLSInsecureBatchVerifier batchVerifier; + CBLSInsecureBatchVerifier batchVerifier(true); size_t verifyCount = 0; for (auto& p : sigSharesByNodes) { @@ -502,7 +502,7 @@ void CSigSharesManager::ProcessPendingSigShares(CConnman& connman) } cxxtimer::Timer verifyTimer; - batchVerifier.Verify(true); + batchVerifier.Verify(); verifyTimer.stop(); LogPrint("llmq", "CSigSharesManager::%s -- verified sig shares. count=%d, vt=%d, nodes=%d\n", __func__, verifyCount, verifyTimer.count(), sigSharesByNodes.size()); diff --git a/src/netmessagemaker.h b/src/netmessagemaker.h index 8e8a6e4a026ee..7a050681bf90f 100644 --- a/src/netmessagemaker.h +++ b/src/netmessagemaker.h @@ -19,6 +19,7 @@ class CNetMsgMaker { CSerializedNetMsg msg; msg.command = std::move(sCommand); + msg.data.reserve(4 * 1024); CVectorWriter{ SER_NETWORK, nFlags | nVersion, msg.data, 0, std::forward(args)... }; return msg; } diff --git a/src/rpc/rpcquorums.cpp b/src/rpc/rpcquorums.cpp index 76ac9389d02c5..db63ea241b4ae 100644 --- a/src/rpc/rpcquorums.cpp +++ b/src/rpc/rpcquorums.cpp @@ -7,6 +7,7 @@ #include "validation.h" #include "llmq/quorums.h" +#include "llmq/quorums_blockprocessor.h" #include "llmq/quorums_debug.h" #include "llmq/quorums_dkgsession.h" #include "llmq/quorums_signing.h" @@ -153,7 +154,25 @@ UniValue quorum_dkgstatus(const JSONRPCRequest& request) } } - return status.ToJson(detailLevel); + auto ret = status.ToJson(detailLevel); + + LOCK(cs_main); + int tipHeight = chainActive.Height(); + + UniValue minableCommitments(UniValue::VOBJ); + for (const auto& p : Params().GetConsensus().llmqs) { + auto& params = p.second; + llmq::CFinalCommitment fqc; + if (llmq::quorumBlockProcessor->GetMinableCommitment(params.type, tipHeight, fqc)) { + UniValue obj(UniValue::VOBJ); + fqc.ToJson(obj); + minableCommitments.push_back(Pair(params.name, obj)); + } + } + + ret.push_back(Pair("minableCommitments", minableCommitments)); + + return ret; } void quorum_sign_help()