feat!: calculate quorum members using v20 cbtx clsig (#5366)

## Issue being fixed or feature implemented

Implementation of Randomness Beacon Part 2.
This PR is the next step of #5262.

Starting from v20 activation fork, members for quorums are sorted using
(if available) the best CL signature found in Coinbase.
If no CL signature is present yet, then the usual way is used (By using
Blockhash instead)

## What was done?

## How Has This Been Tested?
Test `feature_llmq_rotation.py` was updated to cover both rotated and
non-rotated quorums.
2 quorums are mined first to ensure Chainlock are working earlier.
Then dip_24 activation is replaced by v20 activation.

The only direct way to test this change is to make sure that all
expected quorums after v20 activation are properly formed.

Note: A `wait_for_chainlocked_block_all_nodes` is called between every
rotation cycle to ensure that Coinbase will use a different Chainlock
signature.

## Breaking Changes
Yes, quorum members will be calculated differently.

## Checklist:
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have added or updated relevant unit/integration/functional/e2e
tests
- [ ] I have made corresponding changes to the documentation
- [x] I have assigned this pull request to a milestone _(for repository
code-owners and collaborators only)_

---------

Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
This commit is contained in:
Odysseas Gabrielides 2023-05-17 20:27:15 +03:00 committed by GitHub
parent 04a31c76e0
commit 9eee9ee680
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 95 additions and 17 deletions

View File

@ -26,6 +26,11 @@
static constexpr int TESTNET_LLMQ_25_67_ACTIVATION_HEIGHT = 847000;
/**
* Forward declarations
*/
std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex);
namespace llmq
{
@ -43,7 +48,7 @@ static std::vector<std::vector<CDeterministicMNCPtr>> BuildNewQuorumQuarterMembe
static PreviousQuorumQuarters GetPreviousQuorumQuarterMembers(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pBlockHMinusCIndex, const CBlockIndex* pBlockHMinus2CIndex, const CBlockIndex* pBlockHMinus3CIndex, int nHeight);
static std::vector<std::vector<CDeterministicMNCPtr>> GetQuorumQuarterMembersBySnapshot(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pQuorumBaseBlockIndex, const llmq::CQuorumSnapshot& snapshot, int nHeights);
static std::pair<CDeterministicMNList, CDeterministicMNList> GetMNUsageBySnapshot(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex, const llmq::CQuorumSnapshot& snapshot, int nHeight);
static std::pair<CDeterministicMNList, CDeterministicMNList> 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<CDeterministicMNCPtr>& sortedCombinedMns, CQuorumSnapshot& quorumSnapshot, int nHeight, std::vector<int>& 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<CDeterministicMNCPtr> GetAllQuorumMembers(Consensus::LLMQType llmqType, const CBlockIndex* pQuorumBaseBlockIndex, bool reset_cache)
{
static CCriticalSection cs_members;
@ -135,11 +163,19 @@ std::vector<CDeterministicMNCPtr> GetAllQuorumMembers(Consensus::LLMQType llmqTy
std::vector<CDeterministicMNCPtr> 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<std::vector<CDeterministicMNCPtr>> BuildNewQuorumQuarterMembers(cons
size_t quorumSize = static_cast<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()));
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<int>& 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<std::vector<CDeterministicMNCPtr>> GetQuorumQuarterMembersBySnapshot
{
std::vector<CDeterministicMNCPtr> 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<std::vector<CDeterministicMNCPtr>> GetQuorumQuarterMembersBySnapshot
}
}
std::pair<CDeterministicMNList, CDeterministicMNList> GetMNUsageBySnapshot(Consensus::LLMQType llmqType,
std::pair<CDeterministicMNList, CDeterministicMNList> GetMNUsageBySnapshot(const Consensus::LLMQParams& llmqParams,
const CBlockIndex* pQuorumBaseBlockIndex,
const llmq::CQuorumSnapshot& snapshot,
int nHeight)
@ -540,7 +574,7 @@ std::pair<CDeterministicMNList, CDeterministicMNList> 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);

View File

@ -58,7 +58,7 @@ namespace utils
std::vector<CDeterministicMNCPtr> 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<bool>& validMembers, const CBLSPublicKey& pubKey, const uint256& vvecHash);
uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash);

View File

@ -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)