LCOV - code coverage report
Current view: top level - src/rpc - quorums.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 322 896 35.9 %
Date: 2026-06-25 07:23:51 Functions: 24 61 39.3 %

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

Generated by: LCOV version 1.16