diff --git a/src/Makefile.am b/src/Makefile.am index 9e5fa6cc5686..240aa765f3f3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -219,6 +219,7 @@ BITCOIN_CORE_H = \ llmq/dkgsession.h \ llmq/init.h \ llmq/instantsend.h \ + llmq/snapshot.h \ llmq/signing.h \ llmq/signing_shares.h \ llmq/utils.h \ @@ -404,6 +405,7 @@ libdash_server_a_SOURCES = \ llmq/dkgsession.cpp \ llmq/init.cpp \ llmq/instantsend.cpp \ + llmq/snapshot.cpp \ llmq/signing.cpp \ llmq/signing_shares.cpp \ llmq/utils.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 23c6e19b9b45..eef3149439d3 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -240,14 +240,14 @@ class CMainParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_DIP0020].nThresholdMin = 2420; // 60% of 4032 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0020].nFalloffCoeff = 5; // this corresponds to 10 periods - // Deployment of decreased proposal fee, script addresses for Governance Proposals - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].bit = 7; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nStartTime = 1638316800; // Dec 1st, 2021 - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nTimeout = 1669852800; // Dec 1st, 2022 - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nWindowSize = 4032; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nThresholdStart = 3226; // 80% of 4032 - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nThresholdMin = 2420; // 60% of 4032 - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nFalloffCoeff = 5; // this corresponds to 10 periods + // Deployment of Quorum Rotation DIP and decreased proposal fee (Values to be determined) + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].bit = 7; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nStartTime = 1638316800; // Dec 1st, 2021 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nTimeout = 1669852800; // Dec 1st, 2022 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nWindowSize = 4032; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nThresholdStart = 3226; // 80% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nThresholdMin = 2420; // 60% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nFalloffCoeff = 5; // this corresponds to 10 periods // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000549cd3ccb81a55892330"); // 1450000 @@ -299,11 +299,13 @@ class CMainParams : public CChainParams { // long living quorum params AddLLMQ(Consensus::LLMQType::LLMQ_50_60); + AddLLMQ(Consensus::LLMQType::LLMQ_60_75); AddLLMQ(Consensus::LLMQType::LLMQ_400_60); AddLLMQ(Consensus::LLMQType::LLMQ_400_85); AddLLMQ(Consensus::LLMQType::LLMQ_100_67); consensus.llmqTypeChainLocks = Consensus::LLMQType::LLMQ_400_60; consensus.llmqTypeInstantSend = Consensus::LLMQType::LLMQ_50_60; + consensus.llmqTypeDIP0024InstantSend = Consensus::LLMQType::LLMQ_60_75; consensus.llmqTypePlatform = Consensus::LLMQType::LLMQ_100_67; consensus.llmqTypeMnhf = Consensus::LLMQType::LLMQ_400_85; @@ -461,14 +463,14 @@ class CTestNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_DIP0020].nThresholdMin = 60; // 60% of 100 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0020].nFalloffCoeff = 5; // this corresponds to 10 periods - // Deployment of decreased proposal fee, script addresses for Governance Proposals - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].bit = 7; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nStartTime = 999999999999ULL; // TODO renable this before first RC - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nTimeout = 999999999999ULL; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nWindowSize = 4032; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nThresholdStart = 3226; // 80% of 4032 - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nThresholdMin = 2420; // 60% of 4032 - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nFalloffCoeff = 5; // this corresponds to 10 periods + // Deployment of Quorum Rotation DIP and decreased proposal fee (Values to be determined) + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].bit = 7; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nStartTime = 1625097600; // July 1st, 2021 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nTimeout = 1656633600; // July 1st, 2022 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nWindowSize = 4032; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nThresholdStart = 3226; // 80% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nThresholdMin = 2420; // 60% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nFalloffCoeff = 5; // this corresponds to 10 periods // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000022f14ac5d56b5ef"); // 470000 @@ -513,11 +515,13 @@ class CTestNetParams : public CChainParams { // long living quorum params AddLLMQ(Consensus::LLMQType::LLMQ_50_60); + AddLLMQ(Consensus::LLMQType::LLMQ_60_75); AddLLMQ(Consensus::LLMQType::LLMQ_400_60); AddLLMQ(Consensus::LLMQType::LLMQ_400_85); AddLLMQ(Consensus::LLMQType::LLMQ_100_67); consensus.llmqTypeChainLocks = Consensus::LLMQType::LLMQ_50_60; consensus.llmqTypeInstantSend = Consensus::LLMQType::LLMQ_50_60; + consensus.llmqTypeDIP0024InstantSend = Consensus::LLMQType::LLMQ_60_75; consensus.llmqTypePlatform = Consensus::LLMQType::LLMQ_100_67; consensus.llmqTypeMnhf = Consensus::LLMQType::LLMQ_50_60; @@ -654,14 +658,13 @@ class CDevNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_DIP0020].nThresholdMin = 60; // 60% of 100 consensus.vDeployments[Consensus::DEPLOYMENT_DIP0020].nFalloffCoeff = 5; // this corresponds to 10 periods - // Deployment of decreased proposal fee, script addresses for Governance Proposals - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].bit = 7; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nStartTime = 1635724800; // Nov 1st, 2021 - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nTimeout = 999999999999ULL; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nWindowSize = 100; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nThresholdStart = 80; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nThresholdMin = 60; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nFalloffCoeff = 5; // this corresponds to 10 periods + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].bit = 7; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nStartTime = 1625097600; // July 1st, 2021 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nTimeout = 1656633600; // July 1st, 2022 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nWindowSize = 100; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nThresholdStart = 80; // 80% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nThresholdMin = 60; // 60% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nFalloffCoeff = 5; // this corresponds to 10 periods // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000000000000000000"); @@ -707,17 +710,20 @@ class CDevNetParams : public CChainParams { // long living quorum params AddLLMQ(Consensus::LLMQType::LLMQ_50_60); + AddLLMQ(Consensus::LLMQType::LLMQ_60_75); AddLLMQ(Consensus::LLMQType::LLMQ_400_60); AddLLMQ(Consensus::LLMQType::LLMQ_400_85); AddLLMQ(Consensus::LLMQType::LLMQ_100_67); AddLLMQ(Consensus::LLMQType::LLMQ_DEVNET); consensus.llmqTypeChainLocks = Consensus::LLMQType::LLMQ_50_60; consensus.llmqTypeInstantSend = Consensus::LLMQType::LLMQ_50_60; + consensus.llmqTypeDIP0024InstantSend = Consensus::LLMQType::LLMQ_60_75; consensus.llmqTypePlatform = Consensus::LLMQType::LLMQ_100_67; consensus.llmqTypeMnhf = Consensus::LLMQType::LLMQ_50_60; UpdateDevnetLLMQChainLocksFromArgs(args); UpdateDevnetLLMQInstantSendFromArgs(args); + UpdateDevnetLLMQInstantSendDIP0024FromArgs(args); UpdateLLMQDevnetParametersFromArgs(args); UpdateDevnetPowTargetSpacingFromArgs(args); @@ -788,6 +794,14 @@ class CDevNetParams : public CChainParams { consensus.nPowTargetSpacing = nPowTargetSpacing; } + /** + * Allows modifying the LLMQ type for InstantSend (DIP0024). + */ + void UpdateDevnetLLMQDIP0024InstantSend(Consensus::LLMQType llmqType) + { + consensus.llmqTypeDIP0024InstantSend = llmqType; + } + /** * Allows modifying parameters of the devnet LLMQ */ @@ -800,8 +814,10 @@ class CDevNetParams : public CChainParams { params->threshold = threshold; params->dkgBadVotesThreshold = threshold; } + void UpdateLLMQDevnetParametersFromArgs(const ArgsManager& args); void UpdateDevnetLLMQInstantSendFromArgs(const ArgsManager& args); + void UpdateDevnetLLMQInstantSendDIP0024FromArgs(const ArgsManager& args); void UpdateDevnetPowTargetSpacingFromArgs(const ArgsManager& args); }; @@ -879,13 +895,15 @@ class CRegTestParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_DIP0020].nThresholdStart = 80; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0020].nThresholdMin = 60; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0020].nFalloffCoeff = 5; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].bit = 7; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nStartTime = 0; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nTimeout = 999999999999ULL; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nWindowSize = 100; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nThresholdStart = 80; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nThresholdMin = 60; - consensus.vDeployments[Consensus::DEPLOYMENT_GOV_FEE].nFalloffCoeff = 5; + + // Deployment of Quorum Rotation DIP and decreased proposal fee (Values to be determined) + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].bit = 7; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nStartTime = 0; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nTimeout = 999999999999ULL; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nWindowSize = 300; + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nThresholdStart = 240; // 80% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nThresholdMin = 180; // 60% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_DIP0024].nFalloffCoeff = 5; // this corresponds to 10 periods // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00"); @@ -962,8 +980,10 @@ class CRegTestParams : public CChainParams { // long living quorum params AddLLMQ(Consensus::LLMQType::LLMQ_TEST); AddLLMQ(Consensus::LLMQType::LLMQ_TEST_V17); + AddLLMQ(Consensus::LLMQType::LLMQ_TEST_DIP0024); consensus.llmqTypeChainLocks = Consensus::LLMQType::LLMQ_TEST; consensus.llmqTypeInstantSend = Consensus::LLMQType::LLMQ_TEST; + consensus.llmqTypeDIP0024InstantSend = Consensus::LLMQType::LLMQ_TEST_DIP0024; consensus.llmqTypePlatform = Consensus::LLMQType::LLMQ_TEST; consensus.llmqTypeMnhf = Consensus::LLMQType::LLMQ_TEST; @@ -1222,6 +1242,25 @@ void CDevNetParams::UpdateDevnetLLMQInstantSendFromArgs(const ArgsManager& args) UpdateDevnetLLMQInstantSend(llmqType); } +void CDevNetParams::UpdateDevnetLLMQInstantSendDIP0024FromArgs(const ArgsManager& args) +{ + if (!args.IsArgSet("-llmqinstantsenddip0024")) return; + + std::string strLLMQType = gArgs.GetArg("-llmqinstantsenddip0024", std::string(GetLLMQ(consensus.llmqTypeDIP0024InstantSend).name)); + + Consensus::LLMQType llmqType = Consensus::LLMQType::LLMQ_NONE; + for (const auto& params : consensus.llmqs) { + if (params.name == strLLMQType) { + llmqType = params.type; + } + } + if (llmqType == Consensus::LLMQType::LLMQ_NONE) { + throw std::runtime_error("Invalid LLMQ type specified for -llmqinstantsenddip0024."); + } + LogPrintf("Setting llmqinstantsenddip0024 to size=%ld\n", static_cast(llmqType)); + UpdateDevnetLLMQDIP0024InstantSend(llmqType); +} + void CDevNetParams::UpdateDevnetPowTargetSpacingFromArgs(const ArgsManager& args) { if (!args.IsArgSet("-powtargetspacing")) return; @@ -1275,9 +1314,9 @@ std::unique_ptr CreateChainParams(const std::string& chain) return std::make_unique(); else if (chain == CBaseChainParams::TESTNET) return std::make_unique(); - else if (chain == CBaseChainParams::DEVNET) { + else if (chain == CBaseChainParams::DEVNET) return std::make_unique(gArgs); - } else if (chain == CBaseChainParams::REGTEST) + else if (chain == CBaseChainParams::REGTEST) return std::make_unique(gArgs); throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain)); diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 5422f4f29a6b..b0856be8f301 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -27,6 +27,7 @@ void SetupChainParamsBaseOptions() gArgs.AddArg("-llmqchainlocks=", "Override the default LLMQ type used for ChainLocks. Allows using ChainLocks with smaller LLMQs. (default: llmq_50_60, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-llmqdevnetparams=:", "Override the default LLMQ size for the LLMQ_DEVNET quorum (default: 3:2, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-llmqinstantsend=", "Override the default LLMQ type used for InstantSend. Allows using InstantSend with smaller LLMQs. (default: llmq_50_60, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-llmqinstantsenddip0024=", "Override the default LLMQ type used for InstantSendDIP0024. (default: llmq_60_75, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-llmqtestparams=:", "Override the default LLMQ size for the LLMQ_TEST quorum (default: 10:6, regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-powtargetspacing=", "Override the default PowTargetSpacing value in seconds (default: 2.5 minutes, devnet-only)", ArgsManager::ALLOW_INT, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-minimumdifficultyblocks=", "The number of blocks that can be mined with the minimum difficulty at the start of a chain (default: 0, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); diff --git a/src/consensus/params.h b/src/consensus/params.h index 44f64fa69693..11bf5642801f 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -13,17 +13,16 @@ namespace Consensus { -enum DeploymentPos -{ +enum DeploymentPos { DEPLOYMENT_TESTDUMMY, - DEPLOYMENT_CSV, // Deployment of BIP68, BIP112, and BIP113. + DEPLOYMENT_CSV, // Deployment of BIP68, BIP112, and BIP113. DEPLOYMENT_DIP0001, // Deployment of DIP0001 and lower transaction fees. - DEPLOYMENT_BIP147, // Deployment of BIP147 (NULLDUMMY) + DEPLOYMENT_BIP147, // Deployment of BIP147 (NULLDUMMY) DEPLOYMENT_DIP0003, // Deployment of DIP0002 and DIP0003 (txv3 and deterministic MN lists) DEPLOYMENT_DIP0008, // Deployment of ChainLock enforcement DEPLOYMENT_REALLOC, // Deployment of Block Reward Reallocation DEPLOYMENT_DIP0020, // Deployment of DIP0020, DIP0021 and LMQ_100_67 quorums - DEPLOYMENT_GOV_FEE, // Deployment of decreased governance proposal fee + DEPLOYMENT_DIP0024, // Deployment of DIP0024 (Quorum Rotation) and decreased governance proposal fee // NOTE: Also add new deployments to VersionBitsDeploymentInfo in versionbits.cpp MAX_VERSION_BITS_DEPLOYMENTS }; @@ -120,6 +119,7 @@ struct Params { std::vector llmqs; LLMQType llmqTypeChainLocks; LLMQType llmqTypeInstantSend{LLMQType::LLMQ_NONE}; + LLMQType llmqTypeDIP0024InstantSend{LLMQType::LLMQ_NONE}; LLMQType llmqTypePlatform{LLMQType::LLMQ_NONE}; LLMQType llmqTypeMnhf{LLMQType::LLMQ_NONE}; }; diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 82ba3346f2b5..97630048aa17 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -828,7 +828,8 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const C } if (!qc.commitment.IsNull()) { const auto& llmq_params = llmq::GetLLMQParams(qc.commitment.llmqType); - uint32_t quorumHeight = qc.nHeight - (qc.nHeight % llmq_params.dkgInterval); + int qcnHeight = int(qc.nHeight); + int quorumHeight = qcnHeight - (qcnHeight % llmq_params.dkgInterval) + int(qc.commitment.quorumIndex); auto pQuorumBaseBlockIndex = pindexPrev->GetAncestor(quorumHeight); if (!pQuorumBaseBlockIndex || pQuorumBaseBlockIndex->GetBlockHash() != qc.commitment.quorumHash) { // we should actually never get into this case as validation should have caught it...but let's be sure @@ -875,7 +876,7 @@ void CDeterministicMNManager::HandleQuorumCommitment(const llmq::CFinalCommitmen { // The commitment has already been validated at this point, so it's safe to use members of it - auto members = llmq::CLLMQUtils::GetAllQuorumMembers(llmq::GetLLMQParams(qc.llmqType), pQuorumBaseBlockIndex); + auto members = llmq::CLLMQUtils::GetAllQuorumMembers(qc.llmqType, pQuorumBaseBlockIndex); for (size_t i = 0; i < members.size(); i++) { if (!mnList.HasMN(members[i]->proTxHash)) { diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 96ff62085408..8610dc09d500 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -42,7 +42,7 @@ bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVali case TRANSACTION_QUORUM_COMMITMENT: return llmq::CheckLLMQCommitment(tx, pindexPrev, state); case TRANSACTION_MNHF_SIGNAL: - return VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_GOV_FEE) == ThresholdState::ACTIVE && CheckMNHFTx(tx, pindexPrev, state); + return VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024) == ThresholdState::ACTIVE && CheckMNHFTx(tx, pindexPrev, state); } } catch (const std::exception& e) { LogPrintf("%s -- failed: %s\n", __func__, e.what()); diff --git a/src/governance/governance.cpp b/src/governance/governance.cpp index 108a82d570c0..a79ee959f968 100644 --- a/src/governance/governance.cpp +++ b/src/governance/governance.cpp @@ -427,7 +427,7 @@ void CGovernanceManager::UpdateCachesAndClean() } else { // NOTE: triggers are handled via triggerman if (pObj->GetObjectType() == GOVERNANCE_OBJECT_PROPOSAL) { - bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_GOV_FEE) == ThresholdState::ACTIVE); + bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024) == ThresholdState::ACTIVE); bool fAllowLegacyFormat = !fAllowScript; // reusing the same bit to stop accepting proposals in legacy format CProposalValidator validator(pObj->GetDataAsHexString(), fAllowLegacyFormat, fAllowScript); if (!validator.Validate()) { @@ -672,7 +672,7 @@ void CGovernanceManager::SyncObjects(CNode* pnode, CConnman& connman) const LogPrint(BCLog::GOBJECT, "CGovernanceManager::%s -- syncing all objects to peer=%d\n", __func__, pnode->GetId()); - bool fAllowScript = WITH_LOCK(cs_main, return VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_GOV_FEE) == ThresholdState::ACTIVE); + bool fAllowScript = WITH_LOCK(cs_main, return VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024) == ThresholdState::ACTIVE); LOCK(cs); diff --git a/src/governance/object.cpp b/src/governance/object.cpp index a17ff39d7b19..f1480cef162d 100644 --- a/src/governance/object.cpp +++ b/src/governance/object.cpp @@ -464,7 +464,7 @@ bool CGovernanceObject::IsValidLocally(std::string& strError, bool& fMissingConf switch (nObjectType) { case GOVERNANCE_OBJECT_PROPOSAL: { - bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_GOV_FEE) == ThresholdState::ACTIVE); + bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024) == ThresholdState::ACTIVE); bool fAllowLegacyFormat = !fAllowScript; // reusing the same bit to stop accepting proposals in legacy format CProposalValidator validator(GetDataAsHexString(), fAllowLegacyFormat, fAllowScript); // Note: It's ok to have expired proposals @@ -559,7 +559,7 @@ bool CGovernanceObject::IsCollateralValid(std::string& strError, bool& fMissingC findScript << OP_RETURN << ToByteVector(nExpectedHash); AssertLockHeld(cs_main); - bool fork_active = VersionBitsState(LookupBlockIndex(nBlockHash), Params().GetConsensus(), Consensus::DEPLOYMENT_GOV_FEE, versionbitscache) == ThresholdState::ACTIVE; + bool fork_active = VersionBitsState(LookupBlockIndex(nBlockHash), Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024, versionbitscache) == ThresholdState::ACTIVE; CAmount nMinFee = GetMinCollateralFee(fork_active); LogPrint(BCLog::GOBJECT, "CGovernanceObject::IsCollateralValid -- txCollateral->vout.size() = %s, findScript = %s, nMinFee = %lld\n", @@ -683,7 +683,7 @@ void CGovernanceObject::Relay(CConnman& connman) const // But we don't want to relay it to pre-GOVSCRIPT_PROTO_VERSION peers if payment_address is p2sh // because they won't accept it anyway and will simply ban us eventually. LOCK(cs_main); - bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_GOV_FEE) == ThresholdState::ACTIVE); + bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024) == ThresholdState::ACTIVE); if (fAllowScript) { CProposalValidator validator(GetDataAsHexString(), false /* no legacy format */, false /* but also no script */); if (!validator.Validate(false /* ignore expiration */)) { diff --git a/src/init.cpp b/src/init.cpp index fc96dceea19f..fa0afba3f7b0 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -74,10 +74,11 @@ #include #include -#include -#include #include +#include +#include #include +#include #include #include @@ -320,6 +321,7 @@ void PrepareShutdown(InitInterfaces& interfaces) } pblocktree.reset(); llmq::DestroyLLMQSystem(); + llmq::quorumSnapshotManager.reset(); deterministicMNManager.reset(); evoDb.reset(); } @@ -1980,6 +1982,8 @@ bool AppInitMain(InitInterfaces& interfaces) evoDb.reset(new CEvoDB(nEvoDbCache, false, fReset || fReindexChainState)); deterministicMNManager.reset(); deterministicMNManager.reset(new CDeterministicMNManager(*evoDb)); + llmq::quorumSnapshotManager.reset(); + llmq::quorumSnapshotManager.reset(new llmq::CQuorumSnapshotManager(*evoDb)); llmq::InitLLMQSystem(*evoDb, false, fReset || fReindexChainState); diff --git a/src/llmq/blockprocessor.cpp b/src/llmq/blockprocessor.cpp index 71d0a312c7b9..32dc7f506f86 100644 --- a/src/llmq/blockprocessor.cpp +++ b/src/llmq/blockprocessor.cpp @@ -29,6 +29,7 @@ CQuorumBlockProcessor* quorumBlockProcessor; static const std::string DB_MINED_COMMITMENT = "q_mc"; static const std::string DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT = "q_mcih"; +static const std::string DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT_Q_INDEXED = "q_mcihi"; static const std::string DB_BEST_BLOCK_UPGRADE = "q_bbu2"; @@ -58,7 +59,7 @@ void CQuorumBlockProcessor::ProcessMessage(CNode* pfrom, const std::string& strC if (!Params().HasLLMQ(qc.llmqType)) { LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- invalid commitment type %d from peer=%d\n", __func__, - static_cast(qc.llmqType), pfrom->GetId()); + uint8_t(qc.llmqType), pfrom->GetId()); LOCK(cs_main); Misbehaving(pfrom->GetId(), 100); return; @@ -72,21 +73,21 @@ void CQuorumBlockProcessor::ProcessMessage(CNode* pfrom, const std::string& strC pQuorumBaseBlockIndex = LookupBlockIndex(qc.quorumHash); if (!pQuorumBaseBlockIndex) { LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- unknown block %s in commitment, peer=%d\n", __func__, - qc.quorumHash.ToString(), pfrom->GetId()); + qc.quorumHash.ToString(), pfrom->GetId()); // can't really punish the node here, as we might simply be the one that is on the wrong chain or not // fully synced return; } if (::ChainActive().Tip()->GetAncestor(pQuorumBaseBlockIndex->nHeight) != pQuorumBaseBlockIndex) { LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- block %s not in active chain, peer=%d\n", __func__, - qc.quorumHash.ToString(), pfrom->GetId()); + qc.quorumHash.ToString(), pfrom->GetId()); // same, can't punish return; } - int quorumHeight = pQuorumBaseBlockIndex->nHeight - (pQuorumBaseBlockIndex->nHeight % GetLLMQParams(type).dkgInterval); + int quorumHeight = pQuorumBaseBlockIndex->nHeight - (pQuorumBaseBlockIndex->nHeight % GetLLMQParams(type).dkgInterval) + int(qc.quorumIndex); if (quorumHeight != pQuorumBaseBlockIndex->nHeight) { LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- block %s is not the first block in the DKG interval, peer=%d\n", __func__, - qc.quorumHash.ToString(), pfrom->GetId()); + qc.quorumHash.ToString(), pfrom->GetId()); Misbehaving(pfrom->GetId(), 100); return; } @@ -107,15 +108,16 @@ void CQuorumBlockProcessor::ProcessMessage(CNode* pfrom, const std::string& strC } if (!qc.Verify(pQuorumBaseBlockIndex, true)) { - LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- commitment for quorum %s:%d is not valid, peer=%d\n", __func__, - qc.quorumHash.ToString(), static_cast(qc.llmqType), pfrom->GetId()); + LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- commitment for quorum %s:%d is not valid quorumIndex[%d] nversion[%d], peer=%d\n", + __func__, qc.quorumHash.ToString(), + uint8_t(qc.llmqType), qc.quorumIndex, qc.nVersion, pfrom->GetId()); LOCK(cs_main); Misbehaving(pfrom->GetId(), 100); return; } LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- received commitment for quorum %s:%d, validMembers=%d, signers=%d, peer=%d\n", __func__, - qc.quorumHash.ToString(), static_cast(qc.llmqType), qc.CountValidMembers(), qc.CountSigners(), pfrom->GetId()); + qc.quorumHash.ToString(), uint8_t(qc.llmqType), qc.CountValidMembers(), qc.CountSigners(), pfrom->GetId()); AddMineableCommitment(qc); } @@ -131,11 +133,15 @@ bool CQuorumBlockProcessor::ProcessBlock(const CBlock& block, const CBlockIndex* return true; } - std::map qcs; + llmq::CLLMQUtils::PreComputeQuorumMembers(pindex); + + std::multimap qcs; if (!GetCommitmentsFromBlock(block, pindex, qcs, state)) { return false; } + auto blockHash = block.GetHash(); + // 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. @@ -146,27 +152,25 @@ bool CQuorumBlockProcessor::ProcessBlock(const CBlock& block, const CBlockIndex* break; } - // does the currently processed block contain a (possibly null) commitment for the current session? - bool hasCommitmentInNewBlock = qcs.count(params.type) != 0; bool isCommitmentRequired = IsCommitmentRequired(params, pindex->nHeight); + const auto numCommitmentsInNewBlock = qcs.count(params.type); - if (hasCommitmentInNewBlock && !isCommitmentRequired) { - // If we're either not in the mining phase or a non-null commitment was mined already, reject the block + if (!isCommitmentRequired && numCommitmentsInNewBlock > 0) { return state.DoS(100, false, REJECT_INVALID, "bad-qc-not-allowed"); } - if (!hasCommitmentInNewBlock && isCommitmentRequired) { - // If no non-null commitment was mined for the mining phase yet and the new block does not include - // a (possibly null) commitment, the block should be rejected. + if (isCommitmentRequired && numCommitmentsInNewBlock == 0) { return state.DoS(100, false, REJECT_INVALID, "bad-qc-missing"); } + if (llmq::CLLMQUtils::IsQuorumRotationEnabled(params.type, pindex)) { + LogPrintf("[ProcessBlock] h[%d] isCommitmentRequired[%d] numCommitmentsInNewBlock[%d]\n", pindex->nHeight, isCommitmentRequired, numCommitmentsInNewBlock); + } } - auto blockHash = block.GetHash(); - for (const auto& p : qcs) { const auto& qc = p.second; if (!ProcessCommitment(pindex->nHeight, blockHash, qc, state, fJustCheck, fBLSChecks)) { + LogPrintf("[ProcessBlock] failed h[%d] llmqType[%d] version[%d] quorumIndex[%d] quorumHash[%s]\n", pindex->nHeight, static_cast(qc.llmqType), qc.nVersion, qc.quorumIndex, qc.quorumHash.ToString()); return false; } } @@ -184,13 +188,22 @@ static std::tuple BuildInversedHeigh return std::make_tuple(DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT, llmqType, htobe32(std::numeric_limits::max() - nMinedHeight)); } +static std::tuple BuildInversedHeightKeyIndexed(Consensus::LLMQType llmqType, int nMinedHeight, int quorumIndex) +{ + // nMinedHeight must be converted to big endian to make it comparable when serialized + return std::make_tuple(DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT_Q_INDEXED, llmqType, quorumIndex, htobe32(std::numeric_limits::max() - nMinedHeight)); +} + bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockHash, const CFinalCommitment& qc, CValidationState& state, bool fJustCheck, bool fBLSChecks) { AssertLockHeld(cs_main); const auto& llmq_params = GetLLMQParams(qc.llmqType); - uint256 quorumHash = GetQuorumBlockHash(llmq_params, nHeight); + uint256 quorumHash = GetQuorumBlockHash(llmq_params, nHeight, qc.quorumIndex); + + LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s fJustCheck[%d] processing commitment from block.\n", __func__, + nHeight, uint8_t(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString(), fJustCheck); // skip `bad-qc-block` checks below when replaying blocks after the crash if (!::ChainActive().Tip()) { @@ -198,14 +211,20 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH } if (quorumHash.IsNull()) { + LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s quorumHash is null.\n", __func__, + nHeight, uint8_t(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); return state.DoS(100, false, REJECT_INVALID, "bad-qc-block"); } if (quorumHash != qc.quorumHash) { + LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s height=%d, type=%d, quorumIndex=%d, quorumHash=%s, qc.quorumHash=%s signers=%s, validMembers=%d, quorumPublicKey=%s non equal quorumHash.\n", __func__, + nHeight, uint8_t(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); return state.DoS(100, false, REJECT_INVALID, "bad-qc-block"); } if (qc.IsNull()) { if (!qc.VerifyNull()) { + LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%dqc verifynull failed.\n", __func__, + nHeight, uint8_t(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers()); return state.DoS(100, false, REJECT_INVALID, "bad-qc-invalid-null"); } return true; @@ -224,6 +243,8 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH auto pQuorumBaseBlockIndex = LookupBlockIndex(qc.quorumHash); if (!qc.Verify(pQuorumBaseBlockIndex, fBLSChecks)) { + LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s qc verify failed.\n", __func__, + nHeight, uint8_t(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); return state.DoS(100, false, REJECT_INVALID, "bad-qc-invalid"); } @@ -231,10 +252,20 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH return true; } + if (llmq::CLLMQUtils::IsQuorumRotationEnabled(llmq_params.type, pQuorumBaseBlockIndex)) { + LogPrint(BCLog::LLMQ, "[ProcessCommitment] height[%d] pQuorumBaseBlockIndex[%d] quorumIndex[%d] qversion[%d] Built\n", + nHeight, pQuorumBaseBlockIndex->nHeight, qc.quorumIndex, qc.nVersion); + } + // Store commitment in DB auto cacheKey = std::make_pair(llmq_params.type, quorumHash); evoDb.Write(std::make_pair(DB_MINED_COMMITMENT, cacheKey), std::make_pair(qc, blockHash)); - evoDb.Write(BuildInversedHeightKey(llmq_params.type, nHeight), pQuorumBaseBlockIndex->nHeight); + + if (llmq::CLLMQUtils::IsQuorumRotationEnabled(llmq_params.type, pQuorumBaseBlockIndex)) { + evoDb.Write(BuildInversedHeightKeyIndexed(llmq_params.type, nHeight, int(qc.quorumIndex)), pQuorumBaseBlockIndex->nHeight); + } else { + evoDb.Write(BuildInversedHeightKey(llmq_params.type, nHeight), pQuorumBaseBlockIndex->nHeight); + } { LOCK(minableCommitmentsCs); @@ -243,8 +274,8 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH minableCommitments.erase(::SerializeHash(qc)); } - LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- processed commitment from block. type=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s\n", __func__, - static_cast(qc.llmqType), quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); + LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- processed commitment from block. type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s\n", __func__, + uint8_t(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); return true; } @@ -253,7 +284,7 @@ bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, const CBlockIndex* pi { AssertLockHeld(cs_main); - std::map qcs; + std::multimap qcs; CValidationState dummy; if (!GetCommitmentsFromBlock(block, pindex, qcs, dummy)) { return false; @@ -266,7 +297,13 @@ bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, const CBlockIndex* pi } evoDb.Erase(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(qc.llmqType, qc.quorumHash))); - evoDb.Erase(BuildInversedHeightKey(qc.llmqType, pindex->nHeight)); + + if (llmq::CLLMQUtils::IsQuorumRotationEnabled(qc.llmqType, pindex)) { + evoDb.Erase(BuildInversedHeightKeyIndexed(qc.llmqType, pindex->nHeight, int(qc.quorumIndex))); + } else { + evoDb.Erase(BuildInversedHeightKey(qc.llmqType, pindex->nHeight)); + } + { LOCK(minableCommitmentsCs); mapHasMinedCommitmentCache[qc.llmqType].erase(qc.quorumHash); @@ -309,7 +346,7 @@ bool CQuorumBlockProcessor::UpgradeDB() bool r = ReadBlockFromDisk(block, pindex, Params().GetConsensus()); assert(r); - std::map qcs; + std::multimap qcs; CValidationState dummyState; GetCommitmentsFromBlock(block, pindex, qcs, dummyState); @@ -320,7 +357,11 @@ bool CQuorumBlockProcessor::UpgradeDB() } auto pQuorumBaseBlockIndex = LookupBlockIndex(qc.quorumHash); evoDb.GetRawDB().Write(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(qc.llmqType, qc.quorumHash)), std::make_pair(qc, pindex->GetBlockHash())); - evoDb.GetRawDB().Write(BuildInversedHeightKey(qc.llmqType, pindex->nHeight), pQuorumBaseBlockIndex->nHeight); + if (llmq::CLLMQUtils::IsQuorumRotationEnabled(qc.llmqType, pQuorumBaseBlockIndex)) { + evoDb.GetRawDB().Write(BuildInversedHeightKeyIndexed(qc.llmqType, pindex->nHeight, int(qc.quorumIndex)), pQuorumBaseBlockIndex->nHeight); + } else { + evoDb.GetRawDB().Write(BuildInversedHeightKey(qc.llmqType, pindex->nHeight), pQuorumBaseBlockIndex->nHeight); + } } evoDb.GetRawDB().Write(DB_BEST_BLOCK_UPGRADE, pindex->GetBlockHash()); @@ -333,12 +374,11 @@ bool CQuorumBlockProcessor::UpgradeDB() return true; } -bool CQuorumBlockProcessor::GetCommitmentsFromBlock(const CBlock& block, const CBlockIndex* pindex, std::map& ret, CValidationState& state) +bool CQuorumBlockProcessor::GetCommitmentsFromBlock(const CBlock& block, const CBlockIndex* pindex, std::multimap& ret, CValidationState& state) { AssertLockHeld(cs_main); const auto& consensus = Params().GetConsensus(); - bool fDIP0003Active = pindex->nHeight >= consensus.DIP0003Height; ret.clear(); @@ -347,31 +387,52 @@ bool CQuorumBlockProcessor::GetCommitmentsFromBlock(const CBlock& block, const C CFinalCommitmentTxPayload qc; if (!GetTxPayload(*tx, qc)) { // should not happen as it was verified before processing the block + LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s height=%d GetTxPayload fails\n", __func__, pindex->nHeight); return state.DoS(100, false, REJECT_INVALID, "bad-qc-payload"); } - // only allow one commitment per type and per block - if (ret.count(qc.commitment.llmqType)) { - return state.DoS(100, false, REJECT_INVALID, "bad-qc-dup"); + // only allow one commitment per type and per block (This was changed with rotation) + if (!CLLMQUtils::IsQuorumRotationEnabled(qc.commitment.llmqType, pindex)) { + if (ret.count(qc.commitment.llmqType)) { + return state.DoS(100, false, REJECT_INVALID, "bad-qc-dup"); + } } ret.emplace(qc.commitment.llmqType, std::move(qc.commitment)); } } - if (!fDIP0003Active && !ret.empty()) { + if (pindex->nHeight < consensus.DIP0003Height && !ret.empty()) { return state.DoS(100, false, REJECT_INVALID, "bad-qc-premature"); } return true; } -bool CQuorumBlockProcessor::IsMiningPhase(const Consensus::LLMQParams& llmqParams, int nHeight) +bool CQuorumBlockProcessor::IsMiningPhase(const Consensus::LLMQParams& llmqParams, int nHeight) const { - int phaseIndex = nHeight % llmqParams.dkgInterval; - if (phaseIndex >= llmqParams.dkgMiningWindowStart && phaseIndex <= llmqParams.dkgMiningWindowEnd) { - return true; + AssertLockHeld(cs_main); + + // Note: This function can be called for new blocks + assert(nHeight <= ::ChainActive().Height() + 1); + const auto pindex = ::ChainActive().Height() < nHeight ? ::ChainActive().Tip() : ::ChainActive().Tip()->GetAncestor(nHeight); + + if (CLLMQUtils::IsQuorumRotationEnabled(llmqParams.type, pindex)) { + int quorumCycleStartHeight = nHeight - (nHeight % llmqParams.dkgInterval); + int quorumCycleMiningStartHeight = quorumCycleStartHeight + llmqParams.signingActiveQuorumCount + (5 * llmqParams.dkgPhaseBlocks) + 1; + int quorumCycleMiningEndHeight = quorumCycleMiningStartHeight + (llmqParams.dkgMiningWindowEnd - llmqParams.dkgMiningWindowStart); + LogPrint(BCLog::LLMQ, "[IsMiningPhase] nHeight[%d] quorumCycleStartHeight[%d] -- mining[%d-%d]\n", nHeight, quorumCycleStartHeight, quorumCycleMiningStartHeight, quorumCycleMiningEndHeight); + + if (nHeight >= quorumCycleMiningStartHeight && nHeight <= quorumCycleMiningEndHeight) { + return true; + } + } else { + int phaseIndex = nHeight % llmqParams.dkgInterval; + if (phaseIndex >= llmqParams.dkgMiningWindowStart && phaseIndex <= llmqParams.dkgMiningWindowEnd) { + return true; + } } + return false; } @@ -379,28 +440,38 @@ bool CQuorumBlockProcessor::IsCommitmentRequired(const Consensus::LLMQParams& ll { AssertLockHeld(cs_main); - uint256 quorumHash = GetQuorumBlockHash(llmqParams, nHeight); + if (!IsMiningPhase(llmqParams, nHeight)) return false; - // perform extra check for quorumHash.IsNull as the quorum hash is unknown for the first block of a session - // this is because the currently processed block's hash will be the quorumHash of this session - bool isMiningPhase = !quorumHash.IsNull() && IsMiningPhase(llmqParams, nHeight); + // Note: This function can be called for new blocks + assert(nHeight <= ::ChainActive().Height() + 1); + const auto pindex = ::ChainActive().Height() < nHeight ? ::ChainActive().Tip() : ::ChainActive().Tip()->GetAncestor(nHeight); - // did we already mine a non-null commitment for this session? - bool hasMinedCommitment = !quorumHash.IsNull() && HasMinedCommitment(llmqParams.type, quorumHash); + for (int quorumIndex = 0; quorumIndex < llmqParams.signingActiveQuorumCount; ++quorumIndex) { + uint256 quorumHash = GetQuorumBlockHash(llmqParams, nHeight, quorumIndex); + if (quorumHash.IsNull()) return false; + if (HasMinedCommitment(llmqParams.type, quorumHash)) return false; + if (!CLLMQUtils::IsQuorumRotationEnabled(llmqParams.type, pindex)) { + break; + } + } - return isMiningPhase && !hasMinedCommitment; + return true; } // WARNING: This method returns uint256() on the first block of the DKG interval (because the block hash is not known yet) -uint256 CQuorumBlockProcessor::GetQuorumBlockHash(const Consensus::LLMQParams& llmqParams, int nHeight) +uint256 CQuorumBlockProcessor::GetQuorumBlockHash(const Consensus::LLMQParams& llmqParams, int nHeight, int quorumIndex) { AssertLockHeld(cs_main); - int quorumStartHeight = nHeight - (nHeight % llmqParams.dkgInterval); + int quorumStartHeight = nHeight - (nHeight % llmqParams.dkgInterval) + quorumIndex; + uint256 quorumBlockHash; if (!GetBlockHash(quorumBlockHash, quorumStartHeight)) { + LogPrint(BCLog::LLMQ, "[GetQuorumBlockHash] llmqType[%d] h[%d] qi[%d] quorumStartHeight[%d] quorumHash[EMPTY]\n", int(llmqParams.type), nHeight, quorumIndex, quorumStartHeight); return {}; } + + LogPrint(BCLog::LLMQ, "[GetQuorumBlockHash] llmqType[%d] h[%d] qi[%d] quorumStartHeight[%d] quorumHash[%s]\n", int(llmqParams.type), nHeight, quorumIndex, quorumStartHeight, quorumBlockHash.ToString()); return quorumBlockHash; } @@ -477,6 +548,96 @@ std::vector CQuorumBlockProcessor::GetMinedCommitmentsUntilB return ret; } +std::optional CQuorumBlockProcessor::GetLastMinedCommitmentsByQuorumIndexUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, int quorumIndex, size_t cycle) const +{ + LOCK(evoDb.cs); + + auto dbIt = evoDb.GetCurTransaction().NewIteratorUniquePtr(); + + auto firstKey = BuildInversedHeightKeyIndexed(llmqType, pindex->nHeight, quorumIndex); + auto lastKey = BuildInversedHeightKeyIndexed(llmqType, 0, quorumIndex); + + size_t currentCycle = 0; + + dbIt->Seek(firstKey); + + while (dbIt->Valid()) { + decltype(firstKey) curKey; + int quorumHeight; + if (!dbIt->GetKey(curKey) || curKey >= lastKey) { + return std::nullopt; + } + if (std::get<0>(curKey) != DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT_Q_INDEXED || std::get<1>(curKey) != llmqType) { + return std::nullopt; + } + + uint32_t nMinedHeight = std::numeric_limits::max() - be32toh(std::get<3>(curKey)); + if (nMinedHeight > pindex->nHeight) { + return std::nullopt; + } + + if (!dbIt->GetValue(quorumHeight)) { + return std::nullopt; + } + + auto pQuorumBaseBlockIndex = pindex->GetAncestor(quorumHeight); + assert(pQuorumBaseBlockIndex); + + if (currentCycle == cycle) + return std::make_optional(pQuorumBaseBlockIndex); + + currentCycle++; + + dbIt->Next(); + } + + return std::nullopt; +} + +std::vector> CQuorumBlockProcessor::GetLastMinedCommitmentsPerQuorumIndexUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t cycle) const +{ + const Consensus::LLMQParams& llmqParams = GetLLMQParams(llmqType); + std::vector> ret; + + for (int quorumIndex = 0; quorumIndex < llmqParams.signingActiveQuorumCount; ++quorumIndex) { + std::optional q = GetLastMinedCommitmentsByQuorumIndexUntilBlock(llmqType, pindex, quorumIndex, cycle); + if (q.has_value()) { + ret.push_back(std::make_pair(quorumIndex, q.value())); + } + } + + return ret; +} + +std::vector CQuorumBlockProcessor::GetMinedCommitmentsIndexedUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t maxCount) const +{ + std::vector ret; + + size_t cycle = 0; + + while (ret.size() < maxCount) { + std::vector> cycleRet = GetLastMinedCommitmentsPerQuorumIndexUntilBlock(llmqType, pindex, cycle); + + if (cycleRet.empty()) + return ret; + + std::vector cycleRetTransformed; + std::transform(cycleRet.begin(), + cycleRet.end(), + std::back_inserter(cycleRetTransformed), + [](const std::pair& p) { return p.second; }); + + size_t needToCopy = maxCount - ret.size(); + + std::copy_n(cycleRetTransformed.begin(), + std::min(needToCopy, cycleRetTransformed.size()), + std::back_inserter(ret)); + cycle++; + } + + return ret; +} + // The returned quorums are in reversed order, so the most recent one is at index 0 std::map> CQuorumBlockProcessor::GetMinedAndActiveCommitmentsUntilBlock(const CBlockIndex* pindex) const { @@ -485,8 +646,14 @@ std::map> CQuorumBlockProce for (const auto& params : Params().GetConsensus().llmqs) { auto& v = ret[params.type]; v.reserve(params.signingActiveQuorumCount); - auto commitments = GetMinedCommitmentsUntilBlock(params.type, pindex, params.signingActiveQuorumCount); - std::copy(commitments.begin(), commitments.end(), std::back_inserter(v)); + if (CLLMQUtils::IsQuorumRotationEnabled(params.type, pindex)) { + std::vector> commitments = GetLastMinedCommitmentsPerQuorumIndexUntilBlock(params.type, pindex, 0); + std::transform(commitments.begin(), commitments.end(), std::back_inserter(v), + [](const std::pair& p) { return p.second; }); + } else { + auto commitments = GetMinedCommitmentsUntilBlock(params.type, pindex, params.signingActiveQuorumCount); + std::copy(commitments.begin(), commitments.end(), std::back_inserter(v)); + } } return ret; @@ -541,54 +708,81 @@ bool CQuorumBlockProcessor::GetMineableCommitmentByHash(const uint256& commitmen return true; } -// Will return false if no commitment should be mined -// Will return true and a null commitment if no mineable commitment is known and none was mined yet -bool CQuorumBlockProcessor::GetMineableCommitment(const Consensus::LLMQParams& llmqParams, int nHeight, CFinalCommitment& ret) const +// Will return nullopt if no commitment should be mined +// Will return a null commitment if no mineable commitment is known and none was mined yet +std::optional> CQuorumBlockProcessor::GetMineableCommitments(const Consensus::LLMQParams& llmqParams, int nHeight) const { AssertLockHeld(cs_main); - if (!IsCommitmentRequired(llmqParams, nHeight)) { + std::vector ret; + + if (!IsCommitmentRequired(llmqParams, nHeight /*, 0*/)) { // no commitment required - return false; + return std::nullopt; } - uint256 quorumHash = GetQuorumBlockHash(llmqParams, nHeight); - if (quorumHash.IsNull()) { - return false; - } + // Note: This function can be called for new blocks + assert(nHeight <= ::ChainActive().Height() + 1); + const auto pindex = ::ChainActive().Height() < nHeight ? ::ChainActive().Tip() : ::ChainActive().Tip()->GetAncestor(nHeight); - LOCK(minableCommitmentsCs); + std::stringstream ss; + for (int quorumIndex = 0; quorumIndex < llmqParams.signingActiveQuorumCount; ++quorumIndex) { + CFinalCommitment cf; - auto k = std::make_pair(llmqParams.type, quorumHash); - auto it = minableCommitmentsByQuorum.find(k); - if (it == minableCommitmentsByQuorum.end()) { - // null commitment required - ret = CFinalCommitment(llmqParams, quorumHash); - return true; + uint256 quorumHash = GetQuorumBlockHash(llmqParams, nHeight, quorumIndex); + if (quorumHash.IsNull()) { + break; + } + + LOCK(minableCommitmentsCs); + + auto k = std::make_pair(llmqParams.type, quorumHash); + auto it = minableCommitmentsByQuorum.find(k); + if (it == minableCommitmentsByQuorum.end()) { + // null commitment required + cf = CFinalCommitment(llmqParams, quorumHash); + cf.quorumIndex = static_cast(quorumIndex); + if (CLLMQUtils::IsQuorumRotationEnabled(llmqParams.type, pindex)) { + cf.nVersion = CFinalCommitment::INDEXED_QUORUM_VERSION; + } + ss << "{ created nversion[" << cf.nVersion << "] quorumIndex[" << cf.quorumIndex << "] }"; + } else { + cf = minableCommitments.at(it->second); + ss << "{ cached nversion[" << cf.nVersion << "] quorumIndex[" << cf.quorumIndex << "] }"; + } + + ret.push_back(std::move(cf)); + if (!CLLMQUtils::IsQuorumRotationEnabled(llmqParams.type, pindex)) { + break; + } } - ret = minableCommitments.at(it->second); + LogPrint(BCLog::LLMQ, "GetMineableCommitments cf height[%d] content: %s\n", nHeight, ss.str()); - return true; + if (ret.empty()) { + return std::nullopt; + } else { + return std::make_optional(ret); + } } -bool CQuorumBlockProcessor::GetMineableCommitmentTx(const Consensus::LLMQParams& llmqParams, int nHeight, CTransactionRef& ret) const +bool CQuorumBlockProcessor::GetMineableCommitmentsTx(const Consensus::LLMQParams& llmqParams, int nHeight, std::vector& ret) const { AssertLockHeld(cs_main); - - CFinalCommitmentTxPayload qc; - if (!GetMineableCommitment(llmqParams, nHeight, qc.commitment)) { + std::optional> qcs = GetMineableCommitments(llmqParams, nHeight); + if (!qcs.has_value()) return false; - } - qc.nHeight = nHeight; - - CMutableTransaction tx; - tx.nVersion = 3; - tx.nType = TRANSACTION_QUORUM_COMMITMENT; - SetTxPayload(tx, qc); - - ret = MakeTransactionRef(tx); + for (const auto& f : qcs.value()) { + CFinalCommitmentTxPayload qc; + qc.nHeight = nHeight; + qc.commitment = f; + CMutableTransaction tx; + tx.nVersion = 3; + tx.nType = TRANSACTION_QUORUM_COMMITMENT; + SetTxPayload(tx, qc); + ret.push_back(MakeTransactionRef(tx)); + } return true; } diff --git a/src/llmq/blockprocessor.h b/src/llmq/blockprocessor.h index ab7a364bd815..1da778536a1c 100644 --- a/src/llmq/blockprocessor.h +++ b/src/llmq/blockprocessor.h @@ -54,21 +54,23 @@ class CQuorumBlockProcessor void AddMineableCommitment(const CFinalCommitment& fqc); bool HasMineableCommitment(const uint256& hash) const; bool GetMineableCommitmentByHash(const uint256& commitmentHash, CFinalCommitment& ret) const; - bool GetMineableCommitment(const Consensus::LLMQParams& llmqParams, int nHeight, CFinalCommitment& ret) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); - bool GetMineableCommitmentTx(const Consensus::LLMQParams& llmqParams, int nHeight, CTransactionRef& ret) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); - + std::optional> GetMineableCommitments(const Consensus::LLMQParams& llmqParams, int nHeight) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool GetMineableCommitmentsTx(const Consensus::LLMQParams& llmqParams, int nHeight, std::vector& ret) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) const; CFinalCommitmentPtr GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, uint256& retMinedBlockHash) const; std::vector GetMinedCommitmentsUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t maxCount) const; std::map> GetMinedAndActiveCommitmentsUntilBlock(const CBlockIndex* pindex) const; + std::vector GetMinedCommitmentsIndexedUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t maxCount) const; + std::vector> GetLastMinedCommitmentsPerQuorumIndexUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t cycle) const; + std::optional GetLastMinedCommitmentsByQuorumIndexUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, int quorumIndex, size_t cycle) const; private: - static bool GetCommitmentsFromBlock(const CBlock& block, const CBlockIndex* pindex, std::map& ret, CValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + static bool GetCommitmentsFromBlock(const CBlock& block, const CBlockIndex* pindex, std::multimap& ret, CValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool ProcessCommitment(int nHeight, const uint256& blockHash, const CFinalCommitment& qc, CValidationState& state, bool fJustCheck, bool fBLSChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - static bool IsMiningPhase(const Consensus::LLMQParams& llmqParams, int nHeight); + bool IsMiningPhase(const Consensus::LLMQParams& llmqParams, int nHeight) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool IsCommitmentRequired(const Consensus::LLMQParams& llmqParams, int nHeight) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); - static uint256 GetQuorumBlockHash(const Consensus::LLMQParams& llmqParams, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + static uint256 GetQuorumBlockHash(const Consensus::LLMQParams& llmqParams, int nHeight, int quorumIndex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); }; extern CQuorumBlockProcessor* quorumBlockProcessor; diff --git a/src/llmq/commitment.cpp b/src/llmq/commitment.cpp index bc7f5ae75086..a25389277290 100644 --- a/src/llmq/commitment.cpp +++ b/src/llmq/commitment.cpp @@ -29,53 +29,73 @@ CFinalCommitment::CFinalCommitment(const Consensus::LLMQParams& params, const ui bool CFinalCommitment::Verify(const CBlockIndex* pQuorumBaseBlockIndex, bool checkSigs) const { - if (nVersion == 0 || nVersion > CURRENT_VERSION) { + if (nVersion == 0 || nVersion != (CLLMQUtils::IsQuorumRotationEnabled(llmqType, pQuorumBaseBlockIndex) ? INDEXED_QUORUM_VERSION : CURRENT_VERSION)) { + LogPrintfFinalCommitment("q[%s] invalid nVersion=%d\n", quorumHash.ToString(), nVersion); return false; } if (!Params().HasLLMQ(llmqType)) { - LogPrintfFinalCommitment("invalid llmqType=%d\n", static_cast(llmqType)); + LogPrintfFinalCommitment("q[%s] invalid llmqType=%d\n", quorumHash.ToString(), static_cast(llmqType)); return false; } const auto& llmq_params = GetLLMQParams(llmqType); + if (pQuorumBaseBlockIndex->GetBlockHash() != quorumHash) { + LogPrintfFinalCommitment("q[%s] invalid quorumHash\n", quorumHash.ToString()); + return false; + } + + if ((pQuorumBaseBlockIndex->nHeight % llmq_params.dkgInterval) != quorumIndex) { + LogPrintfFinalCommitment("q[%s] invalid quorumIndex=%d\n", quorumHash.ToString(), quorumIndex); + return false; + } + if (!VerifySizes(llmq_params)) { return false; } if (CountValidMembers() < llmq_params.minSize) { - LogPrintfFinalCommitment("invalid validMembers count. validMembersCount=%d\n", CountValidMembers()); + LogPrintfFinalCommitment("q[%s] invalid validMembers count. validMembersCount=%d\n", quorumHash.ToString(), CountValidMembers()); return false; } if (CountSigners() < llmq_params.minSize) { - LogPrintfFinalCommitment("invalid signers count. signersCount=%d\n", CountSigners()); + LogPrintfFinalCommitment("q[%s] invalid signers count. signersCount=%d\n", quorumHash.ToString(), CountSigners()); return false; } if (!quorumPublicKey.IsValid()) { - LogPrintfFinalCommitment("invalid quorumPublicKey\n"); + LogPrintfFinalCommitment("q[%s] invalid quorumPublicKey\n", quorumHash.ToString()); return false; } if (quorumVvecHash.IsNull()) { - LogPrintfFinalCommitment("invalid quorumVvecHash\n"); + LogPrintfFinalCommitment("q[%s] invalid quorumVvecHash\n", quorumHash.ToString()); return false; } if (!membersSig.IsValid()) { - LogPrintfFinalCommitment("invalid membersSig\n"); + LogPrintfFinalCommitment("q[%s] invalid membersSig\n", quorumHash.ToString()); return false; } if (!quorumSig.IsValid()) { - LogPrintfFinalCommitment("invalid vvecSig\n"); + LogPrintfFinalCommitment("q[%s] invalid vvecSig\n"); return false; } + auto members = CLLMQUtils::GetAllQuorumMembers(llmqType, pQuorumBaseBlockIndex); + std::stringstream ss; + for (size_t i = 0; i < llmq_params.size; i++) { + ss << "v[" << i << "]=" << validMembers[i]; + } + std::stringstream ss2; + for (size_t i = 0; i < llmq_params.size; i++) { + ss2 << "s[" << i << "]=" << signers[i]; + } - auto members = CLLMQUtils::GetAllQuorumMembers(llmq_params, pQuorumBaseBlockIndex); + LogPrintf("CFinalCommitment::%s mns[%d] validMembers[%s] signers[%s]\n", __func__, members.size(), ss.str(), ss2.str()); for (size_t i = members.size(); i < size_t(llmq_params.size); i++) { if (validMembers[i]) { - LogPrintfFinalCommitment("invalid validMembers bitset. bit %d should not be set\n", i); + LogPrintfFinalCommitment("q[%s] invalid validMembers bitset. bit %d should not be set\n", quorumHash.ToString(), i); return false; } if (signers[i]) { - LogPrintfFinalCommitment("invalid signers bitset. bit %d should not be set\n", i); + LogPrintfFinalCommitment("q[%s] invalid signers bitset. bit %d should not be set\n", quorumHash.ToString(), i); return false; } } @@ -83,7 +103,11 @@ bool CFinalCommitment::Verify(const CBlockIndex* pQuorumBaseBlockIndex, bool che // sigs are only checked when the block is processed if (checkSigs) { uint256 commitmentHash = CLLMQUtils::BuildCommitmentHash(llmq_params.type, quorumHash, validMembers, quorumPublicKey, quorumVvecHash); - + std::stringstream ss3; + for (const auto& mn : members) { + ss3 << mn->proTxHash.ToString().substr(0, 4) << " | "; + } + LogPrintf("CFinalCommitment::%s members[%s] quorumPublicKey[%s] commitmentHash[%s]\n", __func__, ss3.str(), quorumPublicKey.ToString(), commitmentHash.ToString()); std::vector memberPubKeys; for (size_t i = 0; i < members.size(); i++) { if (!signers[i]) { @@ -93,23 +117,25 @@ bool CFinalCommitment::Verify(const CBlockIndex* pQuorumBaseBlockIndex, bool che } if (!membersSig.VerifySecureAggregated(memberPubKeys, commitmentHash)) { - LogPrintfFinalCommitment("invalid aggregated members signature\n"); + LogPrintfFinalCommitment("q[%s] invalid aggregated members signature\n", quorumHash.ToString()); return false; } if (!quorumSig.VerifyInsecure(quorumPublicKey, commitmentHash)) { - LogPrintfFinalCommitment("invalid quorum signature\n"); + LogPrintfFinalCommitment("q[%s] invalid quorum signature\n", quorumHash.ToString()); return false; } } + LogPrintfFinalCommitment("q[%s] VALID\n", quorumHash.ToString()); + return true; } bool CFinalCommitment::VerifyNull() const { if (!Params().HasLLMQ(llmqType)) { - LogPrintfFinalCommitment("invalid llmqType=%d\n", static_cast(llmqType)); + LogPrintfFinalCommitment("q[%s]invalid llmqType=%d\n", quorumHash.ToString(), static_cast(llmqType)); return false; } @@ -123,11 +149,11 @@ bool CFinalCommitment::VerifyNull() const bool CFinalCommitment::VerifySizes(const Consensus::LLMQParams& params) const { if (signers.size() != size_t(params.size)) { - LogPrintfFinalCommitment("invalid signers.size=%d\n", signers.size()); + LogPrintfFinalCommitment("q[%s] invalid signers.size=%d\n", quorumHash.ToString(), signers.size()); return false; } if (validMembers.size() != size_t(params.size)) { - LogPrintfFinalCommitment("invalid signers.size=%d\n", signers.size()); + LogPrintfFinalCommitment("q[%s] invalid signers.size=%d\n", quorumHash.ToString(), signers.size()); return false; } return true; @@ -137,14 +163,23 @@ bool CheckLLMQCommitment(const CTransaction& tx, const CBlockIndex* pindexPrev, { CFinalCommitmentTxPayload qcTx; if (!GetTxPayload(tx, qcTx)) { + LogPrintfFinalCommitment("h[%d] GetTxPayload failed\n", pindexPrev->nHeight); return state.DoS(100, false, REJECT_INVALID, "bad-qc-payload"); } + const auto& llmq_params = GetLLMQParams(qcTx.commitment.llmqType); + std::stringstream ss; + for (size_t i = 0; i < llmq_params.size; i++) { + ss << "v[" << i << "]=" << qcTx.commitment.validMembers[i]; + } + LogPrintf("%s llmqType[%d] validMembers[%s] signers[]\n", __func__, int(qcTx.commitment.llmqType), ss.str()); if (qcTx.nVersion == 0 || qcTx.nVersion > CFinalCommitmentTxPayload::CURRENT_VERSION) { + LogPrintfFinalCommitment("h[%d] invalid qcTx.nVersion[%d]\n", pindexPrev->nHeight, qcTx.nVersion); return state.DoS(100, false, REJECT_INVALID, "bad-qc-version"); } if (qcTx.nHeight != uint32_t(pindexPrev->nHeight + 1)) { + LogPrintfFinalCommitment("h[%d] invalid qcTx.nHeight[%d]\n", pindexPrev->nHeight, qcTx.nHeight); return state.DoS(100, false, REJECT_INVALID, "bad-qc-height"); } @@ -165,15 +200,19 @@ bool CheckLLMQCommitment(const CTransaction& tx, const CBlockIndex* pindexPrev, if (qcTx.commitment.IsNull()) { if (!qcTx.commitment.VerifyNull()) { + LogPrintfFinalCommitment("h[%d] invalid qcTx.commitment[%s] VerifyNull failed\n", pindexPrev->nHeight, qcTx.commitment.quorumHash.ToString()); return state.DoS(100, false, REJECT_INVALID, "bad-qc-invalid-null"); } return true; } if (!qcTx.commitment.Verify(pQuorumBaseBlockIndex, false)) { + LogPrintfFinalCommitment("h[%d] invalid qcTx.commitment[%s] Verify failed\n", pindexPrev->nHeight, qcTx.commitment.quorumHash.ToString()); return state.DoS(100, false, REJECT_INVALID, "bad-qc-invalid"); } + LogPrintfFinalCommitment("h[%d] CheckLLMQCommitment VALID\n", pindexPrev->nHeight); + return true; } diff --git a/src/llmq/commitment.h b/src/llmq/commitment.h index 6266d7479ac8..5f62a2d65281 100644 --- a/src/llmq/commitment.h +++ b/src/llmq/commitment.h @@ -22,11 +22,13 @@ class CFinalCommitment { public: static constexpr uint16_t CURRENT_VERSION = 1; + static constexpr uint16_t INDEXED_QUORUM_VERSION = 2; public: uint16_t nVersion{CURRENT_VERSION}; Consensus::LLMQType llmqType{Consensus::LLMQType::LLMQ_NONE}; uint256 quorumHash; + int16_t quorumIndex{0}; std::vector signers; std::vector validMembers; @@ -59,7 +61,17 @@ class CFinalCommitment READWRITE( obj.nVersion, obj.llmqType, - obj.quorumHash, + obj.quorumHash + ); + + int16_t _quorumIndex = 0; + SER_WRITE(obj, _quorumIndex = obj.quorumIndex); + if (obj.nVersion == CFinalCommitment::INDEXED_QUORUM_VERSION) { + READWRITE(_quorumIndex); + } + SER_READ(obj, obj.quorumIndex = _quorumIndex); + + READWRITE( DYNBITSET(obj.signers), DYNBITSET(obj.validMembers), obj.quorumPublicKey, @@ -91,6 +103,7 @@ class CFinalCommitment obj.pushKV("version", (int)nVersion); obj.pushKV("llmqType", (int)llmqType); obj.pushKV("quorumHash", quorumHash.ToString()); + obj.pushKV("quorumIndex", quorumIndex); obj.pushKV("signersCount", CountSigners()); obj.pushKV("signers", CLLMQUtils::ToHexStr(signers)); obj.pushKV("validMembersCount", CountValidMembers()); @@ -108,10 +121,11 @@ class CFinalCommitmentTxPayload public: static constexpr auto SPECIALTX_TYPE = TRANSACTION_QUORUM_COMMITMENT; static constexpr uint16_t CURRENT_VERSION = 1; - + // Not sure if this new version is also needed for CFinalCommitmentTxPayload + static constexpr uint16_t QUORUM_INDEXED_VERSION = 2; public: uint16_t nVersion{CURRENT_VERSION}; - uint32_t nHeight{(uint32_t)-1}; + uint32_t nHeight{std::numeric_limits::max()}; CFinalCommitment commitment; public: diff --git a/src/llmq/debug.cpp b/src/llmq/debug.cpp index be9f219e363d..4d97abeb1123 100644 --- a/src/llmq/debug.cpp +++ b/src/llmq/debug.cpp @@ -15,7 +15,7 @@ namespace llmq { CDKGDebugManager* quorumDKGDebugManager; -UniValue CDKGDebugSessionStatus::ToJson(int detailLevel) const +UniValue CDKGDebugSessionStatus::ToJson(int quorumIndex, int detailLevel) const { UniValue ret(UniValue::VOBJ); @@ -27,7 +27,7 @@ UniValue CDKGDebugSessionStatus::ToJson(int detailLevel) const if (detailLevel == 2) { const CBlockIndex* pindex = WITH_LOCK(cs_main, return LookupBlockIndex(quorumHash)); if (pindex != nullptr) { - dmnMembers = CLLMQUtils::GetAllQuorumMembers(GetLLMQParams(llmqType), pindex); + dmnMembers = CLLMQUtils::GetAllQuorumMembers(llmqType, pindex); } } @@ -115,15 +115,20 @@ UniValue CDKGDebugStatus::ToJson(int detailLevel) const ret.pushKV("time", nTime); ret.pushKV("timeStr", FormatISO8601DateTime(nTime)); - UniValue sessionsJson(UniValue::VOBJ); + // TODO Support array of sessions + UniValue sessionsArrJson(UniValue::VARR); for (const auto& p : sessions) { - if (!Params().HasLLMQ(p.first)) { + if (!Params().HasLLMQ(p.first.first)) { continue; } - sessionsJson.pushKV(std::string(GetLLMQParams(p.first).name), p.second.ToJson(detailLevel)); - } + UniValue s(UniValue::VOBJ); + s.pushKV("llmqType", std::string(GetLLMQParams(p.first.first).name)); + s.pushKV("quorumIndex", p.first.second); + s.pushKV("status", p.second.ToJson(p.first.second, detailLevel)); - ret.pushKV("session", sessionsJson); + sessionsArrJson.push_back(s); + } + ret.pushKV("session", sessionsArrJson); return ret; } @@ -134,11 +139,11 @@ void CDKGDebugManager::GetLocalDebugStatus(llmq::CDKGDebugStatus& ret) const ret = localStatus; } -void CDKGDebugManager::ResetLocalSessionStatus(Consensus::LLMQType llmqType) +void CDKGDebugManager::ResetLocalSessionStatus(Consensus::LLMQType llmqType, int quorumIndex) { LOCK(cs); - auto it = localStatus.sessions.find(llmqType); + auto it = localStatus.sessions.find(std::make_pair(llmqType, quorumIndex)); if (it == localStatus.sessions.end()) { return; } @@ -147,13 +152,13 @@ void CDKGDebugManager::ResetLocalSessionStatus(Consensus::LLMQType llmqType) localStatus.nTime = GetAdjustedTime(); } -void CDKGDebugManager::InitLocalSessionStatus(const Consensus::LLMQParams& llmqParams, const uint256& quorumHash, int quorumHeight) +void CDKGDebugManager::InitLocalSessionStatus(const Consensus::LLMQParams& llmqParams, int quorumIndex, const uint256& quorumHash, int quorumHeight) { LOCK(cs); - auto it = localStatus.sessions.find(llmqParams.type); + auto it = localStatus.sessions.find(std::make_pair(llmqParams.type, quorumIndex)); if (it == localStatus.sessions.end()) { - it = localStatus.sessions.emplace(llmqParams.type, CDKGDebugSessionStatus()).first; + it = localStatus.sessions.emplace(std::make_pair(llmqParams.type, quorumIndex), CDKGDebugSessionStatus()).first; } auto& session = it->second; @@ -166,11 +171,11 @@ void CDKGDebugManager::InitLocalSessionStatus(const Consensus::LLMQParams& llmqP session.members.resize((size_t)llmqParams.size); } -void CDKGDebugManager::UpdateLocalSessionStatus(Consensus::LLMQType llmqType, std::function&& func) +void CDKGDebugManager::UpdateLocalSessionStatus(Consensus::LLMQType llmqType, int quorumIndex, std::function&& func) { LOCK(cs); - auto it = localStatus.sessions.find(llmqType); + auto it = localStatus.sessions.find(std::make_pair(llmqType, quorumIndex)); if (it == localStatus.sessions.end()) { return; } @@ -180,11 +185,11 @@ void CDKGDebugManager::UpdateLocalSessionStatus(Consensus::LLMQType llmqType, st } } -void CDKGDebugManager::UpdateLocalMemberStatus(Consensus::LLMQType llmqType, size_t memberIdx, std::function&& func) +void CDKGDebugManager::UpdateLocalMemberStatus(Consensus::LLMQType llmqType, int quorumIndex, size_t memberIdx, std::function&& func) { LOCK(cs); - auto it = localStatus.sessions.find(llmqType); + auto it = localStatus.sessions.find(std::make_pair(llmqType, quorumIndex)); if (it == localStatus.sessions.end()) { return; } diff --git a/src/llmq/debug.h b/src/llmq/debug.h index d4cab07390cd..8df975dd0701 100644 --- a/src/llmq/debug.h +++ b/src/llmq/debug.h @@ -72,7 +72,7 @@ class CDKGDebugSessionStatus public: CDKGDebugSessionStatus() : statusBitset(0) {} - UniValue ToJson(int detailLevel) const; + UniValue ToJson(int quorumIndex, int detailLevel) const; }; class CDKGDebugStatus @@ -80,7 +80,8 @@ class CDKGDebugStatus public: int64_t nTime{0}; - std::map sessions; + std::map, CDKGDebugSessionStatus> sessions; + //std::map sessions; public: UniValue ToJson(int detailLevel) const; @@ -97,11 +98,11 @@ class CDKGDebugManager void GetLocalDebugStatus(CDKGDebugStatus& ret) const; - void ResetLocalSessionStatus(Consensus::LLMQType llmqType); - void InitLocalSessionStatus(const Consensus::LLMQParams& llmqParams, const uint256& quorumHash, int quorumHeight); + void ResetLocalSessionStatus(Consensus::LLMQType llmqType, int quorumIndex); + void InitLocalSessionStatus(const Consensus::LLMQParams& llmqParams, int quorumIndex, const uint256& quorumHash, int quorumHeight); - void UpdateLocalSessionStatus(Consensus::LLMQType llmqType, std::function&& func); - void UpdateLocalMemberStatus(Consensus::LLMQType llmqType, size_t memberIdx, std::function&& func); + void UpdateLocalSessionStatus(Consensus::LLMQType llmqType, int quorumIndex, std::function&& func); + void UpdateLocalMemberStatus(Consensus::LLMQType llmqType, int quorumIndex, size_t memberIdx, std::function&& func); }; extern CDKGDebugManager* quorumDKGDebugManager; diff --git a/src/llmq/dkgsession.cpp b/src/llmq/dkgsession.cpp index 5ffd35968ebc..ab052c49cc1a 100644 --- a/src/llmq/dkgsession.cpp +++ b/src/llmq/dkgsession.cpp @@ -60,6 +60,7 @@ bool CDKGSession::ShouldSimulateError(const std::string& type) const double rate = GetSimulatedErrorRate(type); return GetRandBool(rate); } + CDKGMember::CDKGMember(const CDeterministicMNCPtr& _dmn, size_t _idx) : dmn(_dmn), idx(_idx), @@ -68,10 +69,10 @@ CDKGMember::CDKGMember(const CDeterministicMNCPtr& _dmn, size_t _idx) : } -bool CDKGSession::Init(const CBlockIndex* _pQuorumBaseBlockIndex, const std::vector& mns, const uint256& _myProTxHash) +bool CDKGSession::Init(const CBlockIndex* _pQuorumBaseBlockIndex, const std::vector& mns, const uint256& _myProTxHash, int _quorumIndex) { m_quorum_base_block_index = _pQuorumBaseBlockIndex; - + quorumIndex = _quorumIndex; members.resize(mns.size()); memberIds.resize(members.size()); receivedVvecs.resize(members.size()); @@ -98,14 +99,29 @@ bool CDKGSession::Init(const CBlockIndex* _pQuorumBaseBlockIndex, const std::vec CDKGLogger logger(*this, __func__); + if (CLLMQUtils::IsQuorumRotationEnabled(params.type, m_quorum_base_block_index)) { + int cycleQuorumBaseHeight = m_quorum_base_block_index->nHeight - quorumIndex; + const CBlockIndex* pCycleQuorumBaseBlockIndex = m_quorum_base_block_index->GetAncestor(cycleQuorumBaseHeight); + std::stringstream ss; + for (const auto& mn : members) { + ss << mn->dmn->proTxHash.ToString().substr(0, 4) << " | "; + } + LogPrintf("DKGComposition h[%d] i[%d] DKG:%s\n", pCycleQuorumBaseBlockIndex->nHeight, quorumIndex, ss.str()); + } + if (mns.size() < size_t(params.minSize)) { logger.Batch("not enough members (%d < %d), aborting init", mns.size(), params.minSize); return false; } if (!myProTxHash.IsNull()) { - quorumDKGDebugManager->InitLocalSessionStatus(params, m_quorum_base_block_index->GetBlockHash(), m_quorum_base_block_index->nHeight); + quorumDKGDebugManager->InitLocalSessionStatus(params, quorumIndex, m_quorum_base_block_index->GetBlockHash(), m_quorum_base_block_index->nHeight); relayMembers = CLLMQUtils::GetQuorumRelayMembers(params, m_quorum_base_block_index, myProTxHash, true); + std::stringstream ss; + for (const auto& r : relayMembers) { + ss << r.ToString().substr(0, 4) << " | "; + } + logger.Batch("forMember[%s] relayMembers[%s]", myProTxHash.ToString().substr(0, 4), ss.str()); } if (myProTxHash.IsNull()) { @@ -181,7 +197,7 @@ void CDKGSession::SendContributions(CDKGPendingMessages& pendingMessages) logger.Flush(); - quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) { + quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { status.sentContributions = true; return true; }); @@ -264,7 +280,7 @@ void CDKGSession::ReceiveMessage(const CDKGContribution& qc, bool& retBan) CInv inv(MSG_QUORUM_CONTRIB, hash); RelayInvToParticipants(inv); - quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) { + quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, quorumIndex, member->idx, [&](CDKGDebugMemberStatus& status) { status.receivedContribution = true; return true; }); @@ -304,7 +320,7 @@ void CDKGSession::ReceiveMessage(const CDKGContribution& qc, bool& retBan) if (complain) { member->weComplain = true; - quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) { + quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, quorumIndex, member->idx, [&](CDKGDebugMemberStatus& status) { status.weComplain = true; return true; }); @@ -372,7 +388,7 @@ void CDKGSession::VerifyPendingContributions() const auto& m = members[memberIndexes[i]]; logger.Batch("invalid contribution from %s. will complain later", m->dmn->proTxHash.ToString()); m->weComplain = true; - quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, m->idx, [&](CDKGDebugMemberStatus& status) { + quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, quorumIndex, m->idx, [&](CDKGDebugMemberStatus& status) { status.weComplain = true; return true; }); @@ -498,7 +514,7 @@ void CDKGSession::SendComplaint(CDKGPendingMessages& pendingMessages) logger.Flush(); - quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) { + quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { status.sentComplaint = true; return true; }); @@ -571,7 +587,7 @@ void CDKGSession::ReceiveMessage(const CDKGComplaint& qc, bool& retBan) CInv inv(MSG_QUORUM_COMPLAINT, hash); RelayInvToParticipants(inv); - quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) { + quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, quorumIndex, member->idx, [&](CDKGDebugMemberStatus& status) { status.receivedComplaint = true; return true; }); @@ -597,7 +613,7 @@ void CDKGSession::ReceiveMessage(const CDKGComplaint& qc, bool& retBan) if (qc.complainForMembers[i]) { m->complaintsFromOthers.emplace(qc.proTxHash); m->someoneComplain = true; - quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, m->idx, [&](CDKGDebugMemberStatus& status) { + quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, quorumIndex, m->idx, [&](CDKGDebugMemberStatus& status) { return status.complaintsFromMembers.emplace(member->idx).second; }); if (AreWeMember() && i == myIdx) { @@ -692,7 +708,7 @@ void CDKGSession::SendJustification(CDKGPendingMessages& pendingMessages, const logger.Flush(); - quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) { + quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { status.sentJustification = true; return true; }); @@ -782,7 +798,7 @@ void CDKGSession::ReceiveMessage(const CDKGJustification& qj, bool& retBan) CInv inv(MSG_QUORUM_JUSTIFICATION, hash); RelayInvToParticipants(inv); - quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) { + quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, quorumIndex, member->idx, [&](CDKGDebugMemberStatus& status) { status.receivedJustification = true; return true; }); @@ -1004,7 +1020,7 @@ void CDKGSession::SendCommitment(CDKGPendingMessages& pendingMessages) logger.Flush(); - quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) { + quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { status.sentPrematureCommitment = true; return true; }); @@ -1140,7 +1156,7 @@ void CDKGSession::ReceiveMessage(const CDKGPrematureCommitment& qc, bool& retBan CInv inv(MSG_QUORUM_PREMATURE_COMMITMENT, hash); RelayInvToParticipants(inv); - quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, member->idx, [&](CDKGDebugMemberStatus& status) { + quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, quorumIndex, member->idx, [&](CDKGDebugMemberStatus& status) { status.receivedPrematureCommitment = true; return true; }); @@ -1202,6 +1218,13 @@ std::vector CDKGSession::FinalizeCommitments() fqc.quorumPublicKey = first.quorumPublicKey; fqc.quorumVvecHash = first.quorumVvecHash; + if (CLLMQUtils::IsQuorumRotationEnabled(fqc.llmqType, m_quorum_base_block_index)) { + fqc.nVersion = CFinalCommitment::INDEXED_QUORUM_VERSION; + fqc.quorumIndex = quorumIndex; + } else { + fqc.quorumIndex = 0; + } + uint256 commitmentHash = CLLMQUtils::BuildCommitmentHash(fqc.llmqType, fqc.quorumHash, fqc.validMembers, fqc.quorumPublicKey, fqc.quorumVvecHash); std::vector aggSigs; @@ -1271,7 +1294,7 @@ void CDKGSession::MarkBadMember(size_t idx) if (member->bad) { return; } - quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, idx, [&](CDKGDebugMemberStatus& status) { + quorumDKGDebugManager->UpdateLocalMemberStatus(params.type, quorumIndex, idx, [&](CDKGDebugMemberStatus& status) { status.bad = true; return true; }); @@ -1280,12 +1303,39 @@ void CDKGSession::MarkBadMember(size_t idx) void CDKGSession::RelayInvToParticipants(const CInv& inv) const { + CDKGLogger logger(*this, __func__); + std::stringstream ss; + for (const auto& r : relayMembers) { + ss << r.ToString().substr(0, 4) << " | "; + } + logger.Batch("RelayInvToParticipants inv[%s] relayMembers[%d] GetNodeCount[%d] GetNetworkActive[%d] HasMasternodeQuorumNodes[%d] for quorumHash[%s] forMember[%s] relayMembers[%s]", + inv.ToString(), + relayMembers.size(), + g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL), + g_connman->GetNetworkActive(), + g_connman->HasMasternodeQuorumNodes(params.type, m_quorum_base_block_index->GetBlockHash()), + m_quorum_base_block_index->GetBlockHash().ToString(), + myProTxHash.ToString().substr(0, 4), ss.str()); + + std::stringstream ss2; g_connman->ForEachNode([&](CNode* pnode) { if (pnode->qwatch || (!pnode->GetVerifiedProRegTxHash().IsNull() && relayMembers.count(pnode->GetVerifiedProRegTxHash()))) { pnode->PushInventory(inv); } + + if (pnode->GetVerifiedProRegTxHash().IsNull()) { + logger.Batch("node[%d:%s] not mn", + pnode->GetId(), + pnode->GetAddrName()); + } else if (relayMembers.count(pnode->GetVerifiedProRegTxHash()) == 0) { + ss2 << pnode->GetVerifiedProRegTxHash().ToString().substr(0, 4) << " | "; + } }); + logger.Batch("forMember[%s] NOTrelayMembers[%s]", + myProTxHash.ToString().substr(0, 4), + ss2.str()); + logger.Flush(); } } // namespace llmq diff --git a/src/llmq/dkgsession.h b/src/llmq/dkgsession.h index 7b45bbafa21a..3bb50bfa4851 100644 --- a/src/llmq/dkgsession.h +++ b/src/llmq/dkgsession.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -39,7 +40,7 @@ class CDKGContribution template inline void SerializeWithoutSig(Stream& s) const { - s << llmqType; + s << uint8_t(llmqType); s << quorumHash; s << proTxHash; s << *vvec; @@ -236,6 +237,7 @@ class CDKGSession CDKGSessionManager& dkgManager; const CBlockIndex* m_quorum_base_block_index{nullptr}; + int quorumIndex{0}; private: std::vector> members; @@ -275,7 +277,7 @@ class CDKGSession CDKGSession(const Consensus::LLMQParams& _params, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager) : params(_params), blsWorker(_blsWorker), cache(_blsWorker), dkgManager(_dkgManager) {} - bool Init(const CBlockIndex* pQuorumBaseBlockIndex, const std::vector& mns, const uint256& _myProTxHash); + bool Init(const CBlockIndex* pQuorumBaseBlockIndex, const std::vector& mns, const uint256& _myProTxHash, int _quorumIndex); std::optional GetMyMemberIndex() const { return myIdx; } @@ -337,9 +339,9 @@ class CDKGLogger : public CBatchedLogger { public: CDKGLogger(const CDKGSession& _quorumDkg, std::string_view _func) : - CDKGLogger(_quorumDkg.params.name, _quorumDkg.m_quorum_base_block_index->GetBlockHash(), _quorumDkg.m_quorum_base_block_index->nHeight, _quorumDkg.AreWeMember(), _func) {}; - CDKGLogger(std::string_view _llmqTypeName, const uint256& _quorumHash, int _height, bool _areWeMember, std::string_view _func) : - CBatchedLogger(BCLog::LLMQ_DKG, strprintf("QuorumDKG(type=%s, height=%d, member=%d, func=%s)", _llmqTypeName, _height, _areWeMember, _func)) {}; + CDKGLogger(_quorumDkg.params.name, _quorumDkg.quorumIndex, _quorumDkg.m_quorum_base_block_index->GetBlockHash(), _quorumDkg.m_quorum_base_block_index->nHeight, _quorumDkg.AreWeMember(), _func){}; + CDKGLogger(std::string_view _llmqTypeName, int _quorumIndex, const uint256& _quorumHash, int _height, bool _areWeMember, std::string_view _func) : + CBatchedLogger(BCLog::LLMQ_DKG, strprintf("QuorumDKG(type=%s, quorumIndex=%d, height=%d, member=%d, func=%s)", _llmqTypeName, _quorumIndex, _height, _areWeMember, _func)){}; }; void SetSimulatedDKGErrorRate(const std::string& type, double rate); diff --git a/src/llmq/dkgsessionhandler.cpp b/src/llmq/dkgsessionhandler.cpp index cc671b5c1ae0..60025dea8b41 100644 --- a/src/llmq/dkgsessionhandler.cpp +++ b/src/llmq/dkgsessionhandler.cpp @@ -81,9 +81,15 @@ void CDKGPendingMessages::Clear() void CDKGSessionHandler::UpdatedBlockTip(const CBlockIndex* pindexNew) { + //AssertLockNotHeld(cs_main); + //Indexed quorums (greater than 0) are enabled with Quorum Rotation + if (quorumIndex > 0 && !CLLMQUtils::IsQuorumRotationEnabled(params.type, pindexNew)) { + return; + } LOCK(cs); - int quorumStageInt = pindexNew->nHeight % params.dkgInterval; + int quorumStageInt = (pindexNew->nHeight - quorumIndex) % params.dkgInterval; + const CBlockIndex* pQuorumBaseBlockIndex = pindexNew->GetAncestor(pindexNew->nHeight - quorumStageInt); currentHeight = pindexNew->nHeight; @@ -96,8 +102,8 @@ void CDKGSessionHandler::UpdatedBlockTip(const CBlockIndex* pindexNew) phase = static_cast(phaseInt); } - LogPrint(BCLog::LLMQ_DKG, "CDKGSessionHandler::%s -- %s - currentHeight=%d, pQuorumBaseBlockIndex->nHeight=%d, oldPhase=%d, newPhase=%d\n", __func__, - params.name, currentHeight, pQuorumBaseBlockIndex->nHeight, int(oldPhase), int(phase)); + LogPrint(BCLog::LLMQ_DKG, "CDKGSessionHandler::%s -- %s - quorumIndex=%d, currentHeight=%d, pQuorumBaseBlockIndex->nHeight=%d, oldPhase=%d, newPhase=%d\n", __func__, + params.name, quorumIndex, currentHeight, pQuorumBaseBlockIndex->nHeight, int(oldPhase), int(phase)); } void CDKGSessionHandler::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv) @@ -140,11 +146,12 @@ bool CDKGSessionHandler::InitNewQuorum(const CBlockIndex* pQuorumBaseBlockIndex) return false; } - auto mns = CLLMQUtils::GetAllQuorumMembers(params, pQuorumBaseBlockIndex); - - if (!curSession->Init(pQuorumBaseBlockIndex, mns, WITH_LOCK(activeMasternodeInfoCs, return activeMasternodeInfo.proTxHash))) { - LogPrintf("CDKGSessionManager::%s -- quorum initialization failed for %s\n", __func__, curSession->params.name); + auto mns = CLLMQUtils::GetAllQuorumMembers(params.type, pQuorumBaseBlockIndex); + if (!curSession->Init(pQuorumBaseBlockIndex, mns, WITH_LOCK(activeMasternodeInfoCs, return activeMasternodeInfo.proTxHash), quorumIndex)) { + LogPrintf("CDKGSessionManager::%s -- height[%d] quorum initialization failed for %s mns[%d]\n", __func__, pQuorumBaseBlockIndex->nHeight, curSession->params.name, mns.size()); return false; + } else { + LogPrintf("CDKGSessionManager::%s -- height[%d] quorum initialization OK for %s\n", __func__, pQuorumBaseBlockIndex->nHeight, curSession->params.name); } return true; @@ -191,9 +198,9 @@ void CDKGSessionHandler::WaitForNextPhase(std::optional curPhase, LogPrint(BCLog::LLMQ_DKG, "CDKGSessionManager::%s -- %s - done, curPhase=%d, nextPhase=%d\n", __func__, params.name, curPhase.has_value() ? int(*curPhase) : -1, int(nextPhase)); if (nextPhase == QuorumPhase::Initialized) { - quorumDKGDebugManager->ResetLocalSessionStatus(params.type); + quorumDKGDebugManager->ResetLocalSessionStatus(params.type, quorumIndex); } else { - quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) { + quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { bool changed = status.phase != (uint8_t) nextPhase; status.phase = (uint8_t) nextPhase; return changed; @@ -477,7 +484,7 @@ void CDKGSessionHandler::HandleDKGRound() throw AbortPhaseException(); } - quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) { + quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { bool changed = status.phase != (uint8_t) QuorumPhase::Initialized; status.phase = (uint8_t) QuorumPhase::Initialized; return changed; @@ -539,7 +546,7 @@ void CDKGSessionHandler::PhaseHandlerThread() LogPrint(BCLog::LLMQ_DKG, "CDKGSessionHandler::%s -- %s - starting HandleDKGRound\n", __func__, params.name); HandleDKGRound(); } catch (AbortPhaseException& e) { - quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, [&](CDKGDebugSessionStatus& status) { + quorumDKGDebugManager->UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { status.aborted = true; return true; }); diff --git a/src/llmq/dkgsessionhandler.h b/src/llmq/dkgsessionhandler.h index ba8f6e3b01ae..59889a748433 100644 --- a/src/llmq/dkgsessionhandler.h +++ b/src/llmq/dkgsessionhandler.h @@ -109,6 +109,7 @@ class CDKGSessionHandler std::atomic stopRequested{false}; const Consensus::LLMQParams& params; + const int quorumIndex; CBLSWorker& blsWorker; CDKGSessionManager& dkgManager; @@ -126,10 +127,11 @@ class CDKGSessionHandler CDKGPendingMessages pendingPrematureCommitments; public: - CDKGSessionHandler(const Consensus::LLMQParams& _params, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager) : + CDKGSessionHandler(const Consensus::LLMQParams& _params, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager, int _quorumIndex) : params(_params), blsWorker(_blsWorker), dkgManager(_dkgManager), + quorumIndex(_quorumIndex), curSession(std::make_unique(_params, _blsWorker, _dkgManager)), pendingContributions((size_t)_params.size * 2, MSG_QUORUM_CONTRIB), // we allow size*2 messages as we need to make sure we see bad behavior (double messages) pendingComplaints((size_t)_params.size * 2, MSG_QUORUM_COMPLAINT), diff --git a/src/llmq/dkgsessionmgr.cpp b/src/llmq/dkgsessionmgr.cpp index b428ef9651f7..7b3319b42b73 100644 --- a/src/llmq/dkgsessionmgr.cpp +++ b/src/llmq/dkgsessionmgr.cpp @@ -2,8 +2,9 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include #include +#include +#include #include #include @@ -27,10 +28,14 @@ CDKGSessionManager::CDKGSessionManager(CBLSWorker& _blsWorker, bool unitTests, b { MigrateDKG(); - for (const auto& params : Params().GetConsensus().llmqs) { - dkgSessionHandlers.emplace(std::piecewise_construct, - std::forward_as_tuple(params.type), - std::forward_as_tuple(params, blsWorker, *this)); + const Consensus::Params& consensus_params = Params().GetConsensus(); + for (const auto& params : consensus_params.llmqs) { + auto session_count = (params.type == consensus_params.llmqTypeDIP0024InstantSend) ? params.signingActiveQuorumCount : 1; + for (int i = 0; i < session_count; ++i) { + dkgSessionHandlers.emplace(std::piecewise_construct, + std::forward_as_tuple(params.type, i), + std::forward_as_tuple(params, blsWorker, *this, i)); + } } } @@ -177,15 +182,23 @@ void CDKGSessionManager::ProcessMessage(CNode* pfrom, const std::string& strComm return; } - // peek into the message and see which LLMQType it is. First byte of all messages is always the LLMQType - Consensus::LLMQType llmqType = (Consensus::LLMQType)*vRecv.begin(); - if (!dkgSessionHandlers.count(llmqType)) { + Consensus::LLMQType llmqType; + uint256 quorumHash; + vRecv >> llmqType; + vRecv >> quorumHash; + vRecv.Rewind(sizeof(uint256)); + vRecv.Rewind(sizeof(uint8_t)); + + int quorumIndex = quorumManager->GetQuorumIndexByQuorumHash(llmqType, quorumHash); + + if (!dkgSessionHandlers.count(std::make_pair(llmqType, quorumIndex))) { LOCK(cs_main); + LogPrintf("CDKGSessionManager dkgSessionHandlers NOT FOUND qi[%d]\n", quorumIndex); Misbehaving(pfrom->GetId(), 100); return; } - dkgSessionHandlers.at(llmqType).ProcessMessage(pfrom, strCommand, vRecv); + dkgSessionHandlers.at(std::make_pair(llmqType, quorumIndex)).ProcessMessage(pfrom, strCommand, vRecv); } bool CDKGSessionManager::AlreadyHave(const CInv& inv) const @@ -307,7 +320,7 @@ void CDKGSessionManager::WriteEncryptedContributions(Consensus::LLMQType llmqTyp bool CDKGSessionManager::GetVerifiedContributions(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex, const std::vector& validMembers, std::vector& memberIndexesRet, std::vector& vvecsRet, BLSSecretKeyVector& skContributionsRet) const { LOCK(contributionsCacheCs); - auto members = CLLMQUtils::GetAllQuorumMembers(GetLLMQParams(llmqType), pQuorumBaseBlockIndex); + auto members = CLLMQUtils::GetAllQuorumMembers(llmqType, pQuorumBaseBlockIndex); memberIndexesRet.clear(); vvecsRet.clear(); @@ -341,7 +354,7 @@ bool CDKGSessionManager::GetVerifiedContributions(Consensus::LLMQType llmqType, bool CDKGSessionManager::GetEncryptedContributions(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex, const std::vector& validMembers, const uint256& nProTxHash, std::vector>& vecRet) const { - auto members = CLLMQUtils::GetAllQuorumMembers(GetLLMQParams(llmqType), pQuorumBaseBlockIndex); + auto members = CLLMQUtils::GetAllQuorumMembers(llmqType, pQuorumBaseBlockIndex); vecRet.clear(); vecRet.reserve(members.size()); diff --git a/src/llmq/dkgsessionmgr.h b/src/llmq/dkgsessionmgr.h index 35533172cfb5..8886b9e84aac 100644 --- a/src/llmq/dkgsessionmgr.h +++ b/src/llmq/dkgsessionmgr.h @@ -24,7 +24,8 @@ class CDKGSessionManager std::unique_ptr db{nullptr}; CBLSWorker& blsWorker; - std::map dkgSessionHandlers; + //TODO name struct instead of std::pair + std::map, CDKGSessionHandler> dkgSessionHandlers; mutable CCriticalSection contributionsCacheCs; struct ContributionsCacheKey { diff --git a/src/llmq/instantsend.cpp b/src/llmq/instantsend.cpp index 090c419c2d8d..d22845e084ef 100644 --- a/src/llmq/instantsend.cpp +++ b/src/llmq/instantsend.cpp @@ -485,8 +485,8 @@ void CInstantSendManager::ProcessTx(const CTransaction& tx, bool fRetroactive, c return; } - auto llmqType = params.llmqTypeInstantSend; - if (llmqType == Consensus::LLMQType::LLMQ_NONE) { + if (params.llmqTypeInstantSend == Consensus::LLMQType::LLMQ_NONE || + params.llmqTypeDIP0024InstantSend == Consensus::LLMQType::LLMQ_NONE) { return; } @@ -510,14 +510,20 @@ void CInstantSendManager::ProcessTx(const CTransaction& tx, bool fRetroactive, c // block after we retroactively locked all transactions. if (!IsInstantSendMempoolSigningEnabled() && !fRetroactive) return; - if (!TrySignInputLocks(tx, fRetroactive, llmqType)) return; + if (CLLMQUtils::IsDIP0024Active(WITH_LOCK(cs_main, return ::ChainActive().Tip()))) { + if (!TrySignInputLocks(tx, fRetroactive, params.llmqTypeDIP0024InstantSend, params)) { + return; + } + } else if (!TrySignInputLocks(tx, fRetroactive, params.llmqTypeInstantSend, params)) { + return; + } // We might have received all input locks before we got the corresponding TX. In this case, we have to sign the // islock now instead of waiting for the input locks. TrySignInstantSendLock(tx); } -bool CInstantSendManager::TrySignInputLocks(const CTransaction& tx, bool fRetroactive, Consensus::LLMQType llmqType) +bool CInstantSendManager::TrySignInputLocks(const CTransaction& tx, bool fRetroactive, Consensus::LLMQType llmqType, const Consensus::Params& params) { std::vector ids; ids.reserve(tx.vin.size()); @@ -528,7 +534,9 @@ bool CInstantSendManager::TrySignInputLocks(const CTransaction& tx, bool fRetroa ids.emplace_back(id); uint256 otherTxHash; - if (quorumSigningManager->GetVoteForId(llmqType, id, otherTxHash)) { + // TODO check that we didn't vote for the other IS type also + if (quorumSigningManager->GetVoteForId(params.llmqTypeDIP0024InstantSend, id, otherTxHash) || + quorumSigningManager->GetVoteForId(params.llmqTypeInstantSend, id, otherTxHash)) { if (otherTxHash != tx.GetHash()) { LogPrintf("CInstantSendManager::%s -- txid=%s: input %s is conflicting with previous vote for tx %s\n", __func__, tx.GetHash().ToString(), in.prevout.ToStringShort(), otherTxHash.ToString()); @@ -538,7 +546,8 @@ bool CInstantSendManager::TrySignInputLocks(const CTransaction& tx, bool fRetroa } // don't even try the actual signing if any input is conflicting - if (quorumSigningManager->IsConflicting(llmqType, id, tx.GetHash())) { + if (quorumSigningManager->IsConflicting(params.llmqTypeDIP0024InstantSend, id, tx.GetHash()) || + quorumSigningManager->IsConflicting(params.llmqTypeInstantSend, id, tx.GetHash())) { LogPrintf("CInstantSendManager::%s -- txid=%s: quorumSigningManager->IsConflicting returned true. id=%s\n", __func__, tx.GetHash().ToString(), id.ToString()); return false; @@ -635,8 +644,8 @@ void CInstantSendManager::HandleNewRecoveredSig(const CRecoveredSig& recoveredSi return; } - auto llmqType = Params().GetConsensus().llmqTypeInstantSend; - if (llmqType == Consensus::LLMQType::LLMQ_NONE) { + if (Params().GetConsensus().llmqTypeInstantSend == Consensus::LLMQType::LLMQ_NONE || + Params().GetConsensus().llmqTypeDIP0024InstantSend == Consensus::LLMQType::LLMQ_NONE) { return; } @@ -686,7 +695,7 @@ void CInstantSendManager::HandleNewInputLockRecoveredSig(const CRecoveredSig& re void CInstantSendManager::TrySignInstantSendLock(const CTransaction& tx) { - const auto llmqType = Params().GetConsensus().llmqTypeInstantSend; + const auto llmqType = CLLMQUtils::GetInstantSendLLMQType(WITH_LOCK(cs_main, return ::ChainActive().Tip())); for (auto& in : tx.vin) { auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout)); @@ -704,8 +713,8 @@ void CInstantSendManager::TrySignInstantSendLock(const CTransaction& tx) islock.inputs.emplace_back(in.prevout); } - // compute cycle hash - { + // compute and set cycle hash if islock is deterministic + if (islock.IsDeterministic()) { LOCK(cs_main); const auto dkgInterval = GetLLMQParams(llmqType).dkgInterval; const auto quorumHeight = ::ChainActive().Height() - (::ChainActive().Height() % dkgInterval); @@ -799,13 +808,15 @@ void CInstantSendManager::ProcessMessageInstantSendLock(const CNode* pfrom, cons WITH_LOCK(cs_main, Misbehaving(pfrom->GetId(), 1)); return; } - - const auto llmqType = Params().GetConsensus().llmqTypeInstantSend; - const auto dkgInterval = GetLLMQParams(llmqType).dkgInterval; - if (blockIndex->nHeight % dkgInterval != 0) { + const Consensus::Params& consensus_params = Params().GetConsensus(); + auto llmqType = CLLMQUtils::IsDIP0024Active(blockIndex) ? consensus_params.llmqTypeDIP0024InstantSend : consensus_params.llmqTypeInstantSend; + if (blockIndex->nHeight % GetLLMQParams(llmqType).dkgInterval != 0) { WITH_LOCK(cs_main, Misbehaving(pfrom->GetId(), 100)); return; } + } else if (CLLMQUtils::IsDIP0024Active(WITH_LOCK(cs_main, return ::ChainActive().Tip()))) { + // Ignore non-deterministic islocks once rotation is active + return; } LOCK(cs); @@ -842,6 +853,17 @@ bool CInstantSendManager::PreVerifyInstantSendLock(const llmq::CInstantSendLock& } bool CInstantSendManager::ProcessPendingInstantSendLocks() +{ + const CBlockIndex* pBlockIndexTip = WITH_LOCK(cs_main, return ::ChainActive().Tip()); + if (pBlockIndexTip && CLLMQUtils::IsDIP0024Active(pBlockIndexTip)) { + return ProcessPendingInstantSendLocks(true); + } else { + // Don't short circuit. Try to process deterministic and not deterministic islocks + return ProcessPendingInstantSendLocks(false) & ProcessPendingInstantSendLocks(true); + } +} + +bool CInstantSendManager::ProcessPendingInstantSendLocks(bool deterministic) { decltype(pendingInstantSendLocks) pend; bool fMoreWork{false}; @@ -858,10 +880,13 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks() if (pendingInstantSendLocks.size() <= maxCount) { pend = std::move(pendingInstantSendLocks); } else { - while (pend.size() < maxCount) { - auto it = pendingInstantSendLocks.begin(); - pend.emplace(it->first, std::move(it->second)); - pendingInstantSendLocks.erase(it); + for (auto it = pendingInstantSendLocks.begin(); it != pendingInstantSendLocks.end() && pend.size() < maxCount;) { + if (it->second.second->IsDeterministic() == deterministic) { + pend.emplace(it->first, std::move(it->second)); + pendingInstantSendLocks.erase(it); + } else { + ++it; + } } fMoreWork = true; } @@ -871,11 +896,12 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks() return false; } - auto llmqType = Params().GetConsensus().llmqTypeInstantSend; + //TODO Investigate if leaving this is ok + auto llmqType = deterministic ? Params().GetConsensus().llmqTypeDIP0024InstantSend : Params().GetConsensus().llmqTypeInstantSend; auto dkgInterval = GetLLMQParams(llmqType).dkgInterval; // First check against the current active set and don't ban - auto badISLocks = ProcessPendingInstantSendLocks(0, pend, false); + auto badISLocks = ProcessPendingInstantSendLocks(llmqType, 0, pend, false); if (!badISLocks.empty()) { LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- doing verification on old active set\n", __func__); @@ -888,16 +914,14 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks() } } // Now check against the previous active set and perform banning if this fails - ProcessPendingInstantSendLocks(dkgInterval, pend, true); + ProcessPendingInstantSendLocks(llmqType, dkgInterval, pend, true); } return fMoreWork; } -std::unordered_set CInstantSendManager::ProcessPendingInstantSendLocks(int signOffset, const std::unordered_map, StaticSaltedHasher>& pend, bool ban) +std::unordered_set CInstantSendManager::ProcessPendingInstantSendLocks(const Consensus::LLMQType llmqType, int signOffset, const std::unordered_map, StaticSaltedHasher>& pend, bool ban) { - auto llmqType = Params().GetConsensus().llmqTypeInstantSend; - CBLSBatchVerifier batchVerifier(false, true, 8); std::unordered_map recSigs; @@ -935,7 +959,7 @@ std::unordered_set CInstantSendManager::ProcessPend continue; } - const auto dkgInterval = GetLLMQParams(Params().GetConsensus().llmqTypeInstantSend).dkgInterval; + const auto dkgInterval = GetLLMQParams(llmqType).dkgInterval; if (blockIndex->nHeight + dkgInterval < ::ChainActive().Height()) { nSignHeight = blockIndex->nHeight + dkgInterval - 1; } @@ -1281,7 +1305,7 @@ void CInstantSendManager::TruncateRecoveredSigsForInputs(const llmq::CInstantSen for (auto& in : islock.inputs) { auto inputRequestId = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in)); inputRequestIds.erase(inputRequestId); - quorumSigningManager->TruncateRecoveredSig(consensusParams.llmqTypeInstantSend, inputRequestId); + quorumSigningManager->TruncateRecoveredSig(islock.IsDeterministic() ? consensusParams.llmqTypeDIP0024InstantSend : consensusParams.llmqTypeInstantSend, inputRequestId); } } @@ -1336,7 +1360,7 @@ void CInstantSendManager::HandleFullyConfirmedBlock(const CBlockIndex* pindex) // And we don't need the recovered sig for the ISLOCK anymore, as the block in which it got mined is considered // fully confirmed now - quorumSigningManager->TruncateRecoveredSig(consensusParams.llmqTypeInstantSend, islock->GetRequestId()); + quorumSigningManager->TruncateRecoveredSig(islock->IsDeterministic() ? consensusParams.llmqTypeDIP0024InstantSend : consensusParams.llmqTypeInstantSend, islock->GetRequestId()); } db.RemoveArchivedInstantSendLocks(pindex->nHeight - 100); diff --git a/src/llmq/instantsend.h b/src/llmq/instantsend.h index 29f6e2b74c28..56c73b961dde 100644 --- a/src/llmq/instantsend.h +++ b/src/llmq/instantsend.h @@ -230,13 +230,15 @@ class CInstantSendManager : public CRecoveredSigsListener void HandleNewInputLockRecoveredSig(const CRecoveredSig& recoveredSig, const uint256& txid); void HandleNewInstantSendLockRecoveredSig(const CRecoveredSig& recoveredSig); - bool TrySignInputLocks(const CTransaction& tx, bool allowResigning, Consensus::LLMQType llmqType); + bool TrySignInputLocks(const CTransaction& tx, bool allowResigning, Consensus::LLMQType llmqType, const Consensus::Params& params); void TrySignInstantSendLock(const CTransaction& tx); void ProcessMessageInstantSendLock(const CNode* pfrom, const CInstantSendLockPtr& islock) LOCKS_EXCLUDED(cs); static bool PreVerifyInstantSendLock(const CInstantSendLock& islock); bool ProcessPendingInstantSendLocks(); - std::unordered_set ProcessPendingInstantSendLocks(int signOffset, const std::unordered_map, StaticSaltedHasher>& pend, bool ban); + bool ProcessPendingInstantSendLocks(bool deterministic); + + std::unordered_set ProcessPendingInstantSendLocks(const Consensus::LLMQType llmqType, int signOffset, const std::unordered_map, StaticSaltedHasher>& pend, bool ban); void ProcessInstantSendLock(NodeId from, const uint256& hash, const CInstantSendLockPtr& islock); void AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined); diff --git a/src/llmq/params.h b/src/llmq/params.h index 735d8eab1c99..c0b8cf4d1760 100644 --- a/src/llmq/params.h +++ b/src/llmq/params.h @@ -11,14 +11,14 @@ namespace Consensus { -enum class LLMQType : uint8_t -{ +enum class LLMQType : uint8_t { LLMQ_NONE = 0xff, - LLMQ_50_60 = 1, // 50 members, 30 (60%) threshold, one per hour + 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 + LLMQ_60_75 = 5, // 60 members, 45 (75%) threshold, one every 12 hours // for testing only LLMQ_TEST = 100, // 3 members, 2 (66%) threshold, one per hour. Params might differ when -llmqtestparams is used @@ -28,6 +28,8 @@ enum class LLMQType : uint8_t // 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 + + LLMQ_TEST_DIP0024 = 103, // 4 members, 2 (66%) threshold, one per hour. Params might differ when -llmqtestparams is used }; // Configures a LLMQ and its DKG @@ -95,7 +97,7 @@ struct LLMQParams { }; -static constexpr std::array available_llmqs = { +static constexpr std::array available_llmqs = { /** * llmq_test @@ -145,6 +147,30 @@ static constexpr std::array available_llmqs = { .recoveryMembers = 3, }, + /** + * llmq_test_dip0024 + * This quorum is only used for testing + * + */ + LLMQParams{ + .type = LLMQType::LLMQ_TEST_DIP0024, + .name = "llmq_test_dip0024", + .size = 4, + .minSize = 3, + .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, + }, + /** * llmq_devnet * This quorum is only used for testing on devnets @@ -153,7 +179,7 @@ static constexpr std::array available_llmqs = { LLMQParams{ .type = LLMQType::LLMQ_DEVNET, .name = "llmq_devnet", - .size = 10, + .size = 12, .minSize = 7, .threshold = 6, @@ -163,13 +189,13 @@ static constexpr std::array available_llmqs = { .dkgMiningWindowEnd = 18, .dkgBadVotesThreshold = 7, - .signingActiveQuorumCount = 3, // just a few ones to allow easier testing + .signingActiveQuorumCount = 4, // just a few ones to allow easier testing .keepOldConnections = 4, .recoveryMembers = 6, }, -/** + /** * llmq_50_60 * This quorum is deployed on mainnet and requires * 40 - 50 participants @@ -193,6 +219,30 @@ static constexpr std::array available_llmqs = { .recoveryMembers = 25, }, + /** + * llmq_60_75 + * This quorum is deployed on mainnet and requires + * 50 - 60 participants + * + */ + LLMQParams{ + .type = LLMQType::LLMQ_60_75, + .name = "llmq_60_75", + .size = 60, + .minSize = 50, + .threshold = 45, + + .dkgInterval = 24 * 12, // one DKG every 12 hours + .dkgPhaseBlocks = 2, + .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization + .dkgMiningWindowEnd = 18, + .dkgBadVotesThreshold = 40, + + .signingActiveQuorumCount = 32, + .keepOldConnections = 33, + .recoveryMembers = 25, + }, + /** * llmq_400_60 * This quorum is deployed on mainnet and requires diff --git a/src/llmq/quorums.cpp b/src/llmq/quorums.cpp index ad29222fc52f..743a4f8aa651 100644 --- a/src/llmq/quorums.cpp +++ b/src/llmq/quorums.cpp @@ -165,6 +165,8 @@ CQuorumManager::CQuorumManager(CEvoDB& _evoDb, CBLSWorker& _blsWorker, CDKGSessi { CLLMQUtils::InitQuorumsCache(mapQuorumsCache); CLLMQUtils::InitQuorumsCache(scanQuorumsCache); + CLLMQUtils::InitQuorumsCache(indexedQuorumsCache); + quorumThreadInterrupt.reset(); } @@ -268,9 +270,25 @@ void CQuorumManager::EnsureQuorumConnections(const Consensus::LLMQParams& llmqPa auto connmanQuorumsToDelete = g_connman->GetMasternodeQuorums(llmqParams.type); // don't remove connections for the currently in-progress DKG round - int curDkgHeight = pindexNew->nHeight - (pindexNew->nHeight % llmqParams.dkgInterval); - auto curDkgBlock = pindexNew->GetAncestor(curDkgHeight)->GetBlockHash(); - connmanQuorumsToDelete.erase(curDkgBlock); + if (CLLMQUtils::IsQuorumRotationEnabled(llmqParams.type, pindexNew)) { + int cycleIndexTipHeight = pindexNew->nHeight % llmqParams.dkgInterval; + int cycleQuorumBaseHeight = pindexNew->nHeight - cycleIndexTipHeight; + std::stringstream ss; + for (int quorumIndex = 0; quorumIndex < llmqParams.signingActiveQuorumCount; ++quorumIndex) { + if (quorumIndex <= cycleIndexTipHeight) { + int curDkgHeight = cycleQuorumBaseHeight + quorumIndex; + auto curDkgBlock = pindexNew->GetAncestor(curDkgHeight)->GetBlockHash(); + ss << curDkgHeight << ":" << curDkgBlock.ToString() << " | "; + connmanQuorumsToDelete.erase(curDkgBlock); + } + } + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- llmqType[%d] h[%d] keeping mn quorum connections for rotated quorums: [%s]\n", __func__, int(llmqParams.type), pindexNew->nHeight, ss.str()); + } else { + int curDkgHeight = pindexNew->nHeight - (pindexNew->nHeight % llmqParams.dkgInterval); + auto curDkgBlock = pindexNew->GetAncestor(curDkgHeight)->GetBlockHash(); + connmanQuorumsToDelete.erase(curDkgBlock); + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- llmqType[%d] h[%d] keeping mn quorum connections for quorum: [%d:%s]\n", __func__, int(llmqParams.type), pindexNew->nHeight, curDkgHeight, curDkgBlock.ToString()); + } for (const auto& quorum : lastQuorums) { if (CLLMQUtils::EnsureQuorumConnections(llmqParams, quorum->m_quorum_base_block_index, WITH_LOCK(activeMasternodeInfoCs, return activeMasternodeInfo.proTxHash))) { @@ -292,13 +310,13 @@ CQuorumPtr CQuorumManager::BuildQuorumFromCommitment(const Consensus::LLMQType l uint256 minedBlockHash; CFinalCommitmentPtr qc = quorumBlockProcessor->GetMinedCommitment(llmqType, quorumHash, minedBlockHash); if (qc == nullptr) { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- No mined commitment for llmqType[%d] nHeight[%d] quorumHash[%s]\n", __func__, uint8_t(llmqType), pQuorumBaseBlockIndex->nHeight, pQuorumBaseBlockIndex->GetBlockHash().ToString()); return nullptr; } assert(qc->quorumHash == pQuorumBaseBlockIndex->GetBlockHash()); - const auto& llmqParams = llmq::GetLLMQParams(llmqType); - auto quorum = std::make_shared(llmqParams, blsWorker); - auto members = CLLMQUtils::GetAllQuorumMembers(llmqParams, pQuorumBaseBlockIndex); + auto quorum = std::make_shared(llmq::GetLLMQParams(llmqType), blsWorker); + auto members = CLLMQUtils::GetAllQuorumMembers(qc->llmqType, pQuorumBaseBlockIndex); quorum->Init(std::move(qc), pQuorumBaseBlockIndex, minedBlockHash, members); @@ -310,7 +328,7 @@ CQuorumPtr CQuorumManager::BuildQuorumFromCommitment(const Consensus::LLMQType l quorum->WriteContributions(evoDb); hasValidVvec = true; } else { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- quorum.ReadContributions and BuildQuorumContributions for block %s failed\n", __func__, quorum->qc->quorumHash.ToString()); + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- llmqType[%d] quorumIndex[%d] quorum.ReadContributions and BuildQuorumContributions for quorumHash[%s] failed\n", __func__, uint8_t(llmqType), quorum->qc->quorumIndex, quorum->qc->quorumHash.ToString()); } } @@ -320,7 +338,6 @@ CQuorumPtr CQuorumManager::BuildQuorumFromCommitment(const Consensus::LLMQType l // sessions if the shares would be calculated on-demand StartCachePopulatorThread(quorum); } - mapQuorumsCache[llmqType].insert(quorumHash, quorum); return quorum; @@ -421,33 +438,21 @@ std::vector CQuorumManager::ScanQuorums(Consensus::LLMQType llmqTyp size_t nScanCommitments{nCountRequested}; std::vector vecResultQuorums; - { - LOCK(quorumsCacheCs); - auto& cache = scanQuorumsCache[llmqType]; - fCacheExists = cache.get(pindexStart->GetBlockHash(), vecResultQuorums); - if (fCacheExists) { - // We have exactly what requested so just return it - if (vecResultQuorums.size() == nCountRequested) { - return vecResultQuorums; - } - // If we have more cached than requested return only a subvector - if (vecResultQuorums.size() > nCountRequested) { - const std::vector& ret = {vecResultQuorums.begin(), vecResultQuorums.begin() + nCountRequested}; - return ret; - } - // If we have cached quorums but not enough, subtract what we have from the count and the set correct index where to start - // scanning for the rests - if(vecResultQuorums.size() > 0) { - nScanCommitments -= vecResultQuorums.size(); - pIndexScanCommitments = (void*)vecResultQuorums.back()->m_quorum_base_block_index->pprev; + // TODO implement caching, see [pre-rotation caching](https://github.com/dashpay/dash/blob/e84bf45cefbcf9ee89ca3d706fd8abffdbcc8a84/src/llmq/quorums.cpp#L424-L448) + // for inspiration + + // Get the block indexes of the mined commitments to build the required quorums from + auto pQuorumBaseBlockIndexes = quorumBlockProcessor->GetMinedCommitmentsIndexedUntilBlock(llmqType, static_cast(pIndexScanCommitments), nScanCommitments); + if (pQuorumBaseBlockIndexes.size() < nScanCommitments) { + if (!pQuorumBaseBlockIndexes.empty()) { + nScanCommitments -= pQuorumBaseBlockIndexes.size(); + if (pQuorumBaseBlockIndexes.back()->pprev) { + pIndexScanCommitments = (void*)pQuorumBaseBlockIndexes.back()->pprev; } - } else { - // If there is nothing in cache request at least cache.max_size() because this gets cached then later - nScanCommitments = std::max(nCountRequested, cache.max_size()); } + auto pQuorumBaseBlockIndexesLegacy = quorumBlockProcessor->GetMinedCommitmentsUntilBlock(llmqType, static_cast(pIndexScanCommitments), nScanCommitments); + pQuorumBaseBlockIndexes.insert(pQuorumBaseBlockIndexes.end(), pQuorumBaseBlockIndexesLegacy.begin(), pQuorumBaseBlockIndexesLegacy.end()); } - // Get the block indexes of the mined commitments to build the required quorums from - auto pQuorumBaseBlockIndexes = quorumBlockProcessor->GetMinedCommitmentsUntilBlock(llmqType, static_cast(pIndexScanCommitments), nScanCommitments); vecResultQuorums.reserve(vecResultQuorums.size() + pQuorumBaseBlockIndexes.size()); for (auto& pQuorumBaseBlockIndex : pQuorumBaseBlockIndexes) { @@ -471,9 +476,37 @@ std::vector CQuorumManager::ScanQuorums(Consensus::LLMQType llmqTyp return ret; } +void CQuorumManager::SetQuorumIndexQuorumHash(Consensus::LLMQType llmqType, const uint256& quorumHash, int quorumIndex) +{ + LOCK(indexedQuorumsCacheCs); + + auto& mapCache = indexedQuorumsCache[llmqType]; + if (!mapCache.exists(quorumHash)) { + mapCache.insert(quorumHash, quorumIndex); + } else { + mapCache.erase(quorumHash); + mapCache.insert(quorumHash, quorumIndex); + } +} + +int CQuorumManager::GetQuorumIndexByQuorumHash(Consensus::LLMQType llmqType, const uint256& quorumHash) +{ + LOCK(indexedQuorumsCacheCs); + + auto& mapCache = indexedQuorumsCache[llmqType]; + + int value; + + if (mapCache.get(quorumHash, value)) { + return value; + } + LogPrint(BCLog::LLMQ, "GetQuorumIndexByQuorumHash h[%s] NOT FOUND->0\n", quorumHash.ToString()); + return 0; +} + CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash) const { - CBlockIndex* pQuorumBaseBlockIndex = WITH_LOCK(cs_main, return LookupBlockIndex(quorumHash)); + const CBlockIndex* pQuorumBaseBlockIndex = WITH_LOCK(cs_main, return LookupBlockIndex(quorumHash)); if (!pQuorumBaseBlockIndex) { LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- block %s not found\n", __func__, quorumHash.ToString()); return nullptr; diff --git a/src/llmq/quorums.h b/src/llmq/quorums.h index 784128e63b9e..5f97c49c219b 100644 --- a/src/llmq/quorums.h +++ b/src/llmq/quorums.h @@ -8,8 +8,8 @@ #include #include #include -#include #include +#include #include #include @@ -195,6 +195,9 @@ class CQuorumManager mutable std::map> mapQuorumsCache GUARDED_BY(quorumsCacheCs); mutable std::map, StaticSaltedHasher>> scanQuorumsCache GUARDED_BY(quorumsCacheCs); + mutable Mutex indexedQuorumsCacheCs; + mutable std::map> indexedQuorumsCache GUARDED_BY(indexedQuorumsCacheCs); + mutable ctpl::thread_pool workerPool; mutable CThreadInterrupt quorumThreadInterrupt; @@ -222,6 +225,8 @@ class CQuorumManager // this one is cs_main-free std::vector ScanQuorums(Consensus::LLMQType llmqType, const CBlockIndex* pindexStart, size_t nCountRequested) const; + void SetQuorumIndexQuorumHash(Consensus::LLMQType llmqType, const uint256& quorumHash, int quorumIndex); + int GetQuorumIndexByQuorumHash(Consensus::LLMQType llmqType, const uint256& quorumHash); private: // all private methods here are cs_main-free void EnsureQuorumConnections(const Consensus::LLMQParams& llmqParams, const CBlockIndex *pindexNew) const; diff --git a/src/llmq/signing.cpp b/src/llmq/signing.cpp index e17c5a6f579f..40681ab0307d 100644 --- a/src/llmq/signing.cpp +++ b/src/llmq/signing.cpp @@ -1012,22 +1012,48 @@ CQuorumCPtr CSigningManager::SelectQuorumForSigning(Consensus::LLMQType llmqType pindexStart = ::ChainActive()[startBlockHeight]; } - auto quorums = quorumManager->ScanQuorums(llmqType, pindexStart, poolSize); - if (quorums.empty()) { - return nullptr; - } + if (CLLMQUtils::IsQuorumRotationEnabled(llmqType, pindexStart)) { + auto quorums = quorumManager->ScanQuorums(llmqType, pindexStart, poolSize); + if (quorums.empty()) { + return nullptr; + } + //log2 int + int n = std::log2(GetLLMQParams(llmqType).signingActiveQuorumCount); + //Extract last 64 bits of selectionHash + uint64_t b = selectionHash.GetUint64(3); + //Take last n bits of b + int signer = int((((1 << n) - 1) & (b >> (64 - n - 1)))); + + if (signer > quorums.size()) { + return nullptr; + } + auto itQuorum = std::find_if(quorums.begin(), + quorums.end(), + [signer](const CQuorumCPtr& obj) { + return int(obj->qc->quorumIndex) == signer; + }); + if (itQuorum == quorums.end()) { + return nullptr; + } + return *itQuorum; + } else { + auto quorums = quorumManager->ScanQuorums(llmqType, pindexStart, 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 << llmqType; - h << quorums[i]->qc->quorumHash; - h << selectionHash; - scores.emplace_back(h.GetHash(), i); + std::vector> scores; + scores.reserve(quorums.size()); + for (size_t i = 0; i < quorums.size(); i++) { + CHashWriter h(SER_NETWORK, 0); + h << llmqType; + h << quorums[i]->qc->quorumHash; + h << selectionHash; + scores.emplace_back(h.GetHash(), i); + } + std::sort(scores.begin(), scores.end()); + return quorums[scores.front().second]; } - std::sort(scores.begin(), scores.end()); - return quorums[scores.front().second]; } bool CSigningManager::VerifyRecoveredSig(Consensus::LLMQType llmqType, int signedAtHeight, const uint256& id, const uint256& msgHash, const CBLSSignature& sig, const int signOffset) diff --git a/src/llmq/snapshot.cpp b/src/llmq/snapshot.cpp new file mode 100644 index 000000000000..945e2da9e130 --- /dev/null +++ b/src/llmq/snapshot.cpp @@ -0,0 +1,391 @@ +// Copyright (c) 2021 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace llmq { + +static const std::string DB_QUORUM_SNAPSHOT = "llmq_S"; + +std::unique_ptr quorumSnapshotManager; + +void CQuorumSnapshot::ToJson(UniValue& obj) const +{ + //TODO Check this function if correct + obj.setObject(); + UniValue activeQ(UniValue::VARR); + for (const auto& h : activeQuorumMembers) { + activeQ.push_back(h); + } + obj.pushKV("activeQuorumMembers", activeQ); + obj.pushKV("mnSkipListMode", mnSkipListMode); + UniValue skipList(UniValue::VARR); + for (const auto& h : mnSkipList) { + skipList.push_back(h); + } + obj.pushKV("mnSkipList", skipList); +} + +void CQuorumRotationInfo::ToJson(UniValue& obj) const +{ + obj.setObject(); + obj.pushKV("extraShare", extraShare); + + UniValue objc; + quorumSnapshotAtHMinusC.ToJson(objc); + obj.pushKV("quorumSnapshotAtHMinusC", objc); + + UniValue obj2c; + quorumSnapshotAtHMinus2C.ToJson(obj2c); + obj.pushKV("quorumSnapshotAtHMinus2C", obj2c); + + UniValue obj3c; + quorumSnapshotAtHMinus3C.ToJson(obj3c); + obj.pushKV("quorumSnapshotAtHMinus3C", obj3c); + + if (extraShare && quorumSnapshotAtHMinus4C.has_value()) { + UniValue obj4c; + quorumSnapshotAtHMinus4C.value().ToJson(obj4c); + obj.pushKV("quorumSnapshotAtHMinus4C", obj4c); + } + + UniValue objdifftip; + mnListDiffTip.ToJson(objdifftip); + obj.pushKV("mnListDiffTip", objdifftip); + + UniValue objdiffh; + mnListDiffH.ToJson(objdiffh); + obj.pushKV("mnListDiffH", objdiffh); + + UniValue objdiffc; + mnListDiffAtHMinusC.ToJson(objdiffc); + obj.pushKV("mnListDiffAtHMinusC", objdiffc); + + UniValue objdiff2c; + mnListDiffAtHMinus2C.ToJson(objdiff2c); + obj.pushKV("mnListDiffAtHMinus2C", objdiff2c); + + UniValue objdiff3c; + mnListDiffAtHMinus3C.ToJson(objdiff3c); + obj.pushKV("mnListDiffAtHMinus3C", objdiff3c); + + if (extraShare && mnListDiffAtHMinus4C.has_value()) { + UniValue objdiff4c; + mnListDiffAtHMinus4C.value().ToJson(objdiff4c); + obj.pushKV("mnListDiffAtHMinus4C", objdiff4c); + } + + UniValue hlists(UniValue::VARR); + for (const auto& h : blockHashList) { + hlists.push_back(h.ToString()); + } + obj.pushKV("blockHashList", hlists); + + UniValue snapshotlist(UniValue::VARR); + for (const auto& snap : quorumSnapshotList) { + UniValue o; + o.setObject(); + snap.ToJson(o); + snapshotlist.push_back(o); + } + obj.pushKV("quorumSnapshotList", snapshotlist); + + UniValue mnlistdifflist(UniValue::VARR); + for (const auto& mnlist : mnListDiffList) { + UniValue o; + o.setObject(); + mnlist.ToJson(o); + mnlistdifflist.push_back(o); + } + obj.pushKV("mnListDiffList", mnlistdifflist); +} + +bool BuildQuorumRotationInfo(const CGetQuorumRotationInfo& request, CQuorumRotationInfo& response, std::string& errorRet) +{ + AssertLockHeld(cs_main); + + std::vector baseBlockIndexes; + if (request.baseBlockHashes.size() == 0) { + const CBlockIndex* blockIndex = ::ChainActive().Genesis(); + if (!blockIndex) { + errorRet = strprintf("genesis block not found"); + return false; + } + baseBlockIndexes.push_back(blockIndex); + } else { + for (const auto& blockHash : request.baseBlockHashes) { + const CBlockIndex* blockIndex = LookupBlockIndex(blockHash); + if (!blockIndex) { + errorRet = strprintf("block %s not found", blockHash.ToString()); + return false; + } + if (!::ChainActive().Contains(blockIndex)) { + errorRet = strprintf("block %s is not in the active chain", blockHash.ToString()); + return false; + } + baseBlockIndexes.push_back(blockIndex); + } + std::sort(baseBlockIndexes.begin(), baseBlockIndexes.end(), [](const CBlockIndex* a, const CBlockIndex* b) { + return a->nHeight < b->nHeight; + }); + } + + const CBlockIndex* tipBlockIndex = ::ChainActive().Tip(); + if (!tipBlockIndex) { + errorRet = strprintf("tip block not found"); + return false; + } + //Build MN list Diff always with highest baseblock + if (!BuildSimplifiedMNListDiff(baseBlockIndexes.back()->GetBlockHash(), tipBlockIndex->GetBlockHash(), response.mnListDiffTip, errorRet)) { + return false; + } + + const CBlockIndex* blockIndex = LookupBlockIndex(request.blockRequestHash); + if (!blockIndex) { + errorRet = strprintf("block not found"); + return false; + } + + //Quorum rotation is enabled only for InstantSend atm. + Consensus::LLMQType llmqType = CLLMQUtils::GetInstantSendLLMQType(blockIndex); + + // Since the returned quorums are in reversed order, the most recent one is at index 0 + const Consensus::LLMQParams& llmqParams = GetLLMQParams(llmqType); + const int cycleLength = llmqParams.dkgInterval; + constexpr int workDiff = 8; + + const CBlockIndex* hBlockIndex = blockIndex->GetAncestor(blockIndex->nHeight - (blockIndex->nHeight % cycleLength)); + if (!hBlockIndex) { + errorRet = strprintf("Can not find block H"); + return false; + } + + const CBlockIndex* pWorkBlockIndex = hBlockIndex->GetAncestor(hBlockIndex->nHeight - workDiff); + if (!pWorkBlockIndex) { + errorRet = strprintf("Can not find work block H"); + return false; + } + + //Build MN list Diff always with highest baseblock + if (!BuildSimplifiedMNListDiff(GetLastBaseBlockHash(baseBlockIndexes, pWorkBlockIndex), pWorkBlockIndex->GetBlockHash(), response.mnListDiffH, errorRet)) { + return false; + } + + const CBlockIndex* pBlockHMinusCIndex = tipBlockIndex->GetAncestor(hBlockIndex->nHeight - cycleLength); + if (!pBlockHMinusCIndex) { + errorRet = strprintf("Can not find block H-C"); + return false; + } + const CBlockIndex* pWorkBlockHMinusCIndex = pBlockHMinusCIndex->GetAncestor(pBlockHMinusCIndex->nHeight - workDiff); + if (!pWorkBlockHMinusCIndex) { + errorRet = strprintf("Can not find work block H-C"); + return false; + } + + const CBlockIndex* pBlockHMinus2CIndex = pBlockHMinusCIndex->GetAncestor(hBlockIndex->nHeight - 2 * cycleLength); + if (!pBlockHMinus2CIndex) { + errorRet = strprintf("Can not find block H-2C"); + return false; + } + const CBlockIndex* pWorkBlockHMinus2CIndex = pBlockHMinus2CIndex->GetAncestor(pBlockHMinus2CIndex->nHeight - workDiff); + if (!pWorkBlockHMinus2CIndex) { + errorRet = strprintf("Can not find work block H-2C"); + return false; + } + + const CBlockIndex* pBlockHMinus3CIndex = pBlockHMinusCIndex->GetAncestor(hBlockIndex->nHeight - 3 * cycleLength); + if (!pBlockHMinus3CIndex) { + errorRet = strprintf("Can not find block H-3C"); + return false; + } + const CBlockIndex* pWorkBlockHMinus3CIndex = pBlockHMinus3CIndex->GetAncestor(pBlockHMinus3CIndex->nHeight - workDiff); + if (!pWorkBlockHMinus3CIndex) { + errorRet = strprintf("Can not find work block H-3C"); + return false; + } + + const CBlockIndex* pBlockHMinus4CIndex = pBlockHMinusCIndex->GetAncestor(hBlockIndex->nHeight - 4 * cycleLength); + if (!pBlockHMinus4CIndex) { + errorRet = strprintf("Can not find block H-4C"); + return false; + } + + const CBlockIndex* pWorkBlockHMinus4CIndex = pBlockHMinus4CIndex->GetAncestor(pBlockHMinus4CIndex->nHeight - workDiff); + //Checked later if extraShare is on + + if (!BuildSimplifiedMNListDiff(GetLastBaseBlockHash(baseBlockIndexes, pWorkBlockHMinusCIndex), pWorkBlockHMinusCIndex->GetBlockHash(), response.mnListDiffAtHMinusC, errorRet)) { + return false; + } + + auto snapshotHMinusC = quorumSnapshotManager->GetSnapshotForBlock(llmqType, pBlockHMinusCIndex); + if (!snapshotHMinusC.has_value()) { + errorRet = strprintf("Can not find quorum snapshot at H-C"); + return false; + } else { + response.quorumSnapshotAtHMinusC = std::move(snapshotHMinusC.value()); + } + + if (!BuildSimplifiedMNListDiff(GetLastBaseBlockHash(baseBlockIndexes, pWorkBlockHMinus2CIndex), pWorkBlockHMinus2CIndex->GetBlockHash(), response.mnListDiffAtHMinus2C, errorRet)) { + return false; + } + + auto snapshotHMinus2C = quorumSnapshotManager->GetSnapshotForBlock(llmqType, pBlockHMinus2CIndex); + if (!snapshotHMinus2C.has_value()) { + errorRet = strprintf("Can not find quorum snapshot at H-C"); + return false; + } else { + response.quorumSnapshotAtHMinus2C = std::move(snapshotHMinus2C.value()); + } + + if (!BuildSimplifiedMNListDiff(GetLastBaseBlockHash(baseBlockIndexes, pWorkBlockHMinus3CIndex), pWorkBlockHMinus3CIndex->GetBlockHash(), response.mnListDiffAtHMinus3C, errorRet)) { + return false; + } + + auto snapshotHMinus3C = quorumSnapshotManager->GetSnapshotForBlock(llmqType, pBlockHMinus3CIndex); + if (!snapshotHMinus3C.has_value()) { + errorRet = strprintf("Can not find quorum snapshot at H-C"); + return false; + } else { + response.quorumSnapshotAtHMinus3C = std::move(snapshotHMinus3C.value()); + } + + if (request.extraShare) { + response.extraShare = true; + + if (!pWorkBlockHMinus4CIndex) { + errorRet = strprintf("Can not find work block H-4C"); + return false; + } + + auto snapshotHMinus4C = quorumSnapshotManager->GetSnapshotForBlock(llmqType, pBlockHMinus4CIndex); + if (!snapshotHMinus4C.has_value()) { + errorRet = strprintf("Can not find quorum snapshot at H-4C"); + return false; + } else { + response.quorumSnapshotAtHMinus4C = std::move(snapshotHMinus4C); + } + + CSimplifiedMNListDiff mn4c; + if (!BuildSimplifiedMNListDiff(GetLastBaseBlockHash(baseBlockIndexes, pWorkBlockHMinus4CIndex), pWorkBlockHMinus4CIndex->GetBlockHash(), mn4c, errorRet)) { + return false; + } + + response.mnListDiffAtHMinus4C = std::move(mn4c); + } else { + response.extraShare = false; + response.quorumSnapshotAtHMinus4C = std::nullopt; + response.mnListDiffAtHMinus4C = std::nullopt; + } + + std::set snapshotHeightsNeeded; + + std::vector> qdata = quorumBlockProcessor->GetLastMinedCommitmentsPerQuorumIndexUntilBlock(llmqType, blockIndex, 0); + + for (const auto& obj : qdata) { + response.blockHashList.push_back(obj.second->GetBlockHash()); + + int quorumCycleStartHeight = obj.second->nHeight - (obj.second->nHeight % llmqParams.dkgInterval); + snapshotHeightsNeeded.insert(quorumCycleStartHeight - cycleLength); + snapshotHeightsNeeded.insert(quorumCycleStartHeight - 2 * cycleLength); + snapshotHeightsNeeded.insert(quorumCycleStartHeight - 3 * cycleLength); + } + + snapshotHeightsNeeded.erase(pBlockHMinusCIndex->nHeight); + snapshotHeightsNeeded.erase(pBlockHMinus2CIndex->nHeight); + snapshotHeightsNeeded.erase(pBlockHMinus3CIndex->nHeight); + if (request.extraShare) + snapshotHeightsNeeded.erase(pBlockHMinus4CIndex->nHeight); + + for (const auto& h : snapshotHeightsNeeded) { + const CBlockIndex* pNeededBlockIndex = tipBlockIndex->GetAncestor(h); + if (!pNeededBlockIndex) { + errorRet = strprintf("Can not find needed block H(%d)", h); + return false; + } + const CBlockIndex* pNeededWorkBlockIndex = pNeededBlockIndex->GetAncestor(pNeededBlockIndex->nHeight - workDiff); + if (!pNeededWorkBlockIndex) { + errorRet = strprintf("Can not find needed work block H(%d)", h); + return false; + } + + auto snapshotNeededH = quorumSnapshotManager->GetSnapshotForBlock(llmqType, pNeededBlockIndex); + if (!snapshotNeededH.has_value()) { + errorRet = strprintf("Can not find quorum snapshot at H(%d)", h); + return false; + } else { + response.quorumSnapshotList.push_back(snapshotNeededH.value()); + } + + CSimplifiedMNListDiff mnhneeded; + if (!BuildSimplifiedMNListDiff(GetLastBaseBlockHash(baseBlockIndexes, pNeededWorkBlockIndex), pNeededWorkBlockIndex->GetBlockHash(), mnhneeded, errorRet)) { + return false; + } + + response.mnListDiffList.push_back(mnhneeded); + } + + return true; +} + +uint256 GetLastBaseBlockHash(const std::vector& baseBlockIndexes, const CBlockIndex* blockIndex) +{ + uint256 hash; + for (const auto baseBlock : baseBlockIndexes) { + if (baseBlock->nHeight >= blockIndex->nHeight) + break; + hash = baseBlock->GetBlockHash(); + } + return hash; +} + +CQuorumSnapshotManager::CQuorumSnapshotManager(CEvoDB& _evoDb) : + evoDb(_evoDb), + quorumSnapshotCache(32) +{ +} + +std::optional CQuorumSnapshotManager::GetSnapshotForBlock(const Consensus::LLMQType llmqType, const CBlockIndex* pindex) +{ + CQuorumSnapshot snapshot = {}; + + auto snapshotHash = ::SerializeHash(std::make_pair(llmqType, pindex->GetBlockHash())); + + LOCK(snapshotCacheCs); + // try using cache before reading from disk + if (quorumSnapshotCache.get(snapshotHash, snapshot)) { + return snapshot; + } + LOCK(evoDb.cs); + if (evoDb.Read(std::make_pair(DB_QUORUM_SNAPSHOT, snapshotHash), snapshot)) { + quorumSnapshotCache.insert(snapshotHash, snapshot); + return snapshot; + } + + return std::nullopt; +} + +void CQuorumSnapshotManager::StoreSnapshotForBlock(const Consensus::LLMQType llmqType, const CBlockIndex* pindex, const CQuorumSnapshot& snapshot) +{ + auto snapshotHash = ::SerializeHash(std::make_pair(llmqType, pindex->GetBlockHash())); + + // LOCK(cs_main); + LOCK2(snapshotCacheCs, evoDb.cs); + evoDb.GetRawDB().Write(std::make_pair(DB_QUORUM_SNAPSHOT, snapshotHash), snapshot); + quorumSnapshotCache.insert(snapshotHash, snapshot); +} + +} // namespace llmq diff --git a/src/llmq/snapshot.h b/src/llmq/snapshot.h new file mode 100644 index 000000000000..6d6844484728 --- /dev/null +++ b/src/llmq/snapshot.h @@ -0,0 +1,228 @@ +// Copyright (c) 2021 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_LLMQ_SNAPSHOT_H +#define BITCOIN_LLMQ_SNAPSHOT_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +class CBlockIndex; +class CDeterministicMN; +class CDeterministicMNList; + +namespace llmq { +//TODO use enum class (probably) +enum SnapshotSkipMode : int { + MODE_NO_SKIPPING = 0, + MODE_SKIPPING_ENTRIES = 1, + MODE_NO_SKIPPING_ENTRIES = 2, + MODE_ALL_SKIPPED = 3 +}; + +class CQuorumSnapshot +{ +public: + std::vector activeQuorumMembers; + int mnSkipListMode = 0; + std::vector mnSkipList; + + CQuorumSnapshot() = default; + CQuorumSnapshot(std::vector _activeQuorumMembers, int _mnSkipListMode, std::vector _mnSkipList) : + activeQuorumMembers(std::move(_activeQuorumMembers)), + mnSkipListMode(_mnSkipListMode), + mnSkipList(std::move(_mnSkipList)) + { + } + + template + inline void SerializationOpBase(Stream& s, Operation ser_action) + { + READWRITE(mnSkipListMode); + } + + template + void Serialize(Stream& s) const + { + const_cast(this)->SerializationOpBase(s, CSerActionSerialize()); + + WriteCompactSize(s, activeQuorumMembers.size()); + WriteFixedBitSet(s, activeQuorumMembers, activeQuorumMembers.size()); + WriteCompactSize(s, mnSkipList.size()); + for (const auto& obj : mnSkipList) { + s << obj; + } + } + + template + void Unserialize(Stream& s) + { + SerializationOpBase(s, CSerActionUnserialize()); + + size_t cnt = ReadCompactSize(s); + ReadFixedBitSet(s, activeQuorumMembers, cnt); + cnt = ReadCompactSize(s); + for (size_t i = 0; i < cnt; i++) { + int obj; + s >> obj; + mnSkipList.push_back(obj); + } + } + + void ToJson(UniValue& obj) const; +}; + +class CGetQuorumRotationInfo +{ +public: + std::vector baseBlockHashes; + uint256 blockRequestHash; + bool extraShare; + + SERIALIZE_METHODS(CGetQuorumRotationInfo, obj) + { + READWRITE(obj.baseBlockHashes, obj.blockRequestHash, obj.extraShare); + } +}; + +//TODO Maybe we should split the following class: +// CQuorumSnaphot should include {creationHeight, activeQuorumMembers H_C H_2C H_3C, and skipLists H_C H_2C H3_C} +// Maybe we need to include also blockHash for heights H_C H_2C H_3C +// CSnapshotInfo should include CQuorumSnaphot + mnListDiff Tip H H_C H_2C H3_C +class CQuorumRotationInfo +{ +public: + CQuorumSnapshot quorumSnapshotAtHMinusC; + CQuorumSnapshot quorumSnapshotAtHMinus2C; + CQuorumSnapshot quorumSnapshotAtHMinus3C; + + CSimplifiedMNListDiff mnListDiffTip; + CSimplifiedMNListDiff mnListDiffH; + CSimplifiedMNListDiff mnListDiffAtHMinusC; + CSimplifiedMNListDiff mnListDiffAtHMinus2C; + CSimplifiedMNListDiff mnListDiffAtHMinus3C; + + bool extraShare; + std::optional quorumSnapshotAtHMinus4C; + std::optional mnListDiffAtHMinus4C; + + std::vector blockHashList; + std::vector quorumSnapshotList; + std::vector mnListDiffList; + + template + inline void SerializationOpBase(Stream& s, Operation ser_action) + { + READWRITE(quorumSnapshotAtHMinusC, + quorumSnapshotAtHMinus2C, + quorumSnapshotAtHMinus3C, + mnListDiffTip, + mnListDiffH, + mnListDiffAtHMinusC, + mnListDiffAtHMinus2C, + mnListDiffAtHMinus3C, + extraShare); + } + + template + void Serialize(Stream& s) const + { + const_cast(this)->SerializationOpBase(s, CSerActionSerialize()); + + if (extraShare && quorumSnapshotAtHMinus4C.has_value()) { + ::Serialize(s, quorumSnapshotAtHMinus4C.value()); + } + + if (extraShare && mnListDiffAtHMinus4C.has_value()) { + ::Serialize(s, mnListDiffAtHMinus4C.value()); + } + + WriteCompactSize(s, blockHashList.size()); + for (const auto& obj : blockHashList) { + ::Serialize(s, obj); + } + + WriteCompactSize(s, quorumSnapshotList.size()); + for (const auto& obj : quorumSnapshotList) { + ::Serialize(s, obj); + } + + WriteCompactSize(s, mnListDiffList.size()); + for (const auto& obj : mnListDiffList) { + ::Serialize(s, obj); + } + } + + template + void Unserialize(Stream& s) + { + SerializationOpBase(s, CSerActionUnserialize()); + + if (extraShare && quorumSnapshotAtHMinus4C.has_value()) { + ::Unserialize(s, quorumSnapshotAtHMinus4C.value()); + } + + if (extraShare && mnListDiffAtHMinus4C.has_value()) { + ::Unserialize(s, mnListDiffAtHMinus4C.value()); + } + + size_t cnt = ReadCompactSize(s); + for (size_t i = 0; i < cnt; i++) { + uint256 hash; + ::Unserialize(s, hash); + blockHashList.push_back(std::move(hash)); + } + + cnt = ReadCompactSize(s); + for (size_t i = 0; i < cnt; i++) { + CQuorumSnapshot snap; + ::Unserialize(s, snap); + quorumSnapshotList.push_back(std::move(snap)); + } + + cnt = ReadCompactSize(s); + for (size_t i = 0; i < cnt; i++) { + CSimplifiedMNListDiff mnlist; + ::Unserialize(s, mnlist); + mnListDiffList.push_back(std::move(mnlist)); + } + } + + CQuorumRotationInfo() = default; + CQuorumRotationInfo(const CQuorumRotationInfo& dmn) {} + + void ToJson(UniValue& obj) const; +}; + +bool BuildQuorumRotationInfo(const CGetQuorumRotationInfo& request, CQuorumRotationInfo& quorumRotationInfoRet, std::string& errorRet); +uint256 GetLastBaseBlockHash(const std::vector& baseBlockIndexes, const CBlockIndex* blockIndex); + +class CQuorumSnapshotManager +{ +private: + mutable CCriticalSection snapshotCacheCs; + + CEvoDB& evoDb; + + unordered_lru_cache quorumSnapshotCache GUARDED_BY(snapshotCacheCs); + +public: + explicit CQuorumSnapshotManager(CEvoDB& _evoDb); + + std::optional GetSnapshotForBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex); + void StoreSnapshotForBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, const CQuorumSnapshot& snapshot); +}; + +extern std::unique_ptr quorumSnapshotManager; + +} // namespace llmq + +#endif //BITCOIN_LLMQ_SNAPSHOT_H diff --git a/src/llmq/utils.cpp b/src/llmq/utils.cpp index c1d4157c9afa..321d36b2ad79 100644 --- a/src/llmq/utils.cpp +++ b/src/llmq/utils.cpp @@ -5,7 +5,9 @@ #include #include +//#include #include +#include #include #include @@ -20,18 +22,30 @@ #include #include +#include + namespace llmq { CCriticalSection cs_llmq_vbc; VersionBitsCache llmq_versionbitscache; -std::vector CLLMQUtils::GetAllQuorumMembers(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex) +void CLLMQUtils::PreComputeQuorumMembers(const CBlockIndex* pQuorumBaseBlockIndex) { - static CCriticalSection cs_members; - static std::map, StaticSaltedHasher>> mapQuorumMembers; + for (const Consensus::LLMQParams& params : CLLMQUtils::GetEnabledQuorumParams(pQuorumBaseBlockIndex->pprev)) { + if (llmq::CLLMQUtils::IsQuorumRotationEnabled(params.type, pQuorumBaseBlockIndex) && (pQuorumBaseBlockIndex->nHeight % params.dkgInterval == 0)) { + CLLMQUtils::GetAllQuorumMembers(params.type, pQuorumBaseBlockIndex); + } + } +} - if (!IsQuorumTypeEnabled(llmqParams.type, pQuorumBaseBlockIndex->pprev)) { +std::vector CLLMQUtils::GetAllQuorumMembers(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex) +{ + static CCriticalSection cs_members; + static std::map, StaticSaltedHasher>> mapQuorumMembers GUARDED_BY(cs_members); + static CCriticalSection cs_indexed_members; + static std::map, std::vector, StaticSaltedHasher>> mapIndexedQuorumMembers GUARDED_BY(cs_indexed_members); + if (!IsQuorumTypeEnabled(llmqType, pQuorumBaseBlockIndex->pprev)) { return {}; } std::vector quorumMembers; @@ -40,19 +54,426 @@ std::vector CLLMQUtils::GetAllQuorumMembers(const Consensu if (mapQuorumMembers.empty()) { InitQuorumsCache(mapQuorumMembers); } - if (mapQuorumMembers[llmqParams.type].get(pQuorumBaseBlockIndex->GetBlockHash(), quorumMembers)) { + if (mapQuorumMembers[llmqType].get(pQuorumBaseBlockIndex->GetBlockHash(), quorumMembers)) { return quorumMembers; } } + if (CLLMQUtils::IsQuorumRotationEnabled(llmqType, pQuorumBaseBlockIndex)) { + { + LOCK(cs_indexed_members); + if (mapIndexedQuorumMembers.empty()) { + InitQuorumsCache(mapIndexedQuorumMembers); + } + } + /* + * Quorums created with rotation are now created in a different way. All signingActiveQuorumCount are created during the period of dkgInterval. + * But they are not created exactly in the same block, they are spreaded overtime: one quorum in each block until all signingActiveQuorumCount are created. + * The new concept of quorumIndex is introduced in order to identify them. + * In every dkgInterval blocks (also called CycleQuorumBaseBlock), the spreaded quorum creation starts like this: + * For quorumIndex = 0 : signingActiveQuorumCount + * Quorum Q with quorumIndex is created at height CycleQuorumBaseBlock + quorumIndex + */ + + const Consensus::LLMQParams& llmqParams = GetLLMQParams(llmqType); + int quorumIndex = pQuorumBaseBlockIndex->nHeight % llmqParams.dkgInterval; + if (quorumIndex >= llmqParams.signingActiveQuorumCount) { + return {}; + } + int cycleQuorumBaseHeight = pQuorumBaseBlockIndex->nHeight - quorumIndex; + const CBlockIndex* pCycleQuorumBaseBlockIndex = pQuorumBaseBlockIndex->GetAncestor(cycleQuorumBaseHeight); + + /* + * Since mapQuorumMembers stores Quorum members per block hash, and we don't know yet the block hashes of blocks for all quorumIndexes (since these blocks are not created yet) + * We store them in a second cache mapIndexedQuorumMembers which stores them by {CycleQuorumBaseBlockHash, quorumIndex} + */ + { + LOCK(cs_indexed_members); + if (mapIndexedQuorumMembers[llmqType].get(std::pair(pCycleQuorumBaseBlockIndex->GetBlockHash(), quorumIndex), quorumMembers)) { + LOCK(cs_members); + mapQuorumMembers[llmqType].insert(pQuorumBaseBlockIndex->GetBlockHash(), quorumMembers); + + /* + * We also need to store which quorum block hash corresponds to which quorumIndex + */ + quorumManager->SetQuorumIndexQuorumHash(llmqType, pQuorumBaseBlockIndex->GetBlockHash(), quorumIndex); + return quorumMembers; + } + } + + auto q = ComputeQuorumMembersByQuarterRotation(llmqType, pCycleQuorumBaseBlockIndex); + LOCK(cs_indexed_members); + for (int i = 0; i < static_cast(q.size()); ++i) { + mapIndexedQuorumMembers[llmqType].insert(std::make_pair(pCycleQuorumBaseBlockIndex->GetBlockHash(), i), q[i]); + } + + quorumMembers = q[quorumIndex]; + LOCK(cs_members); + mapQuorumMembers[llmqType].insert(pQuorumBaseBlockIndex->GetBlockHash(), quorumMembers); + quorumManager->SetQuorumIndexQuorumHash(llmqType, pQuorumBaseBlockIndex->GetBlockHash(), quorumIndex); + + return quorumMembers; + } else { + quorumMembers = ComputeQuorumMembers(llmqType, pQuorumBaseBlockIndex); + LOCK(cs_members); + mapQuorumMembers[llmqType].insert(pQuorumBaseBlockIndex->GetBlockHash(), quorumMembers); + } + + return quorumMembers; +} + +std::vector CLLMQUtils::ComputeQuorumMembers(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex) +{ auto allMns = deterministicMNManager->GetListForBlock(pQuorumBaseBlockIndex); - auto modifier = ::SerializeHash(std::make_pair(llmqParams.type, pQuorumBaseBlockIndex->GetBlockHash())); - quorumMembers = allMns.CalculateQuorum(llmqParams.size, modifier); - LOCK(cs_members); - mapQuorumMembers[llmqParams.type].insert(pQuorumBaseBlockIndex->GetBlockHash(), quorumMembers); + auto modifier = ::SerializeHash(std::make_pair(llmqType, pQuorumBaseBlockIndex->GetBlockHash())); + return allMns.CalculateQuorum(GetLLMQParams(llmqType).size, modifier); +} + +std::vector> CLLMQUtils::ComputeQuorumMembersByQuarterRotation(Consensus::LLMQType llmqType, const CBlockIndex* pCycleQuorumBaseBlockIndex) +{ + const Consensus::LLMQParams& llmqParams = GetLLMQParams(llmqType); + + const int cycleLength = llmqParams.dkgInterval; + assert(pCycleQuorumBaseBlockIndex->nHeight % cycleLength == 0); + + const CBlockIndex* pBlockHMinusCIndex = pCycleQuorumBaseBlockIndex->GetAncestor(pCycleQuorumBaseBlockIndex->nHeight - cycleLength); + const CBlockIndex* pBlockHMinus2CIndex = pCycleQuorumBaseBlockIndex->GetAncestor(pCycleQuorumBaseBlockIndex->nHeight - 2 * cycleLength); + const CBlockIndex* pBlockHMinus3CIndex = pCycleQuorumBaseBlockIndex->GetAncestor(pCycleQuorumBaseBlockIndex->nHeight - 3 * cycleLength); + LOCK(deterministicMNManager->cs); + const CBlockIndex* pWorkBlockIndex = pCycleQuorumBaseBlockIndex->GetAncestor(pCycleQuorumBaseBlockIndex->nHeight - 8); + auto allMns = deterministicMNManager->GetListForBlock(pWorkBlockIndex); + LogPrintf("CLLMQUtils::ComputeQuorumMembersByQuarterRotation llmqType[%d] nHeight[%d] allMns[%d]\n", static_cast(llmqType), pCycleQuorumBaseBlockIndex->nHeight, allMns.GetValidMNsCount()); + + PreviousQuorumQuarters previousQuarters = GetPreviousQuorumQuarterMembers(llmqParams, pBlockHMinusCIndex, pBlockHMinus2CIndex, pBlockHMinus3CIndex, pCycleQuorumBaseBlockIndex->nHeight); + + + //TODO Rewrite this part + //Last quorum DKG has failed. Returning and caching the last quorum members + /* + if (!llmq::quorumBlockProcessor->HasMinedCommitment(llmqType, pBlockHMinusCIndex->GetBlockHash())) { + //Only if they formed a full quorum + auto mns = GetAllQuorumMembers(llmqType, pBlockHMinusCIndex); + if(mns.size() == GetLLMQParams(llmqType).size) return mns; + } + */ + + auto nQuorums = size_t(llmqParams.signingActiveQuorumCount); + std::vector> quorumMembers(nQuorums); + + auto newQuarterMembers = CLLMQUtils::BuildNewQuorumQuarterMembers(llmqParams, pCycleQuorumBaseBlockIndex, previousQuarters); + //TODO Check if it is triggered from outside (P2P, block validation). Throwing an exception is probably a wiser choice + //assert (!newQuarterMembers.empty()); + + for (auto i = 0; i < nQuorums; ++i) { + std::stringstream ss; + + ss << " 3Cmns["; + for (auto& m : previousQuarters.quarterHMinus3C[i]) { + ss << m->proTxHash.ToString().substr(0, 4) << " | "; + } + ss << " ] 2Cmns["; + for (auto& m : previousQuarters.quarterHMinus2C[i]) { + ss << m->proTxHash.ToString().substr(0, 4) << " | "; + } + ss << " ] Cmns["; + for (auto& m : previousQuarters.quarterHMinusC[i]) { + ss << m->proTxHash.ToString().substr(0, 4) << " | "; + } + ss << " ] new["; + for (auto& m : newQuarterMembers[i]) { + ss << m->proTxHash.ToString().substr(0, 4) << " | "; + } + ss << " ]"; + LogPrintf("QuarterComposition h[%d] i[%d]:%s\n", pCycleQuorumBaseBlockIndex->nHeight, i, ss.str()); + } + + for (auto i = 0; i < nQuorums; ++i) { + for (auto& m : previousQuarters.quarterHMinus3C[i]) { + quorumMembers[i].push_back(std::move(m)); + } + for (auto& m : previousQuarters.quarterHMinus2C[i]) { + quorumMembers[i].push_back(std::move(m)); + } + for (auto& m : previousQuarters.quarterHMinusC[i]) { + quorumMembers[i].push_back(std::move(m)); + } + for (auto& m : newQuarterMembers[i]) { + quorumMembers[i].push_back(std::move(m)); + } + + std::stringstream ss; + ss << " ["; + for (auto& m : quorumMembers[i]) { + ss << m->proTxHash.ToString().substr(0, 4) << " | "; + } + ss << "]"; + LogPrintf("QuorumComposition h[%d] i[%d]:%s\n", pCycleQuorumBaseBlockIndex->nHeight, i, ss.str()); + } + return quorumMembers; } +PreviousQuorumQuarters CLLMQUtils::GetPreviousQuorumQuarterMembers(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pBlockHMinusCIndex, const CBlockIndex* pBlockHMinus2CIndex, const CBlockIndex* pBlockHMinus3CIndex, int nHeight) +{ + auto nQuorums = size_t(llmqParams.signingActiveQuorumCount); + PreviousQuorumQuarters quarters(nQuorums); + + std::optional quSnapshotHMinusC = quorumSnapshotManager->GetSnapshotForBlock(llmqParams.type, pBlockHMinusCIndex); + if (quSnapshotHMinusC.has_value()) { + quarters.quarterHMinusC = CLLMQUtils::GetQuorumQuarterMembersBySnapshot(llmqParams, pBlockHMinusCIndex, quSnapshotHMinusC.value(), nHeight); + //TODO Check if it is triggered from outside (P2P, block validation). Throwing an exception is probably a wiser choice + //assert (!quarterHMinusC.empty()); + + std::optional quSnapshotHMinus2C = quorumSnapshotManager->GetSnapshotForBlock(llmqParams.type, pBlockHMinus2CIndex); + if (quSnapshotHMinus2C.has_value()) { + quarters.quarterHMinus2C = CLLMQUtils::GetQuorumQuarterMembersBySnapshot(llmqParams, pBlockHMinus2CIndex, quSnapshotHMinus2C.value(), nHeight); + //TODO Check if it is triggered from outside (P2P, block validation). Throwing an exception is probably a wiser choice + //assert (!quarterHMinusC.empty()); + + std::optional quSnapshotHMinus3C = quorumSnapshotManager->GetSnapshotForBlock(llmqParams.type, pBlockHMinus3CIndex); + if (quSnapshotHMinus3C.has_value()) { + quarters.quarterHMinus3C = CLLMQUtils::GetQuorumQuarterMembersBySnapshot(llmqParams, pBlockHMinus3CIndex, quSnapshotHMinus3C.value(), nHeight); + //TODO Check if it is triggered from outside (P2P, block validation). Throwing an exception is probably a wiser choice + //assert (!quarterHMinusC.empty()); + } + } + } + + return quarters; +} + +std::vector> CLLMQUtils::BuildNewQuorumQuarterMembers(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex, const PreviousQuorumQuarters& previousQuarters) +{ + auto nQuorums = size_t(llmqParams.signingActiveQuorumCount); + std::vector> quarterQuorumMembers(nQuorums); + + auto quorumSize = size_t(llmqParams.size); + auto quarterSize = quorumSize / 4; + const CBlockIndex* pWorkBlockIndex = pQuorumBaseBlockIndex->GetAncestor(pQuorumBaseBlockIndex->nHeight - 8); + auto modifier = ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash())); + + LOCK(deterministicMNManager->cs); + auto allMns = deterministicMNManager->GetListForBlock(pWorkBlockIndex); + + if (allMns.GetValidMNsCount() < quarterSize) return quarterQuorumMembers; + + auto MnsUsedAtH = CDeterministicMNList(); + auto MnsNotUsedAtH = CDeterministicMNList(); + std::vector MnsUsedAtHIndexed(nQuorums); + + for (auto i = 0; i < nQuorums; ++i) { + for (const auto& mn : previousQuarters.quarterHMinusC[i]) { + try { + MnsUsedAtH.AddMN(mn); + } catch (std::runtime_error& e) { + } + try { + MnsUsedAtHIndexed[i].AddMN(mn); + } catch (std::runtime_error& e) { + } + } + for (const auto& mn : previousQuarters.quarterHMinus2C[i]) { + try { + MnsUsedAtH.AddMN(mn); + } catch (std::runtime_error& e) { + } + try { + MnsUsedAtHIndexed[i].AddMN(mn); + } catch (std::runtime_error& e) { + } + } + for (const auto& mn : previousQuarters.quarterHMinus3C[i]) { + try { + MnsUsedAtH.AddMN(mn); + } catch (std::runtime_error& e) { + } + try { + MnsUsedAtHIndexed[i].AddMN(mn); + } catch (std::runtime_error& e) { + } + } + } + + allMns.ForEachMNShared(true, [&MnsUsedAtH, &MnsNotUsedAtH](const CDeterministicMNCPtr& dmn) { + if (!MnsUsedAtH.HasMN(dmn->proTxHash)) { + try { + MnsNotUsedAtH.AddMN(dmn); + } catch (std::runtime_error& e) { + } + } + }); + + auto sortedMnsUsedAtHM = MnsUsedAtH.CalculateQuorum(MnsUsedAtH.GetAllMNsCount(), modifier); + auto sortedMnsNotUsedAtH = MnsNotUsedAtH.CalculateQuorum(MnsNotUsedAtH.GetAllMNsCount(), modifier); + auto sortedCombinedMnsList = std::move(sortedMnsNotUsedAtH); + for (auto& m : sortedMnsUsedAtHM) { + sortedCombinedMnsList.push_back(std::move(m)); + } + + std::stringstream ss; + ss << " ["; + for (auto& m : sortedCombinedMnsList) { + ss << m->proTxHash.ToString().substr(0, 4) << " | "; + } + ss << "]"; + LogPrintf("BuildNewQuorumQuarterMembers h[%d] sortedCombinedMnsList:%s\n", pQuorumBaseBlockIndex->nHeight, ss.str()); + + std::vector skipList; + int firstSkippedIndex = 0; + auto idx = 0; + for (auto i = 0; i < nQuorums; ++i) { + auto usedMNsCount = MnsUsedAtHIndexed[i].GetAllMNsCount(); + while (quarterQuorumMembers[i].size() < quarterSize && (usedMNsCount + quarterQuorumMembers[i].size() < sortedCombinedMnsList.size())) { + if (!MnsUsedAtHIndexed[i].HasMN(sortedCombinedMnsList[idx]->proTxHash)) { + quarterQuorumMembers[i].push_back(sortedCombinedMnsList[idx]); + } else { + if (firstSkippedIndex == 0) { + firstSkippedIndex = idx; + skipList.push_back(idx); + } else { + skipList.push_back(idx - firstSkippedIndex); + } + } + if (++idx == sortedCombinedMnsList.size()) { + idx = 0; + } + } + } + + CQuorumSnapshot quorumSnapshot = {}; + + CLLMQUtils::BuildQuorumSnapshot(llmqParams, allMns, MnsUsedAtH, sortedCombinedMnsList, quorumSnapshot, pQuorumBaseBlockIndex->nHeight, skipList, pQuorumBaseBlockIndex); + + quorumSnapshotManager->StoreSnapshotForBlock(llmqParams.type, pQuorumBaseBlockIndex, quorumSnapshot); + + return quarterQuorumMembers; +} + +void CLLMQUtils::BuildQuorumSnapshot(const Consensus::LLMQParams& llmqParams, const CDeterministicMNList& mnAtH, const CDeterministicMNList& mnUsedAtH, std::vector& sortedCombinedMns, CQuorumSnapshot& quorumSnapshot, int nHeight, std::vector& skipList, const CBlockIndex* pQuorumBaseBlockIndex) +{ + quorumSnapshot.activeQuorumMembers.resize(mnAtH.GetAllMNsCount()); + const CBlockIndex* pWorkBlockIndex = pQuorumBaseBlockIndex->GetAncestor(pQuorumBaseBlockIndex->nHeight - 8); + auto modifier = ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash())); + auto sortedAllMns = mnAtH.CalculateQuorum(mnAtH.GetAllMNsCount(), modifier); + + std::fill(quorumSnapshot.activeQuorumMembers.begin(), + quorumSnapshot.activeQuorumMembers.end(), + false); + size_t index = {}; + for (const auto& dmn : sortedAllMns) { + if (mnUsedAtH.HasMN(dmn->proTxHash)) { + quorumSnapshot.activeQuorumMembers[index] = true; + } + index++; + } + + if (skipList.empty()) { + quorumSnapshot.mnSkipListMode = SnapshotSkipMode::MODE_NO_SKIPPING; + quorumSnapshot.mnSkipList.clear(); + } else { + quorumSnapshot.mnSkipListMode = SnapshotSkipMode::MODE_SKIPPING_ENTRIES; + quorumSnapshot.mnSkipList = std::move(skipList); + } +} + +std::vector> CLLMQUtils::GetQuorumQuarterMembersBySnapshot(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex, const llmq::CQuorumSnapshot& snapshot, int nHeight) +{ + auto numQuorums = static_cast(llmqParams.signingActiveQuorumCount); + auto quorumSize = static_cast(llmqParams.size); + auto quarterSize = quorumSize / 4; + + std::vector> quarterQuorumMembers(numQuorums); + + const CBlockIndex* pWorkBlockIndex = pQuorumBaseBlockIndex->GetAncestor(pQuorumBaseBlockIndex->nHeight - 8); + auto modifier = ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash())); + + auto [MnsUsedAtH, MnsNotUsedAtH] = CLLMQUtils::GetMNUsageBySnapshot(llmqParams.type, pQuorumBaseBlockIndex, snapshot, nHeight); + + auto sortedMnsUsedAtH = MnsUsedAtH.CalculateQuorum(MnsUsedAtH.GetAllMNsCount(), modifier); + auto sortedMnsNotUsedAtH = MnsNotUsedAtH.CalculateQuorum(MnsNotUsedAtH.GetAllMNsCount(), modifier); + auto sortedCombinedMns = std::move(sortedMnsNotUsedAtH); + for (auto& m : sortedMnsUsedAtH) { + sortedCombinedMns.push_back(std::move(m)); + } + + //Mode 0: No skipping + if (snapshot.mnSkipListMode == SnapshotSkipMode::MODE_NO_SKIPPING) { + auto itm = sortedCombinedMns.begin(); + for (auto i = 0; i < llmqParams.signingActiveQuorumCount; ++i) { + while (quarterQuorumMembers[i].size() < quarterSize) { + quarterQuorumMembers[i].push_back(*itm); + itm++; + if (itm == sortedCombinedMns.end()) + itm = sortedCombinedMns.begin(); + } + } + } + //Mode 1: List holds entries to be skipped + else if (snapshot.mnSkipListMode == SnapshotSkipMode::MODE_SKIPPING_ENTRIES) { + size_t first_entry_index = {}; + std::vector processesdSkipList; + for (const auto& s : snapshot.mnSkipList) { + if (first_entry_index == 0) { + first_entry_index = s; + processesdSkipList.push_back(s); + } else + processesdSkipList.push_back(first_entry_index + s); + } + + auto idx = 0; + auto itsk = processesdSkipList.begin(); + for (auto i = 0; i < llmqParams.signingActiveQuorumCount; ++i) { + while (quarterQuorumMembers[i].size() < quarterSize) { + if (itsk != processesdSkipList.end() && idx == *itsk) + itsk++; + else + quarterQuorumMembers[i].push_back(sortedCombinedMns[idx]); + idx++; + if (idx == sortedCombinedMns.size()) + idx = 0; + } + } + } + //Mode 2: List holds entries to be kept + else if (snapshot.mnSkipListMode == SnapshotSkipMode::MODE_NO_SKIPPING_ENTRIES) { + //TODO Mode 2 will be written. Not used now + } + //Mode 3: Every node was skipped. Returning empty quarterQuorumMembers + + return quarterQuorumMembers; +} + +std::pair CLLMQUtils::GetMNUsageBySnapshot(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex, const llmq::CQuorumSnapshot& snapshot, int nHeight) +{ + CDeterministicMNList usedMNs; + CDeterministicMNList nonUsedMNs; + LOCK(deterministicMNManager->cs); + + const CBlockIndex* pWorkBlockIndex = pQuorumBaseBlockIndex->GetAncestor(pQuorumBaseBlockIndex->nHeight - 8); + auto modifier = ::SerializeHash(std::make_pair(llmqType, pWorkBlockIndex->GetBlockHash())); + + auto Mns = deterministicMNManager->GetListForBlock(pWorkBlockIndex); + auto sortedAllMns = Mns.CalculateQuorum(Mns.GetAllMNsCount(), modifier); + + size_t i{0}; + for (const auto& dmn : sortedAllMns) { + if (snapshot.activeQuorumMembers[i]) { + try { + usedMNs.AddMN(dmn); + } catch (std::runtime_error& e) { + } + } else { + try { + nonUsedMNs.AddMN(dmn); + } catch (std::runtime_error& e) { + } + } + i++; + } + + return std::make_pair(usedMNs, nonUsedMNs); +} + uint256 CLLMQUtils::BuildCommitmentHash(Consensus::LLMQType llmqType, const uint256& blockHash, const std::vector& validMembers, const CBLSPublicKey& pubKey, const uint256& vvecHash) { CHashWriter hw(SER_NETWORK, 0); @@ -95,6 +516,36 @@ bool CLLMQUtils::IsQuorumPoseEnabled(Consensus::LLMQType llmqType) return EvalSpork(llmqType, sporkManager.GetSporkValue(SPORK_23_QUORUM_POSE)); } +bool CLLMQUtils::IsQuorumRotationEnabled(Consensus::LLMQType llmqType, const CBlockIndex* pindex) +{ + assert(pindex); + + if (llmqType != Params().GetConsensus().llmqTypeDIP0024InstantSend) { + return false; + } + + LOCK(cs_llmq_vbc); + int cycleQuorumBaseHeight = pindex->nHeight - (pindex->nHeight % GetLLMQParams(llmqType).dkgInterval); + if (cycleQuorumBaseHeight < 1) { + return false; + } + // It should activate at least 1 block prior to the cycle start + return CLLMQUtils::IsDIP0024Active(pindex->GetAncestor(cycleQuorumBaseHeight - 1)); +} + +Consensus::LLMQType CLLMQUtils::GetInstantSendLLMQType(const CBlockIndex* pindex) +{ + return IsDIP0024Active(pindex) ? Params().GetConsensus().llmqTypeDIP0024InstantSend : Params().GetConsensus().llmqTypeInstantSend; +} + +bool CLLMQUtils::IsDIP0024Active(const CBlockIndex* pindex) +{ + assert(pindex); + + LOCK(cs_llmq_vbc); + return VersionBitsState(pindex, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024, llmq_versionbitscache) == ThresholdState::ACTIVE; +} + uint256 CLLMQUtils::DeterministicOutboundConnection(const uint256& proTxHash1, const uint256& proTxHash2) { // We need to deterministically select who is going to initiate the connection. The naive way would be to simply @@ -120,7 +571,7 @@ uint256 CLLMQUtils::DeterministicOutboundConnection(const uint256& proTxHash1, c std::set CLLMQUtils::GetQuorumConnections(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex, const uint256& forMember, bool onlyOutbound) { if (IsAllMembersConnectedEnabled(llmqParams.type)) { - auto mns = GetAllQuorumMembers(llmqParams, pQuorumBaseBlockIndex); + auto mns = GetAllQuorumMembers(llmqParams.type, pQuorumBaseBlockIndex); std::set result; for (const auto& dmn : mns) { @@ -141,9 +592,9 @@ std::set CLLMQUtils::GetQuorumConnections(const Consensus::LLMQParams& } } -std::set CLLMQUtils::GetQuorumRelayMembers(const Consensus::LLMQParams& llmqParams, const CBlockIndex *pQuorumBaseBlockIndex, const uint256 &forMember, bool onlyOutbound) +std::set CLLMQUtils::GetQuorumRelayMembers(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex, const uint256& forMember, bool onlyOutbound) { - auto mns = GetAllQuorumMembers(llmqParams, pQuorumBaseBlockIndex); + auto mns = GetAllQuorumMembers(llmqParams.type, pQuorumBaseBlockIndex); std::set result; auto calcOutbound = [&](size_t i, const uint256& proTxHash) { @@ -205,7 +656,7 @@ std::set CLLMQUtils::CalcDeterministicWatchConnections(Consensus::LLMQTy bool CLLMQUtils::EnsureQuorumConnections(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex, const uint256& myProTxHash) { - auto members = GetAllQuorumMembers(llmqParams, pQuorumBaseBlockIndex); + auto members = GetAllQuorumMembers(llmqParams.type, pQuorumBaseBlockIndex); bool isMember = std::find_if(members.begin(), members.end(), [&](const auto& dmn) { return dmn->proTxHash == myProTxHash; }) != members.end(); if (!isMember && !CLLMQUtils::IsWatchQuorumsEnabled()) { @@ -252,7 +703,7 @@ void CLLMQUtils::AddQuorumProbeConnections(const Consensus::LLMQParams& llmqPara return; } - auto members = GetAllQuorumMembers(llmqParams, pQuorumBaseBlockIndex); + auto members = GetAllQuorumMembers(llmqParams.type, pQuorumBaseBlockIndex); auto curTime = GetAdjustedTime(); std::set probeConnections; @@ -311,6 +762,12 @@ bool CLLMQUtils::IsQuorumTypeEnabled(Consensus::LLMQType llmqType, const CBlockI return false; } break; + case Consensus::LLMQType::LLMQ_60_75: + case Consensus::LLMQType::LLMQ_TEST_DIP0024: + if (!CLLMQUtils::IsDIP0024Active(pindex)) { + return false; + } + break; case Consensus::LLMQType::LLMQ_TEST: case Consensus::LLMQType::LLMQ_DEVNET: break; @@ -413,6 +870,7 @@ void CLLMQUtils::InitQuorumsCache(CacheType& cache) template void CLLMQUtils::InitQuorumsCache>>(std::map>& cache); template void CLLMQUtils::InitQuorumsCache, StaticSaltedHasher>>>(std::map, StaticSaltedHasher>>& cache); template void CLLMQUtils::InitQuorumsCache, StaticSaltedHasher, 0ul, 0ul>, std::less, std::allocator, StaticSaltedHasher, 0ul, 0ul>>>>>(std::map, StaticSaltedHasher, 0ul, 0ul>, std::less, std::allocator, StaticSaltedHasher, 0ul, 0ul>>>>&); +template void CLLMQUtils::InitQuorumsCache>>(std::map>& cache); const Consensus::LLMQParams& GetLLMQParams(Consensus::LLMQType llmqType) { diff --git a/src/llmq/utils.h b/src/llmq/utils.h index c7de9b9f4f16..a348b4b68092 100644 --- a/src/llmq/utils.h +++ b/src/llmq/utils.h @@ -7,21 +7,24 @@ #include -#include -#include +#include #include +#include #include -#include +#include #include class CBlockIndex; class CDeterministicMN; +class CDeterministicMNList; using CDeterministicMNCPtr = std::shared_ptr; class CBLSPublicKey; namespace llmq { +class CQuorumSnapshot; + // Use a separate cache instance instead of versionbitscache to avoid locking cs_main // and dealing with all kinds of deadlocks. extern CCriticalSection cs_llmq_vbc; @@ -35,11 +38,32 @@ enum class QvvecSyncMode { OnlyIfTypeMember = 1, }; +//QuorumMembers per quorumIndex at heights H-Cycle, H-2Cycles, H-3Cycles +struct PreviousQuorumQuarters { + std::vector> quarterHMinusC; + std::vector> quarterHMinus2C; + std::vector> quarterHMinus3C; + explicit PreviousQuorumQuarters(size_t s) : + quarterHMinusC(s), quarterHMinus2C(s), quarterHMinus3C(s) {} +}; + class CLLMQUtils { public: // includes members which failed DKG - static std::vector GetAllQuorumMembers(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pindexQuorum); + static std::vector GetAllQuorumMembers(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex); + + static void PreComputeQuorumMembers(const CBlockIndex* pQuorumBaseBlockIndex); + static std::vector ComputeQuorumMembers(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex); + static std::vector> ComputeQuorumMembersByQuarterRotation(Consensus::LLMQType llmqType, const CBlockIndex* pCycleQuorumBaseBlockIndex); + + static std::vector> BuildNewQuorumQuarterMembers(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex, const PreviousQuorumQuarters& quarters); + + static PreviousQuorumQuarters GetPreviousQuorumQuarterMembers(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pBlockHMinusCIndex, const CBlockIndex* pBlockHMinus2CIndex, const CBlockIndex* pBlockHMinus3CIndex, int nHeight); + static std::vector> GetQuorumQuarterMembersBySnapshot(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex, const llmq::CQuorumSnapshot& snapshot, int nHeights); + static std::pair GetMNUsageBySnapshot(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex, const llmq::CQuorumSnapshot& snapshot, int nHeight); + + static void BuildQuorumSnapshot(const Consensus::LLMQParams& llmqParams, const CDeterministicMNList& mnAtH, const CDeterministicMNList& mnUsedAtH, std::vector& sortedCombinedMns, CQuorumSnapshot& quorumSnapshot, int nHeight, std::vector& skipList, const CBlockIndex* pQuorumBaseBlockIndex); static uint256 BuildCommitmentHash(Consensus::LLMQType llmqType, const uint256& blockHash, const std::vector& validMembers, const CBLSPublicKey& pubKey, const uint256& vvecHash); static uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash); @@ -66,6 +90,10 @@ class CLLMQUtils static std::vector GetEnabledQuorumTypes(const CBlockIndex* pindex); static std::vector> GetEnabledQuorumParams(const CBlockIndex* pindex); + static bool IsQuorumRotationEnabled(Consensus::LLMQType llmqType, const CBlockIndex* pindex); + static Consensus::LLMQType GetInstantSendLLMQType(const CBlockIndex* pindex); + static bool IsDIP0024Active(const CBlockIndex* pindex); + /// Returns the state of `-llmq-data-recovery` static bool QuorumDataRecoveryEnabled(); diff --git a/src/masternode/utils.cpp b/src/masternode/utils.cpp index 289d73d03559..55f0b94c3570 100644 --- a/src/masternode/utils.cpp +++ b/src/masternode/utils.cpp @@ -50,6 +50,10 @@ void CMasternodeUtils::ProcessMasternodeConnections(CConnman& connman) if (connman.IsMasternodeQuorumNode(pnode)) { return; } + // keep _verified_ LLMQ relay connections + if (connman.IsMasternodeQuorumRelayMember(pnode->GetVerifiedProRegTxHash())) { + return; + } // keep _verified_ inbound connections if (pnode->fInbound) { return; diff --git a/src/miner.cpp b/src/miner.cpp index 04b97b942b57..f8d8e677514a 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -136,15 +136,17 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc if (fDIP0003Active_context) { for (const Consensus::LLMQParams& params : llmq::CLLMQUtils::GetEnabledQuorumParams(pindexPrev)) { - CTransactionRef qcTx; - if (llmq::quorumBlockProcessor->GetMineableCommitmentTx(params, - nHeight, - qcTx)) { - pblock->vtx.emplace_back(qcTx); - pblocktemplate->vTxFees.emplace_back(0); - pblocktemplate->vTxSigOps.emplace_back(0); - nBlockSize += qcTx->GetTotalSize(); - ++nBlockTx; + std::vector vqcTx; + if (llmq::quorumBlockProcessor->GetMineableCommitmentsTx(params, + nHeight, + vqcTx)) { + for (const auto& qcTx : vqcTx) { + pblock->vtx.emplace_back(qcTx); + pblocktemplate->vTxFees.emplace_back(0); + pblocktemplate->vTxSigOps.emplace_back(0); + nBlockSize += qcTx->GetTotalSize(); + ++nBlockTx; + } } } } diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 9ab79c2fd698..5cd394dcb594 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -46,14 +46,15 @@ #include #include #include -#include #include -#include #include +#include #include #include +#include #include #include +#include #include @@ -3942,6 +3943,29 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr return true; } + if (strCommand == NetMsgType::GETQUORUMROTATIONINFO) { + llmq::CGetQuorumRotationInfo cmd; + vRecv >> cmd; + + LOCK(cs_main); + + llmq::CQuorumRotationInfo quorumRotationInfoRet; + std::string strError; + if (BuildQuorumRotationInfo(cmd, quorumRotationInfoRet, strError)) { + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::QUORUMROTATIONINFO, quorumRotationInfoRet)); + } else { + strError = strprintf("getquorumrotationinfo failed for size(baseBlockHashes)=%d, blockRequestHash=%s. error=%s", cmd.baseBlockHashes.size(), cmd.blockRequestHash.ToString(), strError); + Misbehaving(pfrom->GetId(), 1, strError); + } + return true; + } + + if (strCommand == NetMsgType::QUORUMROTATIONINFO) { + // we have never requested this + LOCK(cs_main); + Misbehaving(pfrom->GetId(), 100, strprintf("received not-requested quorumrotationinfo. peer=%d", pfrom->GetId())); + return true; + } if (strCommand == NetMsgType::NOTFOUND) { // Remove the NOTFOUND transactions from the peer diff --git a/src/protocol.cpp b/src/protocol.cpp index 8e954b53d391..df1171475e4d 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -89,6 +89,8 @@ MAKE_MSG(MNAUTH, "mnauth"); MAKE_MSG(GETHEADERS2, "getheaders2"); MAKE_MSG(SENDHEADERS2, "sendheaders2"); MAKE_MSG(HEADERS2, "headers2"); +MAKE_MSG(GETQUORUMROTATIONINFO, "getqrinfo"); +MAKE_MSG(QUORUMROTATIONINFO, "qrinfo"); }; // namespace NetMsgType /** All known message types. Keep this in the same order as the list of diff --git a/src/protocol.h b/src/protocol.h index a1a522798765..afa9c3df3ae0 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -299,6 +299,8 @@ extern const char *MNAUTH; extern const char *GETHEADERS2; extern const char *SENDHEADERS2; extern const char *HEADERS2; +extern const char *GETQUORUMROTATIONINFO; +extern const char *QUORUMROTATIONINFO; }; /* Get a vector of all valid message types (see above) */ diff --git a/src/rpc/governance.cpp b/src/rpc/governance.cpp index d770c93360db..ecfa78880e3f 100644 --- a/src/rpc/governance.cpp +++ b/src/rpc/governance.cpp @@ -110,8 +110,8 @@ static UniValue gobject_check(const JSONRPCRequest& request) if (govobj.GetObjectType() == GOVERNANCE_OBJECT_PROPOSAL) { LOCK(cs_main); - bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_GOV_FEE) == ThresholdState::ACTIVE); - // Note: we do not allow legacy format in RPC already, no need to reuse DEPLOYMENT_GOV_FEE + bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024) == ThresholdState::ACTIVE); + // Note: we do not allow legacy format in RPC already, no need to reuse DEPLOYMENT_DIP0024 CProposalValidator validator(strDataHex, false, fAllowScript); if (!validator.Validate()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid proposal data, error messages:" + validator.GetErrorMessages()); @@ -188,8 +188,8 @@ static UniValue gobject_prepare(const JSONRPCRequest& request) if (govobj.GetObjectType() == GOVERNANCE_OBJECT_PROPOSAL) { LOCK(cs_main); - bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_GOV_FEE) == ThresholdState::ACTIVE); - // Note: we do not allow legacy format in RPC already, no need to reuse DEPLOYMENT_GOV_FEE + bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024) == ThresholdState::ACTIVE); + // Note: we do not allow legacy format in RPC already, no need to reuse DEPLOYMENT_DIP0024 CProposalValidator validator(strDataHex, false, fAllowScript); if (!validator.Validate()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid proposal data, error messages:" + validator.GetErrorMessages()); @@ -228,7 +228,7 @@ static UniValue gobject_prepare(const JSONRPCRequest& request) CTransactionRef tx; - bool fork_active = VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_GOV_FEE) == ThresholdState::ACTIVE; + bool fork_active = VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024) == ThresholdState::ACTIVE; if (!pwallet->GetBudgetSystemCollateralTX(*locked_chain, tx, govobj.GetHash(), govobj.GetMinCollateralFee(fork_active), outpoint)) { std::string err = "Error making collateral transaction for governance object. Please check your wallet balance and make sure your wallet is unlocked."; @@ -360,8 +360,8 @@ static UniValue gobject_submit(const JSONRPCRequest& request) if (govobj.GetObjectType() == GOVERNANCE_OBJECT_PROPOSAL) { LOCK(cs_main); - bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_GOV_FEE) == ThresholdState::ACTIVE); - // Note: we do not allow legacy format in RPC already, no need to reuse DEPLOYMENT_GOV_FEE + bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024) == ThresholdState::ACTIVE); + // Note: we do not allow legacy format in RPC already, no need to reuse DEPLOYMENT_DIP0024 CProposalValidator validator(strDataHex, false, fAllowScript); if (!validator.Validate()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid proposal data, error messages:" + validator.GetErrorMessages()); diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index f66d36fe0720..696193f7d4a1 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -637,6 +637,7 @@ static UniValue masternodelist(const JSONRPCRequest& request) std::ostringstream streamFull; streamFull << std::setw(18) << dmnToStatus(dmn) << " " << + dmn.pdmnState->nPoSePenalty << " " << payeeStr << " " << std::setw(10) << dmnToLastPaidTime(dmn) << " " << std::setw(6) << dmn.pdmnState->nLastPaidHeight << " " << @@ -649,6 +650,7 @@ static UniValue masternodelist(const JSONRPCRequest& request) std::ostringstream streamInfo; streamInfo << std::setw(18) << dmnToStatus(dmn) << " " << + dmn.pdmnState->nPoSePenalty << " " << payeeStr << " " << dmn.pdmnState->addr.ToString(); std::string strInfo = streamInfo.str(); @@ -661,6 +663,7 @@ static UniValue masternodelist(const JSONRPCRequest& request) dmn.pdmnState->addr.ToString() << " " << payeeStr << " " << dmnToStatus(dmn) << " " << + dmn.pdmnState->nPoSePenalty << " " << dmnToLastPaidTime(dmn) << " " << dmn.pdmnState->nLastPaidHeight << " " << EncodeDestination(dmn.pdmnState->keyIDOwner) << " " << @@ -675,6 +678,7 @@ static UniValue masternodelist(const JSONRPCRequest& request) objMN.pushKV("address", dmn.pdmnState->addr.ToString()); objMN.pushKV("payee", payeeStr); objMN.pushKV("status", dmnToStatus(dmn)); + objMN.pushKV("pospenaltyscore", dmn.pdmnState->nPoSePenalty); objMN.pushKV("lastpaidtime", dmnToLastPaidTime(dmn)); objMN.pushKV("lastpaidblock", dmn.pdmnState->nLastPaidHeight); objMN.pushKV("owneraddress", EncodeDestination(dmn.pdmnState->keyIDOwner)); diff --git a/src/rpc/rpcquorums.cpp b/src/rpc/rpcquorums.cpp index 39b7173dcaad..9e6b66f6c009 100644 --- a/src/rpc/rpcquorums.cpp +++ b/src/rpc/rpcquorums.cpp @@ -11,13 +11,14 @@ #include #include -#include -#include #include +#include #include #include +#include #include #include +#include namespace llmq { extern const std::string CLSIG_REQUESTID_PREFIX; @@ -100,6 +101,7 @@ static UniValue BuildQuorumInfo(const llmq::CQuorumCPtr& quorum, bool includeMem ret.pushKV("height", quorum->m_quorum_base_block_index->nHeight); ret.pushKV("type", std::string(quorum->params.name)); ret.pushKV("quorumHash", quorum->qc->quorumHash.ToString()); + ret.pushKV("quorumIndex", quorum->qc->quorumIndex); ret.pushKV("minedBlock", quorum->minedBlockHash.ToString()); if (includeMembers) { @@ -191,50 +193,71 @@ static UniValue quorum_dkgstatus(const JSONRPCRequest& request) int tipHeight = pindexTip->nHeight; auto proTxHash = WITH_LOCK(activeMasternodeInfoCs, return activeMasternodeInfo.proTxHash); - UniValue minableCommitments(UniValue::VOBJ); - UniValue quorumConnections(UniValue::VOBJ); + + UniValue minableCommitments(UniValue::VARR); + UniValue quorumArrConnections(UniValue::VARR); for (const auto& type : llmq::CLLMQUtils::GetEnabledQuorumTypes(pindexTip)) { const auto& llmq_params = llmq::GetLLMQParams(type); - if (fMasternodeMode) { - const CBlockIndex* pQuorumBaseBlockIndex = WITH_LOCK(cs_main, return ::ChainActive()[tipHeight - (tipHeight % llmq_params.dkgInterval)]); - auto allConnections = llmq::CLLMQUtils::GetQuorumConnections(llmq_params, pQuorumBaseBlockIndex, proTxHash, false); - auto outboundConnections = llmq::CLLMQUtils::GetQuorumConnections(llmq_params, pQuorumBaseBlockIndex, proTxHash, true); - std::map foundConnections; - g_connman->ForEachNode([&](const CNode* pnode) { - auto verifiedProRegTxHash = pnode->GetVerifiedProRegTxHash(); - if (!verifiedProRegTxHash.IsNull() && allConnections.count(verifiedProRegTxHash)) { - foundConnections.emplace(verifiedProRegTxHash, pnode->addr); - } - }); - UniValue arr(UniValue::VARR); - for (auto& ec : allConnections) { - UniValue obj(UniValue::VOBJ); - obj.pushKV("proTxHash", ec.ToString()); - if (foundConnections.count(ec)) { - obj.pushKV("connected", true); - obj.pushKV("address", foundConnections[ec].ToString(false)); - } else { - obj.pushKV("connected", false); + for (int quorumIndex = 0; quorumIndex < llmq_params.signingActiveQuorumCount; ++quorumIndex) { + UniValue obj(UniValue::VOBJ); + obj.pushKV("llmqType", std::string(llmq_params.name)); + obj.pushKV("quorumIndex", quorumIndex); + + if (fMasternodeMode) { + int quorumHeight = tipHeight - (tipHeight % llmq_params.dkgInterval) + quorumIndex; + if (quorumHeight <= tipHeight) { + const CBlockIndex* pQuorumBaseBlockIndex = WITH_LOCK(cs_main, return ::ChainActive()[quorumHeight]); + obj.pushKV("pQuorumBaseBlockIndex", pQuorumBaseBlockIndex->nHeight); + obj.pushKV("quorumHash", pQuorumBaseBlockIndex->GetBlockHash().ToString()); + obj.pushKV("pindexTip", pindexTip->nHeight); + + auto allConnections = llmq::CLLMQUtils::GetQuorumConnections(llmq_params, pQuorumBaseBlockIndex, + proTxHash, false); + auto outboundConnections = llmq::CLLMQUtils::GetQuorumConnections(llmq_params, + pQuorumBaseBlockIndex, proTxHash, + true); + std::map foundConnections; + g_connman->ForEachNode([&](const CNode* pnode) { + auto verifiedProRegTxHash = pnode->GetVerifiedProRegTxHash(); + if (!verifiedProRegTxHash.IsNull() && allConnections.count(verifiedProRegTxHash)) { + foundConnections.emplace(verifiedProRegTxHash, pnode->addr); + } + }); + UniValue arr(UniValue::VARR); + for (auto& ec : allConnections) { + UniValue ecj(UniValue::VOBJ); + ecj.pushKV("proTxHash", ec.ToString()); + if (foundConnections.count(ec)) { + ecj.pushKV("connected", true); + ecj.pushKV("address", foundConnections[ec].ToString(false)); + } else { + ecj.pushKV("connected", false); + } + ecj.pushKV("outbound", outboundConnections.count(ec) != 0); + arr.push_back(ecj); + } + obj.pushKV("quorumConnections", arr); } - obj.pushKV("outbound", outboundConnections.count(ec) != 0); - arr.push_back(obj); } - quorumConnections.pushKV(std::string(llmq_params.name), arr); + quorumArrConnections.push_back(obj); + if (!llmq::CLLMQUtils::IsQuorumRotationEnabled(type, pindexTip)) { + break; + } } LOCK(cs_main); - llmq::CFinalCommitment fqc; - if (llmq::quorumBlockProcessor->GetMineableCommitment(llmq_params, tipHeight, fqc)) { - UniValue obj(UniValue::VOBJ); - fqc.ToJson(obj); - minableCommitments.pushKV(std::string(llmq_params.name), obj); + std::optional> vfqc = llmq::quorumBlockProcessor->GetMineableCommitments(llmq_params, tipHeight); + if (vfqc.has_value()) { + for (const auto& fqc : vfqc.value()) { + UniValue obj(UniValue::VOBJ); + fqc.ToJson(obj); + minableCommitments.push_back(obj); + } } } - + ret.pushKV("quorumConnections", quorumArrConnections); ret.pushKV("minableCommitments", minableCommitments); - ret.pushKV("quorumConnections", quorumConnections); - return ret; } @@ -624,31 +647,82 @@ static UniValue quorum_getdata(const JSONRPCRequest& request) }); } +static void quorum_getrotationinfo_help() +{ + throw std::runtime_error( + RPCHelpMan{ + "quorum rotationinfo", + "Get quorum rotation information\n", + {{"blockRequestHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The blockHash of the request."}, + {"baseBlockHashesNb", RPCArg::Type::NUM, RPCArg::Optional::NO, + "Number of baseBlockHashes"}, + {"extraShare", RPCArg::Type::BOOL, RPCArg::Optional::NO, "Extra share"}}, + RPCResults{}, + RPCExamples{""}, + } + .ToString()); +} + +static UniValue quorum_getrotationdata(const JSONRPCRequest& request) +{ + if (request.fHelp || (request.params.size() < 2)) { + quorum_getrotationinfo_help(); + } + + llmq::CGetQuorumRotationInfo cmd; + llmq::CQuorumRotationInfo quorumRotationInfoRet; + std::string strError; + + cmd.blockRequestHash = ParseHashV(request.params[1], "blockRequestHash"); + size_t baseBlockHashesNb = static_cast(ParseInt32V(request.params[2], "baseBlockHashesNb")); + cmd.extraShare = ParseBoolV(request.params[3], "extraShare"); + + /*if (request.params.size() - 2 != cmd.baseBlockHashesNb) { + quorum_getrotationinfo_help(); + }*/ + + for (auto i = 0; i < baseBlockHashesNb; i++) { + cmd.baseBlockHashes.push_back(ParseHashV(request.params[3 + i], "quorumHash")); + } + LOCK(cs_main); + if (!BuildQuorumRotationInfo(cmd, quorumRotationInfoRet, strError)) { + throw JSONRPCError(RPC_INVALID_REQUEST, strError); + } + + UniValue ret(UniValue::VOBJ); + quorumRotationInfoRet.ToJson(ret); + return ret; +} + [[ noreturn ]] static void quorum_help() { - RPCHelpMan{"quorum", - "Set of commands for quorums/LLMQs.\n" - "To get help on individual commands, use \"help quorum command\".\n" - "\nAvailable commands:\n" - " list - List of on-chain quorums\n" - " info - Return information about a quorum\n" - " dkgsimerror - Simulates DKG errors and malicious behavior\n" - " dkgstatus - Return the status of the current DKG process\n" - " memberof - Checks which quorums the given masternode is a member of\n" - " sign - Threshold-sign a message\n" - " verify - Test if a quorum signature is valid for a request id and a message hash\n" - " 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" - " getdata - Request quorum data from other masternodes in the quorum\n", - { - {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to execute"}, - }, - RPCResults{}, - RPCExamples{""}, - }.Throw(); + throw std::runtime_error( + RPCHelpMan{ + "quorum", + "Set of commands for quorums/LLMQs.\n" + "To get help on individual commands, use \"help quorum command\".\n" + "\nAvailable commands:\n" + " list - List of on-chain quorums\n" + " info - Return information about a quorum\n" + " dkgsimerror - Simulates DKG errors and malicious behavior\n" + " dkgstatus - Return the status of the current DKG process\n" + " memberof - Checks which quorums the given masternode is a member of\n" + " sign - Threshold-sign a message\n" + " verify - Test if a quorum signature is valid for a request id and a message hash\n" + " 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" + " getdata - Request quorum data from other masternodes in the quorum\n" + " rotationinfo - Request quorum rotation information\n", + { + {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to execute"}, + }, + RPCResults{}, + RPCExamples{""}, + } + .ToString()); } static UniValue _quorum(const JSONRPCRequest& request) @@ -678,6 +752,8 @@ static UniValue _quorum(const JSONRPCRequest& request) return quorum_dkgsimerror(request); } else if (command == "getdata") { return quorum_getdata(request); + } else if (command == "rotationinfo") { + return quorum_getrotationdata(request); } else { quorum_help(); } @@ -781,8 +857,16 @@ static UniValue verifyislock(const JSONRPCRequest& request) signHeight = pindexMined->nHeight; } - auto llmqType = Params().GetConsensus().llmqTypeInstantSend; - + CBlockIndex* pBlockIndex; + { + LOCK(cs_main); + if (signHeight == -1) { + pBlockIndex = ::ChainActive().Tip(); + } else { + pBlockIndex = ::ChainActive()[signHeight]; + } + } + auto llmqType = llmq::CLLMQUtils::GetInstantSendLLMQType(pBlockIndex); // First check against the current active set, if it fails check against the last active set int signOffset{llmq::GetLLMQParams(llmqType).dkgInterval}; return llmq::quorumSigningManager->VerifyRecoveredSig(llmqType, signHeight, id, txid, sig, 0) || diff --git a/src/test/block_reward_reallocation_tests.cpp b/src/test/block_reward_reallocation_tests.cpp index 11c222362fd8..d15f3f9794cf 100644 --- a/src/test/block_reward_reallocation_tests.cpp +++ b/src/test/block_reward_reallocation_tests.cpp @@ -161,12 +161,16 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS gArgs.ForceSetArg("-blockversion", "536870912"); for (int i = 0; i < window - num_blocks; ++i) { CreateAndProcessBlock({}, coinbaseKey); + LOCK(cs_main); + deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip()); } gArgs.ForceRemoveArg("-blockversion"); if (num_blocks > 0) { // Mine signalling blocks for (int i = 0; i < num_blocks; ++i) { CreateAndProcessBlock({}, coinbaseKey); + LOCK(cs_main); + deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip()); } } LOCK(cs_main); @@ -203,12 +207,15 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS LOCK(cs_main); // Advance from DEFINED to STARTED at height = 499 BOOST_CHECK_EQUAL(::ChainActive().Height(), 499); + deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip()); + BOOST_ASSERT(deterministicMNManager->GetListAtChainTip().HasMN(tx.GetHash())); BOOST_CHECK_EQUAL(VersionBitsTipState(consensus_params, deployment_id), ThresholdState::STARTED); BOOST_CHECK_EQUAL(VersionBitsTipStatistics(consensus_params, deployment_id).threshold, threshold(0)); // Next block should be signaling by default const auto pblocktemplate = BlockAssembler(Params()).CreateNewBlock(coinbasePubKey); - BOOST_CHECK_EQUAL(::ChainActive().Tip()->nVersion, 536870912); - BOOST_CHECK(pblocktemplate->block.nVersion != 536870912); + const uint32_t bitmask = ((uint32_t)1) << consensus_params.vDeployments[deployment_id].bit; + BOOST_CHECK_EQUAL(::ChainActive().Tip()->nVersion & bitmask, 0); + BOOST_CHECK_EQUAL(pblocktemplate->block.nVersion & bitmask, bitmask); } signal(threshold(0) - 1, false); // 1 block short @@ -217,6 +224,8 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS // Still STARTED but new threshold should be lower at height = 999 LOCK(cs_main); BOOST_CHECK_EQUAL(::ChainActive().Height(), 999); + deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip()); + BOOST_ASSERT(deterministicMNManager->GetListAtChainTip().HasMN(tx.GetHash())); BOOST_CHECK_EQUAL(VersionBitsTipState(consensus_params, deployment_id), ThresholdState::STARTED); BOOST_CHECK_EQUAL(VersionBitsTipStatistics(consensus_params, deployment_id).threshold, threshold(1)); } @@ -227,6 +236,8 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS // Still STARTED but new threshold should be even lower at height = 1499 LOCK(cs_main); BOOST_CHECK_EQUAL(::ChainActive().Height(), 1499); + deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip()); + BOOST_ASSERT(deterministicMNManager->GetListAtChainTip().HasMN(tx.GetHash())); BOOST_CHECK_EQUAL(VersionBitsTipState(consensus_params, deployment_id), ThresholdState::STARTED); BOOST_CHECK_EQUAL(VersionBitsTipStatistics(consensus_params, deployment_id).threshold, threshold(2)); } @@ -237,6 +248,8 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS // Advanced to LOCKED_IN at height = 1999 LOCK(cs_main); BOOST_CHECK_EQUAL(::ChainActive().Height(), 1999); + deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip()); + BOOST_ASSERT(deterministicMNManager->GetListAtChainTip().HasMN(tx.GetHash())); BOOST_CHECK_EQUAL(VersionBitsTipState(consensus_params, deployment_id), ThresholdState::LOCKED_IN); } @@ -252,6 +265,8 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS // Advance from LOCKED_IN to ACTIVE at height = 2499 LOCK(cs_main); BOOST_CHECK_EQUAL(::ChainActive().Height(), 2499); + deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip()); + BOOST_ASSERT(deterministicMNManager->GetListAtChainTip().HasMN(tx.GetHash())); BOOST_CHECK_EQUAL(VersionBitsTipState(consensus_params, deployment_id), ThresholdState::ACTIVE); BOOST_CHECK_EQUAL(VersionBitsTipStateSinceHeight(consensus_params, deployment_id), 2500); } @@ -261,6 +276,8 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS // This applies even if reallocation was activated right at superblock height like it does here. // next block should be signaling by default LOCK(cs_main); + deterministicMNManager->UpdatedBlockTip(::ChainActive().Tip()); + BOOST_ASSERT(deterministicMNManager->GetListAtChainTip().HasMN(tx.GetHash())); auto masternode_payment = GetMasternodePayment(::ChainActive().Height(), GetBlockSubsidy(::ChainActive().Tip()->nBits, ::ChainActive().Height(), consensus_params), 2500); const auto pblocktemplate = BlockAssembler(Params()).CreateNewBlock(coinbasePubKey); BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, masternode_payment); diff --git a/src/test/dynamic_activation_thresholds_tests.cpp b/src/test/dynamic_activation_thresholds_tests.cpp index 35dae99ceb80..03362c99b636 100644 --- a/src/test/dynamic_activation_thresholds_tests.cpp +++ b/src/test/dynamic_activation_thresholds_tests.cpp @@ -73,8 +73,9 @@ struct TestChainDATSetup : public TestChainSetup BOOST_CHECK_EQUAL(VersionBitsTipStatistics(consensus_params, deployment_id).threshold, threshold(0)); // Next block should be signaling by default const auto pblocktemplate = BlockAssembler(Params()).CreateNewBlock(coinbasePubKey); - BOOST_CHECK_EQUAL(::ChainActive().Tip()->nVersion, 536870912); - BOOST_CHECK(pblocktemplate->block.nVersion != 536870912); + const uint32_t bitmask = ((uint32_t)1) << consensus_params.vDeployments[deployment_id].bit; + BOOST_CHECK_EQUAL(::ChainActive().Tip()->nVersion & bitmask, 0); + BOOST_CHECK_EQUAL(pblocktemplate->block.nVersion & bitmask, bitmask); } // Reach activation_index level diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 219dab919b86..94bc5b9440ff 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -31,11 +31,12 @@ #include #include -#include +#include #include #include -#include +#include #include +#include const std::function G_TRANSLATION_FUN = nullptr; @@ -90,6 +91,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName) fCheckBlockIndex = true; evoDb.reset(new CEvoDB(1 << 20, true, true)); deterministicMNManager.reset(new CDeterministicMNManager(*evoDb)); + llmq::quorumSnapshotManager.reset(new llmq::CQuorumSnapshotManager(*evoDb)); static bool noui_connected = false; if (!noui_connected) { noui_connect(); @@ -99,6 +101,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName) BasicTestingSetup::~BasicTestingSetup() { + llmq::quorumSnapshotManager.reset(); deterministicMNManager.reset(); evoDb.reset(); diff --git a/src/version.h b/src/version.h index 294867a31a85..171160664179 100644 --- a/src/version.h +++ b/src/version.h @@ -11,7 +11,7 @@ */ -static const int PROTOCOL_VERSION = 70221; +static const int PROTOCOL_VERSION = 70222; //! initial proto version, to be increased after version/verack negotiation static const int INIT_PROTO_VERSION = 209; diff --git a/src/versionbitsinfo.cpp b/src/versionbitsinfo.cpp index 2b30fbf464d2..aed106cac0b2 100644 --- a/src/versionbitsinfo.cpp +++ b/src/versionbitsinfo.cpp @@ -48,8 +48,8 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B /*.check_mn_protocol =*/ false, }, { - /*.name =*/ "gov_fee", - /*.gbt_force =*/ true, - /*.check_mn_protocol =*/ false, - }, + /*.name =*/"dip0024", + /*.gbt_force =*/true, + /*.check_mn_protocol =*/false, + } }; diff --git a/test/functional/feature_dip4_coinbasemerkleroots.py b/test/functional/feature_dip4_coinbasemerkleroots.py index 8a409a64c6e9..c7f11c92f5a7 100755 --- a/test/functional/feature_dip4_coinbasemerkleroots.py +++ b/test/functional/feature_dip4_coinbasemerkleroots.py @@ -41,7 +41,8 @@ def getmnlistdiff(self, baseBlockHash, blockHash): class LLMQCoinbaseCommitmentsTest(DashTestFramework): def set_test_params(self): - self.set_dash_test_params(4, 3, fast_dip3_enforcement=True) + extra_args = [["-vbparams=dip0024:999999999999:999999999999"]] * 4 # disable dip0024 + self.set_dash_test_params(4, 3, extra_args=extra_args, fast_dip3_enforcement=True) def run_test(self): self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn()) diff --git a/test/functional/feature_llmq_is_migration.py b/test/functional/feature_llmq_is_migration.py new file mode 100755 index 000000000000..e12b2a1953f5 --- /dev/null +++ b/test/functional/feature_llmq_is_migration.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020-2021 The Dash Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +import time + +from test_framework.messages import CTransaction, FromHex, hash256, ser_compact_size, ser_string +from test_framework.test_framework import DashTestFramework +from test_framework.util import wait_until, connect_nodes, sync_blocks + +''' +feature_llmq_is_migration.py + +Test IS LLMQ migration with DIP0024 + +''' + +class LLMQISMigrationTest(DashTestFramework): + def set_test_params(self): + # -whitelist is needed to avoid the trickling logic on node0 + self.set_dash_test_params(16, 15, [["-whitelist=127.0.0.1"], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []], fast_dip3_enforcement=True) + self.set_dash_llmq_test_params(4, 4) + + def get_request_id(self, tx_hex): + tx = FromHex(CTransaction(), tx_hex) + + request_id_buf = ser_string(b"islock") + ser_compact_size(len(tx.vin)) + for txin in tx.vin: + request_id_buf += txin.prevout.serialize() + return hash256(request_id_buf)[::-1].hex() + + def run_test(self): + + for i in range(len(self.nodes)): + if i != 1: + connect_nodes(self.nodes[i], 0) + + self.activate_dip8() + + node = self.nodes[0] + node.spork("SPORK_17_QUORUM_DKG_ENABLED", 0) + node.spork("SPORK_2_INSTANTSEND_ENABLED", 0) + self.wait_for_sporks_same() + + self.mine_quorum() + self.mine_quorum() + + txid1 = node.sendtoaddress(node.getnewaddress(), 1) + self.wait_for_instantlock(txid1, node) + + request_id = self.get_request_id(self.nodes[0].getrawtransaction(txid1)) + wait_until(lambda: node.quorum("hasrecsig", 100, request_id, txid1)) + + rec_sig = node.quorum("getrecsig", 100, request_id, txid1)['sig'] + assert node.verifyislock(request_id, txid1, rec_sig) + + self.activate_dip0024() + self.log.info("Activated DIP0024 at height:" + str(self.nodes[0].getblockcount())) + + #At this point, we need to move forward 3 cycles (3 x 24 blocks) so the first 3 quarters can be created (without DKG sessions) + #self.log.info("Start at H height:" + str(self.nodes[0].getblockcount())) + self.move_to_next_cycle() + self.log.info("Cycle H height:" + str(self.nodes[0].getblockcount())) + self.move_to_next_cycle() + self.log.info("Cycle H+C height:" + str(self.nodes[0].getblockcount())) + self.move_to_next_cycle() + self.log.info("Cycle H+2C height:" + str(self.nodes[0].getblockcount())) + + (quorum_info_0_0, quorum_info_0_1) = self.mine_cycle_quorum("llmq_test_dip0024", 103) + + txid2 = node.sendtoaddress(node.getnewaddress(), 1) + self.wait_for_instantlock(txid2, node) + + request_id2 = self.get_request_id(self.nodes[0].getrawtransaction(txid2)) + wait_until(lambda: node.quorum("hasrecsig", 103, request_id2, txid2)) + + rec_sig2 = node.quorum("getrecsig", 103, request_id2, txid2)['sig'] + assert node.verifyislock(request_id2, txid2, rec_sig2) + + def move_to_next_cycle(self): + cycle_length = 24 + mninfos_online = self.mninfo.copy() + nodes = [self.nodes[0]] + [mn.node for mn in mninfos_online] + cur_block = self.nodes[0].getblockcount() + + # move forward to next DKG + skip_count = cycle_length - (cur_block % cycle_length) + if skip_count != 0: + self.bump_mocktime(1, nodes=nodes) + self.nodes[0].generate(skip_count) + sync_blocks(nodes) + time.sleep(1) + self.log.info('Moved from block %d to %d' % (cur_block, self.nodes[0].getblockcount())) + +if __name__ == '__main__': + LLMQISMigrationTest().main() diff --git a/test/functional/feature_llmq_rotation.py b/test/functional/feature_llmq_rotation.py new file mode 100755 index 000000000000..18f574a0e872 --- /dev/null +++ b/test/functional/feature_llmq_rotation.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-2021 The Dash Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +''' +feature_llmq_rotation.py + +Checks LLMQs Quorum Rotation + +''' +import time +from test_framework.test_framework import DashTestFramework +from test_framework.util import ( + assert_equal, + assert_greater_than_or_equal, + connect_nodes, + sync_blocks, + wait_until, +) + + +def intersection(lst1, lst2): + lst3 = [value for value in lst1 if value in lst2] + return lst3 + + +def extract_quorum_members(quorum_info): + return [d['proTxHash'] for d in quorum_info["members"]] + + +class LLMQQuorumRotationTest(DashTestFramework): + def set_test_params(self): + self.set_dash_test_params(16, 15, fast_dip3_enforcement=True) + self.set_dash_llmq_test_params(4, 4) + + def run_test(self): + + llmq_type=103 + llmq_type_name="llmq_test_dip0024" + + # Connect all nodes to node1 so that we always have the whole network connected + # Otherwise only masternode connections will be established between nodes, which won't propagate TXs/blocks + # Usually node0 is the one that does this, but in this test we isolate it multiple times + + for i in range(len(self.nodes)): + if i != 1: + connect_nodes(self.nodes[i], 0) + + self.activate_dip8() + + self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0) + self.wait_for_sporks_same() + + self.activate_dip0024(expected_activation_height=900) + self.log.info("Activated DIP0024 at height:" + str(self.nodes[0].getblockcount())) + + #At this point, we need to move forward 3 cycles (3 x 24 blocks) so the first 3 quarters can be created (without DKG sessions) + #self.log.info("Start at H height:" + str(self.nodes[0].getblockcount())) + self.move_to_next_cycle() + self.log.info("Cycle H height:" + str(self.nodes[0].getblockcount())) + self.move_to_next_cycle() + self.log.info("Cycle H+C height:" + str(self.nodes[0].getblockcount())) + self.move_to_next_cycle() + self.log.info("Cycle H+2C height:" + str(self.nodes[0].getblockcount())) + + (quorum_info_0_0, quorum_info_0_1) = self.mine_cycle_quorum(llmq_type_name=llmq_type_name, llmq_type=llmq_type) + quorum_members_0_0 = extract_quorum_members(quorum_info_0_0) + quorum_members_0_1 = extract_quorum_members(quorum_info_0_1) + assert_equal(len(intersection(quorum_members_0_0, quorum_members_0_1)), 0) + self.log.info("Quorum #0_0 members: " + str(quorum_members_0_0)) + self.log.info("Quorum #0_1 members: " + str(quorum_members_0_1)) + + (quorum_info_1_0, quorum_info_1_1) = self.mine_cycle_quorum(llmq_type_name=llmq_type_name, llmq_type=llmq_type) + quorum_members_1_0 = extract_quorum_members(quorum_info_1_0) + quorum_members_1_1 = extract_quorum_members(quorum_info_1_1) + assert_equal(len(intersection(quorum_members_1_0, quorum_members_1_1)), 0) + self.log.info("Quorum #1_0 members: " + str(quorum_members_1_0)) + self.log.info("Quorum #1_1 members: " + str(quorum_members_1_1)) + + (quorum_info_2_0, quorum_info_2_1) = self.mine_cycle_quorum(llmq_type_name=llmq_type_name, llmq_type=llmq_type) + quorum_members_2_0 = extract_quorum_members(quorum_info_2_0) + quorum_members_2_1 = extract_quorum_members(quorum_info_2_1) + assert_equal(len(intersection(quorum_members_2_0, quorum_members_2_1)), 0) + self.log.info("Quorum #2_0 members: " + str(quorum_members_2_0)) + self.log.info("Quorum #2_1 members: " + str(quorum_members_2_1)) + + mninfos_online = self.mninfo.copy() + nodes = [self.nodes[0]] + [mn.node for mn in mninfos_online] + sync_blocks(nodes) + quorum_list = self.nodes[0].quorum("list", llmq_type) + quorum_blockhash = self.nodes[0].getbestblockhash() + fallback_blockhash = self.nodes[0].generate(1)[0] + self.log.info("h("+str(self.nodes[0].getblockcount())+") quorum_list:"+str(quorum_list)) + + assert_greater_than_or_equal(len(intersection(quorum_members_0_0, quorum_members_1_0)), 3) + assert_greater_than_or_equal(len(intersection(quorum_members_0_1, quorum_members_1_1)), 3) + + assert_greater_than_or_equal(len(intersection(quorum_members_0_0, quorum_members_2_0)), 2) + assert_greater_than_or_equal(len(intersection(quorum_members_0_1, quorum_members_2_1)), 2) + + assert_greater_than_or_equal(len(intersection(quorum_members_1_0, quorum_members_2_0)), 3) + assert_greater_than_or_equal(len(intersection(quorum_members_1_1, quorum_members_2_1)), 3) + + self.log.info("mine a quorum to invalidate") + (quorum_info_3_0, quorum_info_3_1) = self.mine_cycle_quorum(llmq_type_name=llmq_type_name, llmq_type=llmq_type) + + new_quorum_list = self.nodes[0].quorum("list", llmq_type) + assert_equal(len(new_quorum_list[llmq_type_name]), len(quorum_list[llmq_type_name]) + 2) + new_quorum_blockhash = self.nodes[0].getbestblockhash() + self.log.info("h("+str(self.nodes[0].getblockcount())+") new_quorum_blockhash:"+new_quorum_blockhash) + self.log.info("h("+str(self.nodes[0].getblockcount())+") new_quorum_list:"+str(new_quorum_list)) + assert new_quorum_list != quorum_list + + self.log.info("Invalidate the quorum") + self.bump_mocktime(5) + self.nodes[0].spork("SPORK_19_CHAINLOCKS_ENABLED", 4070908800) + self.wait_for_sporks_same() + self.nodes[0].invalidateblock(fallback_blockhash) + assert_equal(self.nodes[0].getbestblockhash(), quorum_blockhash) + assert_equal(self.nodes[0].quorum("list", llmq_type), quorum_list) + + self.log.info("Reconsider the quorum") + self.bump_mocktime(5) + self.nodes[0].spork("SPORK_19_CHAINLOCKS_ENABLED", 0) + self.wait_for_sporks_same() + self.nodes[0].reconsiderblock(fallback_blockhash) + wait_until(lambda: self.nodes[0].getbestblockhash() == new_quorum_blockhash, sleep=1) + assert_equal(self.nodes[0].quorum("list", llmq_type), new_quorum_list) + + def move_to_next_cycle(self): + cycle_length = 24 + mninfos_online = self.mninfo.copy() + nodes = [self.nodes[0]] + [mn.node for mn in mninfos_online] + cur_block = self.nodes[0].getblockcount() + + # move forward to next DKG + skip_count = cycle_length - (cur_block % cycle_length) + if skip_count != 0: + self.bump_mocktime(1, nodes=nodes) + self.nodes[0].generate(skip_count) + sync_blocks(nodes) + time.sleep(1) + self.log.info('Moved from block %d to %d' % (cur_block, self.nodes[0].getblockcount())) + + +if __name__ == '__main__': + LLMQQuorumRotationTest().main() diff --git a/test/functional/p2p_quorum_data.py b/test/functional/p2p_quorum_data.py index be7ad11b2f01..fe910d352f22 100755 --- a/test/functional/p2p_quorum_data.py +++ b/test/functional/p2p_quorum_data.py @@ -238,7 +238,7 @@ def test_basics(): p2p_mn1 = p2p_connection(mn1.node) id_p2p_mn1 = get_mininode_id(mn1.node) mnauth(mn1.node, id_p2p_mn1, fake_mnauth_1[0], fake_mnauth_1[1]) - qgetdata_invalid_type = msg_qgetdata(quorum_hash_int, 103, 0x01, protx_hash_int) + qgetdata_invalid_type = msg_qgetdata(quorum_hash_int, 104, 0x01, protx_hash_int) qgetdata_invalid_block = msg_qgetdata(protx_hash_int, 100, 0x01, protx_hash_int) qgetdata_invalid_quorum = msg_qgetdata(int(mn1.node.getblockhash(0), 16), 100, 0x01, protx_hash_int) qgetdata_invalid_no_member = msg_qgetdata(quorum_hash_int, 100, 0x02, quorum_hash_int) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 5465415aea13..6ed622cf8ea9 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -755,6 +755,27 @@ def activate_dip8(self, slow_mode=False): self.sync_blocks() self.sync_blocks() + def activate_dip0024(self, slow_mode=False, expected_activation_height=None): + self.log.info("Wait for dip0024 activation") + + if expected_activation_height is not None: + height = self.nodes[0].getblockcount() + batch_size = 100 + while height - expected_activation_height > batch_size: + self.nodes[0].generate(batch_size) + height += batch_size + self.sync_blocks() + assert height - expected_activation_height < batch_size + self.nodes[0].generate(height - expected_activation_height - 1) + self.sync_blocks() + assert self.nodes[0].getblockchaininfo()['bip9_softforks']['dip0024']['status'] != 'active' + + while self.nodes[0].getblockchaininfo()['bip9_softforks']['dip0024']['status'] != 'active': + self.nodes[0].generate(10) + if slow_mode: + self.sync_blocks() + self.sync_blocks() + def set_dash_llmq_test_params(self, llmq_size, llmq_threshold): self.llmq_size = llmq_size self.llmq_threshold = llmq_threshold @@ -770,38 +791,48 @@ def create_simple_node(self): def prepare_masternodes(self): self.log.info("Preparing %d masternodes" % self.mn_count) + rewardsAddr = self.nodes[0].getnewaddress() + for idx in range(0, self.mn_count): - self.prepare_masternode(idx) + self.prepare_masternode(idx, rewardsAddr) + self.sync_all() + + def prepare_masternode(self, idx, rewardsAddr=None): + + register_fund = (idx % 2) == 0 - def prepare_masternode(self, idx): bls = self.nodes[0].bls('generate') address = self.nodes[0].getnewaddress() - txid = self.nodes[0].sendtoaddress(address, MASTERNODE_COLLATERAL) - txraw = self.nodes[0].getrawtransaction(txid, True) + txid = None + txid = self.nodes[0].sendtoaddress(address, MASTERNODE_COLLATERAL) collateral_vout = 0 - for vout_idx in range(0, len(txraw["vout"])): - vout = txraw["vout"][vout_idx] - if vout["value"] == MASTERNODE_COLLATERAL: - collateral_vout = vout_idx - self.nodes[0].lockunspent(False, [{'txid': txid, 'vout': collateral_vout}]) + if not register_fund: + txraw = self.nodes[0].getrawtransaction(txid, True) + for vout_idx in range(0, len(txraw["vout"])): + vout = txraw["vout"][vout_idx] + if vout["value"] == MASTERNODE_COLLATERAL: + collateral_vout = vout_idx + self.nodes[0].lockunspent(False, [{'txid': txid, 'vout': collateral_vout}]) # send to same address to reserve some funds for fees self.nodes[0].sendtoaddress(address, 0.001) ownerAddr = self.nodes[0].getnewaddress() - votingAddr = self.nodes[0].getnewaddress() - rewardsAddr = self.nodes[0].getnewaddress() + # votingAddr = self.nodes[0].getnewaddress() + if rewardsAddr is None: + rewardsAddr = self.nodes[0].getnewaddress() + votingAddr = ownerAddr + # rewardsAddr = ownerAddr port = p2p_port(len(self.nodes) + idx) ipAndPort = '127.0.0.1:%d' % port operatorReward = idx - operatorPayoutAddress = self.nodes[0].getnewaddress() submit = (idx % 4) < 2 - if (idx % 2) == 0 : - self.nodes[0].lockunspent(True, [{'txid': txid, 'vout': collateral_vout}]) + if register_fund: + # self.nodes[0].lockunspent(True, [{'txid': txid, 'vout': collateral_vout}]) protx_result = self.nodes[0].protx('register_fund', address, ipAndPort, ownerAddr, bls['public'], votingAddr, operatorReward, rewardsAddr, address, submit) else: self.nodes[0].generate(1) @@ -812,13 +843,14 @@ def prepare_masternode(self, idx): else: proTxHash = self.nodes[0].sendrawtransaction(protx_result) - self.nodes[0].generate(1) if operatorReward > 0: + self.nodes[0].generate(1) + operatorPayoutAddress = self.nodes[0].getnewaddress() self.nodes[0].protx('update_service', proTxHash, ipAndPort, bls['secret'], operatorPayoutAddress, address) self.mninfo.append(MasternodeInfo(proTxHash, ownerAddr, votingAddr, bls['public'], bls['secret'], address, txid, collateral_vout)) - self.sync_all() + # self.sync_all() self.log.info("Prepared masternode %d: collateral_txid=%s, collateral_vout=%d, protxHash=%s" % (idx, txid, collateral_vout, proTxHash)) @@ -1051,27 +1083,32 @@ 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, nodes, timeout = 60, wait_proc=None): + def wait_for_quorum_connections(self, quorum_hash, expected_connections, nodes, llmq_type_name="llmq_test", timeout = 60, wait_proc=None): def check_quorum_connections(): all_ok = True for node in nodes: s = node.quorum("dkgstatus") - if 'llmq_test' not in s["session"]: - continue - if "quorumConnections" not in s: - all_ok = False - break - s = s["quorumConnections"] - if "llmq_test" not in s: - all_ok = False - break - cnt = 0 - for c in s["llmq_test"]: - if c["connected"]: - cnt += 1 - if cnt < expected_connections: - all_ok = False + mn_ok = True + for qs in s: + if "llmqType" not in qs: + continue + if qs["llmqType"] != llmq_type_name: + continue + if "quorumConnections" not in qs: + continue + qconnections = qs["quorumConnections"] + if qconnections["quorumHash"] != quorum_hash: + mn_ok = False + continue + cnt = 0 + for c in qconnections["quorumConnections"]: + if c["connected"]: + cnt += 1 + if cnt < expected_connections: + mn_ok = False + break break + all_ok = mn_ok if not all_ok and wait_proc is not None: wait_proc() return all_ok @@ -1113,55 +1150,62 @@ def ret(): 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 wait_for_quorum_phase(self, quorum_hash, phase, expected_member_count, check_received_messages, check_received_messages_count, mninfos, llmq_type_name="llmq_test", timeout=30, sleep=1): def check_dkg_session(): all_ok = True member_count = 0 for mn in mninfos: s = mn.node.quorum("dkgstatus")["session"] - if "llmq_test" not in s: - continue - member_count += 1 - s = s["llmq_test"] - if s["quorumHash"] != quorum_hash: - all_ok = False - break - if "phase" not in s: - all_ok = False - break - if s["phase"] != phase: - all_ok = False - break - if check_received_messages is not None: - if s[check_received_messages] < check_received_messages_count: - all_ok = False + mn_ok = True + for qs in s: + if qs["llmqType"] != llmq_type_name: + continue + qstatus = qs["status"] + if qstatus["quorumHash"] != quorum_hash: + continue + member_count += 1 + if "phase" not in qstatus: + mn_ok = False + break + if qstatus["phase"] != phase: + mn_ok = False break + if check_received_messages is not None: + if qstatus[check_received_messages] < check_received_messages_count: + mn_ok = False + break + break + all_ok = mn_ok if all_ok and member_count != expected_member_count: return False return all_ok wait_until(check_dkg_session, timeout=timeout, sleep=sleep) - def wait_for_quorum_commitment(self, quorum_hash, nodes, timeout = 15): + def wait_for_quorum_commitment(self, quorum_hash, nodes, llmq_type=100, timeout=15): def check_dkg_comitments(): + time.sleep(2) all_ok = True for node in nodes: s = node.quorum("dkgstatus") if "minableCommitments" not in s: all_ok = False break - s = s["minableCommitments"] - if "llmq_test" not in s: - all_ok = False - break - s = s["llmq_test"] - if s["quorumHash"] != quorum_hash: - all_ok = False + commits = s["minableCommitments"] + c_ok = False + for c in commits: + if c["llmqType"] != llmq_type: + continue + if c["quorumHash"] != quorum_hash: + continue + c_ok = True break + all_ok = c_ok return all_ok - wait_until(check_dkg_comitments, timeout=timeout, sleep=0.1) + wait_until(check_dkg_comitments, timeout=timeout, sleep=1) def wait_for_quorum_list(self, quorum_hash, nodes, timeout=15, sleep=2): def wait_func(): + self.log.info("quorums: " + str(self.nodes[0].quorum("list"))) if quorum_hash in self.nodes[0].quorum("list")["llmq_test"]: return True self.bump_mocktime(sleep, nodes=nodes) @@ -1170,6 +1214,24 @@ def wait_func(): return False wait_until(wait_func, timeout=timeout, sleep=sleep) + def wait_for_quorums_list(self, quorum_hash_0, quorum_hash_1, nodes, llmq_type_name="llmq_test", timeout=15, sleep=2): + def wait_func(): + self.log.info("h("+str(self.nodes[0].getblockcount())+") quorums: " + str(self.nodes[0].quorum("list"))) + if quorum_hash_0 in self.nodes[0].quorum("list")[llmq_type_name]: + if quorum_hash_1 in self.nodes[0].quorum("list")[llmq_type_name]: + 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 move_blocks(self, nodes, num_blocks): + time.sleep(1) + self.bump_mocktime(1, nodes=nodes) + self.nodes[0].generate(num_blocks) + sync_blocks(nodes) + 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 spork23_active = self.nodes[0].spork('show')['SPORK_23_QUORUM_POSE'] <= 1 @@ -1201,39 +1263,34 @@ def mine_quorum(self, expected_connections=None, expected_members=None, expected sync_blocks(nodes) q = self.nodes[0].getbestblockhash() - + self.log.info("Expected quorum_hash:"+str(q)) self.log.info("Waiting for phase 1 (init)") self.wait_for_quorum_phase(q, 1, expected_members, None, 0, mninfos_online) - self.wait_for_quorum_connections(expected_connections, nodes, wait_proc=lambda: self.bump_mocktime(1, nodes=nodes)) + self.wait_for_quorum_connections(q, expected_connections, nodes, wait_proc=lambda: self.bump_mocktime(1, nodes=nodes)) if spork23_active: self.wait_for_masternode_probes(mninfos_valid, wait_proc=lambda: self.bump_mocktime(1, nodes=nodes)) - self.bump_mocktime(1, nodes=nodes) - self.nodes[0].generate(2) - sync_blocks(nodes) + + self.move_blocks(nodes, 2) self.log.info("Waiting for phase 2 (contribute)") self.wait_for_quorum_phase(q, 2, expected_members, "receivedContributions", expected_contributions, mninfos_online) - self.bump_mocktime(1, nodes=nodes) - self.nodes[0].generate(2) - sync_blocks(nodes) + + self.move_blocks(nodes, 2) self.log.info("Waiting for phase 3 (complain)") self.wait_for_quorum_phase(q, 3, expected_members, "receivedComplaints", expected_complaints, mninfos_online) - self.bump_mocktime(1, nodes=nodes) - self.nodes[0].generate(2) - sync_blocks(nodes) + + self.move_blocks(nodes, 2) self.log.info("Waiting for phase 4 (justify)") self.wait_for_quorum_phase(q, 4, expected_members, "receivedJustifications", expected_justifications, mninfos_online) - self.bump_mocktime(1, nodes=nodes) - self.nodes[0].generate(2) - sync_blocks(nodes) + + self.move_blocks(nodes, 2) self.log.info("Waiting for phase 5 (commit)") self.wait_for_quorum_phase(q, 5, expected_members, "receivedPrematureCommitments", expected_commitments, mninfos_online) - self.bump_mocktime(1, nodes=nodes) - self.nodes[0].generate(2) - sync_blocks(nodes) + + self.move_blocks(nodes, 2) self.log.info("Waiting for phase 6 (mining)") self.wait_for_quorum_phase(q, 6, expected_members, None, 0, mninfos_online) @@ -1259,10 +1316,147 @@ def mine_quorum(self, expected_connections=None, expected_members=None, expected sync_blocks(nodes) - self.log.info("New quorum: height=%d, quorumHash=%s, minedBlock=%s" % (quorum_info["height"], new_quorum, quorum_info["minedBlock"])) + self.log.info("New quorum: height=%d, quorumHash=%s, quorumIndex=%d, minedBlock=%s" % (quorum_info["height"], new_quorum, quorum_info["quorumIndex"], quorum_info["minedBlock"])) return new_quorum + def mine_cycle_quorum(self, llmq_type_name="llmq_test", llmq_type=100, 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 + spork23_active = self.nodes[0].spork('show')['SPORK_23_QUORUM_POSE'] <= 1 + + if expected_connections is None: + expected_connections = (self.llmq_size - 1) if spork21_active else 2 + 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_online is None: + mninfos_online = self.mninfo.copy() + if mninfos_valid is None: + mninfos_valid = self.mninfo.copy() + + self.log.info("Mining quorum: expected_members=%d, expected_connections=%d, expected_contributions=%d, expected_complaints=%d, expected_justifications=%d, " + "expected_commitments=%d" % (expected_members, expected_connections, expected_contributions, expected_complaints, + expected_justifications, expected_commitments)) + + nodes = [self.nodes[0]] + [mn.node for mn in mninfos_online] + + # move forward to next DKG + skip_count = 24 - (self.nodes[0].getblockcount() % 24) + + # if skip_count != 0: + # self.bump_mocktime(1, nodes=nodes) + # self.nodes[0].generate(skip_count) + # time.sleep(4) + # sync_blocks(nodes) + + self.move_blocks(nodes, skip_count) + + q_0 = self.nodes[0].getbestblockhash() + self.log.info("Expected quorum_0 at:" + str(self.nodes[0].getblockcount())) + # time.sleep(4) + self.log.info("Expected quorum_0 hash:" + str(q_0)) + # time.sleep(4) + self.log.info("quorumIndex 0: Waiting for phase 1 (init)") + self.wait_for_quorum_phase(q_0, 1, expected_members, None, 0, mninfos_online, llmq_type_name) + self.log.info("quorumIndex 0: Waiting for quorum connections (init)") + self.wait_for_quorum_connections(q_0, expected_connections, nodes, llmq_type_name, wait_proc=lambda: self.bump_mocktime(1, nodes=nodes)) + if spork23_active: + self.wait_for_masternode_probes(mninfos_valid, wait_proc=lambda: self.bump_mocktime(1, nodes=nodes)) + + self.move_blocks(nodes, 1) + + q_1 = self.nodes[0].getbestblockhash() + self.log.info("Expected quorum_1 at:" + str(self.nodes[0].getblockcount())) + # time.sleep(2) + self.log.info("Expected quorum_1 hash:" + str(q_1)) + # time.sleep(2) + self.log.info("quorumIndex 1: Waiting for phase 1 (init)") + self.wait_for_quorum_phase(q_1, 1, expected_members, None, 0, mninfos_online, llmq_type_name) + self.log.info("quorumIndex 1: Waiting for quorum connections (init)") + self.wait_for_quorum_connections(q_1, expected_connections, nodes, llmq_type_name, wait_proc=lambda: self.bump_mocktime(1, nodes=nodes)) + + self.move_blocks(nodes, 1) + + self.log.info("quorumIndex 0: Waiting for phase 2 (contribute)") + self.wait_for_quorum_phase(q_0, 2, expected_members, "receivedContributions", expected_contributions, mninfos_online, llmq_type_name) + + self.move_blocks(nodes, 1) + + self.log.info("quorumIndex 1: Waiting for phase 2 (contribute)") + self.wait_for_quorum_phase(q_1, 2, expected_members, "receivedContributions", expected_contributions, mninfos_online, llmq_type_name) + + self.move_blocks(nodes, 1) + + self.log.info("quorumIndex 0: Waiting for phase 3 (complain)") + self.wait_for_quorum_phase(q_0, 3, expected_members, "receivedComplaints", expected_complaints, mninfos_online, llmq_type_name) + + self.move_blocks(nodes, 1) + + self.log.info("quorumIndex 1: Waiting for phase 3 (complain)") + self.wait_for_quorum_phase(q_1, 3, expected_members, "receivedComplaints", expected_complaints, mninfos_online, llmq_type_name) + + self.move_blocks(nodes, 1) + + self.log.info("quorumIndex 0: Waiting for phase 4 (justify)") + self.wait_for_quorum_phase(q_0, 4, expected_members, "receivedJustifications", expected_justifications, mninfos_online, llmq_type_name) + + self.move_blocks(nodes, 1) + + self.log.info("quorumIndex 1: Waiting for phase 4 (justify)") + self.wait_for_quorum_phase(q_1, 4, expected_members, "receivedJustifications", expected_justifications, mninfos_online, llmq_type_name) + + self.move_blocks(nodes, 1) + + self.log.info("quorumIndex 0: Waiting for phase 5 (commit)") + self.wait_for_quorum_phase(q_0, 5, expected_members, "receivedPrematureCommitments", expected_commitments, mninfos_online, llmq_type_name) + + self.move_blocks(nodes, 1) + + self.log.info("quorumIndex 1: Waiting for phase 5 (commit)") + self.wait_for_quorum_phase(q_1, 5, expected_members, "receivedPrematureCommitments", expected_commitments, mninfos_online, llmq_type_name) + + self.move_blocks(nodes, 1) + + self.log.info("quorumIndex 0: Waiting for phase 6 (finalization)") + self.wait_for_quorum_phase(q_0, 6, expected_members, None, 0, mninfos_online, llmq_type_name) + + self.move_blocks(nodes, 1) + + self.log.info("quorumIndex 1: Waiting for phase 6 (finalization)") + self.wait_for_quorum_phase(q_1, 6, expected_members, None, 0, mninfos_online, llmq_type_name) + time.sleep(6) + self.log.info("Mining final commitments") + self.bump_mocktime(1, nodes=nodes) + self.nodes[0].getblocktemplate() # this calls CreateNewBlock + self.nodes[0].generate(1) + sync_blocks(nodes) + + time.sleep(6) + self.log.info("Waiting for quorum(s) to appear in the list") + self.wait_for_quorums_list(q_0, q_1, nodes, llmq_type_name) + + quorum_info_0 = self.nodes[0].quorum("info", llmq_type, q_0) + quorum_info_1 = self.nodes[0].quorum("info", llmq_type, q_1) + # Mine 8 (SIGN_HEIGHT_OFFSET) more blocks to make sure that the new quorum gets eligible for signing sessions + self.nodes[0].generate(8) + + sync_blocks(nodes) + self.log.info("New quorum: height=%d, quorumHash=%s, quorumIndex=%d, minedBlock=%s" % (quorum_info_0["height"], q_0, quorum_info_0["quorumIndex"], quorum_info_0["minedBlock"])) + self.log.info("New quorum: height=%d, quorumHash=%s, quorumIndex=%d, minedBlock=%s" % (quorum_info_1["height"], q_1, quorum_info_1["quorumIndex"], quorum_info_1["minedBlock"])) + + self.log.info("quorum_info_0:"+str(quorum_info_0)) + self.log.info("quorum_info_1:"+str(quorum_info_1)) + + best_block_hash = self.nodes[0].getbestblockhash() + block_height = self.nodes[0].getblockcount() + quorum_rotation_info = self.nodes[0].quorum("rotationinfo", best_block_hash, 0, False) + self.log.info("h("+str(block_height)+"):"+str(quorum_rotation_info)) + + return (quorum_info_0, quorum_info_1) + def get_recovered_sig(self, rec_sig_id, rec_sig_msg_hash, llmq_type=100, node=None): # Note: recsigs aren't relayed to regular nodes by default, # make sure to pick a mn as a node to query for recsigs. diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index f1206b3e588a..5e5dd0d703f6 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -259,7 +259,7 @@ def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), sleep= ############################################ # The maximum number of nodes a single test can spawn -MAX_NODES = 15 +MAX_NODES = 20 # Don't assign rpc or p2p ports lower than this PORT_MIN = int(os.getenv('TEST_RUNNER_PORT_MIN', default=11000)) # The number of ports to "reserve" for p2p and rpc, each diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 58ad799b2c1c..39efbb6a0485 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -101,9 +101,11 @@ 'feature_llmq_signing.py', # NOTE: needs dash_hash to pass 'feature_llmq_signing.py --spork21', # NOTE: needs dash_hash to pass 'feature_llmq_chainlocks.py', # NOTE: needs dash_hash to pass + 'feature_llmq_rotation.py', # NOTE: needs dash_hash to pass 'feature_llmq_connections.py', # NOTE: needs dash_hash to pass 'feature_llmq_simplepose.py', # NOTE: needs dash_hash to pass 'feature_llmq_is_cl_conflicts.py', # NOTE: needs dash_hash to pass + 'feature_llmq_is_migration.py', # NOTE: needs dash_hash to pass 'feature_llmq_is_retroactive.py', # NOTE: needs dash_hash to pass 'feature_llmq_dkgerrors.py', # NOTE: needs dash_hash to pass 'feature_dip4_coinbasemerkleroots.py', # NOTE: needs dash_hash to pass diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 22e2a1a6d079..e201cab008f0 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -86,6 +86,13 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "policy/policy -> policy/settings -> policy/policy" "evo/specialtxman -> validation -> evo/specialtxman" "bloom -> llmq/commitment -> llmq/utils -> net -> bloom" + + "evo/simplifiedmns -> llmq/commitment -> llmq/utils -> llmq/snapshot -> evo/simplifiedmns" + "llmq/blockprocessor -> net_processing -> llmq/snapshot -> llmq/blockprocessor" + "llmq/commitment -> llmq/utils -> llmq/snapshot -> llmq/commitment" + "llmq/dkgsession -> llmq/dkgsessionmgr -> llmq/quorums -> llmq/dkgsession" + "llmq/dkgsessionmgr -> llmq/quorums -> llmq/dkgsessionmgr" + "llmq/quorums -> llmq/utils -> llmq/snapshot -> llmq/quorums" ) EXIT_CODE=0 diff --git a/test/lint/lint-cppcheck-dash.sh b/test/lint/lint-cppcheck-dash.sh index 9389a28b3d0a..45996efdb4d3 100755 --- a/test/lint/lint-cppcheck-dash.sh +++ b/test/lint/lint-cppcheck-dash.sh @@ -34,6 +34,8 @@ IGNORED_WARNINGS=( "src/test/dip0020opcodes_tests.cpp:.* warning: There is an unknown macro here somewhere. Configuration is required. If BOOST_FIXTURE_TEST_SUITE is a macro then please configure it." "src/ctpl_stl.h:.*22: warning: Dereferencing '_f' after it is deallocated / released" + "src/llmq/snapshot.cpp:.*:17: warning: Consider using std::copy algorithm instead of a raw loop." + "src/llmq/snapshot.cpp:.*:18: warning: Consider using std::copy algorithm instead of a raw loop." # General catchall, for some reason any value named 'hash' is viewed as never used. "Variable 'hash' is assigned a value that is never used."