Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 10 commits into from
May 17, 2023
56 changes: 45 additions & 11 deletions src/llmq/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{

Expand All @@ -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);

Expand All @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/llmq/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
54 changes: 49 additions & 5 deletions test/functional/feature_llmq_rotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,58 @@ def run_test(self):
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))
Expand All @@ -103,9 +141,12 @@ def run_test(self):
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))
Expand All @@ -122,7 +163,7 @@ def run_test(self):
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)

Expand All @@ -137,6 +178,9 @@ def run_test(self):
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)

Expand Down