diff --git a/src/chainparams.cpp b/src/chainparams.cpp index ed63f6385cdf..c50e780d33e1 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -184,6 +184,26 @@ static Consensus::LLMQParams llmq_test = { .recoveryMembers = 3, }; +// this one is for testing only +static Consensus::LLMQParams llmq_test_v17 = { + .type = Consensus::LLMQ_TEST_V17, + .name = "llmq_test_v17", + .size = 3, + .minSize = 2, + .threshold = 2, + + .dkgInterval = 24, // one DKG per hour + .dkgPhaseBlocks = 2, + .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization + .dkgMiningWindowEnd = 18, + .dkgBadVotesThreshold = 2, + + .signingActiveQuorumCount = 2, // just a few ones to allow easier testing + + .keepOldConnections = 3, + .recoveryMembers = 3, +}; + // this one is for devnets only static Consensus::LLMQParams llmq_devnet = { .type = Consensus::LLMQ_DEVNET, @@ -262,6 +282,26 @@ static Consensus::LLMQParams llmq400_85 = { .recoveryMembers = 100, }; +// Used for Platform +static Consensus::LLMQParams llmq100_67 = { + .type = Consensus::LLMQ_100_67, + .name = "llmq_100_67", + .size = 100, + .minSize = 80, + .threshold = 67, + + .dkgInterval = 24, // one DKG per hour + .dkgPhaseBlocks = 2, + .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization + .dkgMiningWindowEnd = 18, + .dkgBadVotesThreshold = 80, + + .signingActiveQuorumCount = 24, // a full day worth of LLMQs + + .keepOldConnections = 25, + .recoveryMembers = 50, +}; + /** * Main network @@ -417,8 +457,10 @@ class CMainParams : public CChainParams { consensus.llmqs[Consensus::LLMQ_50_60] = llmq50_60; consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60; consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85; + consensus.llmqs[Consensus::LLMQ_100_67] = llmq100_67; consensus.llmqTypeChainLocks = Consensus::LLMQ_400_60; consensus.llmqTypeInstantSend = Consensus::LLMQ_50_60; + consensus.llmqTypePlatform = Consensus::LLMQ_100_67; fDefaultConsistencyChecks = false; fRequireStandard = true; @@ -615,8 +657,10 @@ class CTestNetParams : public CChainParams { consensus.llmqs[Consensus::LLMQ_50_60] = llmq50_60; consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60; consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85; + consensus.llmqs[Consensus::LLMQ_100_67] = llmq100_67; consensus.llmqTypeChainLocks = Consensus::LLMQ_50_60; consensus.llmqTypeInstantSend = Consensus::LLMQ_50_60; + consensus.llmqTypePlatform = Consensus::LLMQ_100_67; fDefaultConsistencyChecks = false; fRequireStandard = false; @@ -794,8 +838,10 @@ class CDevNetParams : public CChainParams { consensus.llmqs[Consensus::LLMQ_50_60] = llmq50_60; consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60; consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85; + consensus.llmqs[Consensus::LLMQ_100_67] = llmq100_67; consensus.llmqTypeChainLocks = Consensus::LLMQ_50_60; consensus.llmqTypeInstantSend = Consensus::LLMQ_50_60; + consensus.llmqTypePlatform = Consensus::LLMQ_100_67; fDefaultConsistencyChecks = false; fRequireStandard = false; @@ -972,8 +1018,10 @@ class CRegTestParams : public CChainParams { // long living quorum params consensus.llmqs[Consensus::LLMQ_TEST] = llmq_test; + consensus.llmqs[Consensus::LLMQ_TEST_V17] = llmq_test_v17; consensus.llmqTypeChainLocks = Consensus::LLMQ_TEST; consensus.llmqTypeInstantSend = Consensus::LLMQ_TEST; + consensus.llmqTypePlatform = Consensus::LLMQ_TEST; } }; diff --git a/src/consensus/params.h b/src/consensus/params.h index 9c3faa21dc2d..b11e9f7cdd10 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -53,12 +53,16 @@ enum LLMQType : uint8_t LLMQ_50_60 = 1, // 50 members, 30 (60%) threshold, one per hour LLMQ_400_60 = 2, // 400 members, 240 (60%) threshold, one every 12 hours LLMQ_400_85 = 3, // 400 members, 340 (85%) threshold, one every 24 hours + LLMQ_100_67 = 4, // 100 members, 67 (67%) threshold, one per hour // for testing only LLMQ_TEST = 100, // 3 members, 2 (66%) threshold, one per hour. Params might differ when -llmqtestparams is used // for devnets only LLMQ_DEVNET = 101, // 10 members, 6 (60%) threshold, one per hour. Params might differ when -llmqdevnetparams is used + + // for testing activation of new quorums only + LLMQ_TEST_V17 = 102, // 3 members, 2 (66%) threshold, one per hour. Params might differ when -llmqtestparams is used }; // Configures a LLMQ and its DKG @@ -190,6 +194,7 @@ struct Params { std::map llmqs; LLMQType llmqTypeChainLocks; LLMQType llmqTypeInstantSend{LLMQ_NONE}; + LLMQType llmqTypePlatform{LLMQ_NONE}; }; } // namespace Consensus diff --git a/src/llmq/quorums_blockprocessor.cpp b/src/llmq/quorums_blockprocessor.cpp index 3799d8a29198..04cafddac9d1 100644 --- a/src/llmq/quorums_blockprocessor.cpp +++ b/src/llmq/quorums_blockprocessor.cpp @@ -132,14 +132,13 @@ bool CQuorumBlockProcessor::ProcessBlock(const CBlock& block, const CBlockIndex* // The following checks make sure that there is always a (possibly null) commitment while in the mining phase // until the first non-null commitment has been mined. After the non-null commitment, no other commitments are // allowed, including null commitments. - for (const auto& p : Params().GetConsensus().llmqs) { + // Note: must only check quorums that were enabled at the _previous_ block height to match mining logic + for (const auto& type : CLLMQUtils::GetEnabledQuorumTypes(pindex->pprev)) { // skip these checks when replaying blocks after the crash if (!chainActive.Tip()) { break; } - auto type = p.first; - // does the currently processed block contain a (possibly null) commitment for the current session? bool hasCommitmentInNewBlock = qcs.count(type) != 0; bool isCommitmentRequired = IsCommitmentRequired(type, pindex->nHeight); diff --git a/src/llmq/quorums_utils.cpp b/src/llmq/quorums_utils.cpp index 80245acb5acc..d3223c000e61 100644 --- a/src/llmq/quorums_utils.cpp +++ b/src/llmq/quorums_utils.cpp @@ -17,6 +17,9 @@ namespace llmq std::vector CLLMQUtils::GetAllQuorumMembers(Consensus::LLMQType llmqType, const CBlockIndex* pindexQuorum) { + if (!IsQuorumTypeEnabled(llmqType, pindexQuorum->pprev)) { + return {}; + } auto& params = Params().GetConsensus().llmqs.at(llmqType); auto allMns = deterministicMNManager->GetListForBlock(pindexQuorum); auto modifier = ::SerializeHash(std::make_pair(llmqType, pindexQuorum->GetBlockHash())); @@ -50,7 +53,7 @@ bool CLLMQUtils::IsAllMembersConnectedEnabled(Consensus::LLMQType llmqType) if (spork21 == 0) { return true; } - if (spork21 == 1 && llmqType != Consensus::LLMQ_400_60 && llmqType != Consensus::LLMQ_400_85) { + if (spork21 == 1 && llmqType != Consensus::LLMQ_100_67 && llmqType != Consensus::LLMQ_400_60 && llmqType != Consensus::LLMQ_400_85) { return true; } return false; @@ -254,5 +257,47 @@ bool CLLMQUtils::IsQuorumActive(Consensus::LLMQType llmqType, const uint256& quo return false; } +bool CLLMQUtils::IsQuorumTypeEnabled(Consensus::LLMQType llmqType, const CBlockIndex* pindex) +{ + const Consensus::Params& consensusParams = Params().GetConsensus(); + bool f_v17_Active = VersionBitsState(pindex, consensusParams, Consensus::DEPLOYMENT_V17, versionbitscache) == ThresholdState::ACTIVE; + + switch (llmqType) + { + case Consensus::LLMQ_50_60: + case Consensus::LLMQ_400_60: + case Consensus::LLMQ_400_85: + break; + case Consensus::LLMQ_100_67: + case Consensus::LLMQ_TEST_V17: + if (!f_v17_Active) { + return false; + } + break; + case Consensus::LLMQ_TEST: + case Consensus::LLMQ_DEVNET: + break; + default: + throw std::runtime_error(strprintf("%s: Unknown LLMQ type %d", __func__, llmqType)); + } + + return true; +} + +std::vector CLLMQUtils::GetEnabledQuorumTypes(const CBlockIndex* pindex) +{ + std::vector ret; + for (const auto& p : Params().GetConsensus().llmqs) { + if (IsQuorumTypeEnabled(p.first, pindex)) { + ret.push_back(p.first); + } + } + return ret; +} + +Consensus::LLMQParams CLLMQUtils::GetLLMQParams(Consensus::LLMQType llmqType) +{ + return Params().GetConsensus().llmqs.at(llmqType); +} } // namespace llmq diff --git a/src/llmq/quorums_utils.h b/src/llmq/quorums_utils.h index 2dea1a15da94..1588ca76e074 100644 --- a/src/llmq/quorums_utils.h +++ b/src/llmq/quorums_utils.h @@ -41,6 +41,9 @@ class CLLMQUtils static void AddQuorumProbeConnections(Consensus::LLMQType llmqType, const CBlockIndex* pindexQuorum, const uint256& myProTxHash); static bool IsQuorumActive(Consensus::LLMQType llmqType, const uint256& quorumHash); + static bool IsQuorumTypeEnabled(Consensus::LLMQType llmqType, const CBlockIndex* pindex); + static std::vector GetEnabledQuorumTypes(const CBlockIndex* pindex); + static Consensus::LLMQParams GetLLMQParams(const Consensus::LLMQType llmqType); template static void IterateNodesRandom(NodesContainer& nodeStates, Continue&& cont, Callback&& callback, FastRandomContext& rnd) diff --git a/src/miner.cpp b/src/miner.cpp index 6de45f9d00f1..ef2131518d71 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -148,9 +148,9 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc : pblock->GetBlockTime(); if (fDIP0003Active_context) { - for (auto& p : chainparams.GetConsensus().llmqs) { + for (const Consensus::LLMQType& type : llmq::CLLMQUtils::GetEnabledQuorumTypes(pindexPrev)) { CTransactionRef qcTx; - if (llmq::quorumBlockProcessor->GetMinableCommitmentTx(p.first, nHeight, qcTx)) { + if (llmq::quorumBlockProcessor->GetMinableCommitmentTx(type, nHeight, qcTx)) { pblock->vtx.emplace_back(qcTx); pblocktemplate->vTxFees.emplace_back(0); pblocktemplate->vTxSigOps.emplace_back(0); diff --git a/src/rpc/rpcquorums.cpp b/src/rpc/rpcquorums.cpp index 95150061350f..4e23d229c984 100644 --- a/src/rpc/rpcquorums.cpp +++ b/src/rpc/rpcquorums.cpp @@ -54,15 +54,16 @@ UniValue quorum_list(const JSONRPCRequest& request) UniValue ret(UniValue::VOBJ); - for (auto& p : Params().GetConsensus().llmqs) { + for (auto& type : llmq::CLLMQUtils::GetEnabledQuorumTypes(chainActive.Tip())) { + const auto& params = llmq::CLLMQUtils::GetLLMQParams(type); UniValue v(UniValue::VARR); - auto quorums = llmq::quorumManager->ScanQuorums(p.first, chainActive.Tip(), count > -1 ? count : p.second.signingActiveQuorumCount); + auto quorums = llmq::quorumManager->ScanQuorums(type, chainActive.Tip(), count > -1 ? count : params.signingActiveQuorumCount); for (auto& q : quorums) { v.push_back(q->qc.quorumHash.ToString()); } - ret.pushKV(p.second.name, v); + ret.pushKV(params.name, v); } @@ -181,8 +182,8 @@ UniValue quorum_dkgstatus(const JSONRPCRequest& request) UniValue minableCommitments(UniValue::VOBJ); UniValue quorumConnections(UniValue::VOBJ); - for (const auto& p : Params().GetConsensus().llmqs) { - auto& params = p.second; + for (const auto& type : llmq::CLLMQUtils::GetEnabledQuorumTypes(chainActive.Tip())) { + const auto& params = llmq::CLLMQUtils::GetLLMQParams(type); if (fMasternodeMode) { const CBlockIndex* pindexQuorum = chainActive[tipHeight - (tipHeight % params.dkgInterval)]; @@ -265,8 +266,8 @@ UniValue quorum_memberof(const JSONRPCRequest& request) UniValue result(UniValue::VARR); - for (const auto& p : Params().GetConsensus().llmqs) { - auto& params = p.second; + for (const auto& type : llmq::CLLMQUtils::GetEnabledQuorumTypes(pindexTip)) { + const auto& params = llmq::CLLMQUtils::GetLLMQParams(type); size_t count = params.signingActiveQuorumCount; if (scanQuorumsCount != -1) { count = (size_t)scanQuorumsCount; diff --git a/test/functional/feature_new_quorum_type_activation.py b/test/functional/feature_new_quorum_type_activation.py new file mode 100755 index 000000000000..061fe8d20459 --- /dev/null +++ b/test/functional/feature_new_quorum_type_activation.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Dash Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal, get_bip9_status + +''' +feature_new_quorum_type_activation.py + +Tests the activation of a new quorum type in v17 via a bip9-like hardfork + +''' + + +class NewQuorumTypeActivationTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def run_test(self): + assert_equal(get_bip9_status(self.nodes[0], 'v17')['status'], 'locked_in') + ql = self.nodes[0].quorum("list") + assert_equal(len(ql), 1) + assert("llmq_test_v17" not in ql) + self.nodes[0].generate(99) + assert_equal(get_bip9_status(self.nodes[0], 'v17')['status'], 'active') + self.nodes[0].generate(1) + ql = self.nodes[0].quorum("list") + assert_equal(len(ql), 2) + assert("llmq_test_v17" in ql) + + +if __name__ == '__main__': + NewQuorumTypeActivationTest().main() diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index c3a19fd52e12..371aafce355a 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -899,6 +899,16 @@ def check_dkg_comitments(): return all_ok wait_until(check_dkg_comitments, timeout=timeout, sleep=0.1) + def wait_for_quorum_list(self, quorum_hash, nodes, timeout=15, sleep=2): + def wait_func(): + if quorum_hash in self.nodes[0].quorum("list")["llmq_test"]: + return True + self.bump_mocktime(sleep, nodes=nodes) + self.nodes[0].generate(1) + sync_blocks(nodes) + return False + wait_until(wait_func, timeout=timeout, sleep=sleep) + def mine_quorum(self, expected_connections=None, expected_members=None, expected_contributions=None, expected_complaints=0, expected_justifications=0, expected_commitments=None, mninfos_online=None, mninfos_valid=None): spork21_active = self.nodes[0].spork('show')['SPORK_21_QUORUM_ALL_CONNECTED'] <= 1 @@ -921,8 +931,6 @@ def mine_quorum(self, expected_connections=None, expected_members=None, expected nodes = [self.nodes[0]] + [mn.node for mn in mninfos_online] - quorums = self.nodes[0].quorum("list") - # move forward to next DKG skip_count = 24 - (self.nodes[0].getblockcount() % 24) if skip_count != 0: @@ -974,12 +982,13 @@ def mine_quorum(self, expected_connections=None, expected_members=None, expected self.log.info("Mining final commitment") self.bump_mocktime(1, nodes=nodes) self.nodes[0].generate(1) - while quorums == self.nodes[0].quorum("list"): - time.sleep(2) - self.bump_mocktime(1, nodes=nodes) - self.nodes[0].generate(1) - sync_blocks(nodes) + sync_blocks(nodes) + + self.log.info("Waiting for quorum to appear in the list") + self.wait_for_quorum_list(q, nodes) + new_quorum = self.nodes[0].quorum("list", 1)["llmq_test"][0] + assert_equal(q, new_quorum) 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 diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 740e22946321..a41e74045151 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -147,6 +147,7 @@ 'wallet_encryption.py', 'feature_dersig.py', 'feature_cltv.py', + 'feature_new_quorum_type_activation.py', 'feature_governance_objects.py', 'rpc_uptime.py', 'wallet_resendwallettransactions.py',