diff --git a/src/llmq/utils.cpp b/src/llmq/utils.cpp index ea83db5b17..8f70433cb5 100644 --- a/src/llmq/utils.cpp +++ b/src/llmq/utils.cpp @@ -26,6 +26,11 @@ static constexpr int TESTNET_LLMQ_25_67_ACTIVATION_HEIGHT = 847000; +/** + * Forward declarations + */ +std::optional> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex); + namespace llmq { @@ -43,7 +48,7 @@ static std::vector> BuildNewQuorumQuarterMembe 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 std::pair GetMNUsageBySnapshot(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex, const llmq::CQuorumSnapshot& snapshot, int nHeight); static void BuildQuorumSnapshot(const Consensus::LLMQParams& llmqParams, const CDeterministicMNList& allMns, const CDeterministicMNList& mnUsedAtH, std::vector& sortedCombinedMns, CQuorumSnapshot& quorumSnapshot, int nHeight, std::vector& skipList, const CBlockIndex* pQuorumBaseBlockIndex); @@ -58,6 +63,29 @@ void PreComputeQuorumMembers(const CBlockIndex* pQuorumBaseBlockIndex, bool rese } } +uint256 GetHashModifier(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex) +{ + const CBlockIndex* pWorkBlockIndex = pQuorumBaseBlockIndex->GetAncestor(pQuorumBaseBlockIndex->nHeight - 8); + + if (IsV20Active(pWorkBlockIndex)) { + // v20 is active: calculate modifier using the new way. + auto cbcl = GetNonNullCoinbaseChainlock(pWorkBlockIndex); + if (cbcl.has_value()) { + // We have a non-null CL signature: calculate modifier using this CL signature + auto& [bestCLSignature, bestCLHeightDiff] = cbcl.value(); + return ::SerializeHash(std::make_tuple(llmqParams.type, pWorkBlockIndex->nHeight, bestCLSignature)); + } + // No non-null CL signature found in coinbase: calculate modifier using block hash only + return ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash())); + } + + // v20 isn't active yet: calculate modifier using the usual way + if (llmqParams.useRotation) { + return ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash())); + } + return ::SerializeHash(std::make_pair(llmqParams.type, pQuorumBaseBlockIndex->GetBlockHash())); +} + std::vector GetAllQuorumMembers(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex, bool reset_cache) { static CCriticalSection cs_members; @@ -135,11 +163,19 @@ std::vector GetAllQuorumMembers(Consensus::LLMQType llmqTy std::vector ComputeQuorumMembers(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex) { - auto allMns = deterministicMNManager->GetListForBlock(pQuorumBaseBlockIndex); - auto modifier = ::SerializeHash(std::make_pair(llmqType, pQuorumBaseBlockIndex->GetBlockHash())); bool HPMNOnly = (Params().GetConsensus().llmqTypePlatform == llmqType) && IsV19Active(pQuorumBaseBlockIndex); const auto& llmq_params_opt = GetLLMQParams(llmqType); assert(llmq_params_opt.has_value()); + if (llmq_params_opt->useRotation) { + ASSERT_IF_DEBUG(false); + return {}; + } + + const CBlockIndex* pWorkBlockIndex = IsV20Active(pQuorumBaseBlockIndex) ? + pQuorumBaseBlockIndex->GetAncestor(pQuorumBaseBlockIndex->nHeight - 8) : + pQuorumBaseBlockIndex; + const auto modifier = GetHashModifier(llmq_params_opt.value(), pQuorumBaseBlockIndex); + auto allMns = deterministicMNManager->GetListForBlock(pWorkBlockIndex); return allMns.CalculateQuorum(llmq_params_opt->size, modifier, HPMNOnly); } @@ -264,7 +300,7 @@ std::vector> BuildNewQuorumQuarterMembers(cons size_t quorumSize = static_cast(llmqParams.size); auto quarterSize{quorumSize / 4}; const CBlockIndex* pWorkBlockIndex = pQuorumBaseBlockIndex->GetAncestor(pQuorumBaseBlockIndex->nHeight - 8); - auto modifier = ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash())); + const auto modifier = GetHashModifier(llmqParams, pQuorumBaseBlockIndex); auto allMns = deterministicMNManager->GetListForBlock(pWorkBlockIndex); @@ -414,8 +450,7 @@ void BuildQuorumSnapshot(const Consensus::LLMQParams& llmqParams, const CDetermi CQuorumSnapshot& quorumSnapshot, int nHeight, std::vector& skipList, const CBlockIndex* pQuorumBaseBlockIndex) { quorumSnapshot.activeQuorumMembers.resize(allMns.GetAllMNsCount()); - const CBlockIndex* pWorkBlockIndex = pQuorumBaseBlockIndex->GetAncestor(pQuorumBaseBlockIndex->nHeight - 8); - auto modifier = ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash())); + const auto modifier = GetHashModifier(llmqParams, pQuorumBaseBlockIndex); auto sortedAllMns = allMns.CalculateQuorum(allMns.GetAllMNsCount(), modifier); LogPrint(BCLog::LLMQ, "BuildQuorumSnapshot h[%d] numMns[%d]\n", pQuorumBaseBlockIndex->nHeight, allMns.GetAllMNsCount()); @@ -447,9 +482,8 @@ std::vector> GetQuorumQuarterMembersBySnapshot { std::vector sortedCombinedMns; { - const CBlockIndex* pWorkBlockIndex = pQuorumBaseBlockIndex->GetAncestor(pQuorumBaseBlockIndex->nHeight - 8); - const auto modifier = ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash())); - const auto [MnsUsedAtH, MnsNotUsedAtH] = GetMNUsageBySnapshot(llmqParams.type, pQuorumBaseBlockIndex, snapshot, nHeight); + const auto modifier = GetHashModifier(llmqParams, pQuorumBaseBlockIndex); + const auto [MnsUsedAtH, MnsNotUsedAtH] = GetMNUsageBySnapshot(llmqParams, pQuorumBaseBlockIndex, snapshot, nHeight); // the list begins with all the unused MNs auto sortedMnsNotUsedAtH = MnsNotUsedAtH.CalculateQuorum(MnsNotUsedAtH.GetAllMNsCount(), modifier); sortedCombinedMns = std::move(sortedMnsNotUsedAtH); @@ -531,7 +565,7 @@ std::vector> GetQuorumQuarterMembersBySnapshot } } -std::pair GetMNUsageBySnapshot(Consensus::LLMQType llmqType, +std::pair GetMNUsageBySnapshot(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex, const llmq::CQuorumSnapshot& snapshot, int nHeight) @@ -540,7 +574,7 @@ std::pair GetMNUsageBySnapshot(Conse CDeterministicMNList nonUsedMNs; const CBlockIndex* pWorkBlockIndex = pQuorumBaseBlockIndex->GetAncestor(pQuorumBaseBlockIndex->nHeight - 8); - auto modifier = ::SerializeHash(std::make_pair(llmqType, pWorkBlockIndex->GetBlockHash())); + const auto modifier = GetHashModifier(llmqParams, pQuorumBaseBlockIndex); auto allMns = deterministicMNManager->GetListForBlock(pWorkBlockIndex); auto sortedAllMns = allMns.CalculateQuorum(allMns.GetAllMNsCount(), modifier); diff --git a/src/llmq/utils.h b/src/llmq/utils.h index 46ee667a44..a5d15e8e2f 100644 --- a/src/llmq/utils.h +++ b/src/llmq/utils.h @@ -58,7 +58,7 @@ namespace utils std::vector GetAllQuorumMembers(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex, bool reset_cache = false); void PreComputeQuorumMembers(const CBlockIndex* pQuorumBaseBlockIndex, bool reset_cache = false); - +uint256 GetHashModifier(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex); uint256 BuildCommitmentHash(Consensus::LLMQType llmqType, const uint256& blockHash, const std::vector& validMembers, const CBLSPublicKey& pubKey, const uint256& vvecHash); uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash); diff --git a/test/functional/feature_llmq_rotation.py b/test/functional/feature_llmq_rotation.py index 3ddbf2c238..887f264996 100755 --- a/test/functional/feature_llmq_rotation.py +++ b/test/functional/feature_llmq_rotation.py @@ -73,20 +73,58 @@ class LLMQQuorumRotationTest(DashTestFramework): self.nodes[0].sporkupdate("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())) + b_h_0 = self.nodes[0].getbestblockhash() + + #Mine 2 quorums so that Chainlocks can be available: Need them to include CL in CbTx as soon as v20 activates + self.log.info("Mining 2 quorums") + h_0 = self.mine_quorum() + h_100_0 = QuorumId(100, int(h_0, 16)) + h_106_0 = QuorumId(106, int(h_0, 16)) + h_104_0 = QuorumId(104, int(h_0, 16)) + h_1 = self.mine_quorum() + h_100_1 = QuorumId(100, int(h_1, 16)) + h_106_1 = QuorumId(106, int(h_1, 16)) + h_104_1 = QuorumId(104, int(h_1, 16)) + + self.log.info("Mine single block, wait for chainlock") + self.nodes[0].generate(1) + self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) + + b_h_1 = self.nodes[0].getbestblockhash() + + expectedDeleted = [] + expectedNew = [h_100_0, h_106_0, h_104_0, h_100_1, h_106_1, h_104_1] + quorumList = self.test_getmnlistdiff_quorums(b_h_0, b_h_1, {}, expectedDeleted, expectedNew) + + self.activate_v20(expected_activation_height=1440) + self.log.info("Activated v20 at height:" + str(self.nodes[0].getblockcount())) + + # v20 is active for the next block, not for the tip + self.nodes[0].generate(1) + + self.log.info("Wait for chainlock") + self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) #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.log.info("Wait for chainlock") + self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) self.move_to_next_cycle() self.log.info("Cycle H+C height:" + str(self.nodes[0].getblockcount())) + self.log.info("Wait for chainlock") + self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) self.move_to_next_cycle() self.log.info("Cycle H+2C height:" + str(self.nodes[0].getblockcount())) + self.log.info("Wait for chainlock") + self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) b_0 = self.nodes[0].getbestblockhash() + self.log.info("Wait for chainlock") + self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) + (quorum_info_0_0, quorum_info_0_1) = self.mine_cycle_quorum(llmq_type_name=llmq_type_name, llmq_type=llmq_type) assert(self.test_quorum_listextended(quorum_info_0_0, llmq_type_name)) assert(self.test_quorum_listextended(quorum_info_0_1, llmq_type_name)) @@ -103,9 +141,12 @@ class LLMQQuorumRotationTest(DashTestFramework): q_103_0_1 = QuorumId(103, int(quorum_info_0_1["quorumHash"], 16)) b_1 = self.nodes[0].getbestblockhash() - expectedDeleted = [] + expectedDeleted = [h_100_0, h_104_0] expectedNew = [q_100_0, q_102_0, q_104_0, q_103_0_0, q_103_0_1] - quorumList = self.test_getmnlistdiff_quorums(b_0, b_1, {}, expectedDeleted, expectedNew) + quorumList = self.test_getmnlistdiff_quorums(b_0, b_1, quorumList, expectedDeleted, expectedNew) + + self.log.info("Wait for chainlock") + self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) (quorum_info_1_0, quorum_info_1_1) = self.mine_cycle_quorum(llmq_type_name=llmq_type_name, llmq_type=llmq_type) assert(self.test_quorum_listextended(quorum_info_1_0, llmq_type_name)) @@ -122,7 +163,7 @@ class LLMQQuorumRotationTest(DashTestFramework): q_103_1_1 = QuorumId(103, int(quorum_info_1_1["quorumHash"], 16)) b_2 = self.nodes[0].getbestblockhash() - expectedDeleted = [q_103_0_0, q_103_0_1] + expectedDeleted = [h_100_1, q_103_0_0, q_103_0_1] expectedNew = [q_100_1, q_102_1, q_103_1_0, q_103_1_1] quorumList = self.test_getmnlistdiff_quorums(b_1, b_2, quorumList, expectedDeleted, expectedNew) @@ -137,6 +178,9 @@ class LLMQQuorumRotationTest(DashTestFramework): 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) + self.log.info("Wait for chainlock") + self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) + 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)