diff --git a/src/Makefile.am b/src/Makefile.am index 42dbfacb18008..0b61f12adb801 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -141,6 +141,7 @@ BITCOIN_CORE_H = \ keystore.h \ dbwrapper.h \ limitedmap.h \ + llmq/quorums.h \ llmq/quorums_blockprocessor.h \ llmq/quorums_commitment.h \ llmq/quorums_debug.h \ @@ -253,6 +254,7 @@ libdash_server_a_SOURCES = \ governance-validators.cpp \ governance-vote.cpp \ governance-votedb.cpp \ + llmq/quorums.cpp \ llmq/quorums_blockprocessor.cpp \ llmq/quorums_commitment.cpp \ llmq/quorums_debug.cpp \ diff --git a/src/dsnotificationinterface.cpp b/src/dsnotificationinterface.cpp index 8450cc26b5c4f..f5f5185b4f9ba 100644 --- a/src/dsnotificationinterface.cpp +++ b/src/dsnotificationinterface.cpp @@ -9,6 +9,7 @@ #include "masternode-payments.h" #include "masternode-sync.h" #include "privatesend.h" +#include "llmq/quorums.h" #include "llmq/quorums_dkgsessionmgr.h" #ifdef ENABLE_WALLET #include "privatesend-client.h" @@ -60,6 +61,7 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, con #endif // ENABLE_WALLET instantsend.UpdatedBlockTip(pindexNew); governance.UpdatedBlockTip(pindexNew, connman); + llmq::quorumManager->UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload); llmq::quorumDKGSessionManager->UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload); } diff --git a/src/llmq/quorums.cpp b/src/llmq/quorums.cpp new file mode 100644 index 0000000000000..ed71e0438308d --- /dev/null +++ b/src/llmq/quorums.cpp @@ -0,0 +1,393 @@ +// 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.h" +#include "quorums_blockprocessor.h" +#include "quorums_commitment.h" +#include "quorums_dkgsession.h" +#include "quorums_dkgsessionmgr.h" +#include "quorums_init.h" +#include "quorums_utils.h" + +#include "evo/specialtx.h" + +#include "activemasternode.h" +#include "chainparams.h" +#include "init.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 << (uint8_t)q.params.type; + hw << q.quorumHash; + for (const auto& dmn : q.members) { + hw << dmn->proTxHash; + } + return hw.GetHash(); +} + +CQuorum::~CQuorum() +{ + // most likely the thread is already done + stopCachePopulatorThread = true; + if (cachePopulatorThread.joinable()) { + cachePopulatorThread.join(); + } +} + +void CQuorum::Init(const uint256& _quorumHash, int _height, const std::vector& _members, const std::vector& _validMembers, const CBLSPublicKey& _quorumPublicKey) +{ + quorumHash = _quorumHash; + height = _height; + members = _members; + validMembers = _validMembers; + quorumPublicKey = _quorumPublicKey; +} + +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 validMembers[i]; + } + } + return false; +} + +CBLSPublicKey CQuorum::GetPubKeyShare(size_t memberIdx) const +{ + if (quorumVvec == nullptr || memberIdx >= members.size() || !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; +} + +bool 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); + } + return true; +} + +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(std::move(qv)); + } else { + return false; + } + + evoDb.Read(std::make_pair(DB_QUORUM_SK_SHARE, dbKey), skShare); + + return true; +} + +void CQuorum::StartCachePopulatorThread(std::shared_ptr _this) +{ + if (_this->quorumVvec == nullptr) { + return; + } + + cxxtimer::Timer t(true); + LogPrintf("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("quorum-cachepop"); + for (size_t i = 0; i < _this->members.size() && !_this->stopCachePopulatorThread && !ShutdownRequested(); i++) { + if (_this->validMembers[i]) { + _this->GetPubKeyShare(i); + } + } + LogPrintf("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, const CBlockIndex* pindexFork, bool fInitialDownload) +{ + if (fInitialDownload) { + return; + } + + LOCK(cs_main); + + for (auto& p : Params().GetConsensus().llmqs) { + EnsureQuorumConnections(p.first, pindexNew); + } +} + +void CQuorumManager::EnsureQuorumConnections(Consensus::LLMQType llmqType, const CBlockIndex* pindexNew) +{ + AssertLockHeld(cs_main); + + const auto& params = Params().GetConsensus().llmqs.at(llmqType); + + auto myProTxHash = activeMasternodeInfo.proTxHash; + auto lastQuorums = ScanQuorums(llmqType, (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 = chainActive[curDkgHeight]->GetBlockHash(); + connmanQuorumsToDelete.erase(curDkgBlock); + + for (auto& quorum : lastQuorums) { + if (!quorum->IsMember(myProTxHash) && !GetBoolArg("-watchquorums", DEFAULT_WATCH_QUORUMS)) { + continue; + } + + if (!g_connman->HasMasternodeQuorumNodes(llmqType, quorum->quorumHash)) { + std::set connections; + if (quorum->IsMember(myProTxHash)) { + connections = CLLMQUtils::GetQuorumConnections(llmqType, quorum->quorumHash, myProTxHash); + } else { + auto cindexes = CLLMQUtils::CalcDeterministicWatchConnections(llmqType, quorum->quorumHash, quorum->members.size(), 1); + for (auto idx : cindexes) { + connections.emplace(quorum->members[idx]->pdmnState->addr); + } + } + if (!connections.empty()) { + std::string debugMsg = strprintf("CQuorumManager::%s -- adding masternodes quorum connections for quorum %s:\n", __func__, quorum->quorumHash.ToString()); + for (auto& c : connections) { + debugMsg += strprintf(" %s\n", c.ToString(false)); + } + LogPrintf(debugMsg); + g_connman->AddMasternodeQuorumNodes(llmqType, quorum->quorumHash, connections); + } + } + connmanQuorumsToDelete.erase(quorum->quorumHash); + } + + for (auto& qh : connmanQuorumsToDelete) { + LogPrintf("CQuorumManager::%s -- removing masternodes quorum connections for quorum %s:\n", __func__, qh.ToString()); + g_connman->RemoveMasternodeQuorumNodes(llmqType, qh); + } +} + +bool CQuorumManager::BuildQuorumFromCommitment(const CFinalCommitment& qc, std::shared_ptr& quorum) const +{ + AssertLockHeld(cs_main); + + if (!mapBlockIndex.count(qc.quorumHash)) { + LogPrintf("CQuorumManager::%s -- block %s not found", __func__, qc.quorumHash.ToString()); + return false; + } + auto& params = Params().GetConsensus().llmqs.at((Consensus::LLMQType)qc.llmqType); + auto quorumIndex = mapBlockIndex[qc.quorumHash]; + auto members = CLLMQUtils::GetAllQuorumMembers((Consensus::LLMQType)qc.llmqType, qc.quorumHash); + + quorum->Init(qc.quorumHash, quorumIndex->nHeight, members, qc.validMembers, qc.quorumPublicKey); + + if (!quorum->ReadContributions(evoDb)) { + if (BuildQuorumContributions(qc, quorum)) { + quorum->WriteContributions(evoDb); + } else { + LogPrintf("CQuorumManager::%s -- quorum.ReadContributions and BuildQuorumContributions for block %s failed", __func__, qc.quorumHash.ToString()); + } + } + + // 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& quorum) const +{ + std::vector memberIndexes; + std::vector vvecs; + BLSSecretKeyVector skContributions; + if (!dkgManager.GetVerifiedContributions((Consensus::LLMQType)fqc.llmqType, fqc.quorumHash, fqc.validMembers, memberIndexes, vvecs, skContributions)) { + return false; + } + + BLSVerificationVectorPtr quorumVvec; + CBLSSecretKey skShare; + + cxxtimer::Timer t2(true); + quorumVvec = blsWorker.BuildQuorumVerificationVector(vvecs); + if (quorumVvec == nullptr) { + LogPrintf("CQuorumManager::%s -- failed to build quorumVvec\n", __func__); + } + skShare = blsWorker.AggregateSecretKeys(skContributions); + if (!skShare.IsValid()) { + LogPrintf("CQuorumManager::%s -- failed to build skShare\n", __func__); + } + t2.stop(); + + LogPrintf("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 CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, size_t maxCount) +{ + LOCK(cs_main); + return ScanQuorums(llmqType, chainActive.Tip()->GetBlockHash(), maxCount); +} + +std::vector CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, const uint256& startBlock, size_t maxCount) +{ + std::vector result; + + LOCK(cs_main); + if (!mapBlockIndex.count(startBlock)) { + return result; + } + + result.reserve(maxCount); + + CBlockIndex* pindex = mapBlockIndex[startBlock]; + + while (pindex != NULL && result.size() < maxCount && deterministicMNManager->IsDIP3Active(pindex->nHeight)) { + if (HasQuorum(llmqType, pindex->GetBlockHash())) { + auto quorum = GetQuorum(llmqType, pindex->GetBlockHash()); + if (quorum) { + result.emplace_back(quorum); + } + } + + // TODO speedup (skip blocks where no quorums could have been mined) + pindex = pindex->pprev; + } + + return result; +} + +CQuorumCPtr CQuorumManager::SelectQuorum(Consensus::LLMQType llmqType, const uint256& selectionHash, size_t poolSize) +{ + LOCK(cs_main); + return SelectQuorum(llmqType, chainActive.Tip()->GetBlockHash(), selectionHash, poolSize); +} + +CQuorumCPtr CQuorumManager::SelectQuorum(Consensus::LLMQType llmqType, const uint256& startBlock, const uint256& selectionHash, size_t poolSize) +{ + auto quorums = ScanQuorums(llmqType, startBlock, poolSize); + if (quorums.empty()) { + return nullptr; + } + + std::vector> scores; + scores.reserve(quorums.size()); + for (size_t i = 0; i < quorums.size(); i++) { + CHashWriter h(SER_NETWORK, 0); + h << (uint8_t)llmqType; + h << quorums[i]->quorumHash; + h << selectionHash; + scores.emplace_back(h.GetHash(), i); + } + std::sort(scores.begin(), scores.end()); + return quorums[scores.front().second]; +} + +CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash) +{ + AssertLockHeld(cs_main); + + // 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; + if (!quorumBlockProcessor->GetMinedCommitment(llmqType, quorumHash, qc)) { + return nullptr; + } + + auto& params = Params().GetConsensus().llmqs.at(llmqType); + + auto quorum = std::make_shared(params, blsWorker); + if (!BuildQuorumFromCommitment(qc, quorum)) { + return nullptr; + } + + quorumsCache.emplace(std::make_pair(llmqType, quorumHash), quorum); + + return quorum; +} + +CQuorumCPtr CQuorumManager::GetNewestQuorum(Consensus::LLMQType llmqType) +{ + auto quorums = ScanQuorums(llmqType, 1); + if (quorums.empty()) { + return nullptr; + } + return quorums.front(); +} + +} diff --git a/src/llmq/quorums.h b/src/llmq/quorums.h new file mode 100644 index 0000000000000..5977c053be91b --- /dev/null +++ b/src/llmq/quorums.h @@ -0,0 +1,116 @@ +// 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. + +#ifndef DASH_QUORUMS_H +#define DASH_QUORUMS_H + +#include "evo/evodb.h" +#include "evo/deterministicmns.h" + +#include "validationinterface.h" +#include "consensus/params.h" + +#include "bls/bls.h" +#include "bls/bls_worker.h" + +namespace llmq +{ + +class CDKGSessionManager; + +/** + * An object of this class represents a quorum which was mined on-chain (through a quorum commitment) + * It at least contains informations about the members and the quorum public key which is needed to verify recovered + * signatures from this quorum. + * + * In case the local node is a member of the same quorum and successfully participated in the DKG, the quorum object + * will also contain the secret key share and the quorum verification vector. The quorum vvec is then used to recover + * the public key shares of individual members, which are needed to verify signature shares of these members. + */ +class CQuorum +{ + friend class CQuorumManager; +public: + const Consensus::LLMQParams& params; + uint256 quorumHash; + int height; + std::vector members; + std::vector validMembers; + CBLSPublicKey quorumPublicKey; + + // These are only valid when we either participated in the DKG or fully watched it + BLSVerificationVectorPtr quorumVvec; + CBLSSecretKey skShare; + +private: + // Recovery of public key shares is very slow, so we start a background thread that pre-populates a cache so that + // the public key shares are ready when needed later + mutable CBLSWorkerCache blsCache; + std::atomic stopCachePopulatorThread; + std::thread cachePopulatorThread; + +public: + CQuorum(const Consensus::LLMQParams& _params, CBLSWorker& _blsWorker) : params(_params), blsCache(_blsWorker), stopCachePopulatorThread(false) {} + ~CQuorum(); + void Init(const uint256& quorumHash, int height, const std::vector& members, const std::vector& validMembers, const CBLSPublicKey& quorumPublicKey); + + bool IsMember(const uint256& proTxHash) const; + bool IsValidMember(const uint256& proTxHash) const; + int GetMemberIndex(const uint256& proTxHash) const; + + CBLSPublicKey GetPubKeyShare(size_t memberIdx) const; + CBLSSecretKey GetSkShare() const; + +private: + bool WriteContributions(CEvoDB& evoDb); + bool ReadContributions(CEvoDB& evoDb); + static void StartCachePopulatorThread(std::shared_ptr _this); +}; +typedef std::shared_ptr CQuorumPtr; +typedef std::shared_ptr CQuorumCPtr; + +/** + * The quorum manager maintains quorums which were mined on chain. When a quorum is requested from the manager, + * it will lookup the commitment (through CQuorumBlockProcessor) and build a CQuorum object from it. + * + * It is also responsible for initialization of the inter-quorum connections for new quorums. + */ +class CQuorumManager +{ +private: + CEvoDB& evoDb; + CBLSWorker& blsWorker; + CDKGSessionManager& dkgManager; + + CCriticalSection quorumsCacheCs; + std::map, CQuorumPtr> quorumsCache; + +public: + CQuorumManager(CEvoDB& _evoDb, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager); + + void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload); + +public: + bool HasQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash); + + // all these methods will lock cs_main + CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType,const uint256& quorumHash); + CQuorumCPtr GetNewestQuorum(Consensus::LLMQType llmqType); + std::vector ScanQuorums(Consensus::LLMQType llmqType, size_t maxCount); + std::vector ScanQuorums(Consensus::LLMQType llmqType, const uint256& startBlock, size_t maxCount); + CQuorumCPtr SelectQuorum(Consensus::LLMQType llmqType, const uint256& selectionHash, size_t poolSize); + CQuorumCPtr SelectQuorum(Consensus::LLMQType llmqType, const uint256& startBlock, const uint256& selectionHash, size_t poolSize); + +private: + void EnsureQuorumConnections(Consensus::LLMQType llmqType, const CBlockIndex *pindexNew); + + bool BuildQuorumFromCommitment(const CFinalCommitment& qc, std::shared_ptr& quorum) const; + bool BuildQuorumContributions(const CFinalCommitment& fqc, std::shared_ptr& quorum) const; +}; + +extern CQuorumManager* quorumManager; + +} + +#endif //DASH_QUORUMS_H diff --git a/src/llmq/quorums_init.cpp b/src/llmq/quorums_init.cpp index 2e8739b9376b4..b646a9e02e668 100644 --- a/src/llmq/quorums_init.cpp +++ b/src/llmq/quorums_init.cpp @@ -4,6 +4,7 @@ #include "quorums_init.h" +#include "quorums.h" #include "quorums_blockprocessor.h" #include "quorums_commitment.h" #include "quorums_debug.h" @@ -21,10 +22,13 @@ void InitLLMQSystem(CEvoDB& evoDb, CScheduler* scheduler) quorumDKGDebugManager = new CDKGDebugManager(scheduler); quorumBlockProcessor = new CQuorumBlockProcessor(evoDb); quorumDKGSessionManager = new CDKGSessionManager(evoDb, blsWorker); + quorumManager = new CQuorumManager(evoDb, blsWorker, *quorumDKGSessionManager); } void DestroyLLMQSystem() { + delete quorumManager; + quorumManager = NULL; delete quorumDKGSessionManager; quorumDKGSessionManager = NULL; delete quorumBlockProcessor; diff --git a/src/rpc/rpcquorums.cpp b/src/rpc/rpcquorums.cpp index c9038fcdbe24e..ed4d3d72f3c3c 100644 --- a/src/rpc/rpcquorums.cpp +++ b/src/rpc/rpcquorums.cpp @@ -2,12 +2,116 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "chainparams.h" #include "server.h" #include "validation.h" +#include "llmq/quorums.h" #include "llmq/quorums_debug.h" #include "llmq/quorums_dkgsession.h" +void quorum_list_help() +{ + throw std::runtime_error( + "quorum list (count)\n" + "\nArguments:\n" + "1. count (number, optional) Number of quorums to list.\n" + ); +} + +UniValue quorum_list(const JSONRPCRequest& request) +{ + if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2)) + quorum_list_help(); + + LOCK(cs_main); + + int count = 10; + if (request.params.size() > 1) { + count = ParseInt32V(request.params[1], "count"); + } + + UniValue ret(UniValue::VOBJ); + + for (auto& p : Params().GetConsensus().llmqs) { + UniValue v(UniValue::VARR); + + auto quorums = llmq::quorumManager->ScanQuorums(p.first, chainActive.Tip()->GetBlockHash(), count); + for (auto& q : quorums) { + v.push_back(q->quorumHash.ToString()); + } + + ret.push_back(Pair(p.second.name, v)); + } + + + return ret; +} + +void quorum_info_help() +{ + throw std::runtime_error( + "quorum info \"llmqType\" \"blockquorumHash\" (includeSkShare)\n" + "\nArguments:\n" + "1. \"llmqType\" (int, required) LLMQ type.\n" + "2. \"quorumHash\" (string, required) Block hash of quorum.\n" + "3. \"includeSkShare\" (boolean, optional) Include secret key share in output.\n" + ); +} + +UniValue quorum_info(const JSONRPCRequest& request) +{ + if (request.fHelp || (request.params.size() != 3 && request.params.size() != 4)) + quorum_info_help(); + + LOCK(cs_main); + + Consensus::LLMQType llmqType = (Consensus::LLMQType)ParseInt32V(request.params[1], "llmqType"); + if (!Params().GetConsensus().llmqs.count(llmqType)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type"); + } + + uint256 blockHash = ParseHashV(request.params[2], "quorumHash"); + bool includeSkShare = false; + if (request.params.size() > 3) { + includeSkShare = ParseBoolV(request.params[3], "includeSkShare"); + } + + auto quorum = llmq::quorumManager->GetQuorum(llmqType, blockHash); + if (!quorum) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found"); + } + + UniValue ret(UniValue::VOBJ); + + ret.push_back(Pair("height", quorum->height)); + ret.push_back(Pair("quorumHash", quorum->quorumHash.ToString())); + + UniValue membersArr(UniValue::VARR); + for (size_t i = 0; i < quorum->members.size(); i++) { + auto& dmn = quorum->members[i]; + UniValue mo(UniValue::VOBJ); + mo.push_back(Pair("proTxHash", dmn->proTxHash.ToString())); + mo.push_back(Pair("valid", quorum->validMembers[i])); + if (quorum->validMembers[i]) { + CBLSPublicKey pubKey = quorum->GetPubKeyShare(i); + if (pubKey.IsValid()) { + mo.push_back(Pair("pubKeyShare", pubKey.ToString())); + } + } + membersArr.push_back(mo); + } + + ret.push_back(Pair("members", membersArr)); + ret.push_back(Pair("quorumPublicKey", quorum->quorumPublicKey.ToString())); + CBLSSecretKey skShare = quorum->GetSkShare(); + if (includeSkShare && skShare.IsValid()) { + ret.push_back(Pair("secretKeyShare", skShare.ToString())); + } + + return ret; +} + void quorum_dkgstatus_help() { throw std::runtime_error( @@ -61,7 +165,11 @@ UniValue quorum(const JSONRPCRequest& request) std::string command = request.params[0].get_str(); - if (command == "dkgstatus") { + if (command == "list") { + return quorum_list(request); + } else if (command == "info") { + return quorum_info(request); + } else if (command == "dkgstatus") { return quorum_dkgstatus(request); } else { throw std::runtime_error("invalid command: " + command);