Line data Source code
1 : // Copyright (c) 2017-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/context.h>
6 : #include <active/masternode.h>
7 : #include <chainlock/chainlock.h>
8 : #include <chainlock/clsig.h>
9 : #include <chainlock/handler.h>
10 : #include <evo/deterministicmns.h>
11 : #include <llmq/blockprocessor.h>
12 : #include <llmq/commitment.h>
13 : #include <llmq/context.h>
14 : #include <llmq/debug.h>
15 : #include <llmq/dkgsession.h>
16 : #include <llmq/observer.h>
17 : #include <llmq/options.h>
18 : #include <llmq/quorumsman.h>
19 : #include <llmq/signhash.h>
20 : #include <llmq/signing.h>
21 : #include <llmq/signing_shares.h>
22 : #include <llmq/snapshot.h>
23 : #include <llmq/utils.h>
24 : #include <rpc/evo_util.h>
25 : #include <util/helpers.h>
26 :
27 : #include <chainparams.h>
28 : #include <core_io.h>
29 : #include <deploymentstatus.h>
30 : #include <index/txindex.h>
31 : #include <net_processing.h>
32 : #include <netmessagemaker.h>
33 : #include <node/context.h>
34 : #include <rpc/server.h>
35 : #include <rpc/server_util.h>
36 : #include <rpc/util.h>
37 : #include <util/check.h>
38 : #include <validation.h>
39 :
40 : #include <iomanip>
41 : #include <optional>
42 :
43 : using node::GetTransaction;
44 : using node::NodeContext;
45 :
46 6892 : static RPCHelpMan quorum_list()
47 : {
48 13784 : return RPCHelpMan{"quorum list",
49 6892 : "List of on-chain quorums\n",
50 13784 : {
51 6892 : {"count", RPCArg::Type::NUM, RPCArg::DefaultHint{"The active quorum count if not specified"},
52 6892 : "Number of quorums to list.\n"
53 : "Can be CPU/disk heavy when the value is larger than the number of active quorums."
54 : },
55 : },
56 6892 : RPCResult{
57 6892 : RPCResult::Type::OBJ, "", "",
58 13784 : {
59 13784 : {RPCResult::Type::ARR, "quorumName", "List of quorum hashes per some quorum type",
60 13784 : {
61 6892 : {RPCResult::Type::STR_HEX, "quorumHash", "Quorum hash. Note: most recent quorums come first."},
62 : }},
63 : }},
64 6892 : RPCExamples{
65 6892 : HelpExampleCli("quorum", "list")
66 6892 : + HelpExampleCli("quorum", "list 10")
67 6892 : + HelpExampleRpc("quorum", "list, 10")
68 : },
69 7646 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
70 : {
71 754 : const NodeContext& node = EnsureAnyNodeContext(request.context);
72 754 : const ChainstateManager& chainman = EnsureChainman(node);
73 754 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
74 :
75 754 : int count = -1;
76 754 : if (!request.params[0].isNull()) {
77 200 : count = request.params[0].getInt<int>();
78 200 : if (count < -1) {
79 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "count can't be negative");
80 : }
81 200 : }
82 :
83 754 : UniValue ret(UniValue::VOBJ);
84 :
85 1508 : CBlockIndex* pindexTip = WITH_LOCK(cs_main, return chainman.ActiveChain().Tip());
86 :
87 3292 : for (const auto& type : llmq::GetEnabledQuorumTypes(chainman, pindexTip)) {
88 2538 : const auto llmq_params_opt = Params().GetLLMQ(type);
89 2538 : CHECK_NONFATAL(llmq_params_opt.has_value());
90 2538 : UniValue v(UniValue::VARR);
91 :
92 2538 : auto quorums = llmq_ctx.qman->ScanQuorums(type, pindexTip, count > -1 ? count : llmq_params_opt->signingActiveQuorumCount);
93 4997 : for (const auto& q : quorums) {
94 2459 : v.push_back(q->qc->quorumHash.ToString());
95 : }
96 :
97 2538 : ret.pushKV(std::string(llmq_params_opt->name), v);
98 2538 : }
99 :
100 754 : return ret;
101 754 : },
102 : };
103 0 : }
104 :
105 6146 : static RPCHelpMan quorum_list_extended()
106 : {
107 12292 : return RPCHelpMan{"quorum listextended",
108 6146 : "Extended list of on-chain quorums\n",
109 12292 : {
110 6146 : {"height", RPCArg::Type::NUM, RPCArg::DefaultHint{"Tip height if not specified"}, "Active quorums at the height."},
111 : },
112 6146 : RPCResult{
113 6146 : RPCResult::Type::OBJ, "", "",
114 12292 : {
115 12292 : {RPCResult::Type::ARR, "quorumName", "List of quorum details per quorum type",
116 12292 : {
117 12292 : {RPCResult::Type::OBJ, "", "",
118 12292 : {
119 12292 : {RPCResult::Type::OBJ, "xxxx", "Quorum hash. Note: most recent quorums come first.",
120 36876 : {
121 6146 : {RPCResult::Type::NUM, "creationHeight", "Block height where the DKG started."},
122 6146 : {RPCResult::Type::NUM, "quorumIndex", "Quorum index (applicable only to rotated quorums)."},
123 6146 : {RPCResult::Type::STR_HEX, "minedBlockHash", "Blockhash where the commitment was mined."},
124 6146 : {RPCResult::Type::NUM, "numValidMembers", "The total of valid members."},
125 6146 : {RPCResult::Type::STR_AMOUNT, "healthRatio", "The ratio of healthy members to quorum size. Range [0.0 - 1.0]."}
126 : }}
127 : }}
128 : }}
129 : }},
130 6146 : RPCExamples{
131 6146 : HelpExampleCli("quorum", "listextended")
132 6146 : + HelpExampleCli("quorum", "listextended 2500")
133 6146 : + HelpExampleRpc("quorum", "listextended, 2500")
134 : },
135 6154 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
136 : {
137 8 : const NodeContext& node = EnsureAnyNodeContext(request.context);
138 8 : const ChainstateManager& chainman = EnsureChainman(node);
139 8 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
140 :
141 8 : int nHeight = -1;
142 8 : if (!request.params[0].isNull()) {
143 0 : nHeight = request.params[0].getInt<int>();
144 0 : if (nHeight < 0 || nHeight > WITH_LOCK(cs_main, return chainman.ActiveChain().Height())) {
145 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
146 : }
147 0 : }
148 :
149 8 : UniValue ret(UniValue::VOBJ);
150 :
151 16 : CBlockIndex* pblockindex = nHeight != -1 ? WITH_LOCK(cs_main, return chainman.ActiveChain()[nHeight]) : WITH_LOCK(cs_main, return chainman.ActiveChain().Tip());
152 :
153 32 : for (const auto& type : llmq::GetEnabledQuorumTypes(chainman, pblockindex)) {
154 24 : const auto llmq_params_opt = Params().GetLLMQ(type);
155 24 : CHECK_NONFATAL(llmq_params_opt.has_value());
156 24 : const auto& llmq_params = llmq_params_opt.value();
157 24 : UniValue v(UniValue::VARR);
158 :
159 24 : auto quorums = llmq_ctx.qman->ScanQuorums(type, pblockindex, llmq_params.signingActiveQuorumCount);
160 56 : for (const auto& q : quorums) {
161 32 : size_t num_members = q->members.size();
162 160 : size_t num_valid_members = std::count_if(q->qc->validMembers.begin(), q->qc->validMembers.begin() + num_members, [](auto val){return val;});
163 32 : double health_ratio = num_members > 0 ? double(num_valid_members) / double(num_members) : 0.0;
164 32 : std::stringstream ss;
165 32 : ss << std::fixed << std::setprecision(2) << health_ratio;
166 32 : UniValue obj(UniValue::VOBJ);
167 : {
168 32 : UniValue j(UniValue::VOBJ);
169 32 : if (llmq_params.useRotation) {
170 16 : j.pushKV("quorumIndex", q->qc->quorumIndex);
171 16 : }
172 32 : j.pushKV("creationHeight", q->m_quorum_base_block_index->nHeight);
173 32 : j.pushKV("minedBlockHash", q->minedBlockHash.ToString());
174 32 : j.pushKV("numValidMembers", num_valid_members);
175 32 : j.pushKV("healthRatio", ss.str());
176 32 : obj.pushKV(q->qc->quorumHash.ToString(),j);
177 32 : }
178 32 : v.push_back(obj);
179 32 : }
180 24 : ret.pushKV(std::string(llmq_params.name), v);
181 24 : }
182 :
183 8 : return ret;
184 8 : },
185 : };
186 0 : }
187 :
188 758 : static UniValue BuildQuorumInfo(const llmq::CQuorumBlockProcessor& quorum_block_processor,
189 : const llmq::CQuorum& quorum, bool includeMembers, bool includeSkShare)
190 : {
191 758 : UniValue ret(UniValue::VOBJ);
192 :
193 758 : ret.pushKV("height", quorum.m_quorum_base_block_index->nHeight);
194 758 : ret.pushKV("type", std::string(quorum.params.name));
195 758 : ret.pushKV("quorumHash", quorum.qc->quorumHash.ToString());
196 758 : ret.pushKV("quorumIndex", quorum.qc->quorumIndex);
197 758 : ret.pushKV("minedBlock", quorum.minedBlockHash.ToString());
198 :
199 758 : if (quorum.params.useRotation) {
200 90 : auto previousActiveCommitment = quorum_block_processor.GetLastMinedCommitmentsByQuorumIndexUntilBlock(quorum.params.type, quorum.m_quorum_base_block_index, quorum.qc->quorumIndex, 0);
201 90 : if (previousActiveCommitment.has_value()) {
202 46 : int previousConsecutiveDKGFailures = (quorum.m_quorum_base_block_index->nHeight - previousActiveCommitment.value()->nHeight) / quorum.params.dkgInterval - 1;
203 46 : ret.pushKV("previousConsecutiveDKGFailures", previousConsecutiveDKGFailures);
204 46 : }
205 : else {
206 44 : ret.pushKV("previousConsecutiveDKGFailures", 0);
207 : }
208 90 : }
209 :
210 758 : if (includeMembers) {
211 748 : UniValue membersArr(UniValue::VARR);
212 3586 : for (size_t i = 0; i < quorum.members.size(); i++) {
213 2838 : const auto& dmn = quorum.members[i];
214 2838 : UniValue mo(UniValue::VOBJ);
215 2838 : mo.pushKV("proTxHash", dmn->proTxHash.ToString());
216 2838 : if (IsDeprecatedRPCEnabled("service")) {
217 0 : mo.pushKV("service", dmn->pdmnState->netInfo->GetPrimary().ToStringAddrPort());
218 0 : }
219 2838 : mo.pushKV("addresses", GetNetInfoWithLegacyFields(*dmn->pdmnState, dmn->nType));
220 2838 : mo.pushKV("pubKeyOperator", dmn->pdmnState->pubKeyOperator.ToString());
221 2838 : mo.pushKV("valid", static_cast<bool>(quorum.qc->validMembers[i]));
222 2838 : if (quorum.qc->validMembers[i]) {
223 2801 : if (quorum.params.is_single_member()) {
224 0 : mo.pushKV("pubKeyShare", dmn->pdmnState->pubKeyOperator.ToString());
225 0 : } else {
226 2801 : CBLSPublicKey pubKey = quorum.GetPubKeyShare(i);
227 2801 : if (pubKey.IsValid()) {
228 833 : mo.pushKV("pubKeyShare", pubKey.ToString());
229 833 : }
230 : }
231 2801 : }
232 2838 : membersArr.push_back(mo);
233 2838 : }
234 :
235 748 : ret.pushKV("members", membersArr);
236 748 : }
237 758 : ret.pushKV("quorumPublicKey", quorum.qc->quorumPublicKey.ToString());
238 758 : const CBLSSecretKey& skShare = quorum.GetSkShare();
239 758 : if (includeSkShare && skShare.IsValid()) {
240 181 : ret.pushKV("secretKeyShare", skShare.ToString());
241 181 : }
242 758 : return ret;
243 758 : }
244 :
245 6886 : static RPCHelpMan quorum_info()
246 : {
247 13772 : return RPCHelpMan{"quorum info",
248 6886 : "Return information about a quorum\n",
249 27544 : {
250 6886 : {"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
251 6886 : {"quorumHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Block hash of quorum."},
252 6886 : {"includeSkShare", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include secret key share in output."},
253 : },
254 6886 : RPCResult{
255 6886 : RPCResult::Type::OBJ, "", "",
256 55088 : {
257 6886 : {RPCResult::Type::NUM, "height", "Quorum Height"},
258 6886 : {RPCResult::Type::STR, "type", "Quorum type"},
259 6886 : GetRpcResult("quorumHash"),
260 6886 : GetRpcResult("quorumIndex"),
261 6886 : {RPCResult::Type::STR_HEX, "minedBlock", "Blockhash where the commitment was mined."},
262 6886 : {RPCResult::Type::NUM, "previousConsecutiveDKGFailures", "Number of previous consecutive DKG failures. Only present for rotation-enabled quorums."},
263 13772 : {RPCResult::Type::ARR, "members", "Members of quorum",
264 13772 : {
265 13772 : {RPCResult::Type::OBJ, "", "",
266 48202 : {
267 6886 : GetRpcResult("proTxHash"),
268 6886 : GetRpcResult("service", /*optional=*/true),
269 6886 : GetRpcResult("addresses"),
270 6886 : GetRpcResult("pubKeyOperator"),
271 6886 : {RPCResult::Type::BOOL, "valid", "True if member is valid for this DKG"},
272 6886 : {RPCResult::Type::STR_HEX, "pubKeyShare", /*optional=*/true, "Share of BLS public key of the member. Only present if member is valid."}
273 : }},
274 : },
275 : },
276 : },
277 : },
278 6886 : RPCExamples{""},
279 7634 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
280 : {
281 748 : const NodeContext& node = EnsureAnyNodeContext(request.context);
282 748 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
283 :
284 748 : const Consensus::LLMQType llmqType{static_cast<Consensus::LLMQType>(request.params[0].getInt<int>())};
285 748 : if (!Params().GetLLMQ(llmqType).has_value()) {
286 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
287 : }
288 :
289 748 : const uint256 quorumHash(ParseHashV(request.params[1], "quorumHash"));
290 748 : bool includeSkShare = false;
291 748 : if (!request.params[2].isNull()) {
292 362 : includeSkShare = ParseBoolV(request.params[2], "includeSkShare");
293 362 : }
294 :
295 748 : const auto quorum = llmq_ctx.qman->GetQuorum(llmqType, quorumHash);
296 748 : if (!quorum) {
297 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found");
298 : }
299 :
300 748 : return BuildQuorumInfo(*llmq_ctx.quorum_block_processor, *quorum, true, includeSkShare);
301 748 : },
302 : };
303 0 : }
304 :
305 21495 : static RPCResult quorum_dkgstatus_help()
306 : {
307 21495 : auto ret = llmq::CDKGDebugManager::GetJsonHelp(/*key=*/"", /*optional=*/false, /*inner_optional=*/true);
308 21495 : auto mod_inner = ret.m_inner;
309 42990 : mod_inner.push_back({RPCResult::Type::ARR, "quorumConnections", "Array of objects containing quorum connection information", {
310 150465 : {RPCResult::Type::OBJ, "", "", {
311 21495 : GetRpcResult("llmqType"),
312 21495 : GetRpcResult("quorumIndex"),
313 21495 : {RPCResult::Type::NUM, "pQuorumBaseBlockIndex", /*optional=*/true, "Height of the quorum’s base block"},
314 21495 : GetRpcResult("quorumHash", /*optional=*/true),
315 21495 : {RPCResult::Type::NUM, "pindexTip", /*optional=*/true, "Height of the quorum index tip"},
316 42990 : {RPCResult::Type::ARR, "quorumConnections", /*optional=*/true, "", {
317 107475 : {RPCResult::Type::OBJ, "", "", {
318 21495 : GetRpcResult("proTxHash"),
319 21495 : {RPCResult::Type::BOOL, "connected", "Returns true if connection is active"},
320 21495 : {RPCResult::Type::STR, "address", /*optional=*/true, "IP address and port of the masternode"},
321 21495 : {RPCResult::Type::BOOL, "outbound", /*optional=*/true, "Returns true if outbound connection"},
322 : }}}}
323 : }}}});
324 42990 : mod_inner.push_back({RPCResult::Type::ARR, "minableCommitments", "Array of objects containing minable commitments", {
325 21495 : llmq::CFinalCommitment::GetJsonHelp(/*key=*/"", /*optional=*/false)}});
326 21495 : return RPCResult{ret.m_type, ret.m_key_name, ret.m_description, mod_inner};
327 21495 : }
328 :
329 21495 : static RPCHelpMan quorum_dkgstatus()
330 : {
331 42990 : return RPCHelpMan{"quorum dkgstatus",
332 21495 : "Return the status of the current DKG process.\n"
333 : "Works only when SPORK_17_QUORUM_DKG_ENABLED spork is ON.\n",
334 42990 : {
335 21495 : {"detail_level", RPCArg::Type::NUM, RPCArg::Default{0},
336 21495 : "Detail level of output.\n"
337 : "0=Only show counts. 1=Show member indexes. 2=Show member's ProTxHashes."},
338 : },
339 21495 : quorum_dkgstatus_help(),
340 21495 : RPCExamples{""},
341 36852 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
342 : {
343 15357 : int detailLevel = 0;
344 15357 : if (!request.params[0].isNull()) {
345 0 : detailLevel = request.params[0].getInt<int>();
346 0 : if (detailLevel < 0 || detailLevel > 2) {
347 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid detail_level");
348 : }
349 0 : }
350 :
351 15357 : UniValue ret(UniValue::VOBJ);
352 15357 : UniValue minableCommitments(UniValue::VARR);
353 15357 : UniValue quorumArrConnections(UniValue::VARR);
354 :
355 15357 : const NodeContext& node = EnsureAnyNodeContext(request.context);
356 15357 : if (const auto* debugman = node.active_ctx ? node.active_ctx->dkgdbgman.get()
357 0 : : node.observer_ctx ? node.observer_ctx->dkgdbgman.get()
358 15357 : : nullptr; debugman) {
359 15357 : ret = debugman->ToJson(detailLevel);
360 15357 : }
361 :
362 15357 : const CConnman& connman = EnsureConnman(node);
363 15357 : const ChainstateManager& chainman = EnsureChainman(node);
364 30714 : const CBlockIndex* const pindexTip = WITH_LOCK(cs_main, return chainman.ActiveChain().Tip());
365 15357 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
366 15357 : const int tipHeight = pindexTip->nHeight;
367 15357 : const uint256 proTxHash = node.active_ctx ? node.active_ctx->nodeman->GetProTxHash() : uint256{};
368 63677 : for (const auto& type : llmq::GetEnabledQuorumTypes(chainman, pindexTip)) {
369 48320 : const auto llmq_params_opt = Params().GetLLMQ(type);
370 48320 : CHECK_NONFATAL(llmq_params_opt.has_value());
371 48320 : const auto& llmq_params = llmq_params_opt.value();
372 48320 : bool rotation_enabled = llmq::IsQuorumRotationEnabled(llmq_params, pindexTip);
373 48320 : int quorums_num = rotation_enabled ? llmq_params.signingActiveQuorumCount : 1;
374 :
375 111707 : for (const int quorumIndex : util::irange(quorums_num)) {
376 63387 : UniValue obj(UniValue::VOBJ);
377 63387 : obj.pushKV("llmqType", std::string(llmq_params.name));
378 63387 : obj.pushKV("quorumIndex", quorumIndex);
379 :
380 63387 : if (node.active_ctx) {
381 63387 : int quorumHeight = tipHeight - (tipHeight % llmq_params.dkgInterval) + quorumIndex;
382 63387 : if (quorumHeight <= tipHeight) {
383 121806 : const CBlockIndex* pQuorumBaseBlockIndex = WITH_LOCK(cs_main, return chainman.ActiveChain()[quorumHeight]);
384 60903 : obj.pushKV("pQuorumBaseBlockIndex", pQuorumBaseBlockIndex->nHeight);
385 60903 : obj.pushKV("quorumHash", pQuorumBaseBlockIndex->GetBlockHash().ToString());
386 60903 : obj.pushKV("pindexTip", pindexTip->nHeight);
387 :
388 60903 : auto allConnections = llmq::utils::GetQuorumConnections(llmq_params, *CHECK_NONFATAL(node.sporkman),
389 60903 : {*node.dmnman, *llmq_ctx.qsnapman, chainman,
390 60903 : pQuorumBaseBlockIndex},
391 : proTxHash, /*onlyOutbound=*/false);
392 60903 : auto outboundConnections = llmq::utils::GetQuorumConnections(llmq_params, *node.sporkman,
393 121806 : {*node.dmnman, *llmq_ctx.qsnapman,
394 121806 : chainman, pQuorumBaseBlockIndex},
395 : proTxHash, /*onlyOutbound=*/true);
396 60903 : std::map<uint256, CAddress> foundConnections;
397 405146 : connman.ForEachNode([&](const CNode* pnode) {
398 344243 : auto verifiedProRegTxHash = pnode->GetVerifiedProRegTxHash();
399 344243 : if (!verifiedProRegTxHash.IsNull() && allConnections.count(verifiedProRegTxHash)) {
400 94483 : foundConnections.emplace(verifiedProRegTxHash, pnode->addr);
401 94483 : }
402 344243 : });
403 60903 : UniValue arr(UniValue::VARR);
404 164361 : for (const auto& ec : allConnections) {
405 103458 : UniValue ecj(UniValue::VOBJ);
406 103458 : ecj.pushKV("proTxHash", ec.ToString());
407 103458 : if (foundConnections.count(ec)) {
408 94483 : ecj.pushKV("connected", true);
409 94483 : ecj.pushKV("address", foundConnections[ec].ToStringAddrPort());
410 94483 : } else {
411 8975 : ecj.pushKV("connected", false);
412 : }
413 103458 : ecj.pushKV("outbound", outboundConnections.count(ec) != 0);
414 103458 : arr.push_back(ecj);
415 103458 : }
416 60903 : obj.pushKV("quorumConnections", arr);
417 60903 : }
418 63387 : }
419 63387 : quorumArrConnections.push_back(obj);
420 63387 : }
421 :
422 48320 : LOCK(cs_main);
423 48320 : std::optional<std::vector<llmq::CFinalCommitment>> vfqc = llmq_ctx.quorum_block_processor->GetMineableCommitments(llmq_params, tipHeight);
424 48320 : if (vfqc.has_value()) {
425 12340 : for (const auto& fqc : vfqc.value()) {
426 6170 : minableCommitments.push_back(fqc.ToJson());
427 : }
428 6170 : }
429 48320 : }
430 15357 : ret.pushKV("quorumConnections", quorumArrConnections);
431 15357 : ret.pushKV("minableCommitments", minableCommitments);
432 15357 : return ret;
433 15357 : },
434 : };
435 0 : }
436 :
437 6145 : static RPCHelpMan quorum_memberof()
438 : {
439 12290 : return RPCHelpMan{"quorum memberof",
440 6145 : "Checks which quorums the given masternode is a member of.\n",
441 18435 : {
442 6145 : {"proTxHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "ProTxHash of the masternode."},
443 6145 : {"scanQuorumsCount", RPCArg::Type::NUM, RPCArg::DefaultHint{"The active quorum count for each specific quorum type is used"},
444 6145 : "Number of quorums to scan for.\n"
445 : "Can be CPU/disk heavy when the value is larger than the number of active quorums."
446 : },
447 : },
448 6145 : RPCResult{
449 6145 : RPCResult::Type::ARR, "quorums", "",
450 12290 : {
451 12290 : {RPCResult::Type::OBJ, "", "Quorum Info",
452 24580 : {
453 6145 : {RPCResult::Type::ELISION, "", "See `help quorum info` for details"},
454 6145 : {RPCResult::Type::BOOL, "isValidMember", ""},
455 6145 : {RPCResult::Type::NUM, "memberIndex", ""},
456 : }},
457 : },
458 : },
459 6145 : RPCExamples{""},
460 6152 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
461 : {
462 7 : const NodeContext& node = EnsureAnyNodeContext(request.context);
463 7 : const ChainstateManager& chainman = EnsureChainman(node);
464 7 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
465 :
466 7 : uint256 protxHash(ParseHashV(request.params[0], "proTxHash"));
467 7 : int scanQuorumsCount = -1;
468 7 : if (!request.params[1].isNull()) {
469 0 : scanQuorumsCount = request.params[1].getInt<int>();
470 0 : if (scanQuorumsCount <= 0) {
471 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid scanQuorumsCount parameter");
472 : }
473 0 : }
474 :
475 14 : const CBlockIndex* pindexTip = WITH_LOCK(cs_main, return chainman.ActiveChain().Tip());
476 7 : auto mnList = CHECK_NONFATAL(node.dmnman)->GetListForBlock(pindexTip);
477 7 : auto dmn = mnList.GetMN(protxHash);
478 7 : if (!dmn) {
479 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "masternode not found");
480 : }
481 :
482 7 : UniValue result(UniValue::VARR);
483 28 : for (const auto& type : llmq::GetEnabledQuorumTypes(chainman, pindexTip)) {
484 21 : const auto llmq_params_opt = Params().GetLLMQ(type);
485 21 : CHECK_NONFATAL(llmq_params_opt.has_value());
486 21 : size_t count = llmq_params_opt->signingActiveQuorumCount;
487 21 : if (scanQuorumsCount != -1) {
488 0 : count = (size_t)scanQuorumsCount;
489 0 : }
490 21 : auto quorums = llmq_ctx.qman->ScanQuorums(llmq_params_opt->type, count);
491 49 : for (auto& quorum : quorums) {
492 28 : if (quorum->IsMember(dmn->proTxHash)) {
493 10 : auto json = BuildQuorumInfo(*llmq_ctx.quorum_block_processor, *quorum, false, false);
494 10 : json.pushKV("isValidMember", quorum->IsValidMember(dmn->proTxHash));
495 10 : json.pushKV("memberIndex", quorum->GetMemberIndex(dmn->proTxHash));
496 10 : result.push_back(json);
497 10 : }
498 : }
499 21 : }
500 :
501 7 : return result;
502 7 : },
503 : };
504 0 : }
505 :
506 290 : static UniValue quorum_sign_helper(const JSONRPCRequest& request, Consensus::LLMQType llmqType)
507 : {
508 290 : const NodeContext& node = EnsureAnyNodeContext(request.context);
509 290 : if (!node.active_ctx) {
510 4 : throw JSONRPCError(RPC_INTERNAL_ERROR, "Only available in masternode mode.");
511 : }
512 :
513 290 : const ChainstateManager& chainman = EnsureChainman(node);
514 290 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
515 :
516 290 : const auto llmq_params_opt = Params().GetLLMQ(llmqType);
517 290 : if (!llmq_params_opt.has_value()) {
518 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
519 : }
520 :
521 290 : const uint256 id(ParseHashV(request.params[0], "id"));
522 290 : const uint256 msgHash(ParseHashV(request.params[1], "msgHash"));
523 :
524 290 : uint256 quorumHash;
525 290 : if (!request.params[2].isNull() && !request.params[2].get_str().empty()) {
526 242 : quorumHash = ParseHashV(request.params[2], "quorumHash");
527 242 : }
528 290 : bool fSubmit{true};
529 290 : if (!request.params[3].isNull()) {
530 10 : fSubmit = ParseBoolV(request.params[3], "submit");
531 10 : }
532 290 : if (fSubmit) {
533 280 : return CHECK_NONFATAL(node.active_ctx)->shareman->AsyncSignIfMember(llmqType, *llmq_ctx.sigman, id, msgHash, quorumHash);
534 : } else {
535 20 : const auto pQuorum = [&]() {
536 10 : if (quorumHash.IsNull()) {
537 2 : return llmq::SelectQuorumForSigning(llmq_params_opt.value(), chainman.ActiveChain(), *llmq_ctx.qman, id);
538 : } else {
539 8 : return llmq_ctx.qman->GetQuorum(llmqType, quorumHash);
540 : }
541 10 : }();
542 :
543 10 : if (pQuorum == nullptr) {
544 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found");
545 : }
546 :
547 8 : auto sigShare = CHECK_NONFATAL(node.active_ctx)->shareman->CreateSigShare(*pQuorum, id, msgHash);
548 :
549 8 : if (!sigShare.has_value() || !sigShare->sigShare.Get().IsValid()) {
550 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "failed to create sigShare");
551 : }
552 :
553 6 : UniValue obj(UniValue::VOBJ);
554 6 : obj.pushKV("llmqType", static_cast<uint8_t>(llmqType));
555 6 : obj.pushKV("quorumHash", sigShare->getQuorumHash().ToString());
556 6 : obj.pushKV("quorumMember", sigShare->getQuorumMember());
557 6 : obj.pushKV("id", id.ToString());
558 6 : obj.pushKV("msgHash", msgHash.ToString());
559 6 : obj.pushKV("signHash", sigShare->GetSignHash().ToString());
560 6 : obj.pushKV("signature", sigShare->sigShare.Get().ToString());
561 :
562 6 : return obj;
563 10 : }
564 290 : }
565 :
566 : namespace {
567 9924 : const RPCResults quorum_sign_result{
568 3308 : RPCResult{"if submit is set to true", RPCResult::Type::BOOL, "result", "result of signing, true if success"},
569 6616 : RPCResult{"if submit is not set or set to false", RPCResult::Type::OBJ, "", "",
570 26464 : {
571 3308 : {RPCResult::Type::NUM, "llmqType", "Quorum type"},
572 3308 : {RPCResult::Type::STR_HEX, "quorumHash", "Quorum Hash"},
573 3308 : {RPCResult::Type::NUM, "quorumMember", "Number of quorum member"},
574 3308 : {RPCResult::Type::STR_HEX, "id", "Request ID"},
575 3308 : {RPCResult::Type::STR_HEX, "msgHash", "Hash of message"},
576 3308 : {RPCResult::Type::STR_HEX, "signHash", "Hash of signature"},
577 3308 : {RPCResult::Type::STR_HEX, "signature", "Hex encoded signature"},
578 : },
579 : },
580 : };
581 : } // anonymous namespace
582 :
583 6270 : static RPCHelpMan quorum_sign()
584 : {
585 12540 : return RPCHelpMan{"quorum sign",
586 6270 : "Threshold-sign a message\n",
587 37620 : {
588 6270 : {"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
589 6270 : {"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
590 6270 : {"msgHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Message hash."},
591 6270 : {"quorumHash", RPCArg::Type::STR_HEX, RPCArg::Default{""}, "The quorum identifier."},
592 6270 : {"submit", RPCArg::Type::BOOL, RPCArg::Default{true}, "Submits the signature share to the network if this is true. "
593 : "Returns an object containing the signature share if this is false."},
594 : },
595 6270 : quorum_sign_result,
596 6270 : RPCExamples{""},
597 6402 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
598 : {
599 132 : const Consensus::LLMQType llmqType{static_cast<Consensus::LLMQType>(request.params[0].getInt<int>())};
600 :
601 132 : JSONRPCRequest new_request{request};
602 132 : new_request.params.setArray();
603 492 : for (unsigned int i = 1; i < request.params.size(); ++i) {
604 360 : new_request.params.push_back(request.params[i]);
605 360 : }
606 132 : return quorum_sign_helper(new_request, llmqType);
607 132 : },
608 : };
609 0 : }
610 :
611 6296 : static RPCHelpMan quorum_platformsign()
612 : {
613 12592 : return RPCHelpMan{"quorum platformsign",
614 6296 : "Threshold-sign a message. It signs messages only for platform quorums\n",
615 31480 : {
616 6296 : {"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
617 6296 : {"msgHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Message hash."},
618 6296 : {"quorumHash", RPCArg::Type::STR_HEX, RPCArg::Default{""}, "The quorum identifier."},
619 6296 : {"submit", RPCArg::Type::BOOL, RPCArg::Default{true}, "Submits the signature share to the network if this is true. "
620 : "Returns an object containing the signature share if this is false."},
621 : },
622 6296 : quorum_sign_result,
623 6296 : RPCExamples{""},
624 6454 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
625 : {
626 158 : const Consensus::LLMQType llmqType{Params().GetConsensus().llmqTypePlatform};
627 158 : return quorum_sign_helper(request, llmqType);
628 : },
629 : };
630 0 : }
631 :
632 35 : static bool VerifyRecoveredSigLatestQuorums(const Consensus::LLMQParams& llmq_params, const CChain& active_chain, const llmq::CQuorumManager& qman,
633 : int signHeight, const uint256& id, const uint256& msgHash, const CBLSSignature& sig)
634 : {
635 : // First check against the current active set, if it fails check against the last active set
636 70 : for (int signOffset : {0, llmq_params.dkgInterval}) {
637 56 : if (llmq::VerifyRecoveredSig(llmq_params.type, active_chain, qman, signHeight, id, msgHash, sig, signOffset) == llmq::VerifyRecSigStatus::Valid) {
638 21 : return true;
639 : }
640 : }
641 14 : return false;
642 35 : }
643 :
644 6176 : static RPCHelpMan quorum_verify()
645 : {
646 12352 : return RPCHelpMan{"quorum verify",
647 6176 : "Test if a quorum signature is valid for a request id and a message hash\n",
648 43232 : {
649 6176 : {"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
650 6176 : {"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
651 6176 : {"msgHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Message hash."},
652 6176 : {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "Quorum signature to verify."},
653 6176 : {"quorumHash", RPCArg::Type::STR_HEX, RPCArg::Default{""},
654 6176 : "The quorum identifier.\n"
655 : "Set to \"\" if you want to specify signHeight instead."},
656 6176 : {"signHeight", RPCArg::Type::NUM, RPCArg::Default{-1},
657 6176 : "The height at which the message was signed.\n"
658 : "Only works when quorumHash is \"\"."},
659 : },
660 6176 : RPCResult{RPCResult::Type::BOOL, "", "Returns true if the signature is valid"},
661 6176 : RPCExamples{""},
662 6214 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
663 : {
664 38 : const NodeContext& node = EnsureAnyNodeContext(request.context);
665 38 : const ChainstateManager& chainman = EnsureChainman(node);
666 38 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
667 :
668 38 : const Consensus::LLMQType llmqType{static_cast<Consensus::LLMQType>(request.params[0].getInt<int>())};
669 :
670 38 : const auto llmq_params_opt = Params().GetLLMQ(llmqType);
671 38 : if (!llmq_params_opt.has_value()) {
672 5 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
673 : }
674 :
675 38 : const uint256 id(ParseHashV(request.params[1], "id"));
676 38 : const uint256 msgHash(ParseHashV(request.params[2], "msgHash"));
677 :
678 38 : const bool use_bls_legacy = bls::bls_legacy_scheme.load();
679 38 : CBLSSignature sig;
680 38 : if (!sig.SetHexStr(request.params[3].get_str(), use_bls_legacy)) {
681 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature format");
682 : }
683 :
684 38 : if (request.params[4].isNull() || (request.params[4].get_str().empty() && !request.params[5].isNull())) {
685 23 : int signHeight{-1};
686 23 : if (!request.params[5].isNull()) {
687 13 : signHeight = request.params[5].getInt<int>();
688 13 : }
689 23 : return VerifyRecoveredSigLatestQuorums(*llmq_params_opt, chainman.ActiveChain(), *llmq_ctx.qman, signHeight, id, msgHash, sig);
690 : }
691 :
692 15 : uint256 quorumHash(ParseHashV(request.params[4], "quorumHash"));
693 15 : const auto quorum = llmq_ctx.qman->GetQuorum(llmqType, quorumHash);
694 :
695 15 : if (!quorum) {
696 5 : throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found");
697 : }
698 :
699 10 : llmq::SignHash signHash{llmqType, quorum->qc->quorumHash, id, msgHash};
700 10 : return sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get());
701 43 : },
702 : };
703 0 : }
704 :
705 8629 : static RPCHelpMan quorum_hasrecsig()
706 : {
707 17258 : return RPCHelpMan{"quorum hasrecsig",
708 8629 : "Test if a valid recovered signature is present\n",
709 34516 : {
710 8629 : {"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
711 8629 : {"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
712 8629 : {"msgHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Message hash."},
713 : },
714 8629 : RPCResult{RPCResult::Type::BOOL, "", "Returns true if node has this recovered signature"},
715 8629 : RPCExamples{""},
716 11120 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
717 : {
718 2491 : const NodeContext& node = EnsureAnyNodeContext(request.context);
719 2491 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
720 :
721 2491 : const Consensus::LLMQType llmqType{static_cast<Consensus::LLMQType>(request.params[0].getInt<int>())};
722 2491 : if (!Params().GetLLMQ(llmqType).has_value()) {
723 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
724 : }
725 :
726 2491 : const uint256 id(ParseHashV(request.params[1], "id"));
727 2491 : const uint256 msgHash(ParseHashV(request.params[2], "msgHash"));
728 :
729 2491 : return llmq_ctx.sigman->HasRecoveredSig(llmqType, id, msgHash);
730 0 : },
731 : };
732 0 : }
733 :
734 6244 : static RPCHelpMan quorum_getrecsig()
735 : {
736 12488 : return RPCHelpMan{"quorum getrecsig",
737 6244 : "Get a recovered signature\n",
738 24976 : {
739 6244 : {"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
740 6244 : {"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
741 6244 : {"msgHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Message hash."},
742 : },
743 6244 : llmq::CRecoveredSig::GetJsonHelp(/*key=*/"", /*optional=*/false),
744 6244 : RPCExamples{""},
745 6350 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
746 : {
747 106 : const NodeContext& node = EnsureAnyNodeContext(request.context);
748 106 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
749 :
750 106 : const Consensus::LLMQType llmqType{static_cast<Consensus::LLMQType>(request.params[0].getInt<int>())};
751 106 : if (!Params().GetLLMQ(llmqType).has_value()) {
752 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
753 : }
754 :
755 106 : const uint256 id(ParseHashV(request.params[1], "id"));
756 106 : const uint256 msgHash(ParseHashV(request.params[2], "msgHash"));
757 :
758 106 : llmq::CRecoveredSig recSig;
759 106 : if (!llmq_ctx.sigman->GetRecoveredSigForId(llmqType, id, recSig)) {
760 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "recovered signature not found");
761 : }
762 106 : if (recSig.getMsgHash() != msgHash) {
763 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "recovered signature not found");
764 : }
765 106 : return recSig.ToJson();
766 106 : },
767 : };
768 0 : }
769 :
770 10398 : static RPCHelpMan quorum_isconflicting()
771 : {
772 20796 : return RPCHelpMan{"quorum isconflicting",
773 10398 : "Test if a conflict exists\n",
774 41592 : {
775 10398 : {"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
776 10398 : {"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
777 10398 : {"msgHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Message hash."},
778 : },
779 10398 : RPCResult{RPCResult::Type::BOOL, "", "Returns true if this msgHash is conflicting with previous signing sessions"},
780 10398 : RPCExamples{""},
781 14658 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
782 : {
783 4260 : const NodeContext& node = EnsureAnyNodeContext(request.context);
784 4260 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
785 :
786 4260 : const Consensus::LLMQType llmqType{static_cast<Consensus::LLMQType>(request.params[0].getInt<int>())};
787 4260 : if (!Params().GetLLMQ(llmqType).has_value()) {
788 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
789 : }
790 :
791 4260 : const uint256 id(ParseHashV(request.params[1], "id"));
792 4260 : const uint256 msgHash(ParseHashV(request.params[2], "msgHash"));
793 :
794 4260 : return llmq_ctx.sigman->IsConflicting(llmqType, id, msgHash);
795 0 : },
796 : };
797 0 : }
798 :
799 6336 : static RPCHelpMan quorum_selectquorum()
800 : {
801 12672 : return RPCHelpMan{"quorum selectquorum",
802 6336 : "Returns the quorum that would/should sign a request\n",
803 19008 : {
804 6336 : {"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "LLMQ type."},
805 6336 : {"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
806 : },
807 6336 : RPCResult{
808 6336 : RPCResult::Type::OBJ, "", "",
809 19008 : {
810 6336 : {RPCResult::Type::STR_HEX, "quorumHash", "Hash of chosen quorum"},
811 12672 : {RPCResult::Type::ARR, "recoveryMembers", "List of members to use for signature recovery",
812 6336 : {{RPCResult::Type::STR_HEX, "hash", "ProTxHash of member"}}
813 : },
814 : }
815 : },
816 6336 : RPCExamples{""},
817 6534 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
818 : {
819 198 : const NodeContext& node = EnsureAnyNodeContext(request.context);
820 198 : const ChainstateManager& chainman = EnsureChainman(node);
821 198 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
822 :
823 198 : const Consensus::LLMQType llmqType{static_cast<Consensus::LLMQType>(request.params[0].getInt<int>())};
824 198 : const auto llmq_params_opt = Params().GetLLMQ(llmqType);
825 198 : if (!llmq_params_opt.has_value()) {
826 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
827 : }
828 :
829 198 : const uint256 id(ParseHashV(request.params[1], "id"));
830 :
831 198 : UniValue ret(UniValue::VOBJ);
832 :
833 198 : const auto quorum = llmq::SelectQuorumForSigning(llmq_params_opt.value(), chainman.ActiveChain(), *llmq_ctx.qman, id);
834 198 : if (!quorum) {
835 0 : throw JSONRPCError(RPC_MISC_ERROR, "no quorums active");
836 : }
837 198 : ret.pushKV("quorumHash", quorum->qc->quorumHash.ToString());
838 :
839 198 : UniValue recoveryMembers(UniValue::VARR);
840 792 : for (int i = 0; i < quorum->params.recoveryMembers; ++i) {
841 594 : auto dmn = llmq::CSigSharesManager::SelectMemberForRecovery(*quorum, id, i);
842 594 : recoveryMembers.push_back(dmn->proTxHash.ToString());
843 594 : }
844 198 : ret.pushKV("recoveryMembers", recoveryMembers);
845 :
846 198 : return ret;
847 198 : },
848 : };
849 0 : }
850 :
851 6164 : static RPCHelpMan quorum_dkgsimerror()
852 : {
853 12328 : return RPCHelpMan{"quorum dkgsimerror",
854 6164 : "This enables simulation of errors and malicious behaviour in the DKG. Do NOT use this on mainnet\n"
855 : "as you will get yourself very likely PoSe banned for this.\n",
856 18492 : {
857 6164 : {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "Error type."},
858 6164 : {"rate", RPCArg::Type::NUM, RPCArg::Optional::NO, "Rate at which to simulate this error type (between 0 and 100)."},
859 : },
860 6164 : RPCResult{RPCResult::Type::NONE, "", ""},
861 6164 : RPCExamples{""},
862 6190 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
863 : {
864 26 : std::string type_str = request.params[0].get_str();
865 26 : int32_t rate = request.params[1].getInt<int>();
866 :
867 26 : if (rate < 0 || rate > 100) {
868 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid rate. Must be between 0 and 100");
869 : }
870 :
871 26 : if (const llmq::DKGError::type type = llmq::DKGError::from_string(type_str);
872 26 : type == llmq::DKGError::type::_COUNT) {
873 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid type. See DKGError class implementation");
874 : } else {
875 26 : llmq::SetSimulatedDKGErrorRate(type, static_cast<double>(rate) / 100);
876 26 : return NullUniValue;
877 : }
878 26 : },
879 : };
880 0 : }
881 :
882 6192 : static RPCHelpMan quorum_getdata()
883 : {
884 12384 : return RPCHelpMan{"quorum getdata",
885 6192 : "Send a QGETDATA message to the specified peer.\n",
886 37152 : {
887 6192 : {"nodeId", RPCArg::Type::NUM, RPCArg::Optional::NO, "The internal nodeId of the peer to request quorum data from."},
888 6192 : {"llmqType", RPCArg::Type::NUM, RPCArg::Optional::NO, "The quorum type related to the quorum data being requested."},
889 6192 : {"quorumHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The quorum hash related to the quorum data being requested."},
890 6192 : {"dataMask", RPCArg::Type::NUM, RPCArg::Optional::NO,
891 6192 : "Specify what data to request.\n"
892 : "Possible values: 1 - Request quorum verification vector\n"
893 : "2 - Request encrypted contributions for member defined by \"proTxHash\". \"proTxHash\" must be specified if this option is used.\n"
894 : "3 - Request both, 1 and 2"},
895 6192 : {"proTxHash", RPCArg::Type::STR_HEX, RPCArg::Default{""}, "The proTxHash the contributions will be requested for. Must be member of the specified LLMQ."},
896 : },
897 6192 : RPCResult{RPCResult::Type::BOOL, "", "Returns true if the message QGETDATA has been successfully sent"},
898 6192 : RPCExamples{""},
899 6246 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
900 : {
901 54 : const NodeContext& node = EnsureAnyNodeContext(request.context);
902 54 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
903 54 : CConnman& connman = EnsureConnman(node);
904 :
905 54 : NodeId nodeId = request.params[0].getInt<int64_t>();
906 54 : Consensus::LLMQType llmqType = static_cast<Consensus::LLMQType>(request.params[1].getInt<int>());
907 66 : uint256 quorumHash(ParseHashV(request.params[2], "quorumHash"));
908 54 : uint16_t nDataMask = static_cast<uint16_t>(request.params[3].getInt<int>());
909 54 : uint256 proTxHash;
910 :
911 : // Check if request wants ENCRYPTED_CONTRIBUTIONS data
912 54 : if (nDataMask & llmq::CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS) {
913 42 : if (!request.params[4].isNull()) {
914 36 : proTxHash = ParseHashV(request.params[4], "proTxHash");
915 36 : if (proTxHash.IsNull()) {
916 6 : throw JSONRPCError(RPC_INVALID_PARAMETER, "proTxHash invalid");
917 : }
918 30 : } else {
919 6 : throw JSONRPCError(RPC_INVALID_PARAMETER, "proTxHash missing");
920 : }
921 30 : }
922 :
923 42 : const auto quorum = llmq_ctx.qman->GetQuorum(llmqType, quorumHash);
924 42 : if (!quorum) {
925 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found");
926 : }
927 84 : return connman.ForNode(nodeId, [&](CNode* pNode) {
928 42 : if (pNode->GetVerifiedProRegTxHash().IsNull()) return false;
929 42 : if (!quorum->m_quorum_base_block_index) return false;
930 42 : const llmq::CQuorumDataRequest request(llmqType, quorum->qc->quorumHash, nDataMask, proTxHash);
931 42 : const llmq::CQuorumDataRequestKey key(pNode->GetVerifiedProRegTxHash(), true, quorum->qc->quorumHash, llmqType);
932 42 : if (!llmq_ctx.qman->RegisterDataRequest(key, request)) return false;
933 42 : connman.PushMessage(pNode, CNetMsgMaker(pNode->GetCommonVersion()).Make(NetMsgType::QGETDATA, request));
934 42 : return true;
935 42 : });
936 54 : },
937 : };
938 0 : }
939 :
940 6142 : static RPCHelpMan quorum_rotationinfo()
941 : {
942 6142 : return RPCHelpMan{
943 6142 : "quorum rotationinfo",
944 6142 : "Get quorum rotation information\n",
945 24568 : {
946 6142 : {"blockRequestHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The blockHash of the request."},
947 6142 : {"extraShare", RPCArg::Type::BOOL, RPCArg::Default{false}, "Extra share"},
948 12284 : {"baseBlockHashes", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "The list of block hashes",
949 12284 : {
950 6142 : {"baseBlockHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
951 : }},
952 : },
953 6142 : llmq::CQuorumRotationInfo::GetJsonHelp(/*key=*/"", /*optional=*/false),
954 6142 : RPCExamples{""},
955 6146 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
956 : {
957 4 : const NodeContext& node = EnsureAnyNodeContext(request.context);
958 4 : const ChainstateManager& chainman = EnsureChainman(node);
959 4 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
960 :
961 4 : llmq::CGetQuorumRotationInfo cmd;
962 4 : llmq::CQuorumRotationInfo quorumRotationInfoRet;
963 4 : std::string strError;
964 :
965 4 : cmd.blockRequestHash = ParseHashV(request.params[0], "blockRequestHash");
966 4 : cmd.extraShare = request.params[1].isNull() ? false : ParseBoolV(request.params[1], "extraShare");
967 :
968 4 : if (!request.params[2].isNull()) {
969 4 : const auto& hashes = request.params[2].get_array();
970 10 : for (const auto& hash : hashes.getValues()) {
971 6 : cmd.baseBlockHashes.emplace_back(ParseHashV(hash, "baseBlockHash"));
972 : }
973 4 : }
974 :
975 4 : LOCK(cs_main);
976 :
977 4 : if (!BuildQuorumRotationInfo(*CHECK_NONFATAL(node.dmnman), *llmq_ctx.qsnapman, chainman, *llmq_ctx.qman,
978 4 : *llmq_ctx.quorum_block_processor, cmd, false, quorumRotationInfoRet, strError)) {
979 0 : throw JSONRPCError(RPC_INVALID_REQUEST, strError);
980 : }
981 :
982 4 : return quorumRotationInfoRet.ToJson();
983 4 : },
984 : };
985 0 : }
986 :
987 6170 : static RPCHelpMan quorum_dkginfo()
988 : {
989 6170 : return RPCHelpMan{
990 6170 : "quorum dkginfo",
991 6170 : "Return information regarding DKGs.\n",
992 6170 : {
993 6170 : {},
994 : },
995 6170 : RPCResult{
996 6170 : RPCResult::Type::OBJ, "", "",
997 18510 : {
998 6170 : {RPCResult::Type::NUM, "active_dkgs", "Total number of active DKG sessions this node is participating in right now"},
999 6170 : {RPCResult::Type::NUM, "next_dkg", "The number of blocks until the next potential DKG session"},
1000 : }
1001 : },
1002 6170 : RPCExamples{""},
1003 6202 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1004 : {
1005 32 : const NodeContext& node = EnsureAnyNodeContext(request.context);
1006 32 : if (!node.active_ctx && !node.observer_ctx) {
1007 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, "Only available in masternode or watch-only mode.");
1008 : }
1009 32 : const auto& dkgdbgman = *(node.active_ctx ? node.active_ctx->dkgdbgman.get() : node.observer_ctx->dkgdbgman.get());
1010 :
1011 32 : UniValue ret(UniValue::VOBJ);
1012 32 : ret.pushKV("active_dkgs", dkgdbgman.GetSessionCount());
1013 :
1014 32 : const ChainstateManager& chainman = EnsureChainman(node);
1015 64 : const int nTipHeight{WITH_LOCK(cs_main, return chainman.ActiveChain().Height())};
1016 64 : auto minNextDKG = [](const Consensus::Params& consensusParams, int nTipHeight) {
1017 32 : int minDkgWindow{std::numeric_limits<int>::max()};
1018 192 : for (const auto& params: consensusParams.llmqs) {
1019 160 : if (params.useRotation && (nTipHeight % params.dkgInterval <= params.signingActiveQuorumCount)) {
1020 0 : return 1;
1021 : }
1022 160 : minDkgWindow = std::min(minDkgWindow, params.dkgInterval - (nTipHeight % params.dkgInterval));
1023 : }
1024 32 : return minDkgWindow;
1025 32 : };
1026 32 : ret.pushKV("next_dkg", minNextDKG(Params().GetConsensus(), nTipHeight));
1027 :
1028 32 : return ret;
1029 32 : },
1030 : };
1031 0 : }
1032 :
1033 6156 : static RPCHelpMan quorum_help()
1034 : {
1035 6156 : return RPCHelpMan{
1036 6156 : "quorum",
1037 6156 : "Set of commands for quorums/LLMQs.\n"
1038 : "To get help on individual commands, use \"help quorum command\".\n"
1039 : "\nAvailable commands:\n"
1040 : " list - List of on-chain quorums\n"
1041 : " listextended - Extended list of on-chain quorums\n"
1042 : " info - Return information about a quorum\n"
1043 : " dkginfo - Return information about DKGs\n"
1044 : " dkgsimerror - Simulates DKG errors and malicious behavior\n"
1045 : " dkgstatus - Return the status of the current DKG process\n"
1046 : " memberof - Checks which quorums the given masternode is a member of\n"
1047 : " sign - Threshold-sign a message\n"
1048 : " verify - Test if a quorum signature is valid for a request id and a message hash\n"
1049 : " hasrecsig - Test if a valid recovered signature is present\n"
1050 : " getrecsig - Get a recovered signature\n"
1051 : " isconflicting - Test if a conflict exists\n"
1052 : " selectquorum - Return the quorum that would/should sign a request\n"
1053 : " getdata - Request quorum data from other masternodes in the quorum\n"
1054 : " rotationinfo - Request quorum rotation information\n",
1055 12312 : {
1056 6156 : {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to execute"},
1057 : },
1058 6156 : RPCResult{RPCResult::Type::NONE, "", ""},
1059 6156 : RPCExamples{""},
1060 6156 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1061 : {
1062 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Must be a valid command");
1063 0 : },
1064 : };
1065 0 : }
1066 :
1067 6190 : static RPCHelpMan verifychainlock()
1068 : {
1069 12380 : return RPCHelpMan{"verifychainlock",
1070 6190 : "Test if a quorum signature is valid for a ChainLock.\n",
1071 24760 : {
1072 6190 : {"blockHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash of the ChainLock."},
1073 6190 : {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature of the ChainLock."},
1074 6190 : {"blockHeight", RPCArg::Type::NUM, RPCArg::DefaultHint{"There will be an internal lookup of \"blockHash\" if this is not provided."}, "The height of the ChainLock."},
1075 : },
1076 6190 : RPCResult{RPCResult::Type::BOOL, "", "Returns true if the chainlock is valid"},
1077 6190 : RPCExamples{""},
1078 6224 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1079 : {
1080 36 : const uint256 nBlockHash(ParseHashV(request.params[0], "blockHash"));
1081 :
1082 34 : const NodeContext& node = EnsureAnyNodeContext(request.context);
1083 34 : const ChainstateManager& chainman = EnsureChainman(node);
1084 :
1085 : int nBlockHeight;
1086 34 : const CBlockIndex* pIndex{nullptr};
1087 34 : if (request.params[2].isNull()) {
1088 12 : pIndex = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(nBlockHash));
1089 6 : if (pIndex == nullptr) {
1090 2 : throw JSONRPCError(RPC_INTERNAL_ERROR, "blockHash not found");
1091 : }
1092 4 : nBlockHeight = pIndex->nHeight;
1093 4 : } else {
1094 28 : nBlockHeight = request.params[2].getInt<int>();
1095 28 : LOCK(cs_main);
1096 28 : if (nBlockHeight < 0) {
1097 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
1098 : }
1099 28 : if (nBlockHeight <= chainman.ActiveChain().Height()) {
1100 26 : pIndex = chainman.ActiveChain()[nBlockHeight];
1101 26 : }
1102 28 : }
1103 :
1104 32 : CBLSSignature sig;
1105 32 : if (pIndex) {
1106 30 : const bool use_legacy_signature{!DeploymentActiveAfter(pIndex, chainman.GetConsensus(), Consensus::DEPLOYMENT_V19)};
1107 30 : if (!sig.SetHexStr(request.params[1].get_str(), use_legacy_signature)) {
1108 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature format");
1109 : }
1110 30 : } else {
1111 2 : if (!sig.SetHexStr(request.params[1].get_str(), false) &&
1112 0 : !sig.SetHexStr(request.params[1].get_str(), true)
1113 : ) {
1114 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature format");
1115 : }
1116 : }
1117 :
1118 32 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
1119 64 : return chainlock::VerifyChainLock(Params().GetConsensus(), chainman.ActiveChain(), *CHECK_NONFATAL(llmq_ctx.qman),
1120 32 : chainlock::ChainLockSig{nBlockHeight, nBlockHash, sig}) ==
1121 : llmq::VerifyRecSigStatus::Valid;
1122 2 : },
1123 : };
1124 0 : }
1125 :
1126 6168 : static RPCHelpMan verifyislock()
1127 : {
1128 12336 : return RPCHelpMan{"verifyislock",
1129 6168 : "Test if a quorum signature is valid for an InstantSend Lock\n",
1130 30840 : {
1131 6168 : {"id", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Request id."},
1132 6168 : {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id."},
1133 6168 : {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The InstantSend Lock signature to verify."},
1134 6168 : {"maxHeight", RPCArg::Type::NUM, RPCArg::Default{-1}, "The maximum height to search quorums from."},
1135 : },
1136 6168 : RPCResult{RPCResult::Type::BOOL, "", "Returns true if the instantsend lock is valid"},
1137 6168 : RPCExamples{""},
1138 6180 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1139 : {
1140 12 : const uint256 id(ParseHashV(request.params[0], "id"));
1141 12 : const uint256 txid(ParseHashV(request.params[1], "txid"));
1142 :
1143 12 : const NodeContext& node = EnsureAnyNodeContext(request.context);
1144 12 : const ChainstateManager& chainman = EnsureChainman(node);
1145 :
1146 12 : if (g_txindex) {
1147 12 : g_txindex->BlockUntilSyncedToCurrentChain();
1148 12 : }
1149 :
1150 12 : const CBlockIndex* pindexMined{nullptr};
1151 : {
1152 12 : LOCK(cs_main);
1153 12 : uint256 hash_block;
1154 12 : CTransactionRef tx = GetTransaction(/* block_index */ nullptr, /* mempool */ nullptr, txid, Params().GetConsensus(), hash_block);
1155 12 : if (tx && !hash_block.IsNull()) {
1156 4 : pindexMined = chainman.m_blockman.LookupBlockIndex(hash_block);
1157 4 : }
1158 12 : }
1159 :
1160 12 : int maxHeight{-1};
1161 12 : if (!request.params[3].isNull()) {
1162 10 : maxHeight = request.params[3].getInt<int>();
1163 10 : }
1164 :
1165 : int signHeight;
1166 12 : if (pindexMined == nullptr || pindexMined->nHeight > maxHeight) {
1167 10 : signHeight = maxHeight;
1168 10 : } else { // pindexMined->nHeight <= maxHeight
1169 2 : signHeight = pindexMined->nHeight;
1170 : }
1171 :
1172 12 : CBlockIndex* pBlockIndex{nullptr};
1173 : {
1174 12 : LOCK(cs_main);
1175 12 : if (signHeight == -1) {
1176 2 : pBlockIndex = chainman.ActiveChain().Tip();
1177 2 : } else {
1178 10 : pBlockIndex = chainman.ActiveChain()[signHeight];
1179 : }
1180 12 : }
1181 :
1182 12 : CHECK_NONFATAL(pBlockIndex != nullptr);
1183 :
1184 12 : CBLSSignature sig;
1185 12 : const bool use_bls_legacy{!DeploymentActiveAfter(pBlockIndex, chainman.GetConsensus(), Consensus::DEPLOYMENT_V19)};
1186 12 : if (!sig.SetHexStr(request.params[2].get_str(), use_bls_legacy)) {
1187 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature format");
1188 : }
1189 :
1190 12 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
1191 :
1192 12 : auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend;
1193 12 : const auto llmq_params_opt = Params().GetLLMQ(llmqType);
1194 12 : CHECK_NONFATAL(llmq_params_opt.has_value());
1195 24 : return VerifyRecoveredSigLatestQuorums(*llmq_params_opt, chainman.ActiveChain(), *CHECK_NONFATAL(llmq_ctx.qman),
1196 12 : signHeight, id, txid, sig);
1197 0 : },
1198 : };
1199 0 : }
1200 :
1201 6165 : static RPCHelpMan submitchainlock()
1202 : {
1203 12330 : return RPCHelpMan{"submitchainlock",
1204 6165 : "Submit a ChainLock signature if needed\n",
1205 24660 : {
1206 6165 : {"blockHash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash of the ChainLock."},
1207 6165 : {"signature", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The signature of the ChainLock."},
1208 6165 : {"blockHeight", RPCArg::Type::NUM, RPCArg::Optional::NO, "The height of the ChainLock."},
1209 : },
1210 6165 : RPCResult{
1211 6165 : RPCResult::Type::NUM, "", "The height of the current best ChainLock"},
1212 6165 : RPCExamples{""},
1213 6174 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1214 : {
1215 12 : const uint256 nBlockHash(ParseHashV(request.params[0], "blockHash"));
1216 :
1217 9 : const int nBlockHeight = request.params[2].getInt<int>();
1218 9 : if (nBlockHeight <= 0) {
1219 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid block height");
1220 : }
1221 9 : const NodeContext& node = EnsureAnyNodeContext(request.context);
1222 9 : const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
1223 9 : CHECK_NONFATAL(node.chainlocks);
1224 9 : const int32_t bestCLHeight = node.chainlocks->GetBestChainLock().getHeight();
1225 9 : if (nBlockHeight <= bestCLHeight) return bestCLHeight;
1226 :
1227 6 : CBLSSignature sig;
1228 6 : if (!sig.SetHexStr(request.params[1].get_str(), false) && !sig.SetHexStr(request.params[1].get_str(), true)) {
1229 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature format");
1230 : }
1231 :
1232 6 : const ChainstateManager& chainman = EnsureChainman(node);
1233 6 : const auto clsig{chainlock::ChainLockSig(nBlockHeight, nBlockHash, sig)};
1234 6 : const llmq::VerifyRecSigStatus ret{
1235 6 : chainlock::VerifyChainLock(Params().GetConsensus(), chainman.ActiveChain(), *llmq_ctx.qman, clsig)};
1236 6 : if (ret == llmq::VerifyRecSigStatus::NoQuorum) {
1237 3 : LOCK(cs_main);
1238 3 : const CBlockIndex* pIndex{chainman.ActiveChain().Tip()};
1239 3 : throw JSONRPCError(RPC_MISC_ERROR, strprintf("No quorum found. Current tip height: %d hash: %s\n", pIndex->nHeight, pIndex->GetBlockHash().ToString()));
1240 3 : }
1241 3 : if (ret != llmq::VerifyRecSigStatus::Valid) {
1242 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature");
1243 : }
1244 :
1245 3 : PeerManager& peerman = EnsurePeerman(node);
1246 3 : CHECK_NONFATAL(node.clhandler);
1247 3 : peerman.PostProcessMessage(node.clhandler->ProcessNewChainLock(-1, clsig, *llmq_ctx.qman, ::SerializeHash(clsig)));
1248 3 : return node.chainlocks->GetBestChainLock().getHeight();
1249 12 : },
1250 : };
1251 0 : }
1252 :
1253 :
1254 3201 : void RegisterQuorumsRPCCommands(CRPCTable &tableRPC)
1255 : {
1256 64581 : static const CRPCCommand commands[]{
1257 3069 : {"evo", &quorum_help},
1258 3069 : {"evo", &quorum_list},
1259 3069 : {"evo", &quorum_list_extended},
1260 3069 : {"evo", &quorum_info},
1261 3069 : {"evo", &quorum_dkginfo},
1262 3069 : {"evo", &quorum_dkgstatus},
1263 3069 : {"evo", &quorum_memberof},
1264 3069 : {"evo", &quorum_sign},
1265 3069 : {"evo", &quorum_platformsign},
1266 3069 : {"evo", &quorum_verify},
1267 3069 : {"evo", &quorum_hasrecsig},
1268 3069 : {"evo", &quorum_getrecsig},
1269 3069 : {"evo", &quorum_isconflicting},
1270 3069 : {"evo", &quorum_selectquorum},
1271 3069 : {"evo", &quorum_dkgsimerror},
1272 3069 : {"evo", &quorum_getdata},
1273 3069 : {"evo", &quorum_rotationinfo},
1274 3069 : {"evo", &submitchainlock},
1275 3069 : {"evo", &verifychainlock},
1276 3069 : {"evo", &verifyislock},
1277 : };
1278 67221 : for (const auto& command : commands) {
1279 64020 : tableRPC.appendCommand(command.name, &command);
1280 : }
1281 3201 : }
|