LCOV - code coverage report
Current view: top level - src/rpc - evo.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 302 1340 22.5 %
Date: 2026-06-25 07:23:51 Functions: 18 92 19.6 %

          Line data    Source code
       1             : // Copyright (c) 2018-2025 The Dash Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <bls/bls.h>
       6             : #include <chainparams.h>
       7             : #include <consensus/validation.h>
       8             : #include <core_io.h>
       9             : #include <deploymentstatus.h>
      10             : #include <evo/chainhelper.h>
      11             : #include <evo/deterministicmns.h>
      12             : #include <evo/dmn_types.h>
      13             : #include <evo/providertx.h>
      14             : #include <evo/smldiff.h>
      15             : #include <evo/specialtx.h>
      16             : #include <evo/specialtxman.h>
      17             : #include <index/txindex.h>
      18             : #include <llmq/context.h>
      19             : #include <masternode/meta.h>
      20             : #include <node/context.h>
      21             : #include <rpc/evo_util.h>
      22             : #include <rpc/server.h>
      23             : #include <rpc/server_util.h>
      24             : #include <rpc/util.h>
      25             : #include <util/check.h>
      26             : #include <util/translation.h>
      27             : #include <validation.h>
      28             : #include <wallet/rpc/util.h>
      29             : #include <walletinitinterface.h>
      30             : 
      31             : #ifdef ENABLE_WALLET
      32             : #include <wallet/coincontrol.h>
      33             : #include <wallet/spend.h>
      34             : #include <wallet/wallet.h>
      35             : #endif // ENABLE_WALLET
      36             : 
      37             : #ifdef ENABLE_WALLET
      38             : extern RPCHelpMan sendrawtransaction();
      39             : namespace wallet {
      40             : extern RPCHelpMan signrawtransactionwithwallet();
      41             : } // namespace wallet
      42             : #else
      43             : namespace wallet {
      44             : class CWallet;
      45             : } // namespace wallet
      46             : #endif // ENABLE_WALLET
      47             : 
      48             : using node::GetTransaction;
      49             : using node::NodeContext;
      50             : using wallet::CWallet;
      51             : #ifdef ENABLE_WALLET
      52             : using wallet::CCoinControl;
      53             : using wallet::CRecipient;
      54             : using wallet::DEFAULT_DISABLE_WALLET;
      55             : using wallet::GetWalletForJSONRPCRequest;
      56             : using wallet::HELP_REQUIRING_PASSPHRASE;
      57             : using wallet::isminetype;
      58             : using wallet::RANDOM_CHANGE_POSITION;
      59             : #endif // ENABLE_WALLET
      60             : 
      61          92 : static RPCArg GetRpcArg(const std::string& strParamName)
      62             : {
      63        1334 :     static const std::map<std::string, RPCArg> mapParamHelp = {
      64          92 :         {"collateralAddress",
      65          46 :             {"collateralAddress", RPCArg::Type::STR, RPCArg::Optional::NO,
      66          46 :                 "The Dash address to send the collateral to."}
      67             :         },
      68          92 :         {"collateralHash",
      69          46 :             {"collateralHash", RPCArg::Type::STR, RPCArg::Optional::NO,
      70          46 :                 "The collateral transaction hash."}
      71             :         },
      72          92 :         {"collateralIndex",
      73          46 :             {"collateralIndex", RPCArg::Type::NUM, RPCArg::Optional::NO,
      74          46 :                 "The collateral transaction output index."}
      75             :         },
      76          92 :         {"feeSourceAddress",
      77          46 :             {"feeSourceAddress", RPCArg::Type::STR, RPCArg::Default{""},
      78          46 :                 "If specified wallet will only use coins from this address to fund ProTx.\n"
      79             :                 "If not specified, payoutAddress is the one that is going to be used.\n"
      80             :                 "The private key belonging to this address must be known in your wallet."}
      81             :         },
      82          92 :         {"fundAddress",
      83          46 :             {"fundAddress", RPCArg::Type::STR, RPCArg::Default{""},
      84          46 :                 "If specified wallet will only use coins from this address to fund ProTx.\n"
      85             :                 "If not specified, payoutAddress is the one that is going to be used.\n"
      86             :                 "The private key belonging to this address must be known in your wallet."}
      87             :         },
      88          92 :         {"coreP2PAddrs",
      89          92 :             {"coreP2PAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
      90          46 :                 "Array of addresses in the form \"ADDR:PORT\". Must be unique on the network.\n"
      91             :                 "Can be set to an empty string, which will require a ProUpServTx afterwards.",
      92          92 :                 {
      93          46 :                     {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
      94             :                 }}
      95             :         },
      96          92 :         {"coreP2PAddrs_update",
      97          92 :             {"coreP2PAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
      98          46 :                 "Array of addresses in the form \"ADDR:PORT\". Must be unique on the network.",
      99          92 :                 {
     100          46 :                     {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
     101             :                 }}
     102             :         },
     103          92 :         {"operatorKey",
     104          46 :             {"operatorKey", RPCArg::Type::STR, RPCArg::Optional::NO,
     105          46 :                 "The operator BLS private key associated with the\n"
     106             :                 "registered operator public key."}
     107             :         },
     108          92 :         {"operatorPayoutAddress",
     109          46 :             {"operatorPayoutAddress", RPCArg::Type::STR, RPCArg::Default{""},
     110          46 :                 "The address used for operator reward payments.\n"
     111             :                 "Only allowed when the ProRegTx had a non-zero operatorReward value.\n"
     112             :                 "If set to an empty string, the currently active payout address is reused."}
     113             :         },
     114          92 :         {"operatorPubKey_register",
     115          46 :             {"operatorPubKey", RPCArg::Type::STR, RPCArg::Optional::NO,
     116          46 :                 "The operator BLS public key. The BLS private key does not have to be known.\n"
     117             :                 "It has to match the BLS private key which is later used when operating the masternode."}
     118             :         },
     119          92 :         {"operatorPubKey_register_legacy",
     120          46 :             {"operatorPubKey", RPCArg::Type::STR, RPCArg::Optional::NO,
     121          46 :                 "The operator BLS public key in legacy scheme. The BLS private key does not have to be known.\n"
     122             :                 "It has to match the BLS private key which is later used when operating the masternode.\n"}
     123             :         },
     124          92 :         {"operatorPubKey_update",
     125          46 :             {"operatorPubKey", RPCArg::Type::STR, RPCArg::Optional::NO,
     126          46 :                 "The operator BLS public key. The BLS private key does not have to be known.\n"
     127             :                 "It has to match the BLS private key which is later used when operating the masternode.\n"
     128             :                 "If set to an empty string, the currently active operator BLS public key is reused."}
     129             :         },
     130          92 :         {"operatorPubKey_update_legacy",
     131          46 :             {"operatorPubKey", RPCArg::Type::STR, RPCArg::Optional::NO,
     132          46 :                 "The operator BLS public key in legacy scheme. The BLS private key does not have to be known.\n"
     133             :                 "It has to match the BLS private key which is later used when operating the masternode.\n"
     134             :                 "If set to an empty string, the currently active operator BLS public key is reused."}
     135             :         },
     136          92 :         {"operatorReward",
     137          46 :             {"operatorReward", RPCArg::Type::STR, RPCArg::Optional::NO,
     138          46 :                 "The fraction in %% to share with the operator.\n"
     139             :                 "The value must be between 0 and 10000."}
     140             :         },
     141          92 :         {"ownerAddress",
     142          46 :             {"ownerAddress", RPCArg::Type::STR, RPCArg::Optional::NO,
     143          46 :                 "The Dash address to use for payee updates and proposal voting.\n"
     144             :                 "The corresponding private key does not have to be known by your wallet.\n"
     145             :                 "The address must be unused and must differ from the collateralAddress."}
     146             :         },
     147          92 :         {"payoutAddress_register",
     148          46 :             {"payoutAddress", RPCArg::Type::STR, RPCArg::Optional::NO,
     149          46 :                 "The Dash address to use for masternode reward payments."}
     150             :         },
     151          92 :         {"payoutAddress_update",
     152          46 :             {"payoutAddress", RPCArg::Type::STR, RPCArg::Optional::NO,
     153          46 :                 "The Dash address to use for masternode reward payments.\n"
     154             :                 "If set to an empty string, the currently active payout address is reused."}
     155             :         },
     156          92 :         {"proTxHash",
     157          46 :             {"proTxHash", RPCArg::Type::STR, RPCArg::Optional::NO,
     158          46 :                 "The hash of the initial ProRegTx."}
     159             :         },
     160          92 :         {"reason",
     161          46 :             {"reason", RPCArg::Type::NUM, RPCArg::DefaultHint{"Reason is not specified"},
     162          46 :                 "The reason for masternode service revocation."}
     163             :         },
     164          92 :         {"submit",
     165          46 :             {"submit", RPCArg::Type::BOOL, RPCArg::Default{true},
     166          46 :                 "If true, the resulting transaction is sent to the network."}
     167             :         },
     168          92 :         {"votingAddress_register",
     169          46 :             {"votingAddress", RPCArg::Type::STR, RPCArg::Optional::NO,
     170          46 :                 "The voting key address. The private key does not have to be known by your wallet.\n"
     171             :                 "It has to match the private key which is later used when voting on proposals.\n"
     172             :                 "If set to an empty string, ownerAddress will be used."}
     173             :         },
     174          92 :         {"votingAddress_update",
     175          46 :             {"votingAddress", RPCArg::Type::STR, RPCArg::Optional::NO,
     176          46 :                 "The voting key address. The private key does not have to be known by your wallet.\n"
     177             :                 "It has to match the private key which is later used when voting on proposals.\n"
     178             :                 "If set to an empty string, the currently active voting key address is reused."}
     179             :         },
     180          92 :         {"platformNodeID",
     181          46 :             {"platformNodeID", RPCArg::Type::STR, RPCArg::Optional::NO,
     182          46 :                 "Platform P2P node ID, derived from P2P public key."}
     183             :         },
     184          92 :         {"platformP2PAddrs",
     185          92 :             {"platformP2PAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
     186          46 :                 "Array of addresses in the form \"ADDR:PORT\" used by Platform for peer-to-peer connection.\n"
     187             :                 "Must be unique on the network. Can be set to an empty string, which will require a ProUpServTx afterwards.",
     188          92 :                 {
     189          46 :                     {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
     190             :                 }}
     191             :         },
     192          92 :         {"platformP2PAddrs_update",
     193          92 :             {"platformP2PAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
     194          46 :                 "Array of addresses in the form \"ADDR:PORT\" used by Platform for peer-to-peer connection.\n"
     195             :                 "Must be unique on the network.",
     196          92 :                 {
     197          46 :                     {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
     198             :                 }}
     199             :         },
     200          92 :         {"platformHTTPSAddrs",
     201          92 :             {"platformHTTPSAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
     202          46 :                 "Array of addresses in the form \"ADDR:PORT\" used by Platform for their HTTPS API.\n"
     203             :                 "Must be unique on the network. Can be set to an empty string, which will require a ProUpServTx afterwards.",
     204          92 :                 {
     205          46 :                     {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
     206             :                 }}
     207             :         },
     208          92 :         {"platformHTTPSAddrs_update",
     209          92 :             {"platformHTTPSAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
     210          46 :                 "Array of addresses in the form \"ADDR:PORT\" used by Platform for their HTTPS API.\n"
     211             :                 "Must be unique on the network.",
     212          92 :                 {
     213          46 :                     {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
     214             :                 }}
     215             :         },
     216             :     };
     217             : 
     218          92 :     auto it = mapParamHelp.find(strParamName);
     219          92 :     if (it == mapParamHelp.end())
     220           0 :         throw std::runtime_error(strprintf("FIXME: WRONG PARAM NAME %s!", strParamName));
     221             : 
     222          92 :     return it->second;
     223           0 : }
     224             : 
     225           7 : static CBLSSecretKey ParseBLSSecretKey(const std::string& hexKey, const std::string& paramName)
     226             : {
     227           7 :     CBLSSecretKey secKey;
     228             : 
     229             :     // Actually, bool flag for bls::PrivateKey has other meaning (modOrder)
     230           7 :     if (!secKey.SetHexStr(hexKey, false)) {
     231           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be a valid BLS secret key", paramName));
     232             :     }
     233           7 :     return secKey;
     234           7 : }
     235             : 
     236             : #ifdef ENABLE_WALLET
     237             : 
     238           0 : static CKeyID ParsePubKeyIDFromAddress(const std::string& strAddress, const std::string& paramName)
     239             : {
     240           0 :     CTxDestination dest = DecodeDestination(strAddress);
     241           0 :     const PKHash *pkhash = std::get_if<PKHash>(&dest);
     242           0 :     if (!pkhash) {
     243           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be a valid P2PKH address, not %s", paramName, strAddress));
     244             :     }
     245           0 :     return ToKeyID(*pkhash);
     246           0 : }
     247             : 
     248           0 : static CBLSPublicKey ParseBLSPubKey(const std::string& hexKey, const std::string& paramName, bool specific_legacy_bls_scheme)
     249             : {
     250           0 :     CBLSPublicKey pubKey;
     251           0 :     if (!pubKey.SetHexStr(hexKey, specific_legacy_bls_scheme)) {
     252           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be a valid BLS public key, not %s", paramName, hexKey));
     253             :     }
     254           0 :     return pubKey;
     255           0 : }
     256             : 
     257             : template <typename SpecialTxPayload>
     258           0 : static void FundSpecialTx(CWallet& wallet, CMutableTransaction& tx, const SpecialTxPayload& payload,
     259             :                           const CTxDestination& fundDest) EXCLUSIVE_LOCKS_REQUIRED(!wallet.cs_wallet)
     260             : {
     261             :     // Make sure the results are valid at least up to the most recent block
     262             :     // the user could have gotten from another RPC command prior to now
     263           0 :     wallet.BlockUntilSyncedToCurrentChain();
     264             : 
     265           0 :     LOCK(wallet.cs_wallet);
     266             : 
     267           0 :     CTxDestination nodest = CNoDestination();
     268           0 :     if (fundDest == nodest) {
     269           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, "No source of funds specified");
     270             :     }
     271             : 
     272           0 :     CDataStream ds(SER_NETWORK, PROTOCOL_VERSION);
     273           0 :     ds << payload;
     274           0 :     tx.vExtraPayload.assign(UCharCast(ds.data()), UCharCast(ds.data() + ds.size()));
     275             : 
     276           0 :     static const CTxOut dummyTxOut(0, CScript() << OP_RETURN);
     277           0 :     std::vector<CRecipient> vecSend;
     278           0 :     bool dummyTxOutAdded = false;
     279             : 
     280           0 :     if (tx.vout.empty()) {
     281             :         // add dummy txout as CreateTransaction requires at least one recipient
     282           0 :         tx.vout.emplace_back(dummyTxOut);
     283           0 :         dummyTxOutAdded = true;
     284           0 :     }
     285             : 
     286           0 :     for (const auto& txOut : tx.vout) {
     287           0 :         CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, false};
     288           0 :         vecSend.push_back(recipient);
     289           0 :     }
     290             : 
     291           0 :     CCoinControl coinControl;
     292           0 :     coinControl.destChange = fundDest;
     293           0 :     coinControl.fRequireAllInputs = false;
     294             : 
     295           0 :     for (const auto& out : AvailableCoinsListUnspent(wallet).all()) {
     296           0 :         CTxDestination txDest;
     297           0 :         if (ExtractDestination(out.txout.scriptPubKey, txDest) && txDest == fundDest) {
     298           0 :             coinControl.Select(out.outpoint);
     299           0 :         }
     300             :     }
     301             : 
     302           0 :     if (!coinControl.HasSelected()) {
     303           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("No funds at specified address %s", EncodeDestination(fundDest)));
     304             :     }
     305             : 
     306           0 :     auto res = CreateTransaction(wallet, vecSend, RANDOM_CHANGE_POSITION, coinControl, /*sign=*/true, tx.vExtraPayload.size());
     307           0 :     if (!res) {
     308           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, util::ErrorString(res).original);
     309             :     }
     310             : 
     311           0 :     const CTransactionRef& newTx = res->tx;
     312           0 :     tx.vin = newTx->vin;
     313           0 :     tx.vout = newTx->vout;
     314             : 
     315           0 :     if (dummyTxOutAdded && tx.vout.size() > 1) {
     316             :         // CreateTransaction added a change output, so we don't need the dummy txout anymore.
     317             :         // Removing it results in slight overpayment of fees, but we ignore this for now (as it's a very low amount).
     318           0 :         auto it = std::find(tx.vout.begin(), tx.vout.end(), dummyTxOut);
     319           0 :         CHECK_NONFATAL(it != tx.vout.end());
     320           0 :         tx.vout.erase(it);
     321           0 :     }
     322           0 : }
     323             : 
     324             : template<typename SpecialTxPayload>
     325           0 : static void UpdateSpecialTxInputsHash(const CMutableTransaction& tx, SpecialTxPayload& payload)
     326             : {
     327           0 :     payload.inputsHash = CalcTxInputsHash(CTransaction(tx));
     328           0 : }
     329             : 
     330             : template<typename SpecialTxPayload>
     331           0 : static void SignSpecialTxPayloadByHash(const CMutableTransaction& tx, SpecialTxPayload& payload, const CKeyID& keyID, const CWallet& wallet)
     332             : {
     333           0 :     UpdateSpecialTxInputsHash(tx, payload);
     334           0 :     payload.vchSig.clear();
     335             : 
     336           0 :     const uint256 hash = ::SerializeHash(payload);
     337           0 :     if (!wallet.SignSpecialTxPayload(hash, keyID, payload.vchSig)) {
     338           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, "failed to sign special tx");
     339             :     }
     340           0 : }
     341             : 
     342             : template <typename SpecialTxPayload>
     343           0 : static void SignSpecialTxPayloadByHash(const CMutableTransaction& tx, SpecialTxPayload& payload,
     344             :                                        const CBLSSecretKey& key, bool use_legacy)
     345             : {
     346           0 :     UpdateSpecialTxInputsHash(tx, payload);
     347             : 
     348           0 :     uint256 hash = ::SerializeHash(payload);
     349           0 :     payload.sig = key.Sign(hash, use_legacy);
     350           0 : }
     351             : 
     352           0 : static std::string SignAndSendSpecialTx(const JSONRPCRequest& request, CChainstateHelper& chain_helper, const ChainstateManager& chainman, const CMutableTransaction& tx, bool fSubmit)
     353             : {
     354             :     {
     355           0 :     LOCK(::cs_main);
     356             : 
     357           0 :     const CBlockIndex* tip{chainman.ActiveChain().Tip()};
     358           0 :     const Consensus::Params& consensus_params{chainman.GetConsensus()};
     359           0 :     if (!DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_DIP0003)) {
     360           0 :         const int current_height{tip ? tip->nHeight : -1};
     361           0 :         const int next_block_height{current_height + 1};
     362           0 :         const int activation_height{consensus_params.DIP0003Height};
     363           0 :         const int blocks_to_mine{
     364           0 :             activation_height > next_block_height ? activation_height - next_block_height : 0
     365             :         };
     366           0 :         throw JSONRPCError(RPC_VERIFY_ERROR, strprintf(
     367             :             "DIP0003 is not active yet; ProTx transactions are valid starting at block height %d "
     368             :             "(current chain height %d, next block height %d). Mine %d more block%s or restart "
     369             :             "this regtest/devnet chain with DIP3 activation parameters that are already active.",
     370             :             activation_height, current_height, next_block_height, blocks_to_mine,
     371           0 :             blocks_to_mine == 1 ? "" : "s"));
     372             :     }
     373             : 
     374           0 :     TxValidationState state;
     375           0 :     if (!chain_helper.special_tx->CheckSpecialTx(CTransaction(tx), tip, chainman.ActiveChainstate().CoinsTip(), true, state)) {
     376           0 :         throw std::runtime_error(state.ToString());
     377             :     }
     378           0 :     } // cs_main
     379             : 
     380           0 :     CDataStream ds(SER_NETWORK, PROTOCOL_VERSION);
     381           0 :     ds << tx;
     382             : 
     383           0 :     JSONRPCRequest signRequest(request);
     384           0 :     signRequest.params.setArray();
     385           0 :     signRequest.params.push_back(HexStr(ds));
     386           0 :     UniValue signResult = wallet::signrawtransactionwithwallet().HandleRequest(signRequest);
     387             : 
     388           0 :     if (!fSubmit) {
     389           0 :         return signResult["hex"].get_str();
     390             :     }
     391             : 
     392           0 :     JSONRPCRequest sendRequest(request);
     393           0 :     sendRequest.params.setArray();
     394           0 :     sendRequest.params.push_back(signResult["hex"].get_str());
     395           0 :     return ::sendrawtransaction().HandleRequest(sendRequest).get_str();
     396           0 : }
     397             : 
     398             : // forward declaration
     399             : namespace {
     400             : enum class ProTxRegisterAction
     401             : {
     402             :     External,
     403             :     Fund,
     404             :     Prepare,
     405             : };
     406             : } // anonumous namespace
     407             : 
     408             : static UniValue protx_register_common_wrapper(const JSONRPCRequest& request,
     409             :                                               const bool specific_legacy_bls_scheme,
     410             :                                               ProTxRegisterAction action,
     411             :                                               const MnType mnType);
     412             : 
     413             : static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& request, const MnType mnType);
     414             : 
     415             : 
     416           0 : static RPCHelpMan protx_register_fund_wrapper(const bool legacy)
     417             : {
     418           0 :     std::string rpc_name = legacy ? "register_fund_legacy" : "register_fund";
     419           0 :     std::string rpc_full_name = std::string("protx ").append(rpc_name);
     420           0 :     std::string pubkey_operator = legacy ? "\"0532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"" : "\"8532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"";
     421           0 :     std::string rpc_example = rpc_name.append(" \"" + EXAMPLE_ADDRESS[0] + "\" \"1.2.3.4:1234\" \"" + EXAMPLE_ADDRESS[1] + "\" ").append(pubkey_operator).append(" \"" + EXAMPLE_ADDRESS[1] + "\" 0 \"" + EXAMPLE_ADDRESS[0] + "\"");
     422           0 :     return RPCHelpMan{rpc_full_name,
     423             :         "\nCreates, funds and sends a ProTx to the network. The resulting transaction will move 1000 Dash\n"
     424             :         "to the address specified by collateralAddress and will then function as the collateral of your\n"
     425             :         "masternode.\n"
     426             :         "A few of the limitations you see in the arguments are temporary and might be lifted after DIP3\n"
     427             :         "is fully deployed.\n"
     428           0 :         + std::string(legacy ? "\nDEPRECATED: May be removed in a future version, pass config option -deprecatedrpc=legacy_mn to use RPC\n" : "")
     429           0 :         + HELP_REQUIRING_PASSPHRASE,
     430           0 :         {
     431           0 :             GetRpcArg("collateralAddress"),
     432           0 :             GetRpcArg("coreP2PAddrs"),
     433           0 :             GetRpcArg("ownerAddress"),
     434           0 :             legacy ? GetRpcArg("operatorPubKey_register_legacy") : GetRpcArg("operatorPubKey_register"),
     435           0 :             GetRpcArg("votingAddress_register"),
     436           0 :             GetRpcArg("operatorReward"),
     437           0 :             GetRpcArg("payoutAddress_register"),
     438           0 :             GetRpcArg("fundAddress"),
     439           0 :             GetRpcArg("submit"),
     440             :         },
     441           0 :         {
     442           0 :             RPCResult{"if \"submit\" is not set or set to true",
     443           0 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
     444           0 :             RPCResult{"if \"submit\" is set to false",
     445           0 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
     446             :         },
     447           0 :         RPCExamples{
     448           0 :             HelpExampleCli("protx",  rpc_example)
     449             :         },
     450           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     451             : {
     452           0 :     if (legacy && !IsDeprecatedRPCEnabled("legacy_mn")) {
     453           0 :         throw std::runtime_error("DEPRECATED: Pass config option -deprecatedrpc=legacy_mn to enable this RPC");
     454             :     }
     455           0 :     return protx_register_common_wrapper(request, self.m_name == "protx register_fund_legacy", ProTxRegisterAction::Fund, MnType::Regular);
     456           0 : },
     457             :     };
     458           0 : }
     459             : 
     460           0 : static RPCHelpMan protx_register_fund() {
     461           0 :     return protx_register_fund_wrapper(false);
     462             : }
     463             : 
     464           0 : static RPCHelpMan protx_register_fund_legacy() {
     465           0 :     return protx_register_fund_wrapper(true);
     466             : }
     467             : 
     468           0 : static RPCHelpMan protx_register_wrapper(bool legacy)
     469             : {
     470           0 :     std::string rpc_name = legacy ? "register_legacy" : "register";
     471           0 :     std::string rpc_full_name = std::string("protx ").append(rpc_name);
     472           0 :     std::string pubkey_operator = legacy ? "\"0532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"" : "\"8532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"";
     473           0 :     std::string rpc_example = rpc_name.append(" \"0123456701234567012345670123456701234567012345670123456701234567\" 0 \"1.2.3.4:1234\" \"" + EXAMPLE_ADDRESS[1] + "\" ").append(pubkey_operator).append(" \"" + EXAMPLE_ADDRESS[1] + "\" 0 \"" + EXAMPLE_ADDRESS[0] + "\"");
     474           0 :     return RPCHelpMan{rpc_full_name,
     475             :         "\nSame as \"protx register_fund\", but with an externally referenced collateral.\n"
     476             :         "The collateral is specified through \"collateralHash\" and \"collateralIndex\" and must be an unspent\n"
     477             :         "transaction output spendable by this wallet. It must also not be used by any other masternode.\n"
     478           0 :         + std::string(legacy ? "\nDEPRECATED: May be removed in a future version, pass config option -deprecatedrpc=legacy_mn to use RPC\n" : "")
     479           0 :         + HELP_REQUIRING_PASSPHRASE,
     480           0 :         {
     481           0 :             GetRpcArg("collateralHash"),
     482           0 :             GetRpcArg("collateralIndex"),
     483           0 :             GetRpcArg("coreP2PAddrs"),
     484           0 :             GetRpcArg("ownerAddress"),
     485           0 :             legacy ? GetRpcArg("operatorPubKey_register_legacy") : GetRpcArg("operatorPubKey_register"),
     486           0 :             GetRpcArg("votingAddress_register"),
     487           0 :             GetRpcArg("operatorReward"),
     488           0 :             GetRpcArg("payoutAddress_register"),
     489           0 :             GetRpcArg("feeSourceAddress"),
     490           0 :             GetRpcArg("submit"),
     491             :         },
     492           0 :         {
     493           0 :             RPCResult{"if \"submit\" is not set or set to true",
     494           0 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
     495           0 :             RPCResult{"if \"submit\" is set to false",
     496           0 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
     497             :         },
     498           0 :         RPCExamples{
     499           0 :             HelpExampleCli("protx", rpc_example),
     500             :         },
     501           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     502             : {
     503           0 :     if (legacy && !IsDeprecatedRPCEnabled("legacy_mn")) {
     504           0 :         throw std::runtime_error("DEPRECATED: Pass config option -deprecatedrpc=legacy_mn to enable this RPC");
     505             :     }
     506           0 :     return protx_register_common_wrapper(request, self.m_name == "protx register_legacy", ProTxRegisterAction::External, MnType::Regular);
     507           0 : },
     508             :     };
     509           0 : }
     510             : 
     511           0 : static RPCHelpMan protx_register()
     512             : {
     513           0 :     return protx_register_wrapper(false);
     514             : }
     515             : 
     516           0 : static RPCHelpMan protx_register_legacy()
     517             : {
     518           0 :     return protx_register_wrapper(true);
     519             : }
     520             : 
     521           0 : static RPCHelpMan protx_register_prepare_wrapper(const bool legacy)
     522             : {
     523           0 :     std::string rpc_name = legacy ? "register_prepare_legacy" : "register_prepare";
     524           0 :     std::string rpc_full_name = std::string("protx ").append(rpc_name);
     525           0 :     std::string pubkey_operator = legacy ? "\"0532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"" : "\"8532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"";
     526           0 :     std::string rpc_example = rpc_name.append(" \"0123456701234567012345670123456701234567012345670123456701234567\" 0 \"1.2.3.4:1234\" \"" + EXAMPLE_ADDRESS[1] + "\" ").append(pubkey_operator).append(" \"" + EXAMPLE_ADDRESS[1] + "\" 0 \"" + EXAMPLE_ADDRESS[0] + "\"");
     527           0 :     return RPCHelpMan{rpc_full_name,
     528             :         "\nCreates an unsigned ProTx and a message that must be signed externally\n"
     529             :         "with the private key that corresponds to collateralAddress to prove collateral ownership.\n"
     530             :         "The prepared transaction will also contain inputs and outputs to cover fees.\n"
     531           0 :         + std::string(legacy ? "\nDEPRECATED: May be removed in a future version, pass config option -deprecatedrpc=legacy_mn to use RPC\n" : ""),
     532           0 :         {
     533           0 :             GetRpcArg("collateralHash"),
     534           0 :             GetRpcArg("collateralIndex"),
     535           0 :             GetRpcArg("coreP2PAddrs"),
     536           0 :             GetRpcArg("ownerAddress"),
     537           0 :             legacy ? GetRpcArg("operatorPubKey_register_legacy") : GetRpcArg("operatorPubKey_register"),
     538           0 :             GetRpcArg("votingAddress_register"),
     539           0 :             GetRpcArg("operatorReward"),
     540           0 :             GetRpcArg("payoutAddress_register"),
     541           0 :             GetRpcArg("feeSourceAddress"),
     542             :         },
     543           0 :         RPCResult{
     544           0 :             RPCResult::Type::OBJ, "", "",
     545           0 :             {
     546           0 :                 {RPCResult::Type::STR_HEX, "tx", "The serialized unsigned ProTx in hex format"},
     547           0 :                 {RPCResult::Type::STR_HEX, "collateralAddress", "The collateral address"},
     548           0 :                 {RPCResult::Type::STR_HEX, "signMessage", "The string message that needs to be signed with the collateral key"},
     549             :             }},
     550           0 :         RPCExamples{
     551           0 :             HelpExampleCli("protx", rpc_example)
     552             :         },
     553           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     554             : {
     555           0 :     if (legacy && !IsDeprecatedRPCEnabled("legacy_mn")) {
     556           0 :         throw std::runtime_error("DEPRECATED: Pass config option -deprecatedrpc=legacy_mn to enable this RPC");
     557             :     }
     558           0 :     return protx_register_common_wrapper(request, self.m_name == "protx register_prepare_legacy", ProTxRegisterAction::Prepare, MnType::Regular);
     559           0 : },
     560             :     };
     561           0 : }
     562             : 
     563           0 : static RPCHelpMan protx_register_prepare()
     564             : {
     565           0 :     return protx_register_prepare_wrapper(false);
     566             : }
     567             : 
     568           0 : static RPCHelpMan protx_register_prepare_legacy()
     569             : {
     570           0 :     return protx_register_prepare_wrapper(true);
     571             : }
     572             : 
     573           0 : static RPCHelpMan protx_register_fund_evo()
     574             : {
     575           0 :     const std::string command_name{"protx register_fund_evo"};
     576           0 :     return RPCHelpMan{
     577           0 :         command_name,
     578             :         "\nCreates, funds and sends a ProTx to the network. The resulting transaction will move 4000 Dash\n"
     579             :         "to the address specified by collateralAddress and will then function as the collateral of your\n"
     580             :         "EvoNode.\n"
     581             :         "A few of the limitations you see in the arguments are temporary and might be lifted after DIP3\n"
     582           0 :         "is fully deployed.\n" +
     583             :             HELP_REQUIRING_PASSPHRASE,
     584           0 :         {
     585           0 :             GetRpcArg("collateralAddress"),
     586           0 :             GetRpcArg("coreP2PAddrs"),
     587           0 :             GetRpcArg("ownerAddress"),
     588           0 :             GetRpcArg("operatorPubKey_register"),
     589           0 :             GetRpcArg("votingAddress_register"),
     590           0 :             GetRpcArg("operatorReward"),
     591           0 :             GetRpcArg("payoutAddress_register"),
     592           0 :             GetRpcArg("platformNodeID"),
     593           0 :             GetRpcArg("platformP2PAddrs"),
     594           0 :             GetRpcArg("platformHTTPSAddrs"),
     595           0 :             GetRpcArg("fundAddress"),
     596           0 :             GetRpcArg("submit"),
     597             :         },
     598           0 :         {
     599           0 :             RPCResult{"if \"submit\" is not set or set to true",
     600           0 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
     601           0 :             RPCResult{"if \"submit\" is set to false",
     602           0 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
     603             :         },
     604           0 :         RPCExamples{
     605           0 :             HelpExampleCli("protx", "register_fund_evo \"" + EXAMPLE_ADDRESS[0] + "\" \"1.2.3.4:1234\" \"" + EXAMPLE_ADDRESS[1] + "\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"" + EXAMPLE_ADDRESS[1] + "\" 0 \"" + EXAMPLE_ADDRESS[0] + "\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")},
     606           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     607             : {
     608           0 :     return protx_register_common_wrapper(request, false, ProTxRegisterAction::Fund, MnType::Evo);
     609             : },
     610             :     };
     611           0 : }
     612             : 
     613           0 : static RPCHelpMan protx_register_evo()
     614             : {
     615           0 :     const std::string command_name{"protx register_evo"};
     616           0 :     return RPCHelpMan{
     617           0 :         command_name,
     618             :         "\nSame as \"protx register_fund_evo\", but with an externally referenced collateral.\n"
     619             :         "The collateral is specified through \"collateralHash\" and \"collateralIndex\" and must be an unspent\n"
     620           0 :         "transaction output spendable by this wallet. It must also not be used by any other masternode.\n" +
     621             :             HELP_REQUIRING_PASSPHRASE,
     622           0 :         {
     623           0 :             GetRpcArg("collateralHash"),
     624           0 :             GetRpcArg("collateralIndex"),
     625           0 :             GetRpcArg("coreP2PAddrs"),
     626           0 :             GetRpcArg("ownerAddress"),
     627           0 :             GetRpcArg("operatorPubKey_register"),
     628           0 :             GetRpcArg("votingAddress_register"),
     629           0 :             GetRpcArg("operatorReward"),
     630           0 :             GetRpcArg("payoutAddress_register"),
     631           0 :             GetRpcArg("platformNodeID"),
     632           0 :             GetRpcArg("platformP2PAddrs"),
     633           0 :             GetRpcArg("platformHTTPSAddrs"),
     634           0 :             GetRpcArg("feeSourceAddress"),
     635           0 :             GetRpcArg("submit"),
     636             :         },
     637           0 :         {
     638           0 :             RPCResult{"if \"submit\" is not set or set to true",
     639           0 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
     640           0 :             RPCResult{"if \"submit\" is set to false",
     641           0 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
     642             :         },
     643           0 :         RPCExamples{
     644           0 :             HelpExampleCli("protx", "register_evo \"0123456701234567012345670123456701234567012345670123456701234567\" 0 \"1.2.3.4:1234\" \"" + EXAMPLE_ADDRESS[1] + "\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"" + EXAMPLE_ADDRESS[1] + "\" 0 \"" + EXAMPLE_ADDRESS[0] + "\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")},
     645           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     646             : {
     647           0 :     return protx_register_common_wrapper(request, false, ProTxRegisterAction::External, MnType::Evo);
     648             : },
     649             :     };
     650           0 : }
     651             : 
     652           0 : static RPCHelpMan protx_register_prepare_evo()
     653             : {
     654           0 :     const std::string command_name{"protx register_prepare_evo"};
     655           0 :     return RPCHelpMan{
     656           0 :         command_name,
     657           0 :         "\nCreates an unsigned ProTx and a message that must be signed externally\n"
     658             :         "with the private key that corresponds to collateralAddress to prove collateral ownership.\n"
     659             :         "The prepared transaction will also contain inputs and outputs to cover fees.\n",
     660           0 :         {
     661           0 :             GetRpcArg("collateralHash"),
     662           0 :             GetRpcArg("collateralIndex"),
     663           0 :             GetRpcArg("coreP2PAddrs"),
     664           0 :             GetRpcArg("ownerAddress"),
     665           0 :             GetRpcArg("operatorPubKey_register"),
     666           0 :             GetRpcArg("votingAddress_register"),
     667           0 :             GetRpcArg("operatorReward"),
     668           0 :             GetRpcArg("payoutAddress_register"),
     669           0 :             GetRpcArg("platformNodeID"),
     670           0 :             GetRpcArg("platformP2PAddrs"),
     671           0 :             GetRpcArg("platformHTTPSAddrs"),
     672           0 :             GetRpcArg("feeSourceAddress"),
     673             :         },
     674           0 :         RPCResult{
     675           0 :             RPCResult::Type::OBJ, "", "", {
     676           0 :                                               {RPCResult::Type::STR_HEX, "tx", "The serialized unsigned ProTx in hex format"},
     677           0 :                                               {RPCResult::Type::STR_HEX, "collateralAddress", "The collateral address"},
     678           0 :                                               {RPCResult::Type::STR_HEX, "signMessage", "The string message that needs to be signed with the collateral key"},
     679             :                                           }},
     680           0 :         RPCExamples{HelpExampleCli("protx", "register_prepare_evo \"0123456701234567012345670123456701234567012345670123456701234567\" 0 \"1.2.3.4:1234\" \"" + EXAMPLE_ADDRESS[1] + "\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"" + EXAMPLE_ADDRESS[1] + "\" 0 \"" + EXAMPLE_ADDRESS[0] + "\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")},
     681           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     682             : {
     683           0 :     return protx_register_common_wrapper(request, false, ProTxRegisterAction::Prepare, MnType::Evo);
     684             : },
     685             :     };
     686           0 : }
     687             : 
     688           0 : static UniValue protx_register_common_wrapper(const JSONRPCRequest& request,
     689             :                                               const bool specific_legacy_bls_scheme,
     690             :                                               const ProTxRegisterAction action,
     691             :                                               const MnType mnType)
     692             : {
     693           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
     694           0 :     const ChainstateManager& chainman = EnsureChainman(node);
     695             : 
     696           0 :     CChainstateHelper& chain_helper = *CHECK_NONFATAL(node.chain_helper);
     697             : 
     698           0 :     const bool isEvoRequested = mnType == MnType::Evo;
     699             : 
     700           0 :     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
     701           0 :     if (!pwallet) return UniValue::VNULL;
     702             : 
     703           0 :     EnsureWalletIsUnlocked(*pwallet);
     704             : 
     705           0 :     size_t paramIdx = 0;
     706             : 
     707           0 :     CMutableTransaction tx;
     708           0 :     tx.nVersion = 3;
     709           0 :     tx.nType = TRANSACTION_PROVIDER_REGISTER;
     710             : 
     711           0 :     const bool use_legacy = specific_legacy_bls_scheme;
     712             : 
     713           0 :     CProRegTx ptx;
     714           0 :     ptx.nType = mnType;
     715           0 :     ptx.nVersion = ProTxVersion::GetMaxFromDeployment<CProRegTx>(WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()),
     716           0 :                                                                  chainman, /*is_basic_override=*/!use_legacy);
     717           0 :     ptx.netInfo = NetInfoInterface::MakeNetInfo(ptx.nVersion);
     718             : 
     719           0 :     if (action == ProTxRegisterAction::Fund) {
     720           0 :         CTxDestination collateralDest = DecodeDestination(request.params[paramIdx].get_str());
     721           0 :         if (!IsValidDestination(collateralDest)) {
     722           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid collaterall address: %s", request.params[paramIdx].get_str()));
     723             :         }
     724           0 :         CScript collateralScript = GetScriptForDestination(collateralDest);
     725             : 
     726           0 :         CAmount fundCollateral = GetMnType(mnType).collat_amount;
     727           0 :         CTxOut collateralTxOut(fundCollateral, collateralScript);
     728           0 :         tx.vout.emplace_back(collateralTxOut);
     729             : 
     730           0 :         paramIdx++;
     731           0 :     } else {
     732           0 :         uint256 collateralHash(ParseHashV(request.params[paramIdx], "collateralHash"));
     733           0 :         int32_t collateralIndex = request.params[paramIdx + 1].getInt<int>();
     734           0 :         if (collateralHash.IsNull() || collateralIndex < 0) {
     735           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid hash or index: %s-%d", collateralHash.ToString(), collateralIndex));
     736             :         }
     737             : 
     738           0 :         ptx.collateralOutpoint = COutPoint(collateralHash, (uint32_t)collateralIndex);
     739           0 :         paramIdx += 2;
     740             :     }
     741             : 
     742           0 :     ProcessNetInfoCore(ptx, request.params[paramIdx], /*optional=*/true);
     743             : 
     744           0 :     ptx.keyIDOwner = ParsePubKeyIDFromAddress(request.params[paramIdx + 1].get_str(), "owner address");
     745           0 :     ptx.pubKeyOperator.Set(ParseBLSPubKey(request.params[paramIdx + 2].get_str(), "operator BLS address", use_legacy), use_legacy);
     746           0 :     CHECK_NONFATAL(ptx.pubKeyOperator.IsLegacy() == (ptx.nVersion == ProTxVersion::LegacyBLS));
     747             : 
     748           0 :     CKeyID keyIDVoting = ptx.keyIDOwner;
     749             : 
     750           0 :     if (!request.params[paramIdx + 3].get_str().empty()) {
     751           0 :         keyIDVoting = ParsePubKeyIDFromAddress(request.params[paramIdx + 3].get_str(), "voting address");
     752           0 :     }
     753             : 
     754             :     int64_t operatorReward;
     755           0 :     if (!ParseFixedPoint(request.params[paramIdx + 4].getValStr(), 2, &operatorReward)) {
     756           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorReward must be a number");
     757             :     }
     758           0 :     if (operatorReward < 0 || operatorReward > 10000) {
     759           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorReward must be between 0 and 10000");
     760             :     }
     761           0 :     ptx.nOperatorReward = operatorReward;
     762             : 
     763           0 :     CTxDestination payoutDest = DecodeDestination(request.params[paramIdx + 5].get_str());
     764           0 :     if (!IsValidDestination(payoutDest)) {
     765           0 :         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[paramIdx + 5].get_str()));
     766             :     }
     767             : 
     768           0 :     if (isEvoRequested) {
     769           0 :         if (!IsHex(request.params[paramIdx + 6].get_str())) {
     770           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "platformNodeID must be hexadecimal string");
     771             :         }
     772           0 :         ptx.platformNodeID.SetHex(request.params[paramIdx + 6].get_str());
     773             : 
     774           0 :         ProcessNetInfoPlatform(ptx, request.params[paramIdx + 7], request.params[paramIdx + 8], /*optional=*/true);
     775             : 
     776           0 :         paramIdx += 3;
     777           0 :     }
     778             : 
     779           0 :     ptx.keyIDVoting = keyIDVoting;
     780           0 :     ptx.scriptPayout = GetScriptForDestination(payoutDest);
     781             : 
     782           0 :     if (action != ProTxRegisterAction::Fund) {
     783             :         // make sure fee calculation works
     784           0 :         ptx.vchSig.resize(65);
     785           0 :     }
     786             : 
     787           0 :     CTxDestination fundDest = payoutDest;
     788           0 :     if (!request.params[paramIdx + 6].isNull()) {
     789           0 :         fundDest = DecodeDestination(request.params[paramIdx + 6].get_str());
     790           0 :         if (!IsValidDestination(fundDest))
     791           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[paramIdx + 6].get_str());
     792           0 :     }
     793             : 
     794           0 :     bool fSubmit{true};
     795           0 :     if ((action == ProTxRegisterAction::External || action == ProTxRegisterAction::Fund) && !request.params[paramIdx + 7].isNull()) {
     796           0 :         fSubmit = ParseBoolV(request.params[paramIdx + 7], "submit");
     797           0 :     }
     798             : 
     799           0 :     if (action == ProTxRegisterAction::Fund) {
     800           0 :         FundSpecialTx(*pwallet, tx, ptx, fundDest);
     801           0 :         UpdateSpecialTxInputsHash(tx, ptx);
     802           0 :         CAmount fundCollateral = GetMnType(mnType).collat_amount;
     803           0 :         uint32_t collateralIndex = (uint32_t) -1;
     804           0 :         for (uint32_t i = 0; i < tx.vout.size(); i++) {
     805           0 :             if (tx.vout[i].nValue == fundCollateral) {
     806           0 :                 collateralIndex = i;
     807           0 :                 break;
     808             :             }
     809           0 :         }
     810           0 :         CHECK_NONFATAL(collateralIndex != (uint32_t) -1);
     811           0 :         ptx.collateralOutpoint.n = collateralIndex;
     812             : 
     813           0 :         SetTxPayload(tx, ptx);
     814           0 :         return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit);
     815             :     } else {
     816             :         // referencing external collateral
     817             : 
     818           0 :         const bool unlockOnError = [&]() {
     819           0 :             if (LOCK(pwallet->cs_wallet); !pwallet->IsLockedCoin(ptx.collateralOutpoint)) {
     820           0 :                 pwallet->LockCoin(ptx.collateralOutpoint);
     821           0 :                 return true;
     822             :             }
     823           0 :             return false;
     824           0 :         }();
     825             :         try {
     826           0 :             FundSpecialTx(*pwallet, tx, ptx, fundDest);
     827           0 :             UpdateSpecialTxInputsHash(tx, ptx);
     828           0 :             Coin coin;
     829           0 :             if (!GetUTXOCoin(chainman.ActiveChainstate(), ptx.collateralOutpoint, coin)) {
     830           0 :                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("collateral not found: %s", ptx.collateralOutpoint.ToStringShort()));
     831             :             }
     832           0 :             CTxDestination txDest;
     833           0 :             ExtractDestination(coin.out.scriptPubKey, txDest);
     834           0 :             const PKHash* pkhash = std::get_if<PKHash>(&txDest);
     835           0 :             if (!pkhash) {
     836           0 :                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("collateral type not supported: %s", ptx.collateralOutpoint.ToStringShort()));
     837             :             }
     838             : 
     839           0 :             if (action == ProTxRegisterAction::Prepare) {
     840             :                 // external signing with collateral key
     841           0 :                 ptx.vchSig.clear();
     842           0 :                 SetTxPayload(tx, ptx);
     843             : 
     844           0 :                 UniValue ret(UniValue::VOBJ);
     845           0 :                 ret.pushKV("tx", EncodeHexTx(CTransaction(tx)));
     846           0 :                 ret.pushKV("collateralAddress", EncodeDestination(txDest));
     847           0 :                 ret.pushKV("signMessage", ptx.MakeSignString());
     848           0 :                 return ret;
     849           0 :             } else {
     850             :                 {
     851           0 :                     LOCK(pwallet->cs_wallet);
     852             :                     // lets prove we own the collateral
     853           0 :                     CScript scriptPubKey = GetScriptForDestination(txDest);
     854           0 :                     std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
     855             : 
     856           0 :                     std::string signed_payload;
     857           0 :                     SigningResult err = pwallet->SignMessage(ptx.MakeSignString(), *pkhash, signed_payload);
     858           0 :                     if (err == SigningResult::SIGNING_FAILED) {
     859           0 :                         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, SigningResultString(err));
     860           0 :                     } else if (err != SigningResult::OK){
     861           0 :                         throw JSONRPCError(RPC_WALLET_ERROR, SigningResultString(err));
     862             :                     }
     863           0 :                     auto opt_vchSig = DecodeBase64(signed_payload);
     864           0 :                     if (!opt_vchSig.has_value()) throw JSONRPCError(RPC_INTERNAL_ERROR, "failed to decode base64 ready signature for protx");
     865           0 :                     ptx.vchSig = opt_vchSig.value();
     866           0 :                 } // cs_wallet
     867           0 :                 SetTxPayload(tx, ptx);
     868           0 :                 return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit);
     869             :             }
     870           0 :         } catch (...) {
     871           0 :             if (unlockOnError) {
     872           0 :                 WITH_LOCK(pwallet->cs_wallet, pwallet->UnlockCoin(ptx.collateralOutpoint));
     873           0 :             }
     874           0 :             throw;
     875           0 :         }
     876             :     }
     877           0 : }
     878             : 
     879           0 : static RPCHelpMan protx_register_submit()
     880             : {
     881           0 :     return RPCHelpMan{"protx register_submit",
     882             :         "\nCombines the unsigned ProTx and a signature of the signMessage, signs all inputs\n"
     883             :         "which were added to cover fees and submits the resulting transaction to the network.\n"
     884             :         "Note: See \"help protx register_prepare\" for more info about creating a ProTx and a message to sign.\n"
     885           0 :         + HELP_REQUIRING_PASSPHRASE,
     886           0 :         {
     887           0 :             {"tx", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The serialized unsigned ProTx in hex format."},
     888           0 :             {"sig", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature signed with the collateral key. Must be in base64 format."},
     889             :         },
     890           0 :         RPCResult{
     891           0 :             RPCResult::Type::STR_HEX, "txid", "The transaction id"
     892             :         },
     893           0 :         RPCExamples{
     894           0 :             HelpExampleCli("protx", "register_submit \"tx\" \"sig\"")
     895             :         },
     896           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     897             : {
     898           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
     899           0 :     const ChainstateManager& chainman = EnsureChainman(node);
     900             : 
     901           0 :     CChainstateHelper& chain_helper = *CHECK_NONFATAL(node.chain_helper);
     902             : 
     903           0 :     const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
     904           0 :     if (!wallet) return UniValue::VNULL;
     905             : 
     906           0 :     EnsureWalletIsUnlocked(*wallet);
     907             : 
     908           0 :     CMutableTransaction tx;
     909           0 :     if (!DecodeHexTx(tx, request.params[0].get_str())) {
     910           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "transaction not deserializable");
     911             :     }
     912           0 :     if (tx.nType != TRANSACTION_PROVIDER_REGISTER) {
     913           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "transaction not a ProRegTx");
     914             :     }
     915           0 :     auto ptx = [&tx]() {
     916           0 :         if (const auto opt_ptx = GetTxPayload<CProRegTx>(tx); opt_ptx.has_value()) {
     917           0 :             return *opt_ptx;
     918             :         }
     919           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "transaction payload not deserializable");
     920           0 :     }();
     921           0 :     if (!ptx.vchSig.empty()) {
     922           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "payload signature not empty");
     923             :     }
     924             : 
     925           0 :     auto opt_vchSig= DecodeBase64(request.params[1].get_str());
     926           0 :     if (!opt_vchSig.has_value()) {
     927           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "malformed base64 encoding");
     928             :     }
     929           0 :     ptx.vchSig = opt_vchSig.value();
     930             : 
     931           0 :     SetTxPayload(tx, ptx);
     932           0 :     return SignAndSendSpecialTx(request, chain_helper, chainman, tx, /*fSubmit=*/true);
     933           0 : },
     934             :     };
     935           0 : }
     936             : 
     937           0 : static RPCHelpMan protx_update_service()
     938             : {
     939           0 :     return RPCHelpMan{"protx update_service",
     940             :         "\nCreates and sends a ProUpServTx to the network. This will update the IP address\n"
     941             :         "of a masternode.\n"
     942             :         "If this is done for a masternode that got PoSe-banned, the ProUpServTx will also revive this masternode.\n"
     943           0 :         + HELP_REQUIRING_PASSPHRASE,
     944           0 :         {
     945           0 :             GetRpcArg("proTxHash"),
     946           0 :             GetRpcArg("coreP2PAddrs_update"),
     947           0 :             GetRpcArg("operatorKey"),
     948           0 :             GetRpcArg("operatorPayoutAddress"),
     949           0 :             GetRpcArg("feeSourceAddress"),
     950           0 :             GetRpcArg("submit"),
     951             :         },
     952           0 :         {
     953           0 :             RPCResult{"if \"submit\" is not set or set to true",
     954           0 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
     955           0 :             RPCResult{"if \"submit\" is set to false",
     956           0 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
     957             :         },
     958           0 :         RPCExamples{
     959           0 :             HelpExampleCli("protx", "update_service \"0123456701234567012345670123456701234567012345670123456701234567\" \"1.2.3.4:1234\" 5a2e15982e62f1e0b7cf9783c64cf7e3af3f90a52d6c40f6f95d624c0b1621cd")
     960             :         },
     961           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     962             : {
     963           0 :     return protx_update_service_common_wrapper(request, MnType::Regular);
     964             : },
     965             :     };
     966           0 : }
     967             : 
     968           0 : static RPCHelpMan protx_update_service_evo()
     969             : {
     970           0 :     const std::string command_name{"protx update_service_evo"};
     971           0 :     return RPCHelpMan{
     972           0 :         command_name,
     973             :         "\nCreates and sends a ProUpServTx to the network. This will update the IP address and the Platform fields\n"
     974             :         "of an EvoNode.\n"
     975           0 :         "If this is done for an EvoNode that got PoSe-banned, the ProUpServTx will also revive this EvoNode.\n" +
     976             :             HELP_REQUIRING_PASSPHRASE,
     977           0 :         {
     978           0 :             GetRpcArg("proTxHash"),
     979           0 :             GetRpcArg("coreP2PAddrs_update"),
     980           0 :             GetRpcArg("operatorKey"),
     981           0 :             GetRpcArg("platformNodeID"),
     982           0 :             GetRpcArg("platformP2PAddrs_update"),
     983           0 :             GetRpcArg("platformHTTPSAddrs_update"),
     984           0 :             GetRpcArg("operatorPayoutAddress"),
     985           0 :             GetRpcArg("feeSourceAddress"),
     986           0 :             GetRpcArg("submit"),
     987             :         },
     988           0 :         {
     989           0 :             RPCResult{"if \"submit\" is not set or set to true",
     990           0 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
     991           0 :             RPCResult{"if \"submit\" is set to false",
     992           0 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
     993             :         },
     994           0 :         RPCExamples{
     995           0 :             HelpExampleCli("protx", "update_service_evo \"0123456701234567012345670123456701234567012345670123456701234567\" \"1.2.3.4:1234\" \"5a2e15982e62f1e0b7cf9783c64cf7e3af3f90a52d6c40f6f95d624c0b1621cd\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")},
     996           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     997             : {
     998           0 :     return protx_update_service_common_wrapper(request, MnType::Evo);
     999             : },
    1000             :     };
    1001           0 : }
    1002             : 
    1003           0 : static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& request, const MnType mnType)
    1004             : {
    1005           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1006           0 :     const ChainstateManager& chainman = EnsureChainman(node);
    1007             : 
    1008           0 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1009           0 :     CChainstateHelper& chain_helper = *CHECK_NONFATAL(node.chain_helper);
    1010             : 
    1011           0 :     const bool isEvoRequested = mnType == MnType::Evo;
    1012           0 :     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    1013           0 :     if (!wallet) return UniValue::VNULL;
    1014             : 
    1015           0 :     EnsureWalletIsUnlocked(*wallet);
    1016             : 
    1017           0 :     CProUpServTx ptx;
    1018           0 :     ptx.proTxHash = ParseHashV(request.params[0], "proTxHash");
    1019           0 :     auto dmn = dmnman.GetListAtChainTip().GetMN(ptx.proTxHash);
    1020           0 :     if (!dmn) {
    1021           0 :         throw std::runtime_error(strprintf("masternode with proTxHash %s not found", ptx.proTxHash.ToString()));
    1022             :     }
    1023             : 
    1024           0 :     ptx.nType = mnType;
    1025           0 :     if (dmn->nType != mnType) {
    1026           0 :         throw std::runtime_error(strprintf("masternode with proTxHash %s is not a %s", ptx.proTxHash.ToString(), GetMnType(mnType).description));
    1027             :     }
    1028             : 
    1029           0 :     ptx.nVersion = ProTxVersion::GetMaxFromDeployment<CProUpServTx>(WITH_LOCK(::cs_main,
    1030             :                                                                               return chainman.ActiveChain().Tip()),
    1031           0 :                                                                     chainman);
    1032             : 
    1033             :     // Legacy masternodes must upgrade to BasicBLS before using higher versions.
    1034             :     // Clamp to BasicBLS to avoid "bad-protx-version-upgrade" validation failure.
    1035           0 :     if (dmn->pdmnState->nVersion == ProTxVersion::LegacyBLS && ptx.nVersion > ProTxVersion::BasicBLS) {
    1036           0 :         ptx.nVersion = ProTxVersion::BasicBLS;
    1037           0 :     }
    1038             : 
    1039           0 :     ptx.netInfo = NetInfoInterface::MakeNetInfo(ptx.nVersion);
    1040             : 
    1041           0 :     ProcessNetInfoCore(ptx, request.params[1], /*optional=*/false);
    1042             : 
    1043           0 :     CBLSSecretKey keyOperator = ParseBLSSecretKey(request.params[2].get_str(), "operatorKey");
    1044             : 
    1045           0 :     size_t paramIdx = 3;
    1046           0 :     if (isEvoRequested) {
    1047           0 :         if (!IsHex(request.params[paramIdx].get_str())) {
    1048           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "platformNodeID must be hexadecimal string");
    1049             :         }
    1050           0 :         ptx.platformNodeID.SetHex(request.params[paramIdx].get_str());
    1051             : 
    1052           0 :         ProcessNetInfoPlatform(ptx, request.params[paramIdx + 1], request.params[paramIdx + 2], /*optional=*/false);
    1053             : 
    1054           0 :         paramIdx += 3;
    1055           0 :     }
    1056             : 
    1057           0 :     if (keyOperator.GetPublicKey() != dmn->pdmnState->pubKeyOperator.Get()) {
    1058           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("the operator key does not belong to the registered public key"));
    1059             :     }
    1060             : 
    1061           0 :     CMutableTransaction tx;
    1062           0 :     tx.nVersion = 3;
    1063           0 :     tx.nType = TRANSACTION_PROVIDER_UPDATE_SERVICE;
    1064             : 
    1065             :     // param operatorPayoutAddress
    1066           0 :     if (!request.params[paramIdx].isNull()) {
    1067           0 :         if (request.params[paramIdx].get_str().empty()) {
    1068           0 :             ptx.scriptOperatorPayout = dmn->pdmnState->scriptOperatorPayout;
    1069           0 :         } else {
    1070           0 :             CTxDestination payoutDest = DecodeDestination(request.params[paramIdx].get_str());
    1071           0 :             if (!IsValidDestination(payoutDest)) {
    1072           0 :                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid operator payout address: %s", request.params[paramIdx].get_str()));
    1073             :             }
    1074           0 :             ptx.scriptOperatorPayout = GetScriptForDestination(payoutDest);
    1075             :         }
    1076           0 :     } else {
    1077           0 :         ptx.scriptOperatorPayout = dmn->pdmnState->scriptOperatorPayout;
    1078             :     }
    1079             : 
    1080           0 :     CTxDestination feeSource;
    1081             : 
    1082             :     // param feeSourceAddress
    1083           0 :     if (!request.params[paramIdx + 1].isNull()) {
    1084           0 :         feeSource = DecodeDestination(request.params[paramIdx + 1].get_str());
    1085           0 :         if (!IsValidDestination(feeSource))
    1086           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[paramIdx + 1].get_str());
    1087           0 :     } else {
    1088           0 :         if (ptx.scriptOperatorPayout != CScript()) {
    1089             :             // use operator reward address as default source for fees
    1090           0 :             ExtractDestination(ptx.scriptOperatorPayout, feeSource);
    1091           0 :         } else {
    1092             :             // use payout address as default source for fees
    1093           0 :             ExtractDestination(dmn->pdmnState->scriptPayout, feeSource);
    1094             :         }
    1095             :     }
    1096             : 
    1097           0 :     bool fSubmit{true};
    1098           0 :     if (!request.params[paramIdx + 2].isNull()) {
    1099           0 :         fSubmit = ParseBoolV(request.params[paramIdx + 2], "submit");
    1100           0 :     }
    1101             : 
    1102           0 :     FundSpecialTx(*wallet, tx, ptx, feeSource);
    1103             : 
    1104           0 :     SignSpecialTxPayloadByHash(tx, ptx, keyOperator, /*use_legacy=*/ptx.nVersion == ProTxVersion::LegacyBLS);
    1105           0 :     SetTxPayload(tx, ptx);
    1106             : 
    1107           0 :     return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit);
    1108           0 : }
    1109             : 
    1110           0 : static RPCHelpMan protx_update_registrar_wrapper(const bool specific_legacy_bls_scheme)
    1111             : {
    1112           0 :     std::string rpc_name = specific_legacy_bls_scheme ? "update_registrar_legacy" : "update_registrar";
    1113           0 :     std::string rpc_full_name = std::string("protx ").append(rpc_name);
    1114           0 :     std::string pubkey_operator = specific_legacy_bls_scheme ? "\"0532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"" : "\"8532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"";
    1115           0 :     std::string rpc_example = rpc_name.append(" \"0123456701234567012345670123456701234567012345670123456701234567\" ").append(pubkey_operator).append(" \"" + EXAMPLE_ADDRESS[1] + "\"");
    1116           0 :     return RPCHelpMan{rpc_full_name,
    1117             :         "\nCreates and sends a ProUpRegTx to the network. This will update the operator key, voting key and payout\n"
    1118             :         "address of the masternode specified by \"proTxHash\".\n"
    1119             :         "The owner key of the masternode must be known to your wallet.\n"
    1120           0 :         + std::string(specific_legacy_bls_scheme ? "\nDEPRECATED: May be removed in a future version, pass config option -deprecatedrpc=legacy_mn to use RPC\n" : "")
    1121           0 :         + HELP_REQUIRING_PASSPHRASE,
    1122           0 :         {
    1123           0 :             GetRpcArg("proTxHash"),
    1124           0 :             specific_legacy_bls_scheme ? GetRpcArg("operatorPubKey_update_legacy") : GetRpcArg("operatorPubKey_update"),
    1125           0 :             GetRpcArg("votingAddress_update"),
    1126           0 :             GetRpcArg("payoutAddress_update"),
    1127           0 :             GetRpcArg("feeSourceAddress"),
    1128           0 :             GetRpcArg("submit"),
    1129             :         },
    1130           0 :         {
    1131           0 :             RPCResult{"if \"submit\" is not set or set to true",
    1132           0 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
    1133           0 :             RPCResult{"if \"submit\" is set to false",
    1134           0 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
    1135             :         },
    1136           0 :         RPCExamples{
    1137           0 :             HelpExampleCli("protx", rpc_example)
    1138             :         },
    1139           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1140             : {
    1141           0 :     const bool use_legacy{self.m_name == "protx update_registrar_legacy"};
    1142           0 :     if (use_legacy && !IsDeprecatedRPCEnabled("legacy_mn")) {
    1143           0 :         throw std::runtime_error("DEPRECATED: Pass config option -deprecatedrpc=legacy_mn to enable this RPC");
    1144             :     }
    1145             : 
    1146           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1147           0 :     const ChainstateManager& chainman = EnsureChainman(node);
    1148             : 
    1149           0 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1150           0 :     CChainstateHelper& chain_helper = *CHECK_NONFATAL(node.chain_helper);
    1151             : 
    1152           0 :     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    1153           0 :     if (!wallet) return UniValue::VNULL;
    1154             : 
    1155           0 :     EnsureWalletIsUnlocked(*wallet);
    1156             : 
    1157           0 :     CProUpRegTx ptx;
    1158           0 :     ptx.nVersion = ProTxVersion::GetMaxFromDeployment<CProUpRegTx>(WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()),
    1159           0 :                                                                    chainman, /*is_basic_override=*/!use_legacy);
    1160             : 
    1161           0 :     ptx.proTxHash = ParseHashV(request.params[0], "proTxHash");
    1162           0 :     auto dmn = dmnman.GetListAtChainTip().GetMN(ptx.proTxHash);
    1163           0 :     if (!dmn) {
    1164           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("masternode %s not found", ptx.proTxHash.ToString()));
    1165             :     }
    1166             : 
    1167           0 :     ptx.keyIDVoting = dmn->pdmnState->keyIDVoting;
    1168           0 :     ptx.scriptPayout = dmn->pdmnState->scriptPayout;
    1169             : 
    1170           0 :     if (!request.params[1].get_str().empty()) {
    1171             :         // new pubkey
    1172           0 :         ptx.pubKeyOperator.Set(ParseBLSPubKey(request.params[1].get_str(), "operator BLS address", use_legacy), use_legacy);
    1173           0 :     } else {
    1174             :         // same pubkey, reuse as is
    1175           0 :         ptx.pubKeyOperator = dmn->pdmnState->pubKeyOperator;
    1176             :     }
    1177             : 
    1178           0 :     CHECK_NONFATAL(ptx.pubKeyOperator.IsLegacy() == (ptx.nVersion == ProTxVersion::LegacyBLS));
    1179             : 
    1180           0 :     if (!request.params[2].get_str().empty()) {
    1181           0 :         ptx.keyIDVoting = ParsePubKeyIDFromAddress(request.params[2].get_str(), "voting address");
    1182           0 :     }
    1183             : 
    1184           0 :     CTxDestination payoutDest;
    1185           0 :     ExtractDestination(ptx.scriptPayout, payoutDest);
    1186           0 :     if (!request.params[3].get_str().empty()) {
    1187           0 :         payoutDest = DecodeDestination(request.params[3].get_str());
    1188           0 :         if (!IsValidDestination(payoutDest)) {
    1189           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[3].get_str()));
    1190             :         }
    1191           0 :         ptx.scriptPayout = GetScriptForDestination(payoutDest);
    1192           0 :     }
    1193             : 
    1194             :     {
    1195           0 :         const auto pkhash{PKHash(dmn->pdmnState->keyIDOwner)};
    1196           0 :         LOCK(wallet->cs_wallet);
    1197           0 :         if (wallet->IsMine(GetScriptForDestination(pkhash)) != isminetype::ISMINE_SPENDABLE) {
    1198           0 :             throw std::runtime_error(strprintf("Private key for owner address %s not found in your wallet", EncodeDestination(pkhash)));
    1199             :         }
    1200           0 :     }
    1201             : 
    1202           0 :     CMutableTransaction tx;
    1203           0 :     tx.nVersion = 3;
    1204           0 :     tx.nType = TRANSACTION_PROVIDER_UPDATE_REGISTRAR;
    1205             : 
    1206             :     // make sure we get anough fees added
    1207           0 :     ptx.vchSig.resize(65);
    1208             : 
    1209           0 :     CTxDestination feeSourceDest = payoutDest;
    1210           0 :     if (!request.params[4].isNull()) {
    1211           0 :         feeSourceDest = DecodeDestination(request.params[4].get_str());
    1212           0 :         if (!IsValidDestination(feeSourceDest))
    1213           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[4].get_str());
    1214           0 :     }
    1215             : 
    1216           0 :     bool fSubmit{true};
    1217           0 :     if (!request.params[5].isNull()) {
    1218           0 :         fSubmit = ParseBoolV(request.params[5], "submit");
    1219           0 :     }
    1220             : 
    1221           0 :     FundSpecialTx(*wallet, tx, ptx, feeSourceDest);
    1222           0 :     SignSpecialTxPayloadByHash(tx, ptx, dmn->pdmnState->keyIDOwner, *wallet);
    1223           0 :     SetTxPayload(tx, ptx);
    1224             : 
    1225           0 :     return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit);
    1226           0 : },
    1227             :     };
    1228           0 : }
    1229             : 
    1230           0 : static RPCHelpMan protx_update_registrar()
    1231             : {
    1232           0 :     return protx_update_registrar_wrapper(false);
    1233             : }
    1234             : 
    1235           0 : static RPCHelpMan protx_update_registrar_legacy()
    1236             : {
    1237           0 :     return protx_update_registrar_wrapper(true);
    1238             : }
    1239             : 
    1240           0 : static RPCHelpMan protx_revoke()
    1241             : {
    1242           0 :     return RPCHelpMan{"protx revoke",
    1243             :         "\nCreates and sends a ProUpRevTx to the network. This will revoke the operator key of the masternode and\n"
    1244             :         "put it into the PoSe-banned state. It will also set the service field of the masternode\n"
    1245             :         "to zero. Use this in case your operator key got compromised or you want to stop providing your service\n"
    1246             :         "to the masternode owner.\n"
    1247           0 :         + HELP_REQUIRING_PASSPHRASE,
    1248           0 :         {
    1249           0 :             GetRpcArg("proTxHash"),
    1250           0 :             GetRpcArg("operatorKey"),
    1251           0 :             GetRpcArg("reason"),
    1252           0 :             GetRpcArg("feeSourceAddress"),
    1253           0 :             GetRpcArg("submit"),
    1254             :         },
    1255           0 :         {
    1256           0 :             RPCResult{"if \"submit\" is not set or set to true",
    1257           0 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
    1258           0 :             RPCResult{"if \"submit\" is set to false",
    1259           0 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
    1260             :         },
    1261           0 :         RPCExamples{
    1262           0 :             HelpExampleCli("protx", "revoke \"0123456701234567012345670123456701234567012345670123456701234567\" \"072f36a77261cdd5d64c32d97bac417540eddca1d5612f416feb07ff75a8e240\"")
    1263             :         },
    1264           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1265             : {
    1266           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1267           0 :     const ChainstateManager& chainman = EnsureChainman(node);
    1268             : 
    1269           0 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1270           0 :     CChainstateHelper& chain_helper = *CHECK_NONFATAL(node.chain_helper);
    1271             : 
    1272           0 :     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
    1273           0 :     if (!pwallet) return UniValue::VNULL;
    1274             : 
    1275           0 :     EnsureWalletIsUnlocked(*pwallet);
    1276             : 
    1277           0 :     CProUpRevTx ptx;
    1278           0 :     ptx.proTxHash = ParseHashV(request.params[0], "proTxHash");
    1279             : 
    1280           0 :     auto dmn = dmnman.GetListAtChainTip().GetMN(ptx.proTxHash);
    1281           0 :     if (!dmn) {
    1282           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("masternode %s not found", ptx.proTxHash.ToString()));
    1283             :     }
    1284             : 
    1285           0 :     ptx.nVersion = ProTxVersion::GetMaxFromDeployment<CProUpRevTx>(WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()),
    1286           0 :                                                                    chainman);
    1287             : 
    1288             :     // Legacy masternodes must upgrade to BasicBLS before using higher versions.
    1289             :     // Clamp to BasicBLS to avoid "bad-protx-version-upgrade" validation failure.
    1290           0 :     if (dmn->pdmnState->nVersion == ProTxVersion::LegacyBLS && ptx.nVersion > ProTxVersion::BasicBLS) {
    1291           0 :         ptx.nVersion = ProTxVersion::BasicBLS;
    1292           0 :     }
    1293             : 
    1294           0 :     CBLSSecretKey keyOperator = ParseBLSSecretKey(request.params[1].get_str(), "operatorKey");
    1295             : 
    1296           0 :     if (!request.params[2].isNull()) {
    1297           0 :         int32_t nReason = request.params[2].getInt<int>();
    1298           0 :         if (nReason < 0 || nReason > CProUpRevTx::REASON_LAST) {
    1299           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("invalid reason %d, must be between 0 and %d", nReason, CProUpRevTx::REASON_LAST));
    1300             :         }
    1301           0 :         ptx.nReason = (uint16_t)nReason;
    1302           0 :     }
    1303             : 
    1304           0 :     if (keyOperator.GetPublicKey() != dmn->pdmnState->pubKeyOperator.Get()) {
    1305           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("the operator key does not belong to the registered public key"));
    1306             :     }
    1307             : 
    1308           0 :     CMutableTransaction tx;
    1309           0 :     tx.nVersion = 3;
    1310           0 :     tx.nType = TRANSACTION_PROVIDER_UPDATE_REVOKE;
    1311             : 
    1312           0 :     if (!request.params[3].isNull()) {
    1313           0 :         CTxDestination feeSourceDest = DecodeDestination(request.params[3].get_str());
    1314           0 :         if (!IsValidDestination(feeSourceDest))
    1315           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[3].get_str());
    1316           0 :         FundSpecialTx(*pwallet, tx, ptx, feeSourceDest);
    1317           0 :     } else if (dmn->pdmnState->scriptOperatorPayout != CScript()) {
    1318             :         // Using funds from previousely specified operator payout address
    1319           0 :         CTxDestination txDest;
    1320           0 :         ExtractDestination(dmn->pdmnState->scriptOperatorPayout, txDest);
    1321           0 :         FundSpecialTx(*pwallet, tx, ptx, txDest);
    1322           0 :     } else if (dmn->pdmnState->scriptPayout != CScript()) {
    1323             :         // Using funds from previousely specified masternode payout address
    1324           0 :         CTxDestination txDest;
    1325           0 :         ExtractDestination(dmn->pdmnState->scriptPayout, txDest);
    1326           0 :         FundSpecialTx(*pwallet, tx, ptx, txDest);
    1327           0 :     } else {
    1328           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, "No payout or fee source addresses found, can't revoke");
    1329             :     }
    1330             : 
    1331           0 :     bool fSubmit{true};
    1332           0 :     if (!request.params[4].isNull()) {
    1333           0 :         fSubmit = ParseBoolV(request.params[4], "submit");
    1334           0 :     }
    1335             : 
    1336           0 :     SignSpecialTxPayloadByHash(tx, ptx, keyOperator, /*use_legacy=*/ptx.nVersion == ProTxVersion::LegacyBLS);
    1337           0 :     SetTxPayload(tx, ptx);
    1338             : 
    1339           0 :     return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit);
    1340           0 : },
    1341             :     };
    1342           0 : }
    1343             : 
    1344             : #endif//ENABLE_WALLET
    1345             : 
    1346             : #ifdef ENABLE_WALLET
    1347           0 : static bool CheckWalletOwnsScript(const CWallet* const pwallet, const CScript& script) {
    1348           0 :     if (!pwallet) {
    1349           0 :         return false;
    1350             :     }
    1351           0 :     return WITH_LOCK(pwallet->cs_wallet, return pwallet->IsMine(script)) == isminetype::ISMINE_SPENDABLE;
    1352           0 : }
    1353             : 
    1354           0 : static bool CheckWalletOwnsKey(const CWallet* const pwallet, const CKeyID& keyID) {
    1355           0 :     return CheckWalletOwnsScript(pwallet, GetScriptForDestination(PKHash(keyID)));
    1356           0 : }
    1357             : #endif
    1358             : 
    1359           0 : static UniValue BuildDMNListEntry(const CWallet* const pwallet, const CDeterministicMN& dmn, CMasternodeMetaMan& mn_metaman, bool detailed, const ChainstateManager& chainman, const CBlockIndex* pindex = nullptr)
    1360             : {
    1361           0 :     if (!detailed) {
    1362           0 :         return dmn.proTxHash.ToString();
    1363             :     }
    1364             : 
    1365           0 :     UniValue o = dmn.ToJson();
    1366             : 
    1367           0 :     CTransactionRef collateralTx{nullptr};
    1368           0 :     int confirmations = GetUTXOConfirmations(chainman.ActiveChainstate(), dmn.collateralOutpoint);
    1369             : 
    1370           0 :     if (pindex != nullptr) {
    1371           0 :         if (confirmations > -1) {
    1372           0 :             confirmations -= WITH_LOCK(::cs_main, return chainman.ActiveChain().Height()) - pindex->nHeight;
    1373           0 :         } else {
    1374           0 :             uint256 minedBlockHash;
    1375           0 :             collateralTx = GetTransaction(/* pindex */ nullptr, /* mempool */ nullptr, dmn.collateralOutpoint.hash, Params().GetConsensus(), minedBlockHash);
    1376           0 :             const CBlockIndex* const pindexMined = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(minedBlockHash));
    1377           0 :             CHECK_NONFATAL(pindexMined != nullptr);
    1378           0 :             CHECK_NONFATAL(pindex->GetAncestor(pindexMined->nHeight) == pindexMined);
    1379           0 :             confirmations = pindex->nHeight - pindexMined->nHeight + 1;
    1380             :         }
    1381           0 :     }
    1382           0 :     o.pushKV("confirmations", confirmations);
    1383             : 
    1384             : #ifdef ENABLE_WALLET
    1385           0 :     bool hasOwnerKey = CheckWalletOwnsKey(pwallet, dmn.pdmnState->keyIDOwner);
    1386           0 :     bool hasVotingKey = CheckWalletOwnsKey(pwallet, dmn.pdmnState->keyIDVoting);
    1387             : 
    1388           0 :     bool ownsCollateral = false;
    1389           0 :     if (Coin coin; GetUTXOCoin(chainman.ActiveChainstate(), dmn.collateralOutpoint, coin)) {
    1390           0 :         ownsCollateral = CheckWalletOwnsScript(pwallet, coin.out.scriptPubKey);
    1391           0 :     } else if (collateralTx != nullptr) {
    1392           0 :         ownsCollateral = CheckWalletOwnsScript(pwallet, collateralTx->vout[dmn.collateralOutpoint.n].scriptPubKey);
    1393           0 :     }
    1394             : 
    1395           0 :     if (pwallet) {
    1396           0 :         UniValue walletObj(UniValue::VOBJ);
    1397           0 :         walletObj.pushKV("hasOwnerKey", hasOwnerKey);
    1398           0 :         walletObj.pushKV("hasOperatorKey", false);
    1399           0 :         walletObj.pushKV("hasVotingKey", hasVotingKey);
    1400           0 :         walletObj.pushKV("ownsCollateral", ownsCollateral);
    1401           0 :         walletObj.pushKV("ownsPayeeScript", CheckWalletOwnsScript(pwallet, dmn.pdmnState->scriptPayout));
    1402           0 :         walletObj.pushKV("ownsOperatorRewardScript", CheckWalletOwnsScript(pwallet, dmn.pdmnState->scriptOperatorPayout));
    1403           0 :         o.pushKV("wallet", walletObj);
    1404           0 :     }
    1405             : #endif
    1406             : 
    1407           0 :     o.pushKV("metaInfo", mn_metaman.GetInfo(dmn.proTxHash).ToJson());
    1408             : 
    1409           0 :     return o;
    1410           0 : }
    1411             : 
    1412          92 : static RPCHelpMan protx_list()
    1413             : {
    1414         184 :     return RPCHelpMan{"protx list",
    1415          92 :         "\nLists all ProTxs in your wallet or on-chain, depending on the given type.\n",
    1416         368 :         {
    1417          92 :             {"type", RPCArg::Type::STR, RPCArg::Default{"registered"},
    1418          92 :                 "\nAvailable types:\n"
    1419             :                 "  registered   - List all ProTx which are registered at the given chain height.\n"
    1420             :                 "                 This will also include ProTx which failed PoSe verification.\n"
    1421             :                 "  valid        - List only ProTx which are active/valid at the given chain height.\n"
    1422             :                 "  evo          - List only ProTx corresponding to EvoNodes at the given chain height.\n"
    1423             : #ifdef ENABLE_WALLET
    1424             :                 "  wallet       - List only ProTx which are found in your wallet at the given chain height.\n"
    1425             :                 "                 This will also include ProTx which failed PoSe verification.\n"
    1426             : #endif
    1427             :             },
    1428          92 :             {"detailed", RPCArg::Type::BOOL, RPCArg::Default{false}, "If not specified, only the hashes of the ProTx will be returned."},
    1429          92 :             {"height", RPCArg::Type::NUM, RPCArg::DefaultHint{"current chain-tip"}, ""},
    1430             :         },
    1431          92 :         RPCResult{
    1432          92 :             RPCResult::Type::ARR, "", "List of masternodes",
    1433         276 :             {
    1434          92 :                 RPCResult{"when detailed=false", RPCResult::Type::STR, "", "ProTx hash"},
    1435         184 :                 RPCResult{"when detailed=true", RPCResult::Type::OBJ, "", "",
    1436         184 :                     {
    1437             :                         // TODO: document fields of the detailed entry
    1438          92 :                         {RPCResult::Type::ELISION, "", ""}
    1439             :                     }},
    1440             :             }},
    1441          92 :         RPCExamples{""},
    1442          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1443             : {
    1444           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1445           0 :     const ChainstateManager& chainman = EnsureChainman(node);
    1446             : 
    1447           0 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1448           0 :     CMasternodeMetaMan& mn_metaman = *CHECK_NONFATAL(node.mn_metaman);
    1449             : 
    1450           0 :     std::shared_ptr<CWallet> wallet{nullptr};
    1451             : #ifdef ENABLE_WALLET
    1452             :     try {
    1453           0 :         wallet = GetWalletForJSONRPCRequest(request);
    1454           0 :     } catch (...) {
    1455           0 :     }
    1456             : #endif
    1457             : 
    1458           0 :     std::string type = "registered";
    1459           0 :     if (!request.params[0].isNull()) {
    1460           0 :         type = request.params[0].get_str();
    1461           0 :     }
    1462             : 
    1463           0 :     UniValue ret(UniValue::VARR);
    1464             : 
    1465           0 :     if (g_txindex) {
    1466           0 :         g_txindex->BlockUntilSyncedToCurrentChain();
    1467           0 :     }
    1468             : 
    1469           0 :     if (type == "wallet") {
    1470           0 :         if (!wallet) {
    1471           0 :             throw std::runtime_error("\"protx list wallet\" not supported when wallet is disabled");
    1472             :         }
    1473             : #ifdef ENABLE_WALLET
    1474             : 
    1475           0 :         if (request.params.size() > 4) {
    1476           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "Too many arguments");
    1477             :         }
    1478             : 
    1479           0 :         bool detailed = !request.params[1].isNull() ? ParseBoolV(request.params[1], "detailed") : false;
    1480             : 
    1481           0 :         LOCK2(wallet->cs_wallet, ::cs_main);
    1482           0 :         int height = !request.params[2].isNull() ? request.params[2].getInt<int>() : chainman.ActiveChain().Height();
    1483           0 :         if (height < 1 || height > chainman.ActiveChain().Height()) {
    1484           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid height specified");
    1485             :         }
    1486             : 
    1487           0 :         std::set<COutPoint> setOutpts;
    1488           0 :         for (const auto& outpt : wallet->ListProTxCoins()) {
    1489           0 :             setOutpts.emplace(outpt);
    1490             :         }
    1491             : 
    1492           0 :         CDeterministicMNList mnList = dmnman.GetListForBlock(chainman.ActiveChain()[height]);
    1493           0 :         mnList.ForEachMN(/*onlyValid=*/false, [&](const auto& dmn) {
    1494           0 :             if (setOutpts.count(dmn.collateralOutpoint) ||
    1495           0 :                 CheckWalletOwnsKey(wallet.get(), dmn.pdmnState->keyIDOwner) ||
    1496           0 :                 CheckWalletOwnsKey(wallet.get(), dmn.pdmnState->keyIDVoting) ||
    1497           0 :                 CheckWalletOwnsScript(wallet.get(), dmn.pdmnState->scriptPayout) ||
    1498           0 :                 CheckWalletOwnsScript(wallet.get(), dmn.pdmnState->scriptOperatorPayout)) {
    1499           0 :                 ret.push_back(BuildDMNListEntry(wallet.get(), dmn, mn_metaman, detailed, chainman));
    1500           0 :             }
    1501           0 :         });
    1502             : #endif
    1503           0 :     } else if (type == "valid" || type == "registered" || type == "evo") {
    1504           0 :         if (request.params.size() > 3) {
    1505           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "Too many arguments");
    1506             :         }
    1507             : 
    1508           0 :         bool detailed = !request.params[1].isNull() ? ParseBoolV(request.params[1], "detailed") : false;
    1509             : 
    1510             : #ifdef ENABLE_WALLET
    1511           0 :         LOCK2(wallet ? wallet->cs_wallet : ::cs_main, ::cs_main);
    1512             : #else
    1513             :         LOCK(::cs_main);
    1514             : #endif
    1515           0 :         int height = !request.params[2].isNull() ? request.params[2].getInt<int>() : chainman.ActiveChain().Height();
    1516           0 :         if (height < 1 || height > chainman.ActiveChain().Height()) {
    1517           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid height specified");
    1518             :         }
    1519             : 
    1520           0 :         CDeterministicMNList mnList = dmnman.GetListForBlock(chainman.ActiveChain()[height]);
    1521           0 :         bool onlyValid = type == "valid";
    1522           0 :         bool onlyEvoNodes = type == "evo";
    1523           0 :         mnList.ForEachMN(onlyValid, [&](const auto& dmn) {
    1524           0 :             if (onlyEvoNodes && dmn.nType != MnType::Evo) return;
    1525           0 :             ret.push_back(BuildDMNListEntry(wallet.get(), dmn, mn_metaman, detailed, chainman));
    1526           0 :         });
    1527           0 :     } else {
    1528           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid type specified");
    1529             :     }
    1530             : 
    1531           0 :     return ret;
    1532           0 : },
    1533             :     };
    1534           0 : }
    1535             : 
    1536          92 : static RPCHelpMan protx_info()
    1537             : {
    1538         184 :     return RPCHelpMan{"protx info",
    1539          92 :         "\nReturns detailed information about a deterministic masternode.\n",
    1540         276 :         {
    1541          92 :             GetRpcArg("proTxHash"),
    1542          92 :             {"blockHash", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"(chain tip)"}, "The hash of the block to get deterministic masternode state at"},
    1543             :         },
    1544          92 :         RPCResult{
    1545          92 :             RPCResult::Type::OBJ, "", "Details about a specific deterministic masternode",
    1546         184 :             {
    1547             :                 // TODO: implement proper doc for protx info
    1548          92 :                 {RPCResult::Type::ELISION, "", ""}
    1549             :             }
    1550             :         },
    1551          92 :         RPCExamples{
    1552          92 :             HelpExampleCli("protx", "info \"0123456701234567012345670123456701234567012345670123456701234567\"")
    1553             :         },
    1554          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1555             : {
    1556           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1557           0 :     const ChainstateManager& chainman = EnsureChainman(node);
    1558             : 
    1559           0 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1560           0 :     CMasternodeMetaMan& mn_metaman = *CHECK_NONFATAL(node.mn_metaman);
    1561             : 
    1562           0 :     std::shared_ptr<CWallet> wallet{nullptr};
    1563             : #ifdef ENABLE_WALLET
    1564             :     try {
    1565           0 :         wallet = GetWalletForJSONRPCRequest(request);
    1566           0 :     } catch (...) {
    1567           0 :     }
    1568             : #endif
    1569             : 
    1570           0 :     if (g_txindex) {
    1571           0 :         g_txindex->BlockUntilSyncedToCurrentChain();
    1572           0 :     }
    1573             : 
    1574           0 :     const CBlockIndex* pindex{nullptr};
    1575             : 
    1576           0 :     uint256 proTxHash(ParseHashV(request.params[0], "proTxHash"));
    1577             : 
    1578           0 :     if (request.params[1].isNull()) {
    1579           0 :         LOCK(::cs_main);
    1580           0 :         pindex = chainman.ActiveChain().Tip();
    1581           0 :     } else {
    1582           0 :         LOCK(::cs_main);
    1583           0 :         uint256 blockHash(ParseHashV(request.params[1], "blockHash"));
    1584           0 :         pindex = chainman.m_blockman.LookupBlockIndex(blockHash);
    1585           0 :         if (pindex == nullptr) {
    1586           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
    1587             :         }
    1588           0 :     }
    1589             : 
    1590           0 :     auto mnList = dmnman.GetListForBlock(pindex);
    1591           0 :     auto dmn = mnList.GetMN(proTxHash);
    1592           0 :     if (!dmn) {
    1593           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s not found", proTxHash.ToString()));
    1594             :     }
    1595           0 :     return BuildDMNListEntry(wallet.get(), *dmn, mn_metaman, true, chainman, pindex);
    1596           0 : },
    1597             :     };
    1598           0 : }
    1599             : 
    1600           0 : static uint256 ParseBlock(const UniValue& v, const ChainstateManager& chainman, const std::string& strName) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
    1601             : {
    1602           0 :     AssertLockHeld(::cs_main);
    1603             : 
    1604             :     try {
    1605           0 :         return ParseHashV(v, strName);
    1606           0 :     } catch (...) {
    1607           0 :         bool fail{false}; int32_t h{0};
    1608           0 :         if (v.isNum()) {
    1609           0 :             h = v.getInt<int>();
    1610           0 :         } else if (!ParseInt32(v.get_str(), &h)) {
    1611           0 :             fail = true;
    1612           0 :         }
    1613           0 :         if (fail || h < 1 || h > chainman.ActiveChain().Height()) {
    1614           0 :             throw std::runtime_error(strprintf("%s must be a block hash or chain height and not %s", strName, v.getValStr()));
    1615             :         }
    1616           0 :         return *chainman.ActiveChain()[h]->phashBlock;
    1617           0 :     }
    1618           0 : }
    1619             : 
    1620           0 : static const CBlockIndex* ParseBlockIndex(const UniValue& v, const ChainstateManager& chainman, const std::string& strName) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
    1621             : {
    1622           0 :     AssertLockHeld(::cs_main);
    1623             : 
    1624             :     try {
    1625           0 :         const auto hash{ParseBlock(v, chainman, strName)};
    1626           0 :         const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
    1627           0 :         if (!pindex) {
    1628           0 :             throw std::runtime_error(strprintf("Block %s with hash %s not found", strName, v.getValStr()));
    1629             :         }
    1630           0 :         return pindex;
    1631           0 :     } catch (...) {
    1632             :         // Same phrasing as ParseBlock() as it can parse heights
    1633           0 :         throw std::runtime_error(strprintf("%s must be a block hash or chain height and not %s", strName, v.getValStr()));
    1634           0 :     }
    1635           0 : }
    1636             : 
    1637          92 : static RPCHelpMan protx_diff()
    1638             : {
    1639         184 :     return RPCHelpMan{"protx diff",
    1640          92 :         "\nCalculates a diff between two deterministic masternode lists. The result also contains proof data.\n",
    1641         368 :         {
    1642          92 :             {"baseBlock", RPCArg::Type::STR, RPCArg::Optional::NO, "The starting block hash or height."},
    1643          92 :             {"block", RPCArg::Type::STR, RPCArg::Optional::NO, "The ending block hash or height."},
    1644          92 :             {"extended", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Show additional fields."},
    1645             :         },
    1646          92 :         CSimplifiedMNListDiff::GetJsonHelp(/*key=*/"", /*optional=*/false),
    1647          92 :         RPCExamples{""},
    1648          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1649             : {
    1650           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1651           0 :     const ChainstateManager& chainman = EnsureChainman(node);
    1652             : 
    1653           0 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1654           0 :     const LLMQContext& llmq_ctx = *CHECK_NONFATAL(node.llmq_ctx);
    1655             : 
    1656           0 :     LOCK(::cs_main);
    1657           0 :     uint256 baseBlockHash = ParseBlock(request.params[0], chainman, "baseBlock");
    1658           0 :     uint256 blockHash = ParseBlock(request.params[1], chainman, "block");
    1659           0 :     bool extended = false;
    1660           0 :     if (!request.params[2].isNull()) {
    1661           0 :         extended = ParseBoolV(request.params[2], "extended");
    1662           0 :     }
    1663             : 
    1664           0 :     CSimplifiedMNListDiff mnListDiff;
    1665           0 :     std::string strError;
    1666             : 
    1667           0 :     if (!BuildSimplifiedMNListDiff(dmnman, chainman, *llmq_ctx.quorum_block_processor, *llmq_ctx.qman, baseBlockHash,
    1668           0 :                                    blockHash, mnListDiff, strError, extended))
    1669             :     {
    1670           0 :         throw std::runtime_error(strError);
    1671             :     }
    1672             : 
    1673           0 :     return mnListDiff.ToJson(extended);
    1674           0 : },
    1675             :     };
    1676           0 : }
    1677             : 
    1678          92 : static RPCHelpMan protx_listdiff()
    1679             : {
    1680         184 :     return RPCHelpMan{"protx listdiff",
    1681          92 :                "\nCalculate a full MN list diff between two masternode lists.\n",
    1682         276 :                {
    1683          92 :                        {"baseBlock", RPCArg::Type::STR, RPCArg::Optional::NO, "The starting block hash or height."},
    1684          92 :                        {"block", RPCArg::Type::STR, RPCArg::Optional::NO, "The ending block hash or height."},
    1685             :                },
    1686          92 :                 RPCResult {
    1687          92 :                     RPCResult::Type::OBJ, "", "",
    1688         552 :                     {
    1689          92 :                         {RPCResult::Type::NUM, "baseHeight", "Height of base (starting) block"},
    1690          92 :                         {RPCResult::Type::NUM, "blockHeight", "Height of target (ending) block"},
    1691         184 :                         {RPCResult::Type::ARR, "addedMNs", "Added masternodes",
    1692          92 :                             {CDeterministicMN::GetJsonHelp(/*key=*/"", /*optional=*/false)}},
    1693         184 :                         {RPCResult::Type::ARR, "removedMns", "Removed masternodes",
    1694          92 :                             {{RPCResult::Type::STR_HEX, "protx", "ProTx of removed masternode"}}},
    1695         184 :                         {RPCResult::Type::ARR, "updatedMNs", "Updated masternodes",
    1696         184 :                             {{RPCResult::Type::OBJ, "<protx_hash>", "",
    1697          92 :                                 {CDeterministicMNStateDiff::GetJsonHelp(/*key=*/"", /*optional=*/false)}}}},
    1698             :                     },
    1699             :                 },
    1700          92 :                 RPCExamples{""},
    1701          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1702             : {
    1703           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1704           0 :     const ChainstateManager& chainman = EnsureChainman(node);
    1705             : 
    1706           0 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1707             : 
    1708           0 :     LOCK(::cs_main);
    1709           0 :     UniValue ret(UniValue::VOBJ);
    1710             : 
    1711           0 :     const CBlockIndex* pBaseBlockIndex = ParseBlockIndex(request.params[0], chainman, "baseBlock");
    1712           0 :     const CBlockIndex* pTargetBlockIndex = ParseBlockIndex(request.params[1], chainman, "block");
    1713             : 
    1714           0 :     if (pBaseBlockIndex == nullptr) {
    1715           0 :         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Base block not found");
    1716             :     }
    1717             : 
    1718           0 :     if (pTargetBlockIndex == nullptr) {
    1719           0 :         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
    1720             :     }
    1721             : 
    1722           0 :     ret.pushKV("baseHeight", pBaseBlockIndex->nHeight);
    1723           0 :     ret.pushKV("blockHeight", pTargetBlockIndex->nHeight);
    1724             : 
    1725           0 :     auto baseBlockMNList = dmnman.GetListForBlock(pBaseBlockIndex);
    1726           0 :     auto blockMNList = dmnman.GetListForBlock(pTargetBlockIndex);
    1727             : 
    1728           0 :     auto mnDiff = baseBlockMNList.BuildDiff(blockMNList);
    1729             : 
    1730           0 :     UniValue jaddedMNs(UniValue::VARR);
    1731           0 :     for(const auto& mn : mnDiff.addedMNs) {
    1732           0 :         jaddedMNs.push_back(mn->ToJson());
    1733             :     }
    1734           0 :     ret.pushKV("addedMNs", jaddedMNs);
    1735             : 
    1736           0 :     UniValue jremovedMNs(UniValue::VARR);
    1737           0 :     for(const auto& internal_id : mnDiff.removedMns) {
    1738           0 :         auto dmn = baseBlockMNList.GetMNByInternalId(internal_id);
    1739             :         // BuildDiff will construct itself with MNs that we already have knowledge
    1740             :         // of, meaning that fetch operations should never fail.
    1741           0 :         CHECK_NONFATAL(dmn);
    1742           0 :         jremovedMNs.push_back(dmn->proTxHash.ToString());
    1743           0 :     }
    1744           0 :     ret.pushKV("removedMNs", jremovedMNs);
    1745             : 
    1746           0 :     UniValue jupdatedMNs(UniValue::VARR);
    1747           0 :     for(const auto& [internal_id, stateDiff] : mnDiff.updatedMNs) {
    1748           0 :         auto dmn = baseBlockMNList.GetMNByInternalId(internal_id);
    1749             :         // BuildDiff will construct itself with MNs that we already have knowledge
    1750             :         // of, meaning that fetch operations should never fail.
    1751           0 :         CHECK_NONFATAL(dmn);
    1752           0 :         UniValue obj(UniValue::VOBJ);
    1753           0 :         obj.pushKV(dmn->proTxHash.ToString(), stateDiff.ToJson(dmn->nType));
    1754           0 :         jupdatedMNs.push_back(obj);
    1755           0 :     }
    1756           0 :     ret.pushKV("updatedMNs", jupdatedMNs);
    1757             : 
    1758           0 :     return ret;
    1759           0 : },
    1760             :     };
    1761           0 : }
    1762             : 
    1763             : // Helper function for evodb verify/repair commands
    1764           0 : static UniValue evodb_verify_or_repair_impl(const JSONRPCRequest& request, bool repair)
    1765             : {
    1766           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1767           0 :     ChainstateManager& chainman = EnsureChainman(node);
    1768           0 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1769           0 :     CChainstateHelper& chain_helper = *CHECK_NONFATAL(node.chain_helper);
    1770             : 
    1771             :     const CBlockIndex* start_index;
    1772             :     const CBlockIndex* stop_index;
    1773             : 
    1774             :     {
    1775           0 :         LOCK(::cs_main);
    1776             :         // Default to DIP0003 activation height if startBlock not specified
    1777           0 :         if (request.params[0].isNull()) {
    1778           0 :             const auto& consensus_params = Params().GetConsensus();
    1779           0 :             start_index = chainman.ActiveChain()[consensus_params.DIP0003Height];
    1780           0 :             if (!start_index) {
    1781           0 :                 throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot find DIP0003 activation block");
    1782             :             }
    1783           0 :         } else {
    1784           0 :             uint256 start_block_hash = ParseBlock(request.params[0], chainman, "startBlock");
    1785           0 :             start_index = chainman.m_blockman.LookupBlockIndex(start_block_hash);
    1786           0 :             if (!start_index) {
    1787           0 :                 throw JSONRPCError(RPC_INVALID_PARAMETER, "Start block not found");
    1788             :             }
    1789             :         }
    1790             : 
    1791             :         // Default to chain tip if stopBlock not specified
    1792           0 :         if (request.params[1].isNull()) {
    1793           0 :             stop_index = chainman.ActiveChain().Tip();
    1794           0 :             if (!stop_index) {
    1795           0 :                 throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot find chain tip");
    1796             :             }
    1797           0 :         } else {
    1798           0 :             uint256 stop_block_hash = ParseBlock(request.params[1], chainman, "stopBlock");
    1799           0 :             stop_index = chainman.m_blockman.LookupBlockIndex(stop_block_hash);
    1800           0 :             if (!stop_index) {
    1801           0 :                 throw JSONRPCError(RPC_INVALID_PARAMETER, "Stop block not found");
    1802             :             }
    1803             :         }
    1804           0 :     }
    1805             : 
    1806           0 :     int start_height = start_index->nHeight;
    1807           0 :     int stop_height = stop_index->nHeight;
    1808             : 
    1809             :     // Validation
    1810           0 :     if (stop_height < start_height) {
    1811           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "stopBlock must be >= startBlock");
    1812             :     }
    1813             : 
    1814             :     // Create a callback that wraps CSpecialTxProcessor::RebuildListFromBlock
    1815           0 :     auto build_list_func = [&chain_helper](const CBlock& block, const CBlockIndex* const pindexPrev,
    1816             :                                            const CDeterministicMNList& prevList, const CCoinsViewCache& view,
    1817             :                                            bool debugLogs, BlockValidationState& state,
    1818             :                                            CDeterministicMNList& mnListRet) -> bool {
    1819           0 :         return chain_helper.special_tx->RebuildListFromBlock(block, pindexPrev, prevList, view, debugLogs, state, mnListRet);
    1820             :     };
    1821             : 
    1822             :     // Call the dmnman method to do the work
    1823           0 :     auto recalc_result = dmnman.RecalculateAndRepairDiffs(start_index, stop_index, chainman, build_list_func, repair);
    1824             : 
    1825             :     // Convert result to UniValue
    1826           0 :     UniValue result(UniValue::VOBJ);
    1827           0 :     UniValue verification_errors(UniValue::VARR);
    1828             : 
    1829           0 :     for (const auto& error : recalc_result.verification_errors) {
    1830           0 :         verification_errors.push_back(error);
    1831             :     }
    1832             : 
    1833           0 :     result.pushKV("startHeight", recalc_result.start_height);
    1834           0 :     result.pushKV("stopHeight", recalc_result.stop_height);
    1835           0 :     result.pushKV("diffsRecalculated", recalc_result.diffs_recalculated);
    1836           0 :     result.pushKV("snapshotsVerified", recalc_result.snapshots_verified);
    1837           0 :     result.pushKV("verificationErrors", verification_errors);
    1838             : 
    1839             :     // Only include repair errors if we're in repair mode
    1840           0 :     if (repair) {
    1841           0 :         UniValue repair_errors(UniValue::VARR);
    1842           0 :         for (const auto& error : recalc_result.repair_errors) {
    1843           0 :             repair_errors.push_back(error);
    1844             :         }
    1845           0 :         result.pushKV("repairErrors", repair_errors);
    1846           0 :     }
    1847             : 
    1848           0 :     return result;
    1849           0 : }
    1850             : 
    1851          92 : static RPCHelpMan evodb_verify()
    1852             : {
    1853         184 :     return RPCHelpMan{"evodb verify",
    1854          92 :         "\nVerifies evodb diff records between specified block heights.\n"
    1855             :         "Checks that all diffs applied between snapshots in the range match the saved snapshots in evodb.\n"
    1856             :         "This is a read-only operation that does not modify the database.\n"
    1857             :         "If no heights are specified, defaults to the full range from DIP0003 activation to chain tip.\n",
    1858         276 :         {
    1859          92 :             {"startBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The starting block hash or height (defaults to DIP0003 activation height)."},
    1860          92 :             {"stopBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The ending block hash or height (defaults to current chain tip)."},
    1861             :         },
    1862          92 :         RPCResult{
    1863          92 :             RPCResult::Type::OBJ, "", "",
    1864         552 :             {
    1865          92 :                 {RPCResult::Type::NUM, "startHeight", "Actual starting block height (may differ from input if clamped to DIP0003 activation)"},
    1866          92 :                 {RPCResult::Type::NUM, "stopHeight", "Ending block height"},
    1867          92 :                 {RPCResult::Type::NUM, "diffsRecalculated", "Number of diffs recalculated (always 0 for verify-only mode)"},
    1868          92 :                 {RPCResult::Type::NUM, "snapshotsVerified", "Number of snapshot pairs that passed verification"},
    1869         184 :                 {RPCResult::Type::ARR, "verificationErrors", "List of verification errors (empty if verification passed)",
    1870         184 :                     {
    1871          92 :                         {RPCResult::Type::STR, "", "Error message"},
    1872             :                     }
    1873             :                 },
    1874             :             }
    1875             :         },
    1876          92 :         RPCExamples{
    1877          92 :             HelpExampleCli("evodb verify", "")
    1878          92 :             + HelpExampleCli("evodb verify", "1000 2000")
    1879          92 :             + HelpExampleRpc("evodb", "\"verify\"")
    1880          92 :             + HelpExampleRpc("evodb", "\"verify\", 1000, 2000")
    1881             :         },
    1882          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1883             : {
    1884           0 :     return evodb_verify_or_repair_impl(request, false);
    1885             : },
    1886             :     };
    1887           0 : }
    1888             : 
    1889          92 : static RPCHelpMan evodb_repair()
    1890             : {
    1891         184 :     return RPCHelpMan{"evodb repair",
    1892          92 :         "\nRepairs corrupted evodb diff records between specified block heights.\n"
    1893             :         "First verifies all diffs applied between snapshots in the range.\n"
    1894             :         "If verification fails, recalculates diffs from blockchain data and replaces corrupted records.\n"
    1895             :         "If no heights are specified, defaults to the full range from DIP0003 activation to chain tip.\n",
    1896         276 :         {
    1897          92 :             {"startBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The starting block hash or height (defaults to DIP0003 activation height)."},
    1898          92 :             {"stopBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The ending block hash or height (defaults to current chain tip)."},
    1899             :         },
    1900          92 :         RPCResult{
    1901          92 :             RPCResult::Type::OBJ, "", "",
    1902         644 :             {
    1903          92 :                 {RPCResult::Type::NUM, "startHeight", "Actual starting block height (may differ from input if clamped to DIP0003 activation)"},
    1904          92 :                 {RPCResult::Type::NUM, "stopHeight", "Ending block height"},
    1905          92 :                 {RPCResult::Type::NUM, "diffsRecalculated", "Number of diffs successfully recalculated and written to database"},
    1906          92 :                 {RPCResult::Type::NUM, "snapshotsVerified", "Number of snapshot pairs that passed verification"},
    1907         184 :                 {RPCResult::Type::ARR, "verificationErrors", "Errors encountered during verification phase (empty if verification passed)",
    1908         184 :                     {
    1909          92 :                         {RPCResult::Type::STR, "", "Error message"},
    1910             :                     }
    1911             :                 },
    1912         184 :                 {RPCResult::Type::ARR, "repairErrors", "Critical errors encountered during repair phase (non-empty means full reindex required)",
    1913         184 :                     {
    1914          92 :                         {RPCResult::Type::STR, "", "Error message"},
    1915             :                     }
    1916             :                 },
    1917             :             }
    1918             :         },
    1919          92 :         RPCExamples{
    1920          92 :             HelpExampleCli("evodb repair", "")
    1921          92 :             + HelpExampleCli("evodb repair", "1000 2000")
    1922          92 :             + HelpExampleRpc("evodb", "\"repair\"")
    1923          92 :             + HelpExampleRpc("evodb", "\"repair\", 1000, 2000")
    1924             :         },
    1925          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1926             : {
    1927           0 :     return evodb_verify_or_repair_impl(request, true);
    1928             : },
    1929             :     };
    1930           0 : }
    1931             : 
    1932          92 : static RPCHelpMan protx_help()
    1933             : {
    1934          92 :     return RPCHelpMan{
    1935          92 :         "protx",
    1936          92 :         "Set of commands to execute ProTx related actions.\n"
    1937             :         "To get help on individual commands, use \"help protx command\".\n"
    1938             :         "\nAvailable commands:\n"
    1939             : #ifdef ENABLE_WALLET
    1940             :         "  register                 - Create and send ProTx to network\n"
    1941             :         "  register_fund            - Fund, create and send ProTx to network\n"
    1942             :         "  register_prepare         - Create an unsigned ProTx\n"
    1943             :         "  register_evo             - Create and send ProTx to network for an EvoNode\n"
    1944             :         "  register_fund_evo        - Fund, create and send ProTx to network for an EvoNode\n"
    1945             :         "  register_prepare_evo     - Create an unsigned ProTx for an EvoNode\n"
    1946             :         "  register_legacy          - (DEPRECATED) Create a ProTx by parsing BLS using the legacy scheme and send it to network\n"
    1947             :         "  register_fund_legacy     - (DEPRECATED) Fund and create a ProTx by parsing BLS using the legacy scheme, then send it to network\n"
    1948             :         "  register_prepare_legacy  - (DEPRECATED) Create an unsigned ProTx by parsing BLS using the legacy scheme\n"
    1949             :         "  register_submit          - Sign and submit a ProTx\n"
    1950             : #endif
    1951             :         "  list                     - List ProTxs\n"
    1952             :         "  info                     - Return information about a ProTx\n"
    1953             : #ifdef ENABLE_WALLET
    1954             :         "  update_service           - Create and send ProUpServTx to network\n"
    1955             :         "  update_service_evo       - Create and send ProUpServTx to network for an EvoNode\n"
    1956             :         "  update_registrar         - Create and send ProUpRegTx to network\n"
    1957             :         "  update_registrar_legacy  - (DEPRECATED) Create ProUpRegTx by parsing BLS using the legacy scheme, then send it to network\n"
    1958             :         "  revoke                   - Create and send ProUpRevTx to network\n"
    1959             : #endif
    1960             :         "  diff                     - Calculate a diff and a proof between two masternode lists\n"
    1961             :         "  listdiff                 - Calculate a full MN list diff between two masternode lists\n",
    1962         184 :         {
    1963          92 :             {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to execute"},
    1964             :         },
    1965          92 :         RPCResult{RPCResult::Type::NONE, "", ""},
    1966          92 :         RPCExamples{""},
    1967          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1968             : {
    1969           0 :     throw JSONRPCError(RPC_INVALID_PARAMETER, "Must be a valid command");
    1970           0 : },
    1971             :     };
    1972           0 : }
    1973             : 
    1974          95 : static RPCHelpMan bls_generate()
    1975             : {
    1976          95 :     return RPCHelpMan{
    1977          95 :         "bls generate",
    1978          95 :         "\nReturns a BLS secret/public key pair.\n",
    1979         190 :         {
    1980          95 :             {"legacy", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED, can be set if -deprecatedrpc=legacy_mn is passed) Set true to use legacy BLS scheme"},
    1981             :         },
    1982          95 :         RPCResult{RPCResult::Type::OBJ,
    1983          95 :                   "",
    1984          95 :                   "",
    1985         285 :                   {{RPCResult::Type::STR_HEX, "secret", "BLS secret key"},
    1986          95 :                    {RPCResult::Type::STR_HEX, "public", "BLS public key"},
    1987          95 :                    {RPCResult::Type::STR_HEX, "scheme", "BLS scheme (valid schemes: legacy, basic)"}}},
    1988          95 :         RPCExamples{HelpExampleCli("bls generate", "")},
    1989          98 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
    1990           3 :             CBLSSecretKey sk;
    1991           3 :             sk.MakeNewKey();
    1992           3 :             bool bls_legacy_scheme{false};
    1993           3 :             if (!request.params[0].isNull()) {
    1994           2 :                 if (!IsDeprecatedRPCEnabled("legacy_mn")) {
    1995           0 :                     throw std::runtime_error("DEPRECATED: Pass config option -deprecatedrpc=legacy_mn to set this argument");
    1996             :                 }
    1997           2 :                 bls_legacy_scheme = ParseBoolV(request.params[0], "bls_legacy_scheme");
    1998           2 :             }
    1999           3 :             UniValue ret(UniValue::VOBJ);
    2000           3 :             ret.pushKV("secret", sk.ToString());
    2001           3 :             ret.pushKV("public", sk.GetPublicKey().ToString(bls_legacy_scheme));
    2002           3 :             std::string bls_scheme_str = bls_legacy_scheme ? "legacy" : "basic";
    2003           3 :             ret.pushKV("scheme", bls_scheme_str);
    2004           3 :             return ret;
    2005           3 :         },
    2006             :     };
    2007           0 : }
    2008             : 
    2009          99 : static RPCHelpMan bls_fromsecret()
    2010             : {
    2011          99 :     return RPCHelpMan{
    2012          99 :         "bls fromsecret",
    2013          99 :         "\nParses a BLS secret key and returns the secret/public key pair.\n",
    2014         297 :         {
    2015          99 :             {"secret", RPCArg::Type::STR, RPCArg::Optional::NO, "The BLS secret key"},
    2016          99 :             {"legacy", RPCArg::Type::BOOL, RPCArg::Default{false}, "Pass true if you need in legacy scheme"},
    2017             :         },
    2018          99 :         RPCResult{RPCResult::Type::OBJ,
    2019          99 :                   "",
    2020          99 :                   "",
    2021         396 :                   {
    2022          99 :                       {RPCResult::Type::STR_HEX, "secret", "BLS secret key"},
    2023          99 :                       {RPCResult::Type::STR_HEX, "public", "BLS public key"},
    2024          99 :                       {RPCResult::Type::STR_HEX, "scheme", "BLS scheme (valid schemes: legacy, basic)"},
    2025             :                   }},
    2026          99 :         RPCExamples{
    2027          99 :             HelpExampleCli("bls fromsecret", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")},
    2028         106 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
    2029           7 :             bool bls_legacy_scheme{false};
    2030           7 :             if (!request.params[1].isNull()) {
    2031           5 :                 bls_legacy_scheme = ParseBoolV(request.params[1], "bls_legacy_scheme");
    2032           5 :             }
    2033           7 :             CBLSSecretKey sk = ParseBLSSecretKey(request.params[0].get_str(), "secretKey");
    2034           7 :             UniValue ret(UniValue::VOBJ);
    2035           7 :             ret.pushKV("secret", sk.ToString());
    2036           7 :             ret.pushKV("public", sk.GetPublicKey().ToString(bls_legacy_scheme));
    2037           7 :             std::string bls_scheme_str = bls_legacy_scheme ? "legacy" : "basic";
    2038           7 :             ret.pushKV("scheme", bls_scheme_str);
    2039           7 :             return ret;
    2040           7 :         },
    2041             :     };
    2042           0 : }
    2043             : 
    2044          92 : static RPCHelpMan bls_help()
    2045             : {
    2046         184 :     return RPCHelpMan{"bls",
    2047          92 :         "Set of commands to execute BLS related actions.\n"
    2048             :         "To get help on individual commands, use \"help bls command\".\n"
    2049             :         "\nAvailable commands:\n"
    2050             :         "  generate          - Create a BLS secret/public key pair\n"
    2051             :         "  fromsecret        - Parse a BLS secret key and return the secret/public key pair\n",
    2052         184 :         {
    2053          92 :             {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to execute"},
    2054             :         },
    2055          92 :         RPCResult{RPCResult::Type::NONE, "", ""},
    2056          92 :         RPCExamples{""},
    2057          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    2058             : {
    2059           0 :     throw JSONRPCError(RPC_INVALID_PARAMETER, "Must be a valid command");
    2060           0 : },
    2061             :     };
    2062           0 : }
    2063             : 
    2064             : #ifdef ENABLE_WALLET
    2065           0 : Span<const CRPCCommand> GetWalletEvoRPCCommands()
    2066             : {
    2067           0 :     static const CRPCCommand commands[]{
    2068           0 :         {"evo", &protx_list},
    2069           0 :         {"evo", &protx_info},
    2070           0 :         {"evo", &protx_register},
    2071           0 :         {"evo", &protx_register_evo},
    2072           0 :         {"evo", &protx_register_fund},
    2073           0 :         {"evo", &protx_register_fund_evo},
    2074           0 :         {"evo", &protx_register_prepare},
    2075           0 :         {"evo", &protx_register_prepare_evo},
    2076           0 :         {"evo", &protx_update_service},
    2077           0 :         {"evo", &protx_update_service_evo},
    2078           0 :         {"evo", &protx_register_submit},
    2079           0 :         {"evo", &protx_update_registrar},
    2080           0 :         {"evo", &protx_revoke},
    2081           0 :         {"hidden", &protx_register_legacy},
    2082           0 :         {"hidden", &protx_register_fund_legacy},
    2083           0 :         {"hidden", &protx_register_prepare_legacy},
    2084           0 :         {"hidden", &protx_update_registrar_legacy},
    2085             :     };
    2086           0 :     return commands;
    2087           0 : }
    2088             : #endif // ENABLE_WALLET
    2089             : 
    2090         178 : void RegisterEvoRPCCommands(CRPCTable& tableRPC)
    2091             : {
    2092         546 :     static const CRPCCommand commands[]{
    2093          46 :         {"evo", &bls_help},
    2094          46 :         {"evo", &bls_generate},
    2095          46 :         {"evo", &bls_fromsecret},
    2096          46 :         {"evo", &protx_help},
    2097          46 :         {"evo", &protx_diff},
    2098          46 :         {"evo", &protx_listdiff},
    2099          46 :         {"hidden", &evodb_verify},
    2100          46 :         {"hidden", &evodb_repair},
    2101             :     };
    2102         270 :     static const CRPCCommand commands_wallet[]{
    2103          46 :         {"evo", &protx_list},
    2104          46 :         {"evo", &protx_info},
    2105             :     };
    2106        1602 :     for (const auto& command : commands) {
    2107        1424 :         tableRPC.appendCommand(command.name, &command);
    2108             :     }
    2109             :     // If we aren't compiling with wallet support, we still need to register RPCs that are
    2110             :     // capable of working without wallet support. We have to do this even if wallet support
    2111             :     // is compiled in but is disabled at runtime because runtime disablement prohibits
    2112             :     // registering wallet RPCs. We still want the reduced functionality RPC to be registered.
    2113             :     // TODO: Spin off these hybrid RPCs into dedicated wallet-only and/or wallet-free RPCs
    2114             :     //       and get rid of this workaround.
    2115         356 :     if (!g_wallet_init_interface.HasWalletSupport()
    2116             : #ifdef ENABLE_WALLET
    2117         178 :         || gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)
    2118             : #endif // ENABLE_WALLET
    2119             :     ) {
    2120           0 :         for (const auto& command : commands_wallet) {
    2121           0 :             tableRPC.appendCommand(command.name, &command);
    2122             :         }
    2123           0 :     }
    2124         178 : }

Generated by: LCOV version 1.16