Skip to content

Commit 2ab971f

Browse files
dash: refuse masternode duty on unvalidated snapshots
Disable DKG participation and quorum signing until snapshot background validation completes. Enforce the refusal at CreateSigShare, the actual share-production boundary, so direct RPC, async, and queued signing paths cannot bypass it. The quorum sign RPC now returns a clear JSON-RPC error for both submit modes, and masternode status exposes the disabled participation state. Add unit coverage for the shared production-gate predicate across snapshot activation.
1 parent 6246361 commit 2ab971f

8 files changed

Lines changed: 71 additions & 1 deletion

File tree

src/active/context.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <llmq/quorums.h>
2121
#include <llmq/quorumsman.h>
2222
#include <llmq/signing_shares.h>
23+
#include <logging.h>
2324
#include <masternode/sync.h>
2425
#include <util/check.h>
2526
#include <validation.h>
@@ -35,6 +36,7 @@ ActiveContext::ActiveContext(CBLSWorker& bls_worker, ChainstateManager& chainman
3536
const CBLSSecretKey& operator_sk, const util::DbWrapperParams& db_params, bool quorums_watch) :
3637
llmq::QuorumRole{qman},
3738
m_bls_worker{bls_worker},
39+
m_chainman{chainman},
3840
m_quorums_watch{quorums_watch},
3941
nodeman{std::make_unique<CActiveMasternodeManager>(connman, dmnman, operator_sk)},
4042
dkgdbgman{std::make_unique<llmq::CDKGDebugManager>(dmnman, qsnapman, chainman)},
@@ -94,6 +96,17 @@ void ActiveContext::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIn
9496
return;
9597

9698
nodeman->UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload);
99+
100+
if (m_chainman.IsSnapshotActiveAndUnvalidated()) {
101+
if (!m_snapshot_duty_blocked.exchange(true)) {
102+
LogPrintf("Masternode DKG participation and quorum signing are disabled until snapshot background validation completes\n");
103+
}
104+
return;
105+
}
106+
if (m_snapshot_duty_blocked.exchange(false)) {
107+
LogPrintf("Snapshot background validation completed; masternode DKG participation and quorum signing are enabled\n");
108+
}
109+
97110
ehf_sighandler->UpdatedBlockTip(pindexNew);
98111
gov_signer->UpdatedBlockTip(pindexNew);
99112
qdkgsman->UpdatedBlockTip(pindexNew, fInitialDownload);

src/active/context.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <gsl/pointers.h>
1313
#include <span.h>
1414

15+
#include <atomic>
1516
#include <memory>
1617

1718
class CActiveMasternodeManager;
@@ -49,7 +50,9 @@ struct DbWrapperParams;
4950
struct ActiveContext final : public llmq::QuorumRole, public CValidationInterface {
5051
private:
5152
CBLSWorker& m_bls_worker;
53+
ChainstateManager& m_chainman;
5254
const bool m_quorums_watch{false};
55+
std::atomic_bool m_snapshot_duty_blocked{false};
5356

5457
public:
5558
ActiveContext() = delete;

src/active/dkgsessionhandler.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <deploymentstatus.h>
1515
#include <logging.h>
1616
#include <util/time.h>
17+
#include <validation.h>
1718

1819
namespace llmq {
1920
ActiveDKGSessionHandler::ActiveDKGSessionHandler(
@@ -41,6 +42,8 @@ ActiveDKGSessionHandler::~ActiveDKGSessionHandler() = default;
4142

4243
void ActiveDKGSessionHandler::UpdatedBlockTip(const CBlockIndex* pindexNew)
4344
{
45+
if (m_chainman.IsSnapshotActiveAndUnvalidated()) return;
46+
4447
//AssertLockNotHeld(cs_main);
4548
//Indexed quorums (greater than 0) are enabled with Quorum Rotation
4649
if (quorumIndex > 0 && !IsQuorumRotationEnabled(params, pindexNew)) {
@@ -76,6 +79,10 @@ std::pair<QuorumPhase, uint256> ActiveDKGSessionHandler::GetPhaseAndQuorumHash()
7679

7780
bool ActiveDKGSessionHandler::InitNewQuorum(gsl::not_null<const CBlockIndex*> pQuorumBaseBlockIndex)
7881
{
82+
if (m_chainman.IsSnapshotActiveAndUnvalidated()) {
83+
LogPrint(BCLog::LLMQ_DKG, "%s -- refusing DKG participation while snapshot background validation is incomplete\n", __func__);
84+
return false;
85+
}
7986
if (!DeploymentDIP0003Enforced(pQuorumBaseBlockIndex->nHeight, Params().GetConsensus())) {
8087
return false;
8188
}
@@ -100,6 +107,10 @@ void ActiveDKGSessionHandler::WaitForNextPhase(std::optional<QuorumPhase> curPha
100107
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - starting, curPhase=%d, nextPhase=%d\n", __func__, params.name, quorumIndex, curPhase.has_value() ? std23::to_underlying(*curPhase) : -1, std23::to_underlying(nextPhase));
101108

102109
while (true) {
110+
if (m_chainman.IsSnapshotActiveAndUnvalidated()) {
111+
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - aborting because snapshot background validation is incomplete\n", __func__, params.name, quorumIndex);
112+
throw AbortPhaseException();
113+
}
103114
if (stopRequested) {
104115
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - aborting due to stop/shutdown requested\n", __func__, params.name, quorumIndex);
105116
throw AbortPhaseException();
@@ -139,6 +150,10 @@ void ActiveDKGSessionHandler::WaitForNewQuorum(const uint256& oldQuorumHash) con
139150
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d]- starting\n", __func__, params.name, quorumIndex);
140151

141152
while (true) {
153+
if (m_chainman.IsSnapshotActiveAndUnvalidated()) {
154+
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - aborting because snapshot background validation is incomplete\n", __func__, params.name, quorumIndex);
155+
throw AbortPhaseException();
156+
}
142157
if (stopRequested) {
143158
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - aborting due to stop/shutdown requested\n", __func__, params.name, quorumIndex);
144159
throw AbortPhaseException();
@@ -186,6 +201,10 @@ void ActiveDKGSessionHandler::SleepBeforePhase(QuorumPhase curPhase, const uint2
186201
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - starting sleep for %d ms, curPhase=%d\n", __func__, params.name, quorumIndex, sleepTime, std23::to_underlying(curPhase));
187202

188203
while (SteadyClock::now() < endTime) {
204+
if (m_chainman.IsSnapshotActiveAndUnvalidated()) {
205+
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - aborting because snapshot background validation is incomplete\n", __func__, params.name, quorumIndex);
206+
throw AbortPhaseException();
207+
}
189208
if (stopRequested) {
190209
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - aborting due to stop/shutdown requested\n", __func__, params.name, quorumIndex);
191210
throw AbortPhaseException();
@@ -220,6 +239,10 @@ void ActiveDKGSessionHandler::HandlePhase(QuorumPhase curPhase, QuorumPhase next
220239
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - starting, curPhase=%d, nextPhase=%d\n", __func__, params.name, quorumIndex, std23::to_underlying(curPhase), std23::to_underlying(nextPhase));
221240

222241
SleepBeforePhase(curPhase, expectedQuorumHash, randomSleepFactor, runWhileWaiting);
242+
if (m_chainman.IsSnapshotActiveAndUnvalidated()) {
243+
LogPrint(BCLog::LLMQ_DKG, "%s -- refusing DKG participation while snapshot background validation is incomplete\n", __func__);
244+
throw AbortPhaseException();
245+
}
223246
startPhaseFunc();
224247
WaitForNextPhase(curPhase, nextPhase, expectedQuorumHash, runWhileWaiting);
225248

src/llmq/signing_shares.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,11 @@ bool CSigSharesManager::AsyncSignIfMember(Consensus::LLMQType llmqType, CSigning
767767
{
768768
AssertLockNotHeld(cs_pendingSigns);
769769

770+
if (!IsQuorumSigningAllowed(m_chainman)) {
771+
LogPrint(BCLog::LLMQ, "%s -- refusing quorum signature while snapshot background validation is incomplete\n", __func__);
772+
return false;
773+
}
774+
770775
if (m_mn_activeman.GetProTxHash().IsNull()) return false;
771776

772777
auto quorum = [&]() {
@@ -1511,6 +1516,11 @@ void CSigSharesManager::AsyncSign(CQuorumCPtr quorum, const uint256& id, const u
15111516
pendingSigns.emplace_back(std::move(quorum), id, msgHash);
15121517
}
15131518

1519+
bool CSigSharesManager::IsQuorumSigningAllowed(const ChainstateManager& chainman)
1520+
{
1521+
return !chainman.IsSnapshotActiveAndUnvalidated();
1522+
}
1523+
15141524
std::optional<CSigShare> CSigSharesManager::CreateSigShareForSingleMember(const CQuorum& quorum, const uint256& id, const uint256& msgHash) const
15151525
{
15161526
cxxtimer::Timer t(true);
@@ -1550,6 +1560,13 @@ std::optional<CSigShare> CSigSharesManager::CreateSigShareForSingleMember(const
15501560

15511561
std::optional<CSigShare> CSigSharesManager::CreateSigShare(const CQuorum& quorum, const uint256& id, const uint256& msgHash) const
15521562
{
1563+
// This is the signature-production boundary. Keep the gate here so direct
1564+
// callers (including `quorum sign ... submit=false`) cannot bypass it.
1565+
if (!IsQuorumSigningAllowed(m_chainman)) {
1566+
LogPrint(BCLog::LLMQ, "%s -- refusing quorum signature while snapshot background validation is incomplete\n", __func__);
1567+
return std::nullopt;
1568+
}
1569+
15531570
auto activeMasterNodeProTxHash = m_mn_activeman.GetProTxHash();
15541571

15551572
if (!quorum.IsValidMember(activeMasterNodeProTxHash)) {

src/llmq/signing_shares.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ class CSigSharesManager : public llmq::CRecoveredSigsListener
498498

499499
void AsyncSign(CQuorumCPtr quorum, const uint256& id, const uint256& msgHash)
500500
EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingSigns, !cs);
501+
static bool IsQuorumSigningAllowed(const ChainstateManager& chainman);
501502
std::optional<CSigShare> CreateSigShare(const CQuorum& quorum, const uint256& id, const uint256& msgHash) const
502503
EXCLUSIVE_LOCKS_REQUIRED(!cs);
503504
void ForceReAnnouncement(const CQuorum& quorum, Consensus::LLMQType llmqType, const uint256& id,

src/rpc/masternode.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ static RPCHelpMan masternode_status()
190190
CDeterministicMNState::GetJsonHelp(/*key=*/"dmnState", /*optional=*/true),
191191
{RPCResult::Type::STR, "state", "Masternode state (human-readable string)"},
192192
{RPCResult::Type::STR, "status", "Masternode status (human-readable string, based on current state)"},
193+
{RPCResult::Type::BOOL, "quorumParticipation", "Whether DKG participation and quorum signing are enabled"},
193194
}
194195
},
195196
RPCExamples{""},
@@ -215,7 +216,11 @@ static RPCHelpMan masternode_status()
215216
mnObj.pushKV("dmnState", dmn->pdmnState->ToJson(dmn->nType));
216217
}
217218
mnObj.pushKV("state", mn_activeman.GetStateString());
218-
mnObj.pushKV("status", mn_activeman.GetStatus());
219+
const bool quorum_participation = !EnsureChainman(node).IsSnapshotActiveAndUnvalidated();
220+
mnObj.pushKV("status", quorum_participation ? mn_activeman.GetStatus() :
221+
strprintf("%s; DKG participation and quorum signing disabled until snapshot background validation completes",
222+
mn_activeman.GetStatus()));
223+
mnObj.pushKV("quorumParticipation", quorum_participation);
219224

220225
return mnObj;
221226
},

src/rpc/quorums.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,10 @@ static UniValue quorum_sign_helper(const JSONRPCRequest& request, Consensus::LLM
529529
if (!request.params[3].isNull()) {
530530
fSubmit = ParseBoolV(request.params[3], "submit");
531531
}
532+
if (!llmq::CSigSharesManager::IsQuorumSigningAllowed(chainman)) {
533+
throw JSONRPCError(RPC_MISC_ERROR,
534+
"Quorum signing is disabled until snapshot background validation completes");
535+
}
532536
if (fSubmit) {
533537
return CHECK_NONFATAL(node.active_ctx)->shareman->AsyncSignIfMember(llmqType, *llmq_ctx.sigman, id, msgHash, quorumHash);
534538
} else {

src/test/validation_chainstatemanager_tests.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
9090
BOOST_REQUIRE(c1.LoadGenesisBlock());
9191

9292
BOOST_CHECK(!manager.IsSnapshotActive());
93+
BOOST_CHECK(!manager.IsSnapshotActiveAndUnvalidated());
94+
BOOST_CHECK(llmq::CSigSharesManager::IsQuorumSigningAllowed(manager));
9395
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated()));
9496
auto all = manager.GetAll();
9597
BOOST_CHECK_EQUAL_COLLECTIONS(all.begin(), all.end(), chainstates.begin(), chainstates.end());
@@ -136,6 +138,8 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
136138

137139
BOOST_CHECK(manager.IsSnapshotActive());
138140
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated()));
141+
BOOST_CHECK(manager.IsSnapshotActiveAndUnvalidated());
142+
BOOST_CHECK(!llmq::CSigSharesManager::IsQuorumSigningAllowed(manager));
139143
BOOST_CHECK_EQUAL(&c2, &manager.ActiveChainstate());
140144
BOOST_CHECK(&c1 != &manager.ActiveChainstate());
141145
auto all2 = manager.GetAll();

0 commit comments

Comments
 (0)