Line data Source code
1 : // Copyright (c) 2018-2021 The Dash Core developers
2 : // Copyright (c) 2022 The PIVX Core developers
3 : // Distributed under the MIT software license, see the accompanying
4 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include "activemasternode.h"
7 : #include "chainparams.h"
8 : #include "llmq/quorums.h"
9 : #include "llmq/quorums_blockprocessor.h"
10 : #include "llmq/quorums_commitment.h"
11 : #include "llmq/quorums_debug.h"
12 : #include "llmq/quorums_dkgsession.h"
13 : #include "llmq/quorums_signing.h"
14 : #include "llmq/quorums_signing_shares.h"
15 : #include "rpc/server.h"
16 : #include "validation.h"
17 :
18 : #include <string>
19 :
20 16 : UniValue signsession(const JSONRPCRequest& request)
21 : {
22 16 : if (!Params().IsTestChain()) {
23 0 : throw JSONRPCError(RPC_MISC_ERROR, "command available only for RegTest and TestNet network");
24 : }
25 16 : if (request.fHelp || (request.params.size() != 3)) {
26 0 : throw std::runtime_error(
27 : "signsession llmqType \"id\" \"msgHash\"\n"
28 : "\nArguments:\n"
29 : "1. llmqType (int, required) LLMQ type.\n"
30 : "2. \"id\" (string, required) Request id.\n"
31 : "3. \"msgHash\" (string, required) Message hash.\n"
32 :
33 : "\nResult:\n"
34 : "n (bool) True if the sign was successful, false otherwise\n"
35 :
36 0 : "\nExample:\n" +
37 0 : HelpExampleRpc("signsession", "100 \"xxx\", \"xxx\"") + HelpExampleCli("signsession", "100 \"xxx\", \"xxx\""));
38 : }
39 16 : Consensus::LLMQType llmqType = static_cast<Consensus::LLMQType>(request.params[0].get_int());
40 16 : if (!Params().GetConsensus().llmqs.count(llmqType)) {
41 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
42 : }
43 :
44 16 : uint256 id = ParseHashV(request.params[1], "id");
45 16 : uint256 msgHash = ParseHashV(request.params[2], "msgHash");
46 16 : return llmq::quorumSigningManager->AsyncSignIfMember(llmqType, id, msgHash);
47 : }
48 :
49 12 : UniValue hasrecoverysignature(const JSONRPCRequest& request)
50 : {
51 12 : if (!Params().IsTestChain()) {
52 0 : throw JSONRPCError(RPC_MISC_ERROR, "command available only for RegTest and TestNet network");
53 : }
54 12 : if (request.fHelp || (request.params.size() != 3)) {
55 0 : throw std::runtime_error(
56 : "hasrecoverysignature llmqType \"id\" \"msgHash\"\n"
57 : "\nArguments:\n"
58 : "1. llmqType (int, required) LLMQ type.\n"
59 : "2. \"id\" (string, required) Request id.\n"
60 : "3. \"msgHash\" (string, required) Message hash.\n"
61 :
62 : "\nResult:\n"
63 : "n (bool) True if you have already received a recovery signature for the given signing session\n"
64 :
65 0 : "\nExample:\n" +
66 0 : HelpExampleRpc("hasrecoverysignature", "100 \"xxx\", \"xxx\"") + HelpExampleCli("hasrecoverysignature", "100 \"xxx\", \"xxx\""));
67 : }
68 12 : Consensus::LLMQType llmqType = static_cast<Consensus::LLMQType>(request.params[0].get_int());
69 12 : if (!Params().GetConsensus().llmqs.count(llmqType)) {
70 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
71 : }
72 :
73 12 : uint256 id = ParseHashV(request.params[1], "id");
74 12 : uint256 msgHash = ParseHashV(request.params[2], "msgHash");
75 12 : return llmq::quorumSigningManager->HasRecoveredSig(llmqType, id, msgHash);
76 : }
77 :
78 0 : UniValue issessionconflicting(const JSONRPCRequest& request)
79 : {
80 0 : if (!Params().IsTestChain()) {
81 0 : throw JSONRPCError(RPC_MISC_ERROR, "command available only for RegTest and TestNet network");
82 : }
83 0 : if (request.fHelp || (request.params.size() != 3)) {
84 0 : throw std::runtime_error(
85 : "issessionconflicting llmqType \"id\" \"msgHash\"\n"
86 : "\nArguments:\n"
87 : "1. llmqType (int, required) LLMQ type.\n"
88 : "2. \"id\" (string, required) Request id.\n"
89 : "3. \"msgHash\" (string, required) Message hash.\n"
90 :
91 : "\nResult:\n"
92 : "n (bool) True if you have the recovery signature of an another signing session with same id but different msgHash\n"
93 :
94 0 : "\nExample:\n" +
95 0 : HelpExampleRpc("issessionconflicting", "100 \"xxx\", \"xxx\"") + HelpExampleCli("issessionconflicting", "100 \"xxx\", \"xxx\""));
96 : }
97 0 : Consensus::LLMQType llmqType = static_cast<Consensus::LLMQType>(request.params[0].get_int());
98 0 : if (!Params().GetConsensus().llmqs.count(llmqType)) {
99 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
100 : }
101 :
102 0 : uint256 id = ParseHashV(request.params[1], "id");
103 0 : uint256 msgHash = ParseHashV(request.params[2], "msgHash");
104 0 : return llmq::quorumSigningManager->IsConflicting(llmqType, id, msgHash);
105 : }
106 :
107 0 : UniValue listquorums(const JSONRPCRequest& request)
108 : {
109 0 : if (request.fHelp || request.params.size() > 1) {
110 0 : throw std::runtime_error(
111 : "listquorums ( count )\n"
112 : "\nArguments:\n"
113 : "1. count (numeric, optional, default=10) Number of quorums to list\n"
114 :
115 : "\nResult:\n"
116 : "{\n"
117 : " \"llmqType\": [ (array of string) A json array of quorum hashes for a given llmqType\n"
118 : " \"quorumhash\", (string) Block hash of the quorum\n"
119 : " ...\n"
120 : " ],\n"
121 : " ...\n"
122 : "}\n"
123 :
124 0 : "\nExample:\n" +
125 0 : HelpExampleRpc("listquorums", "1") + HelpExampleCli("listquorums", "1"));
126 : }
127 :
128 0 : LOCK(cs_main);
129 0 : int count = 10;
130 0 : if(request.params.size() == 1) {
131 0 : count = request.params[0].get_int();
132 0 : if(count <= 0) {
133 0 : throw std::runtime_error(
134 0 : "count cannot be 0 or negative!\n");
135 : }
136 : }
137 0 : UniValue ret(UniValue::VOBJ);
138 :
139 0 : for (auto& p : Params().GetConsensus().llmqs) {
140 0 : UniValue v(UniValue::VARR);
141 :
142 0 : auto quorums = llmq::quorumManager->ScanQuorums(p.first, chainActive.Tip(), count);
143 0 : for (auto& q : quorums) {
144 0 : v.push_back(q->qc.quorumHash.ToString());
145 : }
146 :
147 0 : ret.pushKV(p.second.name, v);
148 : }
149 :
150 :
151 0 : return ret;
152 : }
153 :
154 0 : UniValue getquoruminfo(const JSONRPCRequest& request)
155 : {
156 0 : if (request.fHelp || request.params.size() > 3 || request.params.size() < 2)
157 0 : throw std::runtime_error(
158 : "getquoruminfo llmqType \"quorumHash\" ( includeSkShare )\n"
159 : "\nArguments:\n"
160 : "1. llmqType (numeric, required) LLMQ type.\n"
161 : "2. \"quorumHash\" (string, required) Block hash of quorum.\n"
162 : "3. includeSkShare (boolean, optional) Include secret key share in output.\n"
163 :
164 : "\nResult:\n"
165 : "{\n"
166 : " \"height\": n, (numeric) The starting block height of the quorum\n"
167 : " \"quorumHash\": \"quorumHash\", (string) Block hash of the quorum\n"
168 : " \"members\": [ (array of json objects)\n"
169 : " {\n"
170 : " \"proTxHash\": \"proTxHash\" (string) ProTxHash of the quorum member\n"
171 : " \"valid\": true/false (boolean) True/false if the member is valid/invalid\n"
172 : " \"pubKeyShare\": pubKeyShare (string) Quorum public key share of the member, will be outputted only if the command is performed by another quorum member or watcher\n"
173 : " },\n"
174 : " ...\n"
175 : " ],\n"
176 : " \"quorumPublicKey\": quorumPublicKey, (string) Public key of the quorum\n"
177 : " \"secretKeyShare\": secretKeyShare (string) This is outputted only if includeSkShare=true and the command is performed by a valid member of the quorum. It corresponds to the secret key share of that member\n"
178 : "}\n"
179 :
180 0 : "\nExample:\n" +
181 0 : HelpExampleRpc("getquoruminfo", "2 \"xxx\", true") + HelpExampleCli("getquoruminfo", "2, \"xxx\",true"));
182 :
183 0 : LOCK(cs_main);
184 :
185 0 : Consensus::LLMQType llmqType = static_cast<Consensus::LLMQType>(request.params[0].get_int());
186 0 : if (!Params().GetConsensus().llmqs.count(llmqType)) {
187 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid llmqType");
188 : }
189 :
190 0 : uint256 blockHash = ParseHashV(request.params[1], "quorumHash");
191 0 : bool includeSkShare = false;
192 0 : if (request.params.size() > 2) {
193 0 : includeSkShare = request.params[2].get_bool();
194 : }
195 :
196 0 : auto quorum = llmq::quorumManager->GetQuorum(llmqType, blockHash);
197 0 : if (!quorum) {
198 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found");
199 : }
200 :
201 0 : UniValue ret(UniValue::VOBJ);
202 :
203 0 : ret.pushKV("height", quorum->pindexQuorum->nHeight);
204 0 : ret.pushKV("quorumHash", quorum->qc.quorumHash.ToString());
205 :
206 0 : UniValue membersArr(UniValue::VARR);
207 0 : for (size_t i = 0; i < quorum->members.size(); i++) {
208 0 : auto& dmn = quorum->members[i];
209 0 : UniValue mo(UniValue::VOBJ);
210 0 : mo.pushKV("proTxHash", dmn->proTxHash.ToString());
211 0 : mo.pushKV("valid", quorum->qc.validMembers[i]);
212 0 : if (quorum->qc.validMembers[i]) {
213 0 : CBLSPublicKey pubKey = quorum->GetPubKeyShare(i);
214 0 : if (pubKey.IsValid()) {
215 0 : mo.pushKV("pubKeyShare", pubKey.ToString());
216 : }
217 : }
218 0 : membersArr.push_back(mo);
219 : }
220 :
221 0 : ret.pushKV("members", membersArr);
222 0 : ret.pushKV("quorumPublicKey", quorum->qc.quorumPublicKey.ToString());
223 0 : CBLSSecretKey skShare = quorum->GetSkShare();
224 0 : if (includeSkShare && skShare.IsValid()) {
225 0 : ret.pushKV("secretKeyShare", skShare.ToString());
226 : }
227 :
228 0 : return ret;
229 : }
230 :
231 18 : UniValue getminedcommitment(const JSONRPCRequest& request)
232 : {
233 18 : if (request.fHelp || request.params.size() != 2) {
234 0 : throw std::runtime_error(
235 : "getminedcommitment llmq_type quorum_hash\n"
236 : "Return information about the commitment for given quorum.\n"
237 : "\nArguments:\n"
238 : "1. llmq_type (number, required) LLMQ type.\n"
239 : "2. quorum_hash (hex string, required) LLMQ hash.\n"
240 : "\nExamples:\n"
241 0 : + HelpExampleRpc("getminedcommitment", "2 \"xxx\"")
242 0 : + HelpExampleCli("getminedcommitment", "2, \"xxx\"")
243 0 : );
244 : }
245 :
246 18 : Consensus::LLMQType llmq_type = static_cast<Consensus::LLMQType>(request.params[0].get_int());
247 18 : if (!Params().GetConsensus().llmqs.count(llmq_type)) {
248 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid llmq_type");
249 : }
250 18 : const uint256& quorum_hash = ParseHashV(request.params[1], "quorum_hash");
251 36 : if (WITH_LOCK(cs_main, return LookupBlockIndex(quorum_hash)) == nullptr) {
252 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid quorum_hash");
253 : }
254 :
255 18 : llmq::CFinalCommitment qc;
256 18 : uint256 block_hash;
257 18 : if (!llmq::quorumBlockProcessor->GetMinedCommitment(llmq_type, quorum_hash, qc, block_hash)) {
258 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "mined commitment not found");
259 : }
260 :
261 17 : UniValue ret(UniValue::VOBJ);
262 17 : qc.ToJson(ret);
263 34 : ret.pushKV("block_hash", block_hash.ToString());
264 34 : return ret;
265 : }
266 :
267 19 : UniValue getquorummembers(const JSONRPCRequest& request)
268 : {
269 19 : if (request.fHelp || request.params.size() != 2) {
270 0 : throw std::runtime_error(
271 : "getquorummembers llmq_type quorum_hash\n"
272 : "Return the list of proTx hashes for given quorum.\n"
273 : "\nArguments:\n"
274 : "1. llmq_type (number, required) LLMQ type.\n"
275 : "2. quorum_hash (hex string, required) LLMQ hash.\n"
276 : "\nExamples:\n"
277 0 : + HelpExampleRpc("getquorummembers", "2 \"xxx\"")
278 0 : + HelpExampleCli("getquorummembers", "2, \"xxx\"")
279 0 : );
280 : }
281 :
282 19 : Consensus::LLMQType llmq_type = static_cast<Consensus::LLMQType>(request.params[0].get_int());
283 19 : if (!Params().GetConsensus().llmqs.count(llmq_type)) {
284 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid llmq_type");
285 : }
286 :
287 19 : const uint256& quorum_hash = ParseHashV(request.params[1], "quorum_hash");
288 38 : const CBlockIndex* pindexQuorum = WITH_LOCK(cs_main, return LookupBlockIndex(quorum_hash));
289 19 : if (pindexQuorum == nullptr) {
290 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid quorum_hash");
291 : }
292 :
293 19 : auto mns = deterministicMNManager->GetAllQuorumMembers(llmq_type, pindexQuorum);
294 19 : UniValue ret(UniValue::VARR);
295 76 : for (const auto& dmn : mns) {
296 114 : ret.push_back(dmn->proTxHash.ToString());
297 : }
298 19 : return ret;
299 : }
300 :
301 306 : UniValue quorumdkgstatus(const JSONRPCRequest& request)
302 : {
303 306 : if (request.fHelp || request.params.size() > 1) {
304 0 : throw std::runtime_error(
305 : "quorumdkgstatus ( detail_level )\n"
306 : "Return the status of the current DKG process of the active masternode.\n"
307 : "\nArguments:\n"
308 : "1. detail_level (number, optional, default=0) Detail level of output.\n"
309 : " 0=Only show counts. 1=Show member indexes. 2=Show member's ProTxHashes.\n"
310 : "\nExamples:\n"
311 0 : + HelpExampleRpc("quorumdkgstatus", "2")
312 0 : + HelpExampleCli("quorumdkgstatus", "")
313 0 : );
314 : }
315 :
316 306 : int detailLevel = request.params.size() > 0 ? request.params[0].get_int() : 0;
317 306 : if (detailLevel < 0 || detailLevel > 2) {
318 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("invalid detail_level %d", detailLevel));
319 : }
320 :
321 306 : if (!fMasterNode || !activeMasternodeManager) {
322 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "This is not a (deterministic) masternode");
323 : }
324 :
325 306 : llmq::CDKGDebugStatus status;
326 306 : llmq::quorumDKGDebugManager->GetLocalDebugStatus(status);
327 :
328 306 : auto ret = status.ToJson(detailLevel);
329 :
330 612 : const int tipHeight = WITH_LOCK(cs_main, return chainActive.Height(); );
331 :
332 612 : UniValue minableCommitments(UniValue::VOBJ);
333 612 : for (const auto& p : Params().GetConsensus().llmqs) {
334 306 : auto& params = p.second;
335 612 : llmq::CFinalCommitment fqc;
336 306 : if (llmq::quorumBlockProcessor->GetMinableCommitment(params.type, tipHeight, fqc)) {
337 102 : UniValue obj(UniValue::VOBJ);
338 51 : fqc.ToJson(obj);
339 51 : minableCommitments.pushKV(params.name, obj);
340 : }
341 : }
342 306 : ret.pushKV("minableCommitments", minableCommitments);
343 :
344 612 : return ret;
345 : }
346 :
347 0 : UniValue quorumselectquorum(const JSONRPCRequest& request)
348 : {
349 0 : if (request.fHelp || request.params.size() != 2) {
350 0 : throw std::runtime_error(
351 : "quorum selectquorum llmqType \"id\"\n"
352 : "Returns the quorum that would/should sign a request\n"
353 : "\nArguments:\n"
354 : "1. llmqType (int, required) LLMQ type.\n"
355 0 : "2. \"id\" (string, required) Request id.\n");
356 : }
357 :
358 0 : Consensus::LLMQType llmqType = static_cast<Consensus::LLMQType>(request.params[0].get_int());
359 0 : if (!Params().GetConsensus().llmqs.count(llmqType)) {
360 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
361 : }
362 :
363 0 : uint256 id = ParseHashV(request.params[1], "id");
364 :
365 0 : UniValue ret(UniValue::VOBJ);
366 :
367 0 : auto quorum = llmq::quorumSigningManager->SelectQuorumForSigning(llmqType, id);
368 0 : if (!quorum) {
369 0 : throw JSONRPCError(RPC_MISC_ERROR, "no quorums active");
370 : }
371 0 : ret.pushKV("quorumHash", quorum->qc.quorumHash.ToString());
372 :
373 0 : UniValue recoveryMembers(UniValue::VARR);
374 0 : for (int i = 0; i < quorum->params.recoveryMembers; i++) {
375 0 : auto dmn = llmq::quorumSigSharesManager->SelectMemberForRecovery(quorum, id, i);
376 0 : recoveryMembers.push_back(dmn->proTxHash.ToString());
377 : }
378 0 : ret.pushKV("recoveryMembers", recoveryMembers);
379 :
380 0 : return ret;
381 : }
382 :
383 61 : UniValue quorumdkgsimerror(const JSONRPCRequest& request)
384 : {
385 61 : if (request.fHelp || request.params.size() != 2) {
386 0 : throw std::runtime_error(
387 : "quorumdkgsimerror \"error_type\" rate\n"
388 : "This enables simulation of errors and malicious behaviour in the DKG.\n"
389 : "Only available on testnet/regtest for LLMQ_TEST llmq type.\n"
390 : "\nArguments:\n"
391 : "1. \"error_type\" (string, required) Error type.\n"
392 : "2. rate (number, required) Rate at which to simulate this error type.\n"
393 : "\nExamples:\n"
394 0 : + HelpExampleRpc("quorumdkgsimerror", "\"justify-lie\", 0.1")
395 0 : + HelpExampleCli("quorumdkgsimerror", "\"justify-lie\" 0.1")
396 0 : );
397 : }
398 :
399 61 : if (!Params().IsTestChain()) {
400 0 : throw JSONRPCError(RPC_MISC_ERROR, "This command cannot be used on main net.");
401 : }
402 :
403 61 : std::string error_type = request.params[0].get_str();
404 61 : double rate = ParseDoubleV(request.params[1], "rate");
405 61 : if (rate < 0 || rate > 1) {
406 4 : throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid rate. Must be between 0 and 1");
407 : }
408 :
409 59 : if (!llmq::SetSimulatedDKGErrorRate(error_type, rate)) {
410 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("invalid error_type: %s", error_type));
411 : }
412 :
413 116 : return NullUniValue;
414 : }
415 :
416 : // clang-format off
417 : static const CRPCCommand commands[] =
418 : { // category name actor (function) okSafe argNames
419 : // -------------- ------------------------- --------------------- ------ --------
420 : { "evo", "getminedcommitment", &getminedcommitment, true, {"llmq_type", "quorum_hash"} },
421 : { "evo", "getquorummembers", &getquorummembers, true, {"llmq_type", "quorum_hash"} },
422 : { "evo", "quorumselectquorum", &quorumselectquorum, true, {"llmq_type", "id"} },
423 : { "evo", "quorumdkgsimerror", &quorumdkgsimerror, true, {"error_type", "rate"} },
424 : { "evo", "quorumdkgstatus", &quorumdkgstatus, true, {"detail_level"} },
425 : { "evo", "listquorums", &listquorums, true, {"count"} },
426 : { "evo", "getquoruminfo", &getquoruminfo, true, {"llmqType", "quorumHash", "includeSkShare"} },
427 :
428 : /** Not shown in help */
429 : { "hidden", "signsession", &signsession, true, {"llmqType", "id", "msgHash"} },
430 : { "hidden", "hasrecoverysignature", &hasrecoverysignature,true, {"llmqType", "id", "msgHash"} },
431 : { "hidden", "issessionconflicting", &issessionconflicting,true, {"llmqType", "id", "msgHash"} },
432 : };
433 : // clang-format on
434 :
435 494 : void RegisterQuorumsRPCCommands(CRPCTable& tableRPC)
436 : {
437 5434 : for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
438 4940 : tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
439 494 : }
|