From 45064d8dc99856099ea33f71678f138e1946cd6d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 16 Mar 2020 19:14:34 +0100 Subject: [PATCH 01/10] Rename sigSharesToSend to sigShareBatchesToSend --- src/llmq/quorums_signing_shares.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/llmq/quorums_signing_shares.cpp b/src/llmq/quorums_signing_shares.cpp index cff1a59b6f04..05e638e48f51 100644 --- a/src/llmq/quorums_signing_shares.cpp +++ b/src/llmq/quorums_signing_shares.cpp @@ -983,7 +983,7 @@ void CSigSharesManager::CollectSigSharesToAnnounce(std::unordered_map> sigSharesToRequest; - std::unordered_map> sigSharesToSend; + std::unordered_map> sigShareBatchesToSend; std::unordered_map> sigSharesToAnnounce; std::unordered_map> sigSessionAnnouncements; @@ -1009,7 +1009,7 @@ bool CSigSharesManager::SendMessages() { LOCK(cs); CollectSigSharesToRequest(sigSharesToRequest); - CollectSigSharesToSend(sigSharesToSend); + CollectSigSharesToSend(sigShareBatchesToSend); CollectSigSharesToAnnounce(sigSharesToAnnounce); for (auto& p : sigSharesToRequest) { @@ -1017,7 +1017,7 @@ bool CSigSharesManager::SendMessages() p2.second.sessionId = addSigSesAnnIfNeeded(p.first, p2.first); } } - for (auto& p : sigSharesToSend) { + for (auto& p : sigShareBatchesToSend) { for (auto& p2 : p.second) { p2.second.sessionId = addSigSesAnnIfNeeded(p.first, p2.first); } @@ -1076,8 +1076,8 @@ bool CSigSharesManager::SendMessages() } } - auto jt = sigSharesToSend.find(pnode->GetId()); - if (jt != sigSharesToSend.end()) { + auto jt = sigShareBatchesToSend.find(pnode->GetId()); + if (jt != sigShareBatchesToSend.end()) { size_t totalSigsCount = 0; std::vector msgs; for (auto& p : jt->second) { From b212f21c155a7ac1cf8771571b11ec2998393b42 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 17 Mar 2020 07:08:37 +0100 Subject: [PATCH 02/10] Implement new way of concentrated signature recovery Instead of propagating all sig shares to all LLMQ members, this will now make all members send their individual sig share to a single member, which is then responsible for the recovery and propagation of the recovered signature. This process is repeated by all members every second for another target/recovering member, until a recovered signature appears. --- src/chainparams.cpp | 5 + src/consensus/params.h | 3 + src/llmq/quorums_signing_shares.cpp | 171 ++++++++++++++++++++++++++-- src/llmq/quorums_signing_shares.h | 35 +++++- src/protocol.cpp | 2 + src/protocol.h | 1 + 6 files changed, 209 insertions(+), 8 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 0bd36d6693b1..cc16444524ea 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -173,6 +173,7 @@ static Consensus::LLMQParams llmq_test = { .signingActiveQuorumCount = 2, // just a few ones to allow easier testing .keepOldConnections = 3, + .recoveryMembers = 3, }; // this one is for devnets only @@ -192,6 +193,7 @@ static Consensus::LLMQParams llmq_devnet = { .signingActiveQuorumCount = 3, // just a few ones to allow easier testing .keepOldConnections = 4, + .recoveryMembers = 6, }; static Consensus::LLMQParams llmq50_60 = { @@ -210,6 +212,7 @@ static Consensus::LLMQParams llmq50_60 = { .signingActiveQuorumCount = 24, // a full day worth of LLMQs .keepOldConnections = 25, + .recoveryMembers = 25, }; static Consensus::LLMQParams llmq400_60 = { @@ -228,6 +231,7 @@ static Consensus::LLMQParams llmq400_60 = { .signingActiveQuorumCount = 4, // two days worth of LLMQs .keepOldConnections = 5, + .recoveryMembers = 100, }; // Used for deployment and min-proto-version signalling, so it needs a higher threshold @@ -247,6 +251,7 @@ static Consensus::LLMQParams llmq400_85 = { .signingActiveQuorumCount = 4, // four days worth of LLMQs .keepOldConnections = 5, + .recoveryMembers = 100, }; diff --git a/src/consensus/params.h b/src/consensus/params.h index 0c5be250f0c0..f6cf8c60ff4b 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -114,6 +114,9 @@ struct LLMQParams { // Used for inter-quorum communication. This is the number of quorums for which we should keep old connections. This // should be at least one more then the active quorums set. int keepOldConnections; + + // How many members should we try to send all sigShares to before we give up. + int recoveryMembers; }; /** diff --git a/src/llmq/quorums_signing_shares.cpp b/src/llmq/quorums_signing_shares.cpp index 05e638e48f51..aee2e7d62a10 100644 --- a/src/llmq/quorums_signing_shares.cpp +++ b/src/llmq/quorums_signing_shares.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -236,6 +237,23 @@ void CSigSharesManager::ProcessMessage(CNode* pfrom, const std::string& strComma return; } + if (sporkManager.IsSporkActive(SPORK_21_QUORUM_ALL_CONNECTED)) { + if (strCommand == NetMsgType::QSIGSHARE) { + std::vector sigShares; + vRecv >> sigShares; + + if (sigShares.size() > MAX_MSGS_SIG_SHARES) { + LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- too many sigs in QSIGSHARE message. cnt=%d, max=%d, node=%d\n", __func__, sigShares.size(), MAX_MSGS_SIG_SHARES, pfrom->GetId()); + BanNode(pfrom->GetId()); + return; + } + + for (auto& sigShare : sigShares) { + ProcessMessageSigShare(pfrom->GetId(), sigShare, connman); + } + } + } + if (strCommand == NetMsgType::QSIGSESANN) { std::vector msgs; vRecv >> msgs; @@ -465,6 +483,57 @@ bool CSigSharesManager::ProcessMessageBatchedSigShares(CNode* pfrom, const CBatc return true; } +void CSigSharesManager::ProcessMessageSigShare(NodeId fromId, const CSigShare& sigShare, CConnman& connman) +{ + auto quorum = quorumManager->GetQuorum(sigShare.llmqType, sigShare.quorumHash); + if (!quorum) { + return; + } + if (!CLLMQUtils::IsQuorumActive(sigShare.llmqType, quorum->qc.quorumHash)) { + // quorum is too old + return; + } + if (!quorum->IsMember(activeMasternodeInfo.proTxHash)) { + // we're not a member so we can't verify it (we actually shouldn't have received it) + return; + } + if (quorum->quorumVvec == nullptr) { + // TODO we should allow to ask other nodes for the quorum vvec if we missed it in the DKG + LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- we don't have the quorum vvec for %s, no verification possible. node=%d\n", __func__, + quorum->qc.quorumHash.ToString(), fromId); + return; + } + + if (sigShare.quorumMember >= quorum->members.size()) { + LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- quorumMember out of bounds\n", __func__); + BanNode(fromId); + return; + } + if (!quorum->qc.validMembers[sigShare.quorumMember]) { + LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- quorumMember not valid\n", __func__); + BanNode(fromId); + return; + } + + { + LOCK(cs); + + if (sigShares.Has(sigShare.GetKey())) { + return; + } + + if (quorumSigningManager->HasRecoveredSigForId((Consensus::LLMQType)sigShare.llmqType, sigShare.id)) { + return; + } + + auto& nodeState = nodeStates[fromId]; + nodeState.pendingIncomingSigShares.Add(sigShare.GetKey(), sigShare); + } + + LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- signHash=%s, id=%s, msgHash=%s, member=%d, node=%d\n", __func__, + sigShare.GetSignHash().ToString(), sigShare.id.ToString(), sigShare.msgHash.ToString(), sigShare.quorumMember, fromId); +} + bool CSigSharesManager::PreVerifyBatchedSigShares(NodeId nodeId, const CSigSharesNodeState::SessionInfo& session, const CBatchedSigShares& batchedSigShares, bool& retBan) { retBan = false; @@ -668,8 +737,10 @@ void CSigSharesManager::ProcessSigShare(NodeId nodeId, const CSigShare& sigShare // prepare node set for direct-push in case this is our sig share std::set quorumNodes; - if (sigShare.quorumMember == quorum->GetMemberIndex(activeMasternodeInfo.proTxHash)) { - quorumNodes = connman.GetMasternodeQuorumNodes((Consensus::LLMQType) sigShare.llmqType, sigShare.quorumHash); + if (!sporkManager.IsSporkActive(SPORK_21_QUORUM_ALL_CONNECTED)) { + if (sigShare.quorumMember == quorum->GetMemberIndex(activeMasternodeInfo.proTxHash)) { + quorumNodes = connman.GetMasternodeQuorumNodes((Consensus::LLMQType) sigShare.llmqType, sigShare.quorumHash); + } } if (quorumSigningManager->HasRecoveredSigForId(llmqType, sigShare.id)) { @@ -780,6 +851,21 @@ void CSigSharesManager::TryRecoverSig(const CQuorumCPtr& quorum, const uint256& quorumSigningManager->ProcessRecoveredSig(-1, rs, quorum, connman); } +CDeterministicMNCPtr CSigSharesManager::SelectMemberForRecovery(const CQuorumCPtr& quorum, const uint256 &id, int attempt) +{ + assert(attempt < quorum->members.size()); + + std::vector> v; + v.reserve(quorum->members.size()); + for (size_t i = 0; i < quorum->members.size(); i++) { + auto h = ::SerializeHash(std::make_pair(quorum->members[i]->proTxHash, id)); + v.emplace_back(h, quorum->members[i]); + } + std::sort(v.begin(), v.end()); + + return v[attempt].second; +} + void CSigSharesManager::CollectSigSharesToRequest(std::unordered_map>& sigSharesToRequest) { AssertLockHeld(cs); @@ -928,6 +1014,43 @@ void CSigSharesManager::CollectSigSharesToSend(std::unordered_map>& sigSharesToSend, const std::vector& vNodes) +{ + AssertLockHeld(cs); + + std::unordered_map proTxToNode; + for (CNode* pnode : vNodes) { + if (pnode->verifiedProRegTxHash.IsNull()) { + continue; + } + proTxToNode.emplace(pnode->verifiedProRegTxHash, pnode); + } + + auto curTime = GetAdjustedTime(); + + for (auto& p : signedSessions) { + if (p.second.attempt > p.second.quorum->params.recoveryMembers) { + continue; + } + + if (curTime >= p.second.nextAttemptTime) { + p.second.nextAttemptTime = curTime + SEND_FOR_RECOVERY_TIMEOUT; + auto dmn = SelectMemberForRecovery(p.second.quorum, p.second.sigShare.id, p.second.attempt); + LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- sending to %s, signHash=%s\n", __func__, + dmn->proTxHash.ToString(), p.second.sigShare.GetSignHash().ToString()); + p.second.attempt++; + + auto it = proTxToNode.find(dmn->proTxHash); + if (it == proTxToNode.end()) { + continue; + } + + auto& m = sigSharesToSend[it->second->GetId()]; + m.emplace_back(p.second.sigShare); + } + } +} + void CSigSharesManager::CollectSigSharesToAnnounce(std::unordered_map>& sigSharesToAnnounce) { AssertLockHeld(cs); @@ -984,6 +1107,7 @@ bool CSigSharesManager::SendMessages() { std::unordered_map> sigSharesToRequest; std::unordered_map> sigShareBatchesToSend; + std::unordered_map> sigSharesToSend; std::unordered_map> sigSharesToAnnounce; std::unordered_map> sigSessionAnnouncements; @@ -1006,11 +1130,17 @@ bool CSigSharesManager::SendMessages() return session->sendSessionId; }; + std::vector vNodesCopy = g_connman->CopyNodeVector(CConnman::FullyConnectedOnly); + { LOCK(cs); - CollectSigSharesToRequest(sigSharesToRequest); - CollectSigSharesToSend(sigShareBatchesToSend); - CollectSigSharesToAnnounce(sigSharesToAnnounce); + if (!sporkManager.IsSporkActive(SPORK_21_QUORUM_ALL_CONNECTED)) { + CollectSigSharesToRequest(sigSharesToRequest); + CollectSigSharesToSend(sigShareBatchesToSend); + CollectSigSharesToAnnounce(sigSharesToAnnounce); + } else { + CollectSigSharesToSend(sigSharesToSend, vNodesCopy); + } for (auto& p : sigSharesToRequest) { for (auto& p2 : p.second) { @@ -1031,8 +1161,6 @@ bool CSigSharesManager::SendMessages() bool didSend = false; - std::vector vNodesCopy = g_connman->CopyNodeVector(CConnman::FullyConnectedOnly); - for (auto& pnode : vNodesCopy) { CNetMsgMaker msgMaker(pnode->GetSendVersion()); @@ -1119,6 +1247,25 @@ bool CSigSharesManager::SendMessages() didSend = true; } } + + auto lt = sigSharesToSend.find(pnode->GetId()); + if (lt != sigSharesToSend.end()) { + std::vector msgs; + for (auto& sigShare : lt->second) { + LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::SendMessages -- QSIGSHARE signHash=%s, node=%d\n", + sigShare.GetSignHash().ToString(), pnode->GetId()); + msgs.emplace_back(std::move(sigShare)); + if (msgs.size() == MAX_MSGS_SIG_SHARES) { + g_connman->PushMessage(pnode, msgMaker.Make(NetMsgType::QSIGSHARE, msgs), false); + msgs.clear(); + didSend = true; + } + } + if (!msgs.empty()) { + g_connman->PushMessage(pnode, msgMaker.Make(NetMsgType::QSIGSHARE, msgs), false); + didSend = true; + } + } } // looped through all nodes, release them @@ -1285,6 +1432,7 @@ void CSigSharesManager::RemoveSigSharesForSession(const uint256& signHash) sigSharesRequested.EraseAllForSignHash(signHash); sigSharesToAnnounce.EraseAllForSignHash(signHash); sigShares.EraseAllForSignHash(signHash); + signedSessions.erase(signHash); timeSeenForSessions.erase(signHash); } @@ -1431,6 +1579,15 @@ void CSigSharesManager::Sign(const CQuorumCPtr& quorum, const uint256& id, const LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- signed sigShare. signHash=%s, id=%s, msgHash=%s, llmqType=%d, quorum=%s, time=%s\n", __func__, signHash.ToString(), sigShare.id.ToString(), sigShare.msgHash.ToString(), quorum->params.type, quorum->qc.quorumHash.ToString(), t.count()); ProcessSigShare(-1, sigShare, *g_connman, quorum); + + if (sporkManager.IsSporkActive(SPORK_21_QUORUM_ALL_CONNECTED)) { + LOCK(cs); + auto& session = signedSessions[sigShare.GetSignHash()]; + session.sigShare = sigShare; + session.quorum = quorum; + session.nextAttemptTime = 0; + session.attempt = 0; + } } // causes all known sigShares to be re-announced diff --git a/src/llmq/quorums_signing_shares.h b/src/llmq/quorums_signing_shares.h index 3573ef7f7313..f1c6d38ed15e 100644 --- a/src/llmq/quorums_signing_shares.h +++ b/src/llmq/quorums_signing_shares.h @@ -30,7 +30,6 @@ namespace llmq // typedef std::pair SigShareKey; -// this one does not get transmitted over the wire as it is batched inside CBatchedSigShares class CSigShare { public: @@ -54,6 +53,22 @@ class CSigShare assert(!key.first.IsNull()); return key.first; } + + ADD_SERIALIZE_METHODS + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(llmqType); + READWRITE(quorumHash); + READWRITE(quorumMember); + READWRITE(id); + READWRITE(msgHash); + READWRITE(sigShare); + + if (ser_action.ForRead()) { + UpdateKey(); + } + } }; // Nodes will first announce a signing session with a sessionId to be used in all future P2P messages related to that @@ -327,6 +342,16 @@ class CSigSharesNodeState void RemoveSession(const uint256& signHash); }; +class CSignedSession +{ +public: + CSigShare sigShare; + CQuorumCPtr quorum; + + int64_t nextAttemptTime{0}; + int attempt{0}; +}; + class CSigSharesManager : public CRecoveredSigsListener { static const int64_t SESSION_NEW_SHARES_TIMEOUT = 60; @@ -339,6 +364,9 @@ class CSigSharesManager : public CRecoveredSigsListener // 400 is the maximum quorum size, so this is also the maximum number of sigs we need to support const size_t MAX_MSGS_TOTAL_BATCHED_SIGS = 400; + const int64_t SEND_FOR_RECOVERY_TIMEOUT = 1; + const size_t MAX_MSGS_SIG_SHARES = 32; + private: CCriticalSection cs; @@ -346,6 +374,7 @@ class CSigSharesManager : public CRecoveredSigsListener CThreadInterrupt workInterrupt; SigShareMap sigShares; + std::unordered_map signedSessions; // stores time of last receivedSigShare. Used to detect timeouts std::unordered_map timeSeenForSessions; @@ -381,12 +410,15 @@ class CSigSharesManager : public CRecoveredSigsListener void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig); + static CDeterministicMNCPtr SelectMemberForRecovery(const CQuorumCPtr& quorum, const uint256& id, int attempt); + private: // all of these return false when the currently processed message should be aborted (as each message actually contains multiple messages) bool ProcessMessageSigSesAnn(CNode* pfrom, const CSigSesAnn& ann, CConnman& connman); bool ProcessMessageSigSharesInv(CNode* pfrom, const CSigSharesInv& inv, CConnman& connman); bool ProcessMessageGetSigShares(CNode* pfrom, const CSigSharesInv& inv, CConnman& connman); bool ProcessMessageBatchedSigShares(CNode* pfrom, const CBatchedSigShares& batchedSigShares, CConnman& connman); + void ProcessMessageSigShare(NodeId fromId, const CSigShare& sigShare, CConnman& connman); bool VerifySigSharesInv(NodeId from, Consensus::LLMQType llmqType, const CSigSharesInv& inv); bool PreVerifyBatchedSigShares(NodeId nodeId, const CSigSharesNodeState::SessionInfo& session, const CBatchedSigShares& batchedSigShares, bool& retBan); @@ -417,6 +449,7 @@ class CSigSharesManager : public CRecoveredSigsListener bool SendMessages(); void CollectSigSharesToRequest(std::unordered_map>& sigSharesToRequest); void CollectSigSharesToSend(std::unordered_map>& sigSharesToSend); + void CollectSigSharesToSend(std::unordered_map>& sigSharesToSend, const std::vector& vNodes); void CollectSigSharesToAnnounce(std::unordered_map>& sigSharesToAnnounce); bool SignPendingSigShares(); void WorkThreadMain(); diff --git a/src/protocol.cpp b/src/protocol.cpp index e114ade4bd7d..cc49478ebc2b 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -69,6 +69,7 @@ const char *QSIGSHARESINV="qsigsinv"; const char *QGETSIGSHARES="qgetsigs"; const char *QBSIGSHARES="qbsigs"; const char *QSIGREC="qsigrec"; +const char *QSIGSHARE="qsigshare"; const char *CLSIG="clsig"; const char *ISLOCK="islock"; const char *MNAUTH="mnauth"; @@ -135,6 +136,7 @@ const static std::string allNetMessageTypes[] = { NetMsgType::QGETSIGSHARES, NetMsgType::QBSIGSHARES, NetMsgType::QSIGREC, + NetMsgType::QSIGSHARE, NetMsgType::CLSIG, NetMsgType::ISLOCK, NetMsgType::MNAUTH, diff --git a/src/protocol.h b/src/protocol.h index b89e0cd2b1e0..2a31dfe73850 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -266,6 +266,7 @@ extern const char *QSIGSHARESINV; extern const char *QGETSIGSHARES; extern const char *QBSIGSHARES; extern const char *QSIGREC; +extern const char *QSIGSHARE; extern const char *CLSIG; extern const char *ISLOCK; extern const char *MNAUTH; From 5edbeafceaca629e52a5fddcc21c19d2b6a7b34d Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Fri, 27 Mar 2020 17:29:28 +0100 Subject: [PATCH 03/10] Also run llmq-signing.py with spork21 enabled --- test/functional/llmq-signing.py | 6 ++++++ test/functional/test_runner.py | 1 + 2 files changed, 7 insertions(+) diff --git a/test/functional/llmq-signing.py b/test/functional/llmq-signing.py index 2262458f2934..5b1aecde2377 100755 --- a/test/functional/llmq-signing.py +++ b/test/functional/llmq-signing.py @@ -21,9 +21,15 @@ def set_test_params(self): self.set_dash_test_params(6, 5, fast_dip3_enforcement=True) self.set_dash_llmq_test_params(5, 3) + def add_options(self, parser): + parser.add_option("--spork21", dest="spork21", default=False, action="store_true", + help="Test with spork21 enabled") + def run_test(self): self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0) + if self.options.spork21: + self.nodes[0].spork("SPORK_21_QUORUM_ALL_CONNECTED", 0) self.wait_for_sporks_same() self.mine_quorum() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 1edcea5bd326..a3ba1a735f07 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -69,6 +69,7 @@ 'listtransactions.py', 'multikeysporks.py', 'llmq-signing.py', # NOTE: needs dash_hash to pass + 'llmq-signing.py --spork21', # NOTE: needs dash_hash to pass 'llmq-chainlocks.py', # NOTE: needs dash_hash to pass 'llmq-connections.py', # NOTE: needs dash_hash to pass 'llmq-simplepose.py', # NOTE: needs dash_hash to pass From 80533f6c0df206af5736872f39cf64eecf3819b1 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 Mar 2020 09:44:57 +0200 Subject: [PATCH 04/10] Implement "quorum selectquorum" RPC --- src/rpc/rpcquorums.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/rpc/rpcquorums.cpp b/src/rpc/rpcquorums.cpp index 7d8b4e85b13a..a5180294237c 100644 --- a/src/rpc/rpcquorums.cpp +++ b/src/rpc/rpcquorums.cpp @@ -13,6 +13,7 @@ #include #include #include +#include void quorum_list_help() { @@ -377,6 +378,54 @@ UniValue quorum_sigs_cmd(const JSONRPCRequest& request) } } +void quorum_selectquorum_help() +{ + throw std::runtime_error( + "quorum selectquorum llmqType \"id\"\n" + "Returns the quorum that would/should sign a request\n" + "\nArguments:\n" + "1. llmqType (int, required) LLMQ type.\n" + "2. \"id\" (string, required) Request id.\n" + ); +} + +UniValue quorum_selectquorum(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 3) { + quorum_selectquorum_help(); + } + + 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 id = ParseHashV(request.params[2], "id"); + + int tipHeight; + { + LOCK(cs_main); + tipHeight = chainActive.Height(); + } + + UniValue ret(UniValue::VOBJ); + + auto quorum = llmq::quorumSigningManager->SelectQuorumForSigning(llmqType, tipHeight, id); + if (!quorum) { + throw JSONRPCError(RPC_MISC_ERROR, "no quorums active"); + } + ret.push_back(Pair("quorumHash", quorum->qc.quorumHash.ToString())); + + UniValue recoveryMembers(UniValue::VARR); + for (int i = 0; i < quorum->params.recoveryMembers; i++) { + auto dmn = llmq::quorumSigSharesManager->SelectMemberForRecovery(quorum, id, i); + recoveryMembers.push_back(dmn->proTxHash.ToString()); + } + ret.push_back(Pair("recoveryMembers", recoveryMembers)); + + return ret; +} + void quorum_dkgsimerror_help() { throw std::runtime_error( @@ -427,6 +476,7 @@ UniValue quorum_dkgsimerror(const JSONRPCRequest& request) " hasrecsig - Test if a valid recovered signature is present\n" " getrecsig - Get a recovered signature\n" " isconflicting - Test if a conflict exists\n" + " selectquorum - Return the quorum that would/should sign a request\n" ); } @@ -451,6 +501,8 @@ UniValue quorum(const JSONRPCRequest& request) return quorum_memberof(request); } else if (command == "sign" || command == "hasrecsig" || command == "getrecsig" || command == "isconflicting") { return quorum_sigs_cmd(request); + } else if (command == "selectquorum") { + return quorum_selectquorum(request); } else if (command == "dkgsimerror") { return quorum_dkgsimerror(request); } else { From beaf3f40b205ff4f4e854ba8899d7fd528143c34 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 Mar 2020 09:46:09 +0200 Subject: [PATCH 05/10] Implement DashTestFramework.get_mninfo --- test/functional/test_framework/test_framework.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 1f84490969ee..d281b4de354f 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -939,12 +939,15 @@ def get_quorum_masternodes(self, q): qi = self.nodes[0].quorum('info', 100, q) result = [] for m in qi['members']: - for mn in self.mninfo: - if mn.proTxHash == m['proTxHash']: - result.append(mn) - break + result.append(self.get_mninfo(m['proTxHash'])) return result + def get_mninfo(self, proTxHash): + for mn in self.mninfo: + if mn.proTxHash == proTxHash: + return mn + return None + def wait_for_mnauth(self, node, count, timeout=10): def test(): pi = node.getpeerinfo() From 91f811edd9df72b1a1cb7ac1193d630e65d277d6 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Mon, 30 Mar 2020 09:45:53 +0200 Subject: [PATCH 06/10] Test failure of recovery on first node --- test/functional/llmq-signing.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/functional/llmq-signing.py b/test/functional/llmq-signing.py index 5b1aecde2377..642ec75fd02f 100755 --- a/test/functional/llmq-signing.py +++ b/test/functional/llmq-signing.py @@ -100,5 +100,24 @@ def assert_sigs_nochange(hasrecsigs, isconflicting1, isconflicting2, timeout): self.mninfo[i].node.quorum("sign", 100, id, msgHash) wait_for_sigs(True, False, True, 15) + id = "0000000000000000000000000000000000000000000000000000000000000002" + + if self.options.spork21: + # Isolate the node that is responsible for the recovery of a signature and assert that recovery fails + q = self.nodes[0].quorum('selectquorum', 100, id) + mn = self.get_mninfo(q['recoveryMembers'][0]) + mn.node.setnetworkactive(False) + wait_until(lambda: mn.node.getconnectioncount() == 0) + for i in range(4): + self.mninfo[i].node.quorum("sign", 100, id, msgHash) + assert_sigs_nochange(False, False, False, 3) + # Need to re-connect so that it later gets the recovered sig + mn.node.setnetworkactive(True) + connect_nodes(mn.node, 0) + # Let 1 second pass so that the next node is used for recovery, which should succeed + self.bump_mocktime(1) + set_node_times(self.nodes, self.mocktime) + wait_for_sigs(True, False, True, 5) + if __name__ == '__main__': LLMQSigningTest().main() From 900ee0f19435bf0fbcbce551c9b96d18040bc5f9 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 Mar 2020 19:56:02 +0200 Subject: [PATCH 07/10] Use range based for loop in SelectMemberForRecovery --- src/llmq/quorums_signing_shares.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/llmq/quorums_signing_shares.cpp b/src/llmq/quorums_signing_shares.cpp index aee2e7d62a10..3013a4d73bbf 100644 --- a/src/llmq/quorums_signing_shares.cpp +++ b/src/llmq/quorums_signing_shares.cpp @@ -857,9 +857,9 @@ CDeterministicMNCPtr CSigSharesManager::SelectMemberForRecovery(const CQuorumCPt std::vector> v; v.reserve(quorum->members.size()); - for (size_t i = 0; i < quorum->members.size(); i++) { - auto h = ::SerializeHash(std::make_pair(quorum->members[i]->proTxHash, id)); - v.emplace_back(h, quorum->members[i]); + for (const auto& dmn : quorum->members) { + auto h = ::SerializeHash(std::make_pair(dmn->proTxHash, id)); + v.emplace_back(h, dmn); } std::sort(v.begin(), v.end()); From 76b6614fe566bb87bb3f7c747ca8576e1b4f9498 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Tue, 31 Mar 2020 19:58:03 +0200 Subject: [PATCH 08/10] Use auto keyword in CollectSigSharesToSend --- src/llmq/quorums_signing_shares.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llmq/quorums_signing_shares.cpp b/src/llmq/quorums_signing_shares.cpp index 3013a4d73bbf..b59586187184 100644 --- a/src/llmq/quorums_signing_shares.cpp +++ b/src/llmq/quorums_signing_shares.cpp @@ -1019,7 +1019,7 @@ void CSigSharesManager::CollectSigSharesToSend(std::unordered_map proTxToNode; - for (CNode* pnode : vNodes) { + for (const auto& pnode : vNodes) { if (pnode->verifiedProRegTxHash.IsNull()) { continue; } From 47bd5d7edec36eb0c9384215bcb16b4dd5e2b791 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 1 Apr 2020 16:12:04 +0200 Subject: [PATCH 09/10] Only update id in spork21 case Co-Authored-By: UdjinM6 --- test/functional/llmq-signing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/llmq-signing.py b/test/functional/llmq-signing.py index 642ec75fd02f..95fe08bc116c 100755 --- a/test/functional/llmq-signing.py +++ b/test/functional/llmq-signing.py @@ -100,9 +100,9 @@ def assert_sigs_nochange(hasrecsigs, isconflicting1, isconflicting2, timeout): self.mninfo[i].node.quorum("sign", 100, id, msgHash) wait_for_sigs(True, False, True, 15) - id = "0000000000000000000000000000000000000000000000000000000000000002" - if self.options.spork21: + id = "0000000000000000000000000000000000000000000000000000000000000002" + # Isolate the node that is responsible for the recovery of a signature and assert that recovery fails q = self.nodes[0].quorum('selectquorum', 100, id) mn = self.get_mninfo(q['recoveryMembers'][0]) From 148bbdd5cfa5c83413e07bf9b2e46d623bc423a7 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 1 Apr 2020 18:05:44 +0200 Subject: [PATCH 10/10] Use GetTime instead of GetAdjustedTime Co-Authored-By: UdjinM6 --- src/llmq/quorums_signing_shares.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llmq/quorums_signing_shares.cpp b/src/llmq/quorums_signing_shares.cpp index b59586187184..9a4d96c8ff75 100644 --- a/src/llmq/quorums_signing_shares.cpp +++ b/src/llmq/quorums_signing_shares.cpp @@ -1026,7 +1026,7 @@ void CSigSharesManager::CollectSigSharesToSend(std::unordered_mapverifiedProRegTxHash, pnode); } - auto curTime = GetAdjustedTime(); + auto curTime = GetTime(); for (auto& p : signedSessions) { if (p.second.attempt > p.second.quorum->params.recoveryMembers) {