Skip to content

Commit 6bfe688

Browse files
Merge #7063: refactor: extract CActiveMasternodeManager from LLMQContext (2/n, CQuorumManager handler separation)
d41e5bd chore: apply review suggestions (Kittywhiskers Van Gogh) 8a3ad09 refactor: abstract away parent implementation from handler (Kittywhiskers Van Gogh) 718ee50 refactor: streamline quorum cache logic (Kittywhiskers Van Gogh) 1360d9d refactor: drop unnecessary `CConnman` argument in handlers (Kittywhiskers Van Gogh) 2a57a44 refactor: move quorum manager to separate source file (Kittywhiskers Van Gogh) 50a5f26 refactor: pull handler initialization out of `CQuorumManager` (Kittywhiskers Van Gogh) 61da16d refactor: separate observer/common routines into dedicated class (Kittywhiskers Van Gogh) e8b771e move-only: move observer/common routines to `llmq/observer` (Kittywhiskers Van Gogh) 4078de0 refactor: disentangle watch-only and masternode mode conn checking (Kittywhiskers Van Gogh) a2d909b refactor: pull deletable quorums routine to dedicated function (Kittywhiskers Van Gogh) eaee1a8 refactor: disentangle watch-only and masternode mode thread triggers (Kittywhiskers Van Gogh) e6d8e69 refactor: pull data recovery lambda to a dedicated function (Kittywhiskers Van Gogh) 1fe85be refactor: separate observer/participant logic into dedicated class (Kittywhiskers Van Gogh) ba24b4e move-only: move observer/participant logic to `active/quorums.cpp` (Kittywhiskers Van Gogh) de0ce4e refactor: extract masternode-mode specific `SetSecretKeyShare()` (Kittywhiskers Van Gogh) 201ccfc refactor: extract `ENCRYPTED_CONTRIBUTIONS` processing case (Kittywhiskers Van Gogh) Pull request description: ## Additional Information * Depends on #7062 * Dependency for #7065 * To enforce the split between masternode mode (which can participate in quorums and seek quorum data) and watch-only mode (which can **only** seek quorum data), threading logic is split between `StartDataRecoveryThread()` and `StartVvecSyncThread()`, they both call the same underlying `DataRecoveryThread()` but one has access to masternode-specific parameters and the other does not. This becomes relevant as the entities are split out and the access to specific parameters are enforced by the relevant class members outright not existing. * It is recommended to use `TryStartVvecSyncThread()` as it will not start threads if no data is actually needed, calling `StartVvecSyncThread()` directly bypasses this check. * `CQuorumManager` exposes both `IsWatching()` and `IsMasternode()` to allow P2P code and interfaces to query the node's state (this is most relevant in `PeerManager` which can trivially detect masternode mode but not watch-only status). * Watch-only nodes cannot be masternodes but masternodes can **also** be watch-only (the term "observer" has been used in the codebase where possible as watch-only becomes a bit of a misnomer in this case but is the established term) nodes. * The `CQuorumManager` cache warmer was one of the tasks allocated to the common worker pool, as the rest of the activities are managed by the observer context, rather than keeping the worker pool in the quorum manager and then exposing it through the interface for the sake of one task, the worker pool has been moved to the observer context and we have a regular thread for the quorum manager instead. ## Breaking Changes None expected. ## 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 - [x] I have made corresponding changes to the documentation **(note: N/A)** - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: UdjinM6: utACK d41e5bd Tree-SHA512: 9d4f0c67149dcec10d50a74dafe18b165590b8977203f82f3104645bf5a6e1e109fa901c1aaebf543ebc215aa910bd47b8b88dc993a66363655fb0c6ea57ed73
2 parents c40f3db + d41e5bd commit 6bfe688

39 files changed

+1959
-1375
lines changed

src/Makefile.am

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ endif
151151
.PHONY: FORCE check-symbols check-security
152152
# dash core #
153153
BITCOIN_CORE_H = \
154+
active/quorums.h \
154155
addrdb.h \
155156
addressindex.h \
156157
spentindex.h \
@@ -278,6 +279,7 @@ BITCOIN_CORE_H = \
278279
llmq/options.h \
279280
llmq/params.h \
280281
llmq/quorums.h \
282+
llmq/quorumsman.h \
281283
llmq/signhash.h \
282284
llmq/signing.h \
283285
llmq/net_signing.h \
@@ -286,6 +288,7 @@ BITCOIN_CORE_H = \
286288
llmq/types.h \
287289
llmq/utils.h \
288290
llmq/observer/context.h \
291+
llmq/observer/quorums.h \
289292
logging.h \
290293
logging/timer.h \
291294
mapport.h \
@@ -479,6 +482,7 @@ libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h
479482
libbitcoin_node_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS)
480483
libbitcoin_node_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
481484
libbitcoin_node_a_SOURCES = \
485+
active/quorums.cpp \
482486
addrdb.cpp \
483487
addressindex.cpp \
484488
addrman.cpp \
@@ -549,12 +553,14 @@ libbitcoin_node_a_SOURCES = \
549553
llmq/net_signing.cpp \
550554
llmq/options.cpp \
551555
llmq/quorums.cpp \
556+
llmq/quorumsman.cpp \
552557
llmq/signhash.cpp \
553558
llmq/signing.cpp \
554559
llmq/signing_shares.cpp \
555560
llmq/snapshot.cpp \
556561
llmq/utils.cpp \
557562
llmq/observer/context.cpp \
563+
llmq/observer/quorums.cpp \
558564
mapport.cpp \
559565
masternode/active/context.cpp \
560566
masternode/active/notificationinterface.cpp \

src/active/quorums.cpp

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
// Copyright (c) 2018-2025 The Dash Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <active/quorums.h>
6+
7+
#include <bls/bls_ies.h>
8+
#include <bls/bls_worker.h>
9+
#include <evo/deterministicmns.h>
10+
#include <llmq/commitment.h>
11+
#include <llmq/dkgsessionmgr.h>
12+
#include <llmq/options.h>
13+
#include <llmq/quorums.h>
14+
#include <llmq/utils.h>
15+
#include <masternode/node.h>
16+
#include <masternode/sync.h>
17+
18+
#include <chain.h>
19+
#include <chainparams.h>
20+
#include <logging.h>
21+
#include <net.h>
22+
#include <netmessagemaker.h>
23+
#include <validation.h>
24+
25+
#include <cxxtimer.hpp>
26+
27+
namespace llmq {
28+
QuorumParticipant::QuorumParticipant(CBLSWorker& bls_worker, CConnman& connman, CDeterministicMNManager& dmnman,
29+
QuorumObserverParent& qman, CQuorumSnapshotManager& qsnapman,
30+
const CActiveMasternodeManager& mn_activeman, const ChainstateManager& chainman,
31+
const CMasternodeSync& mn_sync, const CSporkManager& sporkman,
32+
const llmq::QvvecSyncModeMap& sync_map, bool quorums_recovery, bool quorums_watch) :
33+
QuorumObserver(connman, dmnman, qman, qsnapman, chainman, mn_sync, sporkman, sync_map, quorums_recovery),
34+
m_bls_worker{bls_worker},
35+
m_mn_activeman{mn_activeman},
36+
m_quorums_watch{quorums_watch}
37+
{
38+
}
39+
40+
QuorumParticipant::~QuorumParticipant() = default;
41+
42+
void QuorumParticipant::CheckQuorumConnections(const Consensus::LLMQParams& llmqParams,
43+
gsl::not_null<const CBlockIndex*> pindexNew) const
44+
{
45+
auto lastQuorums = m_qman.ScanQuorums(llmqParams.type, pindexNew, (size_t)llmqParams.keepOldConnections);
46+
auto deletableQuorums = GetQuorumsToDelete(llmqParams, pindexNew);
47+
48+
const uint256 proTxHash = m_mn_activeman.GetProTxHash();
49+
const bool watchOtherISQuorums = llmqParams.type == Params().GetConsensus().llmqTypeDIP0024InstantSend &&
50+
ranges::any_of(lastQuorums, [&proTxHash](const auto& old_quorum){ return old_quorum->IsMember(proTxHash); });
51+
52+
for (const auto& quorum : lastQuorums) {
53+
if (utils::EnsureQuorumConnections(llmqParams, m_connman, m_sporkman, {m_dmnman, m_qsnapman, m_chainman, quorum->m_quorum_base_block_index},
54+
m_dmnman.GetListAtChainTip(), proTxHash, /*is_masternode=*/true, m_quorums_watch)) {
55+
if (deletableQuorums.erase(quorum->qc->quorumHash) > 0) {
56+
LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- llmqType[%d] h[%d] keeping mn quorum connections for quorum: [%d:%s]\n", __func__, ToUnderlying(llmqParams.type), pindexNew->nHeight, quorum->m_quorum_base_block_index->nHeight, quorum->m_quorum_base_block_index->GetBlockHash().ToString());
57+
}
58+
} else if (watchOtherISQuorums && !quorum->IsMember(proTxHash)) {
59+
Uint256HashSet connections;
60+
const auto& cindexes = utils::CalcDeterministicWatchConnections(llmqParams.type, quorum->m_quorum_base_block_index, quorum->members.size(), 1);
61+
for (auto idx : cindexes) {
62+
connections.emplace(quorum->members[idx]->proTxHash);
63+
}
64+
if (!connections.empty()) {
65+
if (!m_connman.HasMasternodeQuorumNodes(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash())) {
66+
LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- llmqType[%d] h[%d] adding mn inter-quorum connections for quorum: [%d:%s]\n", __func__, ToUnderlying(llmqParams.type), pindexNew->nHeight, quorum->m_quorum_base_block_index->nHeight, quorum->m_quorum_base_block_index->GetBlockHash().ToString());
67+
m_connman.SetMasternodeQuorumNodes(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash(), connections);
68+
m_connman.SetMasternodeQuorumRelayMembers(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash(), connections);
69+
}
70+
if (deletableQuorums.erase(quorum->qc->quorumHash) > 0) {
71+
LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- llmqType[%d] h[%d] keeping mn inter-quorum connections for quorum: [%d:%s]\n", __func__, ToUnderlying(llmqParams.type), pindexNew->nHeight, quorum->m_quorum_base_block_index->nHeight, quorum->m_quorum_base_block_index->GetBlockHash().ToString());
72+
}
73+
}
74+
}
75+
}
76+
77+
for (const auto& quorumHash : deletableQuorums) {
78+
LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- removing masternodes quorum connections for quorum %s:\n", __func__, quorumHash.ToString());
79+
m_connman.RemoveMasternodeQuorumNodes(llmqParams.type, quorumHash);
80+
}
81+
}
82+
83+
bool QuorumParticipant::SetQuorumSecretKeyShare(CQuorum& quorum, Span<CBLSSecretKey> skContributions) const
84+
{
85+
return quorum.SetSecretKeyShare(m_bls_worker.AggregateSecretKeys(skContributions), m_mn_activeman.GetProTxHash());
86+
}
87+
88+
size_t QuorumParticipant::GetQuorumRecoveryStartOffset(const CQuorum& quorum, gsl::not_null<const CBlockIndex*> pIndex) const
89+
{
90+
auto mns = m_dmnman.GetListForBlock(pIndex);
91+
std::vector<uint256> vecProTxHashes;
92+
vecProTxHashes.reserve(mns.GetValidMNsCount());
93+
mns.ForEachMN(/*onlyValid=*/true,
94+
[&](const auto& pMasternode) { vecProTxHashes.emplace_back(pMasternode.proTxHash); });
95+
std::sort(vecProTxHashes.begin(), vecProTxHashes.end());
96+
size_t nIndex{0};
97+
{
98+
auto my_protx_hash = m_mn_activeman.GetProTxHash();
99+
for (const auto i : irange::range(vecProTxHashes.size())) {
100+
// cppcheck-suppress useStlAlgorithm
101+
if (my_protx_hash == vecProTxHashes[i]) {
102+
nIndex = i;
103+
break;
104+
}
105+
}
106+
}
107+
return nIndex % quorum.qc->validMembers.size();
108+
}
109+
110+
MessageProcessingResult QuorumParticipant::ProcessContribQGETDATA(bool request_limit_exceeded, CDataStream& vStream,
111+
const CQuorum& quorum, CQuorumDataRequest& request,
112+
gsl::not_null<const CBlockIndex*> block_index)
113+
{
114+
if (request.GetDataMask() & CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS) {
115+
assert(block_index);
116+
117+
int memberIdx = quorum.GetMemberIndex(request.GetProTxHash());
118+
if (memberIdx == -1) {
119+
request.SetError(CQuorumDataRequest::Errors::MASTERNODE_IS_NO_MEMBER);
120+
return request_limit_exceeded ? MisbehavingError{25, "request limit exceeded"} : MessageProcessingResult{};
121+
}
122+
123+
std::vector<CBLSIESEncryptedObject<CBLSSecretKey>> vecEncrypted;
124+
if (!m_qman.GetEncryptedContributions(request.GetLLMQType(), block_index,
125+
quorum.qc->validMembers, request.GetProTxHash(), vecEncrypted)) {
126+
request.SetError(CQuorumDataRequest::Errors::ENCRYPTED_CONTRIBUTIONS_MISSING);
127+
return request_limit_exceeded ? MisbehavingError{25, "request limit exceeded"} : MessageProcessingResult{};
128+
}
129+
130+
vStream << vecEncrypted;
131+
}
132+
133+
return {};
134+
}
135+
136+
MessageProcessingResult QuorumParticipant::ProcessContribQDATA(CNode& pfrom, CDataStream& vStream,
137+
CQuorum& quorum, CQuorumDataRequest& request)
138+
{
139+
if (request.GetDataMask() & CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS) {
140+
if (WITH_LOCK(quorum.cs_vvec_shShare, return !quorum.HasVerificationVectorInternal()
141+
|| quorum.quorumVvec->size() != size_t(quorum.params.threshold))) {
142+
// Don't bump score because we asked for it
143+
LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- %s: No valid quorum verification vector available, from peer=%d\n", __func__, NetMsgType::QDATA, pfrom.GetId());
144+
return {};
145+
}
146+
147+
int memberIdx = quorum.GetMemberIndex(request.GetProTxHash());
148+
if (memberIdx == -1) {
149+
// Don't bump score because we asked for it
150+
LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- %s: Not a member of the quorum, from peer=%d\n", __func__, NetMsgType::QDATA, pfrom.GetId());
151+
return {};
152+
}
153+
154+
std::vector<CBLSIESEncryptedObject<CBLSSecretKey>> vecEncrypted;
155+
vStream >> vecEncrypted;
156+
157+
std::vector<CBLSSecretKey> vecSecretKeys;
158+
vecSecretKeys.resize(vecEncrypted.size());
159+
for (const auto i : irange::range(vecEncrypted.size())) {
160+
if (!m_mn_activeman.Decrypt(vecEncrypted[i], memberIdx, vecSecretKeys[i], PROTOCOL_VERSION)) {
161+
return MisbehavingError{10, "failed to decrypt"};
162+
}
163+
}
164+
165+
if (!quorum.SetSecretKeyShare(m_bls_worker.AggregateSecretKeys(vecSecretKeys), m_mn_activeman.GetProTxHash())) {
166+
return MisbehavingError{10, "invalid secret key share received"};
167+
}
168+
}
169+
170+
return {};
171+
}
172+
173+
bool QuorumParticipant::IsMasternode() const
174+
{
175+
// We are only initialized if masternode mode is enabled
176+
return true;
177+
}
178+
179+
bool QuorumParticipant::IsWatching() const
180+
{
181+
// Watch-only mode can co-exist with masternode mode
182+
return m_quorums_watch;
183+
}
184+
185+
void QuorumParticipant::StartDataRecoveryThread(gsl::not_null<const CBlockIndex*> pIndex, CQuorumCPtr pQuorum,
186+
uint16_t nDataMaskIn) const
187+
{
188+
bool expected = false;
189+
if (!pQuorum->fQuorumDataRecoveryThreadRunning.compare_exchange_strong(expected, true)) {
190+
LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- Already running\n", __func__);
191+
return;
192+
}
193+
194+
workerPool.push([pQuorum = std::move(pQuorum), pIndex, nDataMaskIn, this](int threadId) mutable {
195+
const size_t size_offset = GetQuorumRecoveryStartOffset(*pQuorum, pIndex);
196+
DataRecoveryThread(pIndex, std::move(pQuorum), nDataMaskIn, m_mn_activeman.GetProTxHash(), size_offset);
197+
});
198+
}
199+
200+
void QuorumParticipant::TriggerQuorumDataRecoveryThreads(gsl::not_null<const CBlockIndex*> block_index) const
201+
{
202+
if (!m_quorums_recovery) {
203+
return;
204+
}
205+
206+
LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- Process block %s\n", __func__, block_index->GetBlockHash().ToString());
207+
208+
const uint256 proTxHash = m_mn_activeman.GetProTxHash();
209+
210+
for (const auto& params : Params().GetConsensus().llmqs) {
211+
auto vecQuorums = m_qman.ScanQuorums(params.type, block_index, params.keepOldConnections);
212+
const bool fWeAreQuorumTypeMember = ranges::any_of(vecQuorums, [&proTxHash](const auto& pQuorum) { return pQuorum->IsValidMember(proTxHash); });
213+
214+
for (auto& pQuorum : vecQuorums) {
215+
if (pQuorum->IsValidMember(proTxHash)) {
216+
uint16_t nDataMask{0};
217+
if (!pQuorum->HasVerificationVector()) {
218+
nDataMask |= CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR;
219+
}
220+
if (!pQuorum->GetSkShare().IsValid()) {
221+
nDataMask |= CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS;
222+
}
223+
if (nDataMask != 0) {
224+
StartDataRecoveryThread(block_index, std::move(pQuorum), nDataMask);
225+
} else {
226+
LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- No data needed from (%d, %s) at height %d\n", __func__,
227+
ToUnderlying(pQuorum->qc->llmqType), pQuorum->qc->quorumHash.ToString(), block_index->nHeight);
228+
}
229+
} else {
230+
TryStartVvecSyncThread(block_index, std::move(pQuorum), fWeAreQuorumTypeMember);
231+
}
232+
}
233+
}
234+
}
235+
} // namespace llmq

src/active/quorums.h

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright (c) 2018-2025 The Dash Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_ACTIVE_QUORUMS_H
6+
#define BITCOIN_ACTIVE_QUORUMS_H
7+
8+
#include <llmq/observer/quorums.h>
9+
#include <llmq/types.h>
10+
11+
#include <consensus/params.h>
12+
#include <saltedhasher.h>
13+
#include <span.h>
14+
#include <sync.h>
15+
#include <threadsafety.h>
16+
#include <uint256.h>
17+
18+
#include <map>
19+
20+
class CActiveMasternodeManager;
21+
class CBlockIndex;
22+
class CBLSWorker;
23+
class CConnman;
24+
class CDeterministicMNManager;
25+
class CDKGSessionManager;
26+
class CNode;
27+
class CSporkManager;
28+
struct MessageProcessingResult;
29+
namespace llmq {
30+
class CQuorum;
31+
class CQuorumDataRequest;
32+
class CQuorumSnapshotManager;
33+
enum class QvvecSyncMode : int8_t;
34+
} // namespace llmq
35+
36+
namespace llmq {
37+
class QuorumParticipant final : public QuorumObserver
38+
{
39+
private:
40+
CBLSWorker& m_bls_worker;
41+
const CActiveMasternodeManager& m_mn_activeman;
42+
const bool m_quorums_watch{false};
43+
44+
public:
45+
QuorumParticipant() = delete;
46+
QuorumParticipant(const QuorumParticipant&) = delete;
47+
QuorumParticipant& operator=(const QuorumParticipant&) = delete;
48+
explicit QuorumParticipant(CBLSWorker& bls_worker, CConnman& connman, CDeterministicMNManager& dmnman,
49+
QuorumObserverParent& qman, CQuorumSnapshotManager& qsnapman,
50+
const CActiveMasternodeManager& mn_activeman, const ChainstateManager& chainman,
51+
const CMasternodeSync& mn_sync, const CSporkManager& sporkman,
52+
const llmq::QvvecSyncModeMap& sync_map, bool quorums_recovery, bool quorums_watch);
53+
~QuorumParticipant();
54+
55+
public:
56+
// QuorumObserver
57+
bool IsMasternode() const override;
58+
bool IsWatching() const override;
59+
bool SetQuorumSecretKeyShare(CQuorum& quorum, Span<CBLSSecretKey> skContributions) const override;
60+
[[nodiscard]] MessageProcessingResult ProcessContribQGETDATA(bool request_limit_exceeded, CDataStream& vStream,
61+
const CQuorum& quorum, CQuorumDataRequest& request,
62+
gsl::not_null<const CBlockIndex*> block_index) override;
63+
[[nodiscard]] MessageProcessingResult ProcessContribQDATA(CNode& pfrom, CDataStream& vStream, CQuorum& quorum,
64+
CQuorumDataRequest& request) override;
65+
66+
protected:
67+
// QuorumObserver
68+
void CheckQuorumConnections(const Consensus::LLMQParams& llmqParams,
69+
gsl::not_null<const CBlockIndex*> pindexNew) const override;
70+
void TriggerQuorumDataRecoveryThreads(gsl::not_null<const CBlockIndex*> block_index) const override;
71+
72+
private:
73+
/// Returns the start offset for the masternode with the given proTxHash. This offset is applied when picking data
74+
/// recovery members of a quorum's memberlist and is calculated based on a list of all member of all active quorums
75+
/// for the given llmqType in a way that each member should receive the same number of request if all active
76+
/// llmqType members requests data from one llmqType quorum.
77+
size_t GetQuorumRecoveryStartOffset(const CQuorum& quorum, gsl::not_null<const CBlockIndex*> pIndex) const;
78+
79+
void StartDataRecoveryThread(gsl::not_null<const CBlockIndex*> pIndex, CQuorumCPtr pQuorum, uint16_t nDataMaskIn) const;
80+
};
81+
} // namespace llmq
82+
83+
#endif // BITCOIN_ACTIVE_QUORUMS_H

src/chainlock/chainlock.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#include <validationinterface.h>
1818

1919
#include <instantsend/instantsend.h>
20-
#include <llmq/quorums.h>
20+
#include <llmq/quorumsman.h>
2121
#include <masternode/sync.h>
2222
#include <spork.h>
2323
#include <stats/client.h>

src/chainlock/chainlock.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ namespace llmq {
3838
class CInstantSendManager;
3939
class CQuorumManager;
4040
class CSigningManager;
41-
enum class VerifyRecSigStatus;
41+
enum class VerifyRecSigStatus : uint8_t;
4242

4343
class CChainLocksHandler final : public chainlock::ChainLockSignerParent
4444
{

0 commit comments

Comments
 (0)