Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 170 additions & 17 deletions src/llmq/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ arith_uint256 calculateQuorumScore(const CDeterministicMNCPtr& dmn, const uint25
return UintToArith256(h);
}

uint256 GetHashModifierFromWorkBlock(const Consensus::LLMQParams& llmqParams, const CBlockIndex* pWorkBlockIndex)
{
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()));
}

uint256 GetHashModifier(const Consensus::LLMQParams& llmqParams, const Consensus::Params& consensus_params,
gsl::not_null<const CBlockIndex*> pCycleQuorumBaseBlockIndex)
{
Expand All @@ -91,14 +103,7 @@ uint256 GetHashModifier(const Consensus::LLMQParams& llmqParams, const Consensus

if (DeploymentActiveAfter(pWorkBlockIndex, consensus_params, Consensus::DEPLOYMENT_V20)) {
// 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()));
return GetHashModifierFromWorkBlock(llmqParams, pWorkBlockIndex);
}

// v20 isn't active yet: calculate modifier using the usual way
Expand Down Expand Up @@ -337,9 +342,13 @@ void BuildQuorumSnapshot(const Consensus::LLMQParams& llmqParams, const Consensu
std::vector<QuorumMembers> BuildNewQuorumQuarterMembers(const Consensus::LLMQParams& llmqParams,
const llmq::UtilParameters& util_params,
const CDeterministicMNList& allMns,
const PreviousQuorumQuarters& previousQuarters)
const PreviousQuorumQuarters& previousQuarters,
const uint256& modifier,
gsl::not_null<const CBlockIndex*> pDeploymentIndex,
int cycleBaseHeight,
bool storeSnapshot)
{
if (!llmqParams.useRotation || util_params.m_base_index->nHeight % llmqParams.dkgInterval != 0) {
if (!llmqParams.useRotation || cycleBaseHeight % llmqParams.dkgInterval != 0) {
ASSERT_IF_DEBUG(false);
return {};
}
Expand All @@ -349,7 +358,6 @@ std::vector<QuorumMembers> BuildNewQuorumQuarterMembers(const Consensus::LLMQPar

size_t quorumSize = static_cast<size_t>(llmqParams.size);
auto quarterSize{quorumSize / 4};
const auto modifier = GetHashModifier(llmqParams, util_params.m_chainman.GetConsensus(), util_params.m_base_index);

if (allMns.GetCounts().enabled() < quarterSize) {
return quarterQuorumMembers;
Expand All @@ -358,7 +366,7 @@ std::vector<QuorumMembers> BuildNewQuorumQuarterMembers(const Consensus::LLMQPar
auto MnsUsedAtH = CDeterministicMNList();
std::vector<CDeterministicMNList> MnsUsedAtHIndexed{nQuorums};

bool skipRemovedMNs = DeploymentActiveAfter(util_params.m_base_index, util_params.m_chainman.GetConsensus(),
bool skipRemovedMNs = DeploymentActiveAfter(pDeploymentIndex.get(), util_params.m_chainman.GetConsensus(),
Consensus::DEPLOYMENT_V19) ||
(util_params.m_chainman.GetParams().NetworkIDString() == CBaseChainParams::TESTNET);

Expand Down Expand Up @@ -399,7 +407,7 @@ std::vector<QuorumMembers> BuildNewQuorumQuarterMembers(const Consensus::LLMQPar
}

if (LogAcceptDebug(BCLog::LLMQ)) {
LogPrint(BCLog::LLMQ, "%s h[%d] sortedCombinedMns[%s]\n", __func__, util_params.m_base_index->nHeight,
LogPrint(BCLog::LLMQ, "%s h[%d] sortedCombinedMns[%s]\n", __func__, cycleBaseHeight,
ToString(sortedCombinedMnsList));
}

Expand Down Expand Up @@ -445,14 +453,31 @@ std::vector<QuorumMembers> BuildNewQuorumQuarterMembers(const Consensus::LLMQPar
}
}

llmq::CQuorumSnapshot quorumSnapshot{};
BuildQuorumSnapshot(llmqParams, util_params.m_chainman.GetConsensus(), allMns, MnsUsedAtH, sortedCombinedMnsList,
quorumSnapshot, skipList, util_params.m_base_index);
util_params.m_qsnapman.StoreSnapshotForBlock(llmqParams.type, util_params.m_base_index, quorumSnapshot);
if (storeSnapshot) {
llmq::CQuorumSnapshot quorumSnapshot{};
BuildQuorumSnapshot(llmqParams, util_params.m_chainman.GetConsensus(), allMns, MnsUsedAtH, sortedCombinedMnsList,
quorumSnapshot, skipList, util_params.m_base_index);
util_params.m_qsnapman.StoreSnapshotForBlock(llmqParams.type, util_params.m_base_index, quorumSnapshot);
}

return quarterQuorumMembers;
}

std::vector<QuorumMembers> BuildNewQuorumQuarterMembers(const Consensus::LLMQParams& llmqParams,
const llmq::UtilParameters& util_params,
const CDeterministicMNList& allMns,
const PreviousQuorumQuarters& previousQuarters)
{
if (!llmqParams.useRotation || util_params.m_base_index->nHeight % llmqParams.dkgInterval != 0) {
ASSERT_IF_DEBUG(false);
return {};
}
const auto modifier = GetHashModifier(llmqParams, util_params.m_chainman.GetConsensus(), util_params.m_base_index);
return BuildNewQuorumQuarterMembers(llmqParams, util_params, allMns, previousQuarters, modifier,
util_params.m_base_index, util_params.m_base_index->nHeight,
/*storeSnapshot=*/true);
}

std::vector<QuorumMembers> ComputeQuorumMembersByQuarterRotation(const Consensus::LLMQParams& llmqParams,
const llmq::UtilParameters& util_params)
{
Expand Down Expand Up @@ -564,6 +589,134 @@ void BlsCheck::swap(BlsCheck& obj)
std::swap(m_id_string, obj.m_id_string);
}

std::optional<std::vector<CDeterministicMNCPtr>> ComputeNonRotatedQuorumMembersFromWorkBlock(
Consensus::LLMQType llmqType, const CChainParams& chainparams, const CDeterministicMNList& mn_list,
gsl::not_null<const CBlockIndex*> pWorkBlockIndex, int quorumHeight,
gsl::not_null<const CBlockIndex*> pDeploymentTipIndex)
{
const auto& llmq_params_opt = chainparams.GetLLMQ(llmqType);
if (!llmq_params_opt.has_value()) {
ASSERT_IF_DEBUG(false);
return std::nullopt;
}
const auto& llmq_params = llmq_params_opt.value();
if (llmq_params.useRotation || quorumHeight % llmq_params.dkgInterval != 0) {
ASSERT_IF_DEBUG(false);
return std::nullopt;
}

if (!DeploymentActiveAfter(pWorkBlockIndex.get(), chainparams.GetConsensus(), Consensus::DEPLOYMENT_V20)) {
// pre-v20 modifier calculation needs the future quorum base block hash, which isn't known yet.
return std::nullopt;
}

// Canonical member selection gates EvoOnly on the future quorum base block. Buried
// deployments are monotonic and the deployment-tip context is at or before the future
// quorum base block on our chain, so V19 active at the tip implies V19 active there.
// If it is not active at the tip, we cannot know whether it will activate between the
// tip and the future quorum base block, so refuse to predict.
const bool is_platform_llmq = (llmq_params.type == chainparams.GetConsensus().llmqTypePlatform);
const bool v19_active_at_tip = DeploymentActiveAfter(pDeploymentTipIndex.get(), chainparams.GetConsensus(),
Consensus::DEPLOYMENT_V19);
if (is_platform_llmq && !v19_active_at_tip) {
return std::nullopt;
}
const bool EvoOnly = is_platform_llmq && v19_active_at_tip;

const auto modifier = GetHashModifierFromWorkBlock(llmq_params, pWorkBlockIndex.get());

return CalculateQuorum(mn_list, modifier, llmq_params.size, EvoOnly);
Comment thread
thepastaclaw marked this conversation as resolved.
}

std::optional<std::vector<CDeterministicMNCPtr>> ComputeRotatedQuorumMembersFromWorkBlock(
Consensus::LLMQType llmqType, const UtilParameters& util_params, gsl::not_null<const CBlockIndex*> pWorkBlockIndex,
int quorumHeight, gsl::not_null<const CBlockIndex*> pDeploymentTipIndex)
{
const auto& llmq_params_opt = util_params.m_chainman.GetParams().GetLLMQ(llmqType);
if (!llmq_params_opt.has_value()) {
ASSERT_IF_DEBUG(false);
return std::nullopt;
}
const auto& llmq_params = llmq_params_opt.value();
if (!llmq_params.useRotation) {
ASSERT_IF_DEBUG(false);
return std::nullopt;
}

const int quorumIndex{quorumHeight % llmq_params.dkgInterval};
if (quorumIndex < 0 || quorumIndex >= llmq_params.signingActiveQuorumCount) {
ASSERT_IF_DEBUG(false);
return std::nullopt;
}
const int cycleBaseHeight{quorumHeight - quorumIndex};
if (cycleBaseHeight % llmq_params.dkgInterval != 0 ||
pWorkBlockIndex->nHeight != cycleBaseHeight - llmq::WORK_DIFF_DEPTH) {
ASSERT_IF_DEBUG(false);
return std::nullopt;
}

if (!DeploymentActiveAfter(pWorkBlockIndex.get(), util_params.m_chainman.GetConsensus(), Consensus::DEPLOYMENT_V20)) {
// pre-v20 modifier calculation needs the future cycle base block context, which isn't known yet.
return std::nullopt;
}

// Canonical BuildNewQuorumQuarterMembers gates skipRemovedMNs on V19 at the future cycle
// base block. Deployments are monotonic and the deployment tip is at or before that base
// on our chain, so V19 active at the tip implies V19 active there. If it is not active at
// the tip, the state at the future cycle base is unknown, so refuse to predict.
const bool v19_active_at_tip = DeploymentActiveAfter(pDeploymentTipIndex.get(), util_params.m_chainman.GetConsensus(),
Consensus::DEPLOYMENT_V19);
if (!v19_active_at_tip && util_params.m_chainman.GetParams().NetworkIDString() != CBaseChainParams::TESTNET) {
return std::nullopt;
}

const auto nQuorums{static_cast<size_t>(llmq_params.signingActiveQuorumCount)};
PreviousQuorumQuarters previousQuarters(nQuorums);
auto prev_cycles{previousQuarters.GetCycles()};
for (size_t idx{0}; idx < prev_cycles.size(); idx++) {
const int previousCycleHeight{cycleBaseHeight - llmq_params.dkgInterval * static_cast<int>(idx + 1)};
if (previousCycleHeight < 0) {
return std::nullopt;
}
prev_cycles[idx]->m_cycle_index = pWorkBlockIndex->GetAncestor(previousCycleHeight);
if (prev_cycles[idx]->m_cycle_index == nullptr) {
return std::nullopt;
}
if (auto opt_snap = util_params.m_qsnapman.GetSnapshotForBlock(llmq_params.type, prev_cycles[idx]->m_cycle_index);
opt_snap.has_value()) {
prev_cycles[idx]->m_snap = opt_snap.value();
} else {
return std::nullopt;
}
prev_cycles[idx]->m_members = GetQuorumQuarterMembersBySnapshot(llmq_params, util_params.m_dmnman,
util_params.m_chainman.GetConsensus(),
prev_cycles[idx]->m_cycle_index,
prev_cycles[idx]->m_snap,
cycleBaseHeight);
}

CDeterministicMNList allMns = util_params.m_dmnman.GetListForBlock(pWorkBlockIndex);
const auto modifier = GetHashModifierFromWorkBlock(llmq_params, pWorkBlockIndex.get());
auto newQuarterMembers = BuildNewQuorumQuarterMembers(llmq_params, util_params, allMns, previousQuarters, modifier,
pDeploymentTipIndex, cycleBaseHeight,
/*storeSnapshot=*/false);
Comment thread
thepastaclaw marked this conversation as resolved.
if (newQuarterMembers.size() != nQuorums) {
return std::nullopt;
}

QuorumMembers quorumMembers;
for (auto* prev_cycle : prev_cycles | std::views::reverse) {
quorumMembers.insert(quorumMembers.end(),
prev_cycle->m_members[quorumIndex].begin(),
prev_cycle->m_members[quorumIndex].end());
}
quorumMembers.insert(quorumMembers.end(),
newQuarterMembers[quorumIndex].begin(),
newQuarterMembers[quorumIndex].end());

return quorumMembers;
}

QuorumMembers GetAllQuorumMembers(Consensus::LLMQType llmqType, const UtilParameters& util_params, bool reset_cache)
{
static RecursiveMutex cs_members;
Expand Down
20 changes: 20 additions & 0 deletions src/llmq/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@

#include <gsl/pointers.h>

#include <optional>
#include <unordered_set>
#include <vector>

class CBlockIndex;
class CChainParams;
class CDeterministicMNList;
class CDeterministicMNManager;
class ChainstateManager;
class CSporkManager;
Expand Down Expand Up @@ -67,6 +70,23 @@ std::unordered_set<size_t> CalcDeterministicWatchConnections(Consensus::LLMQType
std::vector<CDeterministicMNCPtr> GetAllQuorumMembers(Consensus::LLMQType llmqType, const UtilParameters& util_params,
bool reset_cache = false);

// Predicts the members of a future non-rotated v20 quorum from its already-known work block,
// before the quorum's own base block exists on chain. Returns std::nullopt when prediction is
// not possible (e.g. V20 not yet active at the work block, or V19 activation state at the
// future quorum base block cannot be confirmed from pDeploymentTipIndex).
std::optional<std::vector<CDeterministicMNCPtr>> ComputeNonRotatedQuorumMembersFromWorkBlock(
Consensus::LLMQType llmqType, const CChainParams& chainparams, const CDeterministicMNList& mn_list,
gsl::not_null<const CBlockIndex*> pWorkBlockIndex, int quorumHeight,
gsl::not_null<const CBlockIndex*> pDeploymentTipIndex);

// Predicts the members of a future rotated v20 quorum from its already-known cycle work block,
// before the next cycle base block exists on chain. Returns std::nullopt when the historical
// snapshots needed for quarter rotation are not available, or when the deployment state at the
// future cycle base cannot be confirmed from pDeploymentTipIndex.
std::optional<std::vector<CDeterministicMNCPtr>> ComputeRotatedQuorumMembersFromWorkBlock(
Consensus::LLMQType llmqType, const UtilParameters& util_params, gsl::not_null<const CBlockIndex*> pWorkBlockIndex,
int quorumHeight, gsl::not_null<const CBlockIndex*> pDeploymentTipIndex);

Uint256HashSet GetQuorumConnections(const Consensus::LLMQParams& llmqParams, const CSporkManager& sporkman,
const UtilParameters& util_params, const uint256& forMember, bool onlyOutbound);

Expand Down
Loading
Loading