diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 0bd36d6693b1..d542d9dc25a8 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -122,6 +122,7 @@ void CChainParams::UpdateLLMQTestParams(int size, int threshold) { params.size = size; params.minSize = threshold; params.threshold = threshold; + params.dkgBadVotesThreshold = threshold; } void CChainParams::UpdateLLMQDevnetParams(int size, int threshold) @@ -130,6 +131,7 @@ void CChainParams::UpdateLLMQDevnetParams(int size, int threshold) params.size = size; params.minSize = threshold; params.threshold = threshold; + params.dkgBadVotesThreshold = threshold; } static CBlock FindDevNetGenesisBlock(const Consensus::Params& params, const CBlock &prevBlock, const CAmount& reward) @@ -168,7 +170,7 @@ static Consensus::LLMQParams llmq_test = { .dkgPhaseBlocks = 2, .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 18, - .dkgBadVotesThreshold = 8, + .dkgBadVotesThreshold = 2, .signingActiveQuorumCount = 2, // just a few ones to allow easier testing @@ -726,6 +728,7 @@ class CDevNetParams : public CChainParams { fDefaultConsistencyChecks = false; fRequireStandard = false; + fRequireRoutableExternalIP = true; fMineBlocksOnDemand = false; fAllowMultipleAddressesFromGroup = true; fAllowMultiplePorts = true; diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp index cda988c488af..5f80bc43cd3c 100644 --- a/src/evo/providertx.cpp +++ b/src/evo/providertx.cpp @@ -23,7 +23,7 @@ static bool CheckService(const uint256& proTxHash, const ProTx& proTx, CValidati if (!proTx.addr.IsValid()) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-ipaddr"); } - if (Params().NetworkIDString() != CBaseChainParams::REGTEST && !proTx.addr.IsRoutable()) { + if (Params().RequireRoutableExternalIP() && !proTx.addr.IsRoutable()) { return state.DoS(10, false, REJECT_INVALID, "bad-protx-ipaddr"); } diff --git a/src/init.cpp b/src/init.cpp index 0094cfdd8823..9ef043aa4b0f 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1459,7 +1459,7 @@ bool AppInitParameterInteraction() } if (gArgs.IsArgSet("-masternodeblsprivkey")) { - if (!gArgs.GetBoolArg("-listen", DEFAULT_LISTEN)) { + if (!gArgs.GetBoolArg("-listen", DEFAULT_LISTEN) && Params().RequireRoutableExternalIP()) { return InitError("Masternode must accept connections from outside, set -listen=1"); } if (!gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { diff --git a/src/llmq/quorums_dkgsession.cpp b/src/llmq/quorums_dkgsession.cpp index 1c77b9b1dafa..4a500cbe4fb3 100644 --- a/src/llmq/quorums_dkgsession.cpp +++ b/src/llmq/quorums_dkgsession.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -437,9 +438,49 @@ void CDKGSession::VerifyAndComplain(CDKGPendingMessages& pendingMessages) logger.Batch("verified contributions. time=%d", t1.count()); logger.Flush(); + VerifyConnectionAndMinProtoVersions(); + SendComplaint(pendingMessages); } +void CDKGSession::VerifyConnectionAndMinProtoVersions() +{ + if (!sporkManager.IsSporkActive(SPORK_21_QUORUM_ALL_CONNECTED)) { + return; + } + + CDKGLogger logger(*this, __func__); + + std::unordered_map protoMap; + g_connman->ForEachNode([&](const CNode* pnode) { + if (pnode->verifiedProRegTxHash.IsNull()) { + return; + } + protoMap.emplace(pnode->verifiedProRegTxHash, pnode->nVersion); + }); + + for (auto& m : members) { + if (m->dmn->proTxHash == myProTxHash) { + continue; + } + + auto it = protoMap.find(m->dmn->proTxHash); + if (it == protoMap.end()) { + m->badConnection = true; + logger.Batch("%s is not connected to us", m->dmn->proTxHash.ToString()); + } else if (it != protoMap.end() && it->second < MIN_MASTERNODE_PROTO_VERSION) { + m->badConnection = true; + logger.Batch("%s does not have min proto version %d (has %d)", m->dmn->proTxHash.ToString(), MIN_MASTERNODE_PROTO_VERSION, it->second); + } + + auto lastOutbound = mmetaman.GetMetaInfo(m->dmn->proTxHash)->GetLastOutboundSuccess(); + if (GetAdjustedTime() - lastOutbound > 60 * 60) { + m->badConnection = true; + logger.Batch("%s no outbound connection since %d seconds", m->dmn->proTxHash.ToString(), GetAdjustedTime() - lastOutbound); + } + } +} + void CDKGSession::SendComplaint(CDKGPendingMessages& pendingMessages) { CDKGLogger logger(*this, __func__); @@ -455,7 +496,7 @@ void CDKGSession::SendComplaint(CDKGPendingMessages& pendingMessages) int complaintCount = 0; for (size_t i = 0; i < members.size(); i++) { auto& m = members[i]; - if (m->bad) { + if (m->bad || m->badConnection) { qc.badMembers[i] = true; badCount++; } else if (m->weComplain) { diff --git a/src/llmq/quorums_dkgsession.h b/src/llmq/quorums_dkgsession.h index 05393299f115..21d127b7ba53 100644 --- a/src/llmq/quorums_dkgsession.h +++ b/src/llmq/quorums_dkgsession.h @@ -217,6 +217,7 @@ class CDKGMember std::set complaintsFromOthers; bool bad{false}; + bool badConnection{false}; bool weComplain{false}; bool someoneComplain{false}; }; @@ -310,6 +311,7 @@ class CDKGSession // Phase 2: complaint void VerifyAndComplain(CDKGPendingMessages& pendingMessages); + void VerifyConnectionAndMinProtoVersions(); void SendComplaint(CDKGPendingMessages& pendingMessages); bool PreVerifyMessage(const uint256& hash, const CDKGComplaint& qc, bool& retBan) const; void ReceiveMessage(const uint256& hash, const CDKGComplaint& qc, bool& retBan); diff --git a/src/masternode/activemasternode.cpp b/src/masternode/activemasternode.cpp index 4aa829142043..f33d4946e6d2 100644 --- a/src/masternode/activemasternode.cpp +++ b/src/masternode/activemasternode.cpp @@ -68,7 +68,7 @@ void CActiveMasternodeManager::Init(const CBlockIndex* pindex) if (!deterministicMNManager->IsDIP3Enforced(pindex->nHeight)) return; // Check that our local network configuration is correct - if (!fListen) { + if (!fListen && Params().RequireRoutableExternalIP()) { // listen option is probably overwritten by something else, no good state = MASTERNODE_ERROR; strError = "Masternode must accept connections from outside. Make sure listen configuration option is not overwritten by some another parameter."; @@ -119,7 +119,7 @@ void CActiveMasternodeManager::Init(const CBlockIndex* pindex) bool fConnected = ConnectSocketDirectly(activeMasternodeInfo.service, hSocket, nConnectTimeout) && IsSelectableSocket(hSocket); CloseSocket(hSocket); - if (!fConnected) { + if (!fConnected && Params().RequireRoutableExternalIP()) { state = MASTERNODE_ERROR; strError = "Could not connect to " + activeMasternodeInfo.service.ToString(); LogPrintf("CActiveMasternodeManager::Init -- ERROR: %s\n", strError); @@ -190,7 +190,7 @@ bool CActiveMasternodeManager::GetLocalAddress(CService& addrRet) if (LookupHost("8.8.8.8", addrDummyPeer, false)) { fFoundLocal = GetLocal(addrRet, &addrDummyPeer) && IsValidNetAddr(addrRet); } - if (!fFoundLocal && Params().NetworkIDString() == CBaseChainParams::REGTEST) { + if (!fFoundLocal && !Params().RequireRoutableExternalIP()) { if (Lookup("127.0.0.1", addrRet, GetListenPort(), false)) { fFoundLocal = true; } @@ -218,6 +218,6 @@ bool CActiveMasternodeManager::IsValidNetAddr(CService addrIn) { // TODO: regtest is fine with any addresses for now, // should probably be a bit smarter if one day we start to implement tests for this - return Params().NetworkIDString() == CBaseChainParams::REGTEST || + return !Params().RequireRoutableExternalIP() || (addrIn.IsIPv4() && IsReachable(addrIn) && addrIn.IsRoutable()); } diff --git a/src/masternode/masternode-sync.cpp b/src/masternode/masternode-sync.cpp index ff8d62f186e4..d1de7d17ec0a 100644 --- a/src/masternode/masternode-sync.cpp +++ b/src/masternode/masternode-sync.cpp @@ -115,7 +115,7 @@ void CMasternodeSync::ProcessTick(CConnman& connman) // reset the sync process if the last call to this function was more than 60 minutes ago (client was in sleep mode) static int64_t nTimeLastProcess = GetTime(); - if(GetTime() - nTimeLastProcess > 60*60) { + if(GetTime() - nTimeLastProcess > 60*60 && !fMasternodeMode) { LogPrintf("CMasternodeSync::ProcessTick -- WARNING: no actions for too long, restarting sync...\n"); Reset(); SwitchToNextAsset(connman); diff --git a/src/net.cpp b/src/net.cpp index 601d9dd19316..69ac406dea63 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2197,7 +2197,7 @@ void CConnman::ThreadOpenMasternodeConnections() int64_t lastAttempt = mmetaman.GetMetaInfo(dmn->proTxHash)->GetLastOutboundAttempt(); // back off trying connecting to an address if we already tried recently - if (nANow - lastAttempt < 60) { + if (nANow - lastAttempt < chainParams.LLMQConnectionRetryTimeout()) { continue; } pending.emplace_back(dmn); @@ -2285,6 +2285,12 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai { LOCK(cs_vNodes); vNodes.push_back(pnode); + + if (!fNetworkActive) { + // there is a small chance of fNetworkActive becoming false between the start of this method + // and the successful lock of cs_vNodes + pnode->CloseSocketDisconnect(); + } } } diff --git a/src/net_processing.cpp b/src/net_processing.cpp index af64b2a1357c..86926a7ca0e7 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -304,6 +304,8 @@ void UpdatePreferredDownload(CNode* node, CNodeState* state) void PushNodeVersion(CNode *pnode, CConnman* connman, int64_t nTime) { + const auto& params = Params(); + ServiceFlags nLocalNodeServices = pnode->GetLocalServices(); uint64_t nonce = pnode->GetLocalNonce(); int nNodeStartingHeight = pnode->GetMyStartingHeight(); @@ -320,13 +322,18 @@ void PushNodeVersion(CNode *pnode, CConnman* connman, int64_t nTime) pnode->sentMNAuthChallenge = mnauthChallenge; } - connman->PushMessage(pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe, + int nProtocolVersion = PROTOCOL_VERSION; + if (params.NetworkIDString() != CBaseChainParams::MAIN && gArgs.IsArgSet("-pushversion")) { + nProtocolVersion = gArgs.GetArg("-pushversion", PROTOCOL_VERSION); + } + + connman->PushMessage(pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, nProtocolVersion, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe, nonce, strSubVersion, nNodeStartingHeight, ::fRelayTxes, mnauthChallenge, pnode->fMasternode)); if (fLogIPs) { - LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, them=%s, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), addrYou.ToString(), nodeid); + LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, them=%s, peer=%d\n", nProtocolVersion, nNodeStartingHeight, addrMe.ToString(), addrYou.ToString(), nodeid); } else { - LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), nodeid); + LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, peer=%d\n", nProtocolVersion, nNodeStartingHeight, addrMe.ToString(), nodeid); } } diff --git a/src/version.h b/src/version.h index 2a06f25f3bbd..9614172cf0de 100644 --- a/src/version.h +++ b/src/version.h @@ -22,6 +22,9 @@ static const int GETHEADERS_VERSION = 70077; //! disconnect from peers older than this proto version static const int MIN_PEER_PROTO_VERSION = 70213; +//! minimum proto version of masternode to accept in DKGs +static const int MIN_MASTERNODE_PROTO_VERSION = 70217; + //! nTime field added to CAddress, starting with this version; //! if possible, avoid requesting addresses nodes older than this static const int CADDR_TIME_VERSION = 31402; diff --git a/test/functional/llmq-simplepose.py b/test/functional/llmq-simplepose.py index d48980fa5e84..ca28c8983f29 100755 --- a/test/functional/llmq-simplepose.py +++ b/test/functional/llmq-simplepose.py @@ -26,24 +26,104 @@ def run_test(self): self.wait_for_sporks_same() # check if mining quorums with all nodes being online succeeds without punishment/banning + self.test_no_banning() + + # Now lets isolate MNs one by one and verify that punishment/banning happens + def isolate_mn(mn): + mn.node.setnetworkactive(False) + wait_until(lambda: mn.node.getconnectioncount() == 0) + self.test_banning(isolate_mn, False, True, False) + + self.repair_masternodes(False) + + self.nodes[0].spork("SPORK_21_QUORUM_ALL_CONNECTED", 0) + self.wait_for_sporks_same() + + self.reset_probe_timeouts() + + # Make sure no banning happens with spork21 enabled + self.test_no_banning(expected_connections=4, expected_probes=4) + + # Lets restart masternodes with closed ports and verify that they get banned even though they are connected to other MNs (via outbound connections) + def close_mn_port(mn): + self.stop_node(mn.node.index) + self.start_masternode(mn, ["-listen=0", "-mocktime=%d" % self.mocktime]) + connect_nodes(mn.node, 0) + # Make sure the to-be-banned node is still connected well via outbound connections + for mn2 in self.mninfo: + if mn2 is not mn: + connect_nodes(mn.node, mn2.node.index) + self.reset_probe_timeouts() + self.test_banning(close_mn_port, True, False, True) + + self.repair_masternodes(True) + self.reset_probe_timeouts() + + def force_old_mn_proto(mn): + self.stop_node(mn.node.index) + self.start_masternode(mn, ["-pushversion=70216", "-mocktime=%d" % self.mocktime]) + connect_nodes(mn.node, 0) + self.reset_probe_timeouts() + self.test_banning(force_old_mn_proto, True, False, False) + + def test_no_banning(self, expected_connections=1, expected_probes=0): for i in range(3): - self.mine_quorum() + self.mine_quorum(expected_connections=expected_connections, expected_probes=expected_probes) for mn in self.mninfo: - assert(not self.check_punished(mn) and not self.check_punished(mn)) + assert(not self.check_punished(mn) and not self.check_banned(mn)) - # Now lets kill MNs one by one and verify that punishment/banning happens - for i in range(len(self.mninfo), len(self.mninfo) - 2, -1): - mn = self.mninfo[len(self.mninfo) - 1] - self.mninfo.remove(mn) - self.stop_node(mn.nodeIdx) - self.nodes.remove(mn.node) + def test_banning(self, invalidate_proc, check_probes, expect_contribution_to_fail, expect_probes_to_fail): + online_mninfos = self.mninfo.copy() + for i in range(2): + mn = online_mninfos[len(online_mninfos) - 1] + online_mninfos.remove(mn) + invalidate_proc(mn) t = time.time() while (not self.check_punished(mn) or not self.check_banned(mn)) and (time.time() - t) < 120: - self.mine_quorum(expected_connections=1, expected_members=i-1, expected_contributions=i-1, expected_complaints=i-1, expected_commitments=i-1) + expected_probes = 0 + expected_contributors = len(online_mninfos) + 1 + if check_probes: + expected_probes = len(online_mninfos) + if expect_probes_to_fail: + expected_probes -= 1 + if expect_contribution_to_fail: + expected_contributors -= 1 + self.mine_quorum(expected_connections=1, expected_probes=expected_probes, expected_members=len(online_mninfos), expected_contributions=expected_contributors, expected_complaints=expected_contributors-1, expected_commitments=expected_contributors, mninfos=online_mninfos) assert(self.check_punished(mn) and self.check_banned(mn)) + def repair_masternodes(self, restart): + # Repair all nodes + for mn in self.mninfo: + if self.check_banned(mn) or self.check_punished(mn): + addr = self.nodes[0].getnewaddress() + self.nodes[0].sendtoaddress(addr, 0.1) + self.nodes[0].protx('update_service', mn.proTxHash, '127.0.0.1:%d' % p2p_port(mn.node.index), mn.keyOperator, "", addr) + self.nodes[0].generate(1) + assert(not self.check_banned(mn)) + + if restart: + self.stop_node(mn.node.index) + self.start_masternode(mn, ["-mocktime=%d" % self.mocktime]) + else: + mn.node.setnetworkactive(True) + connect_nodes(mn.node, 0) + self.sync_all() + + # Isolate and re-connect all MNs (otherwise there might be open connections with no MNAUTH for MNs which were banned before) + for mn in self.mninfo: + mn.node.setnetworkactive(False) + wait_until(lambda: mn.node.getconnectioncount() == 0) + mn.node.setnetworkactive(True) + connect_nodes(mn.node, 0) + + def reset_probe_timeouts(self): + # Make sure all masternodes will reconnect/re-probe + self.bump_mocktime(60 * 60 + 1) + set_node_times(self.nodes, self.mocktime) + self.sync_all() + def check_punished(self, mn): info = self.nodes[0].protx('info', mn.proTxHash) if info['state']['PoSePenalty'] > 0: diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 1f84490969ee..1216d9fce4dc 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -606,13 +606,6 @@ def start_masternodes(self): self.add_nodes(self.mn_count) executor = ThreadPoolExecutor(max_workers=20) - def do_start(idx): - args = ['-masternodeblsprivkey=%s' % self.mninfo[idx].keyOperator] + self.extra_args[idx + start_idx] - self.start_node(idx + start_idx, extra_args=args) - self.mninfo[idx].nodeIdx = idx + start_idx - self.mninfo[idx].node = self.nodes[idx + start_idx] - force_finish_mnsync(self.mninfo[idx].node) - def do_connect(idx): # Connect to the control node only, masternodes should take care of intra-quorum connections themselves connect_nodes(self.mninfo[idx].node, 0) @@ -621,7 +614,8 @@ def do_connect(idx): # start up nodes in parallel for idx in range(0, self.mn_count): - jobs.append(executor.submit(do_start, idx)) + self.mninfo[idx].nodeIdx = idx + start_idx + jobs.append(executor.submit(self.start_masternode, self.mninfo[idx])) # wait for all nodes to start up for job in jobs: @@ -639,6 +633,14 @@ def do_connect(idx): executor.shutdown() + def start_masternode(self, mninfo, extra_args=None): + args = ['-masternodeblsprivkey=%s' % mninfo.keyOperator] + self.extra_args[mninfo.nodeIdx] + if extra_args is not None: + args += extra_args + self.start_node(mninfo.nodeIdx, extra_args=args) + mninfo.node = self.nodes[mninfo.nodeIdx] + force_finish_mnsync(mninfo.node) + def setup_network(self): self.log.info("Creating and starting controller node") self.add_nodes(1, extra_args=[self.extra_args[0]]) @@ -771,10 +773,10 @@ def check_sporks_same(): return all(node.spork('show') == sporks for node in self.nodes[1:]) wait_until(check_sporks_same, timeout=timeout, sleep=0.5) - def wait_for_quorum_connections(self, expected_connections=2, timeout = 60, wait_proc=None): + def wait_for_quorum_connections(self, expected_connections, nodes, timeout = 60, wait_proc=None): def check_quorum_connections(): all_ok = True - for node in self.nodes: + for node in nodes: s = node.quorum("dkgstatus") if s["session"] == {}: continue @@ -797,11 +799,27 @@ def check_quorum_connections(): return all_ok wait_until(check_quorum_connections, timeout=timeout, sleep=1) - def wait_for_quorum_phase(self, quorum_hash, phase, expected_member_count, check_received_messages, check_received_messages_count, timeout=30, sleep=0.1): + def wait_for_masternode_probes(self, expected_probes, mninfos, timeout = 30, wait_proc=None): + def check_probes(): + for mn in mninfos: + l = mn.node.protx('list', 'registered', 1) + cnt = 0 + for mn2 in l: + if mn2['proTxHash'] != mn.proTxHash: + if mn2['metaInfo']['lastOutboundSuccessElapsed'] <= 60: + cnt += 1 + if cnt < expected_probes: + if wait_proc is not None: + wait_proc() + return False + return True + wait_until(check_probes, timeout=timeout, sleep=1) + + def wait_for_quorum_phase(self, quorum_hash, phase, expected_member_count, check_received_messages, check_received_messages_count, mninfos, timeout=30, sleep=0.1): def check_dkg_session(): all_ok = True member_count = 0 - for mn in self.mninfo: + for mn in mninfos: s = mn.node.quorum("dkgstatus")["session"] if "llmq_test" not in s: continue @@ -825,10 +843,10 @@ def check_dkg_session(): return all_ok wait_until(check_dkg_session, timeout=timeout, sleep=sleep) - def wait_for_quorum_commitment(self, quorum_hash, timeout = 15): + def wait_for_quorum_commitment(self, quorum_hash, nodes, timeout = 15): def check_dkg_comitments(): all_ok = True - for node in self.nodes: + for node in nodes: s = node.quorum("dkgstatus") if "minableCommitments" not in s: all_ok = False @@ -844,92 +862,97 @@ def check_dkg_comitments(): return all_ok wait_until(check_dkg_comitments, timeout=timeout, sleep=0.1) - def mine_quorum(self, expected_members=None, expected_connections=2, expected_contributions=None, expected_complaints=0, expected_justifications=0, expected_commitments=None): + def mine_quorum(self, expected_members=None, expected_connections=2, expected_probes=0, expected_contributions=None, expected_complaints=0, expected_justifications=0, expected_commitments=None, mninfos=None): if expected_members is None: expected_members = self.llmq_size if expected_contributions is None: expected_contributions = self.llmq_size if expected_commitments is None: expected_commitments = self.llmq_size + if mninfos is None: + mninfos = self.mninfo - self.log.info("Mining quorum: expected_members=%d, expected_contributions=%d, expected_complaints=%d, expected_justifications=%d, " - "expected_commitments=%d" % (expected_members, expected_contributions, expected_complaints, + self.log.info("Mining quorum: expected_members=%d, expected_connections=%d, expected_probes=%d, expected_contributions=%d, expected_complaints=%d, expected_justifications=%d, " + "expected_commitments=%d" % (expected_members, expected_connections, expected_probes, expected_contributions, expected_complaints, expected_justifications, expected_commitments)) + nodes = [self.nodes[0]] + [mn.node for mn in mninfos] + quorums = self.nodes[0].quorum("list") # move forward to next DKG skip_count = 24 - (self.nodes[0].getblockcount() % 24) if skip_count != 0: self.bump_mocktime(1) - set_node_times(self.nodes, self.mocktime) + set_node_times(nodes, self.mocktime) self.nodes[0].generate(skip_count) - sync_blocks(self.nodes) + sync_blocks(nodes) q = self.nodes[0].getbestblockhash() self.log.info("Waiting for phase 1 (init)") def bump_time(): self.bump_mocktime(1) - set_node_times(self.nodes, self.mocktime) - self.wait_for_quorum_phase(q, 1, expected_members, None, 0) - self.wait_for_quorum_connections(expected_connections=expected_connections, wait_proc=bump_time) + set_node_times(nodes, self.mocktime) + self.wait_for_quorum_phase(q, 1, expected_members, None, 0, mninfos) + self.wait_for_quorum_connections(expected_connections, nodes, wait_proc=bump_time) + self.wait_for_masternode_probes(expected_probes, mninfos, wait_proc=bump_time) self.bump_mocktime(1) - set_node_times(self.nodes, self.mocktime) + set_node_times(nodes, self.mocktime) self.nodes[0].generate(2) - sync_blocks(self.nodes) + sync_blocks(nodes) self.log.info("Waiting for phase 2 (contribute)") - self.wait_for_quorum_phase(q, 2, expected_members, "receivedContributions", expected_contributions) + self.wait_for_quorum_phase(q, 2, expected_members, "receivedContributions", expected_contributions, mninfos) self.bump_mocktime(1) - set_node_times(self.nodes, self.mocktime) + set_node_times(nodes, self.mocktime) self.nodes[0].generate(2) - sync_blocks(self.nodes) + sync_blocks(nodes) self.log.info("Waiting for phase 3 (complain)") - self.wait_for_quorum_phase(q, 3, expected_members, "receivedComplaints", expected_complaints) + self.wait_for_quorum_phase(q, 3, expected_members, "receivedComplaints", expected_complaints, mninfos) self.bump_mocktime(1) - set_node_times(self.nodes, self.mocktime) + set_node_times(nodes, self.mocktime) self.nodes[0].generate(2) - sync_blocks(self.nodes) + sync_blocks(nodes) self.log.info("Waiting for phase 4 (justify)") - self.wait_for_quorum_phase(q, 4, expected_members, "receivedJustifications", expected_justifications) + self.wait_for_quorum_phase(q, 4, expected_members, "receivedJustifications", expected_justifications, mninfos) self.bump_mocktime(1) - set_node_times(self.nodes, self.mocktime) + set_node_times(nodes, self.mocktime) self.nodes[0].generate(2) - sync_blocks(self.nodes) + sync_blocks(nodes) self.log.info("Waiting for phase 5 (commit)") - self.wait_for_quorum_phase(q, 5, expected_members, "receivedPrematureCommitments", expected_commitments) + self.wait_for_quorum_phase(q, 5, expected_members, "receivedPrematureCommitments", expected_commitments, mninfos) self.bump_mocktime(1) - set_node_times(self.nodes, self.mocktime) + set_node_times(nodes, self.mocktime) self.nodes[0].generate(2) - sync_blocks(self.nodes) + sync_blocks(nodes) self.log.info("Waiting for phase 6 (mining)") - self.wait_for_quorum_phase(q, 6, expected_members, None, 0) + self.wait_for_quorum_phase(q, 6, expected_members, None, 0, mninfos) self.log.info("Waiting final commitment") - self.wait_for_quorum_commitment(q) + self.wait_for_quorum_commitment(q, nodes) self.log.info("Mining final commitment") self.bump_mocktime(1) - set_node_times(self.nodes, self.mocktime) + set_node_times(nodes, self.mocktime) self.nodes[0].generate(1) while quorums == self.nodes[0].quorum("list"): time.sleep(2) self.bump_mocktime(1) - set_node_times(self.nodes, self.mocktime) + set_node_times(nodes, self.mocktime) self.nodes[0].generate(1) - sync_blocks(self.nodes) + sync_blocks(nodes) new_quorum = self.nodes[0].quorum("list", 1)["llmq_test"][0] quorum_info = self.nodes[0].quorum("info", 100, new_quorum) # Mine 8 (SIGN_HEIGHT_OFFSET) more blocks to make sure that the new quorum gets eligable for signing sessions self.nodes[0].generate(8) - sync_blocks(self.nodes) + sync_blocks(nodes) self.log.info("New quorum: height=%d, quorumHash=%s, minedBlock=%s" % (quorum_info["height"], new_quorum, quorum_info["minedBlock"]))