LCOV - code coverage report
Current view: top level - src/rpc - quorums.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 825 896 92.1 %
Date: 2026-06-25 07:23:43 Functions: 58 61 95.1 %

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

Generated by: LCOV version 1.16