LCOV - code coverage report
Current view: top level - src/rpc - evo.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 1124 1340 83.9 %
Date: 2026-06-25 07:23:43 Functions: 80 92 87.0 %

          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      379603 : static RPCArg GetRpcArg(const std::string& strParamName)
      62             : {
      63      462466 :     static const std::map<std::string, RPCArg> mapParamHelp = {
      64        6138 :         {"collateralAddress",
      65        3069 :             {"collateralAddress", RPCArg::Type::STR, RPCArg::Optional::NO,
      66        3069 :                 "The Dash address to send the collateral to."}
      67             :         },
      68        6138 :         {"collateralHash",
      69        3069 :             {"collateralHash", RPCArg::Type::STR, RPCArg::Optional::NO,
      70        3069 :                 "The collateral transaction hash."}
      71             :         },
      72        6138 :         {"collateralIndex",
      73        3069 :             {"collateralIndex", RPCArg::Type::NUM, RPCArg::Optional::NO,
      74        3069 :                 "The collateral transaction output index."}
      75             :         },
      76        6138 :         {"feeSourceAddress",
      77        3069 :             {"feeSourceAddress", RPCArg::Type::STR, RPCArg::Default{""},
      78        3069 :                 "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        6138 :         {"fundAddress",
      83        3069 :             {"fundAddress", RPCArg::Type::STR, RPCArg::Default{""},
      84        3069 :                 "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        6138 :         {"coreP2PAddrs",
      89        6138 :             {"coreP2PAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
      90        3069 :                 "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        6138 :                 {
      93        3069 :                     {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
      94             :                 }}
      95             :         },
      96        6138 :         {"coreP2PAddrs_update",
      97        6138 :             {"coreP2PAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
      98        3069 :                 "Array of addresses in the form \"ADDR:PORT\". Must be unique on the network.",
      99        6138 :                 {
     100        3069 :                     {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
     101             :                 }}
     102             :         },
     103        6138 :         {"operatorKey",
     104        3069 :             {"operatorKey", RPCArg::Type::STR, RPCArg::Optional::NO,
     105        3069 :                 "The operator BLS private key associated with the\n"
     106             :                 "registered operator public key."}
     107             :         },
     108        6138 :         {"operatorPayoutAddress",
     109        3069 :             {"operatorPayoutAddress", RPCArg::Type::STR, RPCArg::Default{""},
     110        3069 :                 "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        6138 :         {"operatorPubKey_register",
     115        3069 :             {"operatorPubKey", RPCArg::Type::STR, RPCArg::Optional::NO,
     116        3069 :                 "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        6138 :         {"operatorPubKey_register_legacy",
     120        3069 :             {"operatorPubKey", RPCArg::Type::STR, RPCArg::Optional::NO,
     121        3069 :                 "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        6138 :         {"operatorPubKey_update",
     125        3069 :             {"operatorPubKey", RPCArg::Type::STR, RPCArg::Optional::NO,
     126        3069 :                 "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        6138 :         {"operatorPubKey_update_legacy",
     131        3069 :             {"operatorPubKey", RPCArg::Type::STR, RPCArg::Optional::NO,
     132        3069 :                 "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        6138 :         {"operatorReward",
     137        3069 :             {"operatorReward", RPCArg::Type::STR, RPCArg::Optional::NO,
     138        3069 :                 "The fraction in %% to share with the operator.\n"
     139             :                 "The value must be between 0 and 10000."}
     140             :         },
     141        6138 :         {"ownerAddress",
     142        3069 :             {"ownerAddress", RPCArg::Type::STR, RPCArg::Optional::NO,
     143        3069 :                 "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        6138 :         {"payoutAddress_register",
     148        3069 :             {"payoutAddress", RPCArg::Type::STR, RPCArg::Optional::NO,
     149        3069 :                 "The Dash address to use for masternode reward payments."}
     150             :         },
     151        6138 :         {"payoutAddress_update",
     152        3069 :             {"payoutAddress", RPCArg::Type::STR, RPCArg::Optional::NO,
     153        3069 :                 "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        6138 :         {"proTxHash",
     157        3069 :             {"proTxHash", RPCArg::Type::STR, RPCArg::Optional::NO,
     158        3069 :                 "The hash of the initial ProRegTx."}
     159             :         },
     160        6138 :         {"reason",
     161        3069 :             {"reason", RPCArg::Type::NUM, RPCArg::DefaultHint{"Reason is not specified"},
     162        3069 :                 "The reason for masternode service revocation."}
     163             :         },
     164        6138 :         {"submit",
     165        3069 :             {"submit", RPCArg::Type::BOOL, RPCArg::Default{true},
     166        3069 :                 "If true, the resulting transaction is sent to the network."}
     167             :         },
     168        6138 :         {"votingAddress_register",
     169        3069 :             {"votingAddress", RPCArg::Type::STR, RPCArg::Optional::NO,
     170        3069 :                 "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        6138 :         {"votingAddress_update",
     175        3069 :             {"votingAddress", RPCArg::Type::STR, RPCArg::Optional::NO,
     176        3069 :                 "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        6138 :         {"platformNodeID",
     181        3069 :             {"platformNodeID", RPCArg::Type::STR, RPCArg::Optional::NO,
     182        3069 :                 "Platform P2P node ID, derived from P2P public key."}
     183             :         },
     184        6138 :         {"platformP2PAddrs",
     185        6138 :             {"platformP2PAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
     186        3069 :                 "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        6138 :                 {
     189        3069 :                     {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
     190             :                 }}
     191             :         },
     192        6138 :         {"platformP2PAddrs_update",
     193        6138 :             {"platformP2PAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
     194        3069 :                 "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        6138 :                 {
     197        3069 :                     {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
     198             :                 }}
     199             :         },
     200        6138 :         {"platformHTTPSAddrs",
     201        6138 :             {"platformHTTPSAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
     202        3069 :                 "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        6138 :                 {
     205        3069 :                     {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
     206             :                 }}
     207             :         },
     208        6138 :         {"platformHTTPSAddrs_update",
     209        6138 :             {"platformHTTPSAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO,
     210        3069 :                 "Array of addresses in the form \"ADDR:PORT\" used by Platform for their HTTPS API.\n"
     211             :                 "Must be unique on the network.",
     212        6138 :                 {
     213        3069 :                     {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
     214             :                 }}
     215             :         },
     216             :     };
     217             : 
     218      379603 :     auto it = mapParamHelp.find(strParamName);
     219      379603 :     if (it == mapParamHelp.end())
     220           0 :         throw std::runtime_error(strprintf("FIXME: WRONG PARAM NAME %s!", strParamName));
     221             : 
     222      379603 :     return it->second;
     223           0 : }
     224             : 
     225         399 : static CBLSSecretKey ParseBLSSecretKey(const std::string& hexKey, const std::string& paramName)
     226             : {
     227         399 :     CBLSSecretKey secKey;
     228             : 
     229             :     // Actually, bool flag for bls::PrivateKey has other meaning (modOrder)
     230         399 :     if (!secKey.SetHexStr(hexKey, false)) {
     231           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be a valid BLS secret key", paramName));
     232             :     }
     233         399 :     return secKey;
     234         399 : }
     235             : 
     236             : #ifdef ENABLE_WALLET
     237             : 
     238        1102 : static CKeyID ParsePubKeyIDFromAddress(const std::string& strAddress, const std::string& paramName)
     239             : {
     240        1102 :     CTxDestination dest = DecodeDestination(strAddress);
     241        1102 :     const PKHash *pkhash = std::get_if<PKHash>(&dest);
     242        1102 :     if (!pkhash) {
     243           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be a valid P2PKH address, not %s", paramName, strAddress));
     244             :     }
     245        1102 :     return ToKeyID(*pkhash);
     246           0 : }
     247             : 
     248         555 : static CBLSPublicKey ParseBLSPubKey(const std::string& hexKey, const std::string& paramName, bool specific_legacy_bls_scheme)
     249             : {
     250         555 :     CBLSPublicKey pubKey;
     251         555 :     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         555 :     return pubKey;
     255           0 : }
     256             : 
     257             : template <typename SpecialTxPayload>
     258         879 : 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         879 :     wallet.BlockUntilSyncedToCurrentChain();
     264             : 
     265         879 :     LOCK(wallet.cs_wallet);
     266             : 
     267         879 :     CTxDestination nodest = CNoDestination();
     268         879 :     if (fundDest == nodest) {
     269           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, "No source of funds specified");
     270             :     }
     271             : 
     272         879 :     CDataStream ds(SER_NETWORK, PROTOCOL_VERSION);
     273         879 :     ds << payload;
     274         879 :     tx.vExtraPayload.assign(UCharCast(ds.data()), UCharCast(ds.data() + ds.size()));
     275             : 
     276         879 :     static const CTxOut dummyTxOut(0, CScript() << OP_RETURN);
     277         879 :     std::vector<CRecipient> vecSend;
     278         879 :     bool dummyTxOutAdded = false;
     279             : 
     280         879 :     if (tx.vout.empty()) {
     281             :         // add dummy txout as CreateTransaction requires at least one recipient
     282         678 :         tx.vout.emplace_back(dummyTxOut);
     283         678 :         dummyTxOutAdded = true;
     284         678 :     }
     285             : 
     286        1758 :     for (const auto& txOut : tx.vout) {
     287         879 :         CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, false};
     288         879 :         vecSend.push_back(recipient);
     289         879 :     }
     290             : 
     291         879 :     CCoinControl coinControl;
     292         879 :     coinControl.destChange = fundDest;
     293         879 :     coinControl.fRequireAllInputs = false;
     294             : 
     295       39859 :     for (const auto& out : AvailableCoinsListUnspent(wallet).all()) {
     296       38980 :         CTxDestination txDest;
     297       38980 :         if (ExtractDestination(out.txout.scriptPubKey, txDest) && txDest == fundDest) {
     298        1072 :             coinControl.Select(out.outpoint);
     299        1072 :         }
     300             :     }
     301             : 
     302         879 :     if (!coinControl.HasSelected()) {
     303           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("No funds at specified address %s", EncodeDestination(fundDest)));
     304             :     }
     305             : 
     306         879 :     auto res = CreateTransaction(wallet, vecSend, RANDOM_CHANGE_POSITION, coinControl, /*sign=*/true, tx.vExtraPayload.size());
     307         879 :     if (!res) {
     308           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, util::ErrorString(res).original);
     309             :     }
     310             : 
     311         879 :     const CTransactionRef& newTx = res->tx;
     312         879 :     tx.vin = newTx->vin;
     313         879 :     tx.vout = newTx->vout;
     314             : 
     315         879 :     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         678 :         auto it = std::find(tx.vout.begin(), tx.vout.end(), dummyTxOut);
     319         678 :         CHECK_NONFATAL(it != tx.vout.end());
     320         678 :         tx.vout.erase(it);
     321         678 :     }
     322         879 : }
     323             : 
     324             : template<typename SpecialTxPayload>
     325         879 : static void UpdateSpecialTxInputsHash(const CMutableTransaction& tx, SpecialTxPayload& payload)
     326             : {
     327         879 :     payload.inputsHash = CalcTxInputsHash(CTransaction(tx));
     328         879 : }
     329             : 
     330             : template<typename SpecialTxPayload>
     331           8 : static void SignSpecialTxPayloadByHash(const CMutableTransaction& tx, SpecialTxPayload& payload, const CKeyID& keyID, const CWallet& wallet)
     332             : {
     333           8 :     UpdateSpecialTxInputsHash(tx, payload);
     334           8 :     payload.vchSig.clear();
     335             : 
     336           8 :     const uint256 hash = ::SerializeHash(payload);
     337           8 :     if (!wallet.SignSpecialTxPayload(hash, keyID, payload.vchSig)) {
     338           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, "failed to sign special tx");
     339             :     }
     340           8 : }
     341             : 
     342             : template <typename SpecialTxPayload>
     343         392 : static void SignSpecialTxPayloadByHash(const CMutableTransaction& tx, SpecialTxPayload& payload,
     344             :                                        const CBLSSecretKey& key, bool use_legacy)
     345             : {
     346         392 :     UpdateSpecialTxInputsHash(tx, payload);
     347             : 
     348         392 :     uint256 hash = ::SerializeHash(payload);
     349         392 :     payload.sig = key.Sign(hash, use_legacy);
     350         392 : }
     351             : 
     352         879 : static std::string SignAndSendSpecialTx(const JSONRPCRequest& request, CChainstateHelper& chain_helper, const ChainstateManager& chainman, const CMutableTransaction& tx, bool fSubmit)
     353             : {
     354             :     {
     355         879 :     LOCK(::cs_main);
     356             : 
     357         879 :     const CBlockIndex* tip{chainman.ActiveChain().Tip()};
     358         879 :     const Consensus::Params& consensus_params{chainman.GetConsensus()};
     359         879 :     if (!DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_DIP0003)) {
     360           8 :         const int current_height{tip ? tip->nHeight : -1};
     361           8 :         const int next_block_height{current_height + 1};
     362           8 :         const int activation_height{consensus_params.DIP0003Height};
     363           8 :         const int blocks_to_mine{
     364           8 :             activation_height > next_block_height ? activation_height - next_block_height : 0
     365             :         };
     366          16 :         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           8 :             blocks_to_mine == 1 ? "" : "s"));
     372             :     }
     373             : 
     374         871 :     TxValidationState state;
     375         871 :     if (!chain_helper.special_tx->CheckSpecialTx(CTransaction(tx), tip, chainman.ActiveChainstate().CoinsTip(), true, state)) {
     376          17 :         throw std::runtime_error(state.ToString());
     377             :     }
     378         879 :     } // cs_main
     379             : 
     380         854 :     CDataStream ds(SER_NETWORK, PROTOCOL_VERSION);
     381         854 :     ds << tx;
     382             : 
     383         854 :     JSONRPCRequest signRequest(request);
     384         854 :     signRequest.params.setArray();
     385         854 :     signRequest.params.push_back(HexStr(ds));
     386         854 :     UniValue signResult = wallet::signrawtransactionwithwallet().HandleRequest(signRequest);
     387             : 
     388         854 :     if (!fSubmit) {
     389         527 :         return signResult["hex"].get_str();
     390             :     }
     391             : 
     392         327 :     JSONRPCRequest sendRequest(request);
     393         327 :     sendRequest.params.setArray();
     394         327 :     sendRequest.params.push_back(signResult["hex"].get_str());
     395         327 :     return ::sendrawtransaction().HandleRequest(sendRequest).get_str();
     396         879 : }
     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        5945 : static RPCHelpMan protx_register_fund_wrapper(const bool legacy)
     417             : {
     418        5945 :     std::string rpc_name = legacy ? "register_fund_legacy" : "register_fund";
     419        5945 :     std::string rpc_full_name = std::string("protx ").append(rpc_name);
     420        5945 :     std::string pubkey_operator = legacy ? "\"0532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"" : "\"8532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"";
     421        5945 :     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       11890 :     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        5945 :         + std::string(legacy ? "\nDEPRECATED: May be removed in a future version, pass config option -deprecatedrpc=legacy_mn to use RPC\n" : "")
     429        5945 :         + HELP_REQUIRING_PASSPHRASE,
     430       59450 :         {
     431        5945 :             GetRpcArg("collateralAddress"),
     432        5945 :             GetRpcArg("coreP2PAddrs"),
     433        5945 :             GetRpcArg("ownerAddress"),
     434        5945 :             legacy ? GetRpcArg("operatorPubKey_register_legacy") : GetRpcArg("operatorPubKey_register"),
     435        5945 :             GetRpcArg("votingAddress_register"),
     436        5945 :             GetRpcArg("operatorReward"),
     437        5945 :             GetRpcArg("payoutAddress_register"),
     438        5945 :             GetRpcArg("fundAddress"),
     439        5945 :             GetRpcArg("submit"),
     440             :         },
     441       17835 :         {
     442       11890 :             RPCResult{"if \"submit\" is not set or set to true",
     443        5945 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
     444       11890 :             RPCResult{"if \"submit\" is set to false",
     445        5945 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
     446             :         },
     447        5945 :         RPCExamples{
     448        5945 :             HelpExampleCli("protx",  rpc_example)
     449             :         },
     450        6146 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     451             : {
     452         201 :     if (legacy && !IsDeprecatedRPCEnabled("legacy_mn")) {
     453           0 :         throw std::runtime_error("DEPRECATED: Pass config option -deprecatedrpc=legacy_mn to enable this RPC");
     454             :     }
     455         201 :     return protx_register_common_wrapper(request, self.m_name == "protx register_fund_legacy", ProTxRegisterAction::Fund, MnType::Regular);
     456           0 : },
     457             :     };
     458        5945 : }
     459             : 
     460        3064 : static RPCHelpMan protx_register_fund() {
     461        3064 :     return protx_register_fund_wrapper(false);
     462             : }
     463             : 
     464        2881 : static RPCHelpMan protx_register_fund_legacy() {
     465        2881 :     return protx_register_fund_wrapper(true);
     466             : }
     467             : 
     468        5950 : static RPCHelpMan protx_register_wrapper(bool legacy)
     469             : {
     470        5950 :     std::string rpc_name = legacy ? "register_legacy" : "register";
     471        5950 :     std::string rpc_full_name = std::string("protx ").append(rpc_name);
     472        5950 :     std::string pubkey_operator = legacy ? "\"0532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"" : "\"8532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"";
     473        5950 :     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       11900 :     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        5950 :         + std::string(legacy ? "\nDEPRECATED: May be removed in a future version, pass config option -deprecatedrpc=legacy_mn to use RPC\n" : "")
     479        5950 :         + HELP_REQUIRING_PASSPHRASE,
     480       65450 :         {
     481        5950 :             GetRpcArg("collateralHash"),
     482        5950 :             GetRpcArg("collateralIndex"),
     483        5950 :             GetRpcArg("coreP2PAddrs"),
     484        5950 :             GetRpcArg("ownerAddress"),
     485        5950 :             legacy ? GetRpcArg("operatorPubKey_register_legacy") : GetRpcArg("operatorPubKey_register"),
     486        5950 :             GetRpcArg("votingAddress_register"),
     487        5950 :             GetRpcArg("operatorReward"),
     488        5950 :             GetRpcArg("payoutAddress_register"),
     489        5950 :             GetRpcArg("feeSourceAddress"),
     490        5950 :             GetRpcArg("submit"),
     491             :         },
     492       17850 :         {
     493       11900 :             RPCResult{"if \"submit\" is not set or set to true",
     494        5950 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
     495       11900 :             RPCResult{"if \"submit\" is set to false",
     496        5950 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
     497             :         },
     498        5950 :         RPCExamples{
     499        5950 :             HelpExampleCli("protx", rpc_example),
     500             :         },
     501        6156 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     502             : {
     503         206 :     if (legacy && !IsDeprecatedRPCEnabled("legacy_mn")) {
     504           0 :         throw std::runtime_error("DEPRECATED: Pass config option -deprecatedrpc=legacy_mn to enable this RPC");
     505             :     }
     506         206 :     return protx_register_common_wrapper(request, self.m_name == "protx register_legacy", ProTxRegisterAction::External, MnType::Regular);
     507           0 : },
     508             :     };
     509        5950 : }
     510             : 
     511        3069 : static RPCHelpMan protx_register()
     512             : {
     513        3069 :     return protx_register_wrapper(false);
     514             : }
     515             : 
     516        2881 : static RPCHelpMan protx_register_legacy()
     517             : {
     518        2881 :     return protx_register_wrapper(true);
     519             : }
     520             : 
     521        5744 : static RPCHelpMan protx_register_prepare_wrapper(const bool legacy)
     522             : {
     523        5744 :     std::string rpc_name = legacy ? "register_prepare_legacy" : "register_prepare";
     524        5744 :     std::string rpc_full_name = std::string("protx ").append(rpc_name);
     525        5744 :     std::string pubkey_operator = legacy ? "\"0532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"" : "\"8532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"";
     526        5744 :     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       11488 :     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        5744 :         + std::string(legacy ? "\nDEPRECATED: May be removed in a future version, pass config option -deprecatedrpc=legacy_mn to use RPC\n" : ""),
     532       57440 :         {
     533        5744 :             GetRpcArg("collateralHash"),
     534        5744 :             GetRpcArg("collateralIndex"),
     535        5744 :             GetRpcArg("coreP2PAddrs"),
     536        5744 :             GetRpcArg("ownerAddress"),
     537        5744 :             legacy ? GetRpcArg("operatorPubKey_register_legacy") : GetRpcArg("operatorPubKey_register"),
     538        5744 :             GetRpcArg("votingAddress_register"),
     539        5744 :             GetRpcArg("operatorReward"),
     540        5744 :             GetRpcArg("payoutAddress_register"),
     541        5744 :             GetRpcArg("feeSourceAddress"),
     542             :         },
     543        5744 :         RPCResult{
     544        5744 :             RPCResult::Type::OBJ, "", "",
     545       22976 :             {
     546        5744 :                 {RPCResult::Type::STR_HEX, "tx", "The serialized unsigned ProTx in hex format"},
     547        5744 :                 {RPCResult::Type::STR_HEX, "collateralAddress", "The collateral address"},
     548        5744 :                 {RPCResult::Type::STR_HEX, "signMessage", "The string message that needs to be signed with the collateral key"},
     549             :             }},
     550        5744 :         RPCExamples{
     551        5744 :             HelpExampleCli("protx", rpc_example)
     552             :         },
     553        5744 :         [&](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        5744 : }
     562             : 
     563        2872 : static RPCHelpMan protx_register_prepare()
     564             : {
     565        2872 :     return protx_register_prepare_wrapper(false);
     566             : }
     567             : 
     568        2872 : static RPCHelpMan protx_register_prepare_legacy()
     569             : {
     570        2872 :     return protx_register_prepare_wrapper(true);
     571             : }
     572             : 
     573        2872 : static RPCHelpMan protx_register_fund_evo()
     574             : {
     575        2872 :     const std::string command_name{"protx register_fund_evo"};
     576        2872 :     return RPCHelpMan{
     577        2872 :         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        2872 :         "is fully deployed.\n" +
     583             :             HELP_REQUIRING_PASSPHRASE,
     584       37336 :         {
     585        2872 :             GetRpcArg("collateralAddress"),
     586        2872 :             GetRpcArg("coreP2PAddrs"),
     587        2872 :             GetRpcArg("ownerAddress"),
     588        2872 :             GetRpcArg("operatorPubKey_register"),
     589        2872 :             GetRpcArg("votingAddress_register"),
     590        2872 :             GetRpcArg("operatorReward"),
     591        2872 :             GetRpcArg("payoutAddress_register"),
     592        2872 :             GetRpcArg("platformNodeID"),
     593        2872 :             GetRpcArg("platformP2PAddrs"),
     594        2872 :             GetRpcArg("platformHTTPSAddrs"),
     595        2872 :             GetRpcArg("fundAddress"),
     596        2872 :             GetRpcArg("submit"),
     597             :         },
     598        8616 :         {
     599        5744 :             RPCResult{"if \"submit\" is not set or set to true",
     600        2872 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
     601        5744 :             RPCResult{"if \"submit\" is set to false",
     602        2872 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
     603             :         },
     604        2872 :         RPCExamples{
     605        2872 :             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        2872 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     607             : {
     608           0 :     return protx_register_common_wrapper(request, false, ProTxRegisterAction::Fund, MnType::Evo);
     609             : },
     610             :     };
     611        2872 : }
     612             : 
     613        3030 : static RPCHelpMan protx_register_evo()
     614             : {
     615        3030 :     const std::string command_name{"protx register_evo"};
     616        3030 :     return RPCHelpMan{
     617        3030 :         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        3030 :         "transaction output spendable by this wallet. It must also not be used by any other masternode.\n" +
     621             :             HELP_REQUIRING_PASSPHRASE,
     622       42420 :         {
     623        3030 :             GetRpcArg("collateralHash"),
     624        3030 :             GetRpcArg("collateralIndex"),
     625        3030 :             GetRpcArg("coreP2PAddrs"),
     626        3030 :             GetRpcArg("ownerAddress"),
     627        3030 :             GetRpcArg("operatorPubKey_register"),
     628        3030 :             GetRpcArg("votingAddress_register"),
     629        3030 :             GetRpcArg("operatorReward"),
     630        3030 :             GetRpcArg("payoutAddress_register"),
     631        3030 :             GetRpcArg("platformNodeID"),
     632        3030 :             GetRpcArg("platformP2PAddrs"),
     633        3030 :             GetRpcArg("platformHTTPSAddrs"),
     634        3030 :             GetRpcArg("feeSourceAddress"),
     635        3030 :             GetRpcArg("submit"),
     636             :         },
     637        9090 :         {
     638        6060 :             RPCResult{"if \"submit\" is not set or set to true",
     639        3030 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
     640        6060 :             RPCResult{"if \"submit\" is set to false",
     641        3030 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
     642             :         },
     643        3030 :         RPCExamples{
     644        3030 :             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        3188 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     646             : {
     647         158 :     return protx_register_common_wrapper(request, false, ProTxRegisterAction::External, MnType::Evo);
     648             : },
     649             :     };
     650        3030 : }
     651             : 
     652        2872 : static RPCHelpMan protx_register_prepare_evo()
     653             : {
     654        2872 :     const std::string command_name{"protx register_prepare_evo"};
     655        2872 :     return RPCHelpMan{
     656        2872 :         command_name,
     657        2872 :         "\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       37336 :         {
     661        2872 :             GetRpcArg("collateralHash"),
     662        2872 :             GetRpcArg("collateralIndex"),
     663        2872 :             GetRpcArg("coreP2PAddrs"),
     664        2872 :             GetRpcArg("ownerAddress"),
     665        2872 :             GetRpcArg("operatorPubKey_register"),
     666        2872 :             GetRpcArg("votingAddress_register"),
     667        2872 :             GetRpcArg("operatorReward"),
     668        2872 :             GetRpcArg("payoutAddress_register"),
     669        2872 :             GetRpcArg("platformNodeID"),
     670        2872 :             GetRpcArg("platformP2PAddrs"),
     671        2872 :             GetRpcArg("platformHTTPSAddrs"),
     672        2872 :             GetRpcArg("feeSourceAddress"),
     673             :         },
     674        2872 :         RPCResult{
     675       11488 :             RPCResult::Type::OBJ, "", "", {
     676        2872 :                                               {RPCResult::Type::STR_HEX, "tx", "The serialized unsigned ProTx in hex format"},
     677        2872 :                                               {RPCResult::Type::STR_HEX, "collateralAddress", "The collateral address"},
     678        2872 :                                               {RPCResult::Type::STR_HEX, "signMessage", "The string message that needs to be signed with the collateral key"},
     679             :                                           }},
     680        2872 :         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        2872 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     682             : {
     683           0 :     return protx_register_common_wrapper(request, false, ProTxRegisterAction::Prepare, MnType::Evo);
     684             : },
     685             :     };
     686        2872 : }
     687             : 
     688         565 : 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         565 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
     694         565 :     const ChainstateManager& chainman = EnsureChainman(node);
     695             : 
     696         565 :     CChainstateHelper& chain_helper = *CHECK_NONFATAL(node.chain_helper);
     697             : 
     698         565 :     const bool isEvoRequested = mnType == MnType::Evo;
     699             : 
     700         565 :     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
     701         565 :     if (!pwallet) return UniValue::VNULL;
     702             : 
     703         565 :     EnsureWalletIsUnlocked(*pwallet);
     704             : 
     705         565 :     size_t paramIdx = 0;
     706             : 
     707         565 :     CMutableTransaction tx;
     708         565 :     tx.nVersion = 3;
     709         565 :     tx.nType = TRANSACTION_PROVIDER_REGISTER;
     710             : 
     711         565 :     const bool use_legacy = specific_legacy_bls_scheme;
     712             : 
     713         565 :     CProRegTx ptx;
     714         565 :     ptx.nType = mnType;
     715        1695 :     ptx.nVersion = ProTxVersion::GetMaxFromDeployment<CProRegTx>(WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()),
     716         565 :                                                                  chainman, /*is_basic_override=*/!use_legacy);
     717         565 :     ptx.netInfo = NetInfoInterface::MakeNetInfo(ptx.nVersion);
     718             : 
     719         565 :     if (action == ProTxRegisterAction::Fund) {
     720         201 :         CTxDestination collateralDest = DecodeDestination(request.params[paramIdx].get_str());
     721         201 :         if (!IsValidDestination(collateralDest)) {
     722           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid collaterall address: %s", request.params[paramIdx].get_str()));
     723             :         }
     724         201 :         CScript collateralScript = GetScriptForDestination(collateralDest);
     725             : 
     726         201 :         CAmount fundCollateral = GetMnType(mnType).collat_amount;
     727         201 :         CTxOut collateralTxOut(fundCollateral, collateralScript);
     728         201 :         tx.vout.emplace_back(collateralTxOut);
     729             : 
     730         201 :         paramIdx++;
     731         201 :     } else {
     732         364 :         uint256 collateralHash(ParseHashV(request.params[paramIdx], "collateralHash"));
     733         364 :         int32_t collateralIndex = request.params[paramIdx + 1].getInt<int>();
     734         364 :         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         364 :         ptx.collateralOutpoint = COutPoint(collateralHash, (uint32_t)collateralIndex);
     739         364 :         paramIdx += 2;
     740             :     }
     741             : 
     742         565 :     ProcessNetInfoCore(ptx, request.params[paramIdx], /*optional=*/true);
     743             : 
     744         547 :     ptx.keyIDOwner = ParsePubKeyIDFromAddress(request.params[paramIdx + 1].get_str(), "owner address");
     745         547 :     ptx.pubKeyOperator.Set(ParseBLSPubKey(request.params[paramIdx + 2].get_str(), "operator BLS address", use_legacy), use_legacy);
     746         547 :     CHECK_NONFATAL(ptx.pubKeyOperator.IsLegacy() == (ptx.nVersion == ProTxVersion::LegacyBLS));
     747             : 
     748         547 :     CKeyID keyIDVoting = ptx.keyIDOwner;
     749             : 
     750         547 :     if (!request.params[paramIdx + 3].get_str().empty()) {
     751         547 :         keyIDVoting = ParsePubKeyIDFromAddress(request.params[paramIdx + 3].get_str(), "voting address");
     752         547 :     }
     753             : 
     754             :     int64_t operatorReward;
     755         547 :     if (!ParseFixedPoint(request.params[paramIdx + 4].getValStr(), 2, &operatorReward)) {
     756           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorReward must be a number");
     757             :     }
     758         547 :     if (operatorReward < 0 || operatorReward > 10000) {
     759           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "operatorReward must be between 0 and 10000");
     760             :     }
     761         547 :     ptx.nOperatorReward = operatorReward;
     762             : 
     763         547 :     CTxDestination payoutDest = DecodeDestination(request.params[paramIdx + 5].get_str());
     764         547 :     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         547 :     if (isEvoRequested) {
     769         140 :         if (!IsHex(request.params[paramIdx + 6].get_str())) {
     770           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "platformNodeID must be hexadecimal string");
     771             :         }
     772         140 :         ptx.platformNodeID.SetHex(request.params[paramIdx + 6].get_str());
     773             : 
     774         140 :         ProcessNetInfoPlatform(ptx, request.params[paramIdx + 7], request.params[paramIdx + 8], /*optional=*/true);
     775             : 
     776          72 :         paramIdx += 3;
     777          72 :     }
     778             : 
     779         479 :     ptx.keyIDVoting = keyIDVoting;
     780         479 :     ptx.scriptPayout = GetScriptForDestination(payoutDest);
     781             : 
     782         479 :     if (action != ProTxRegisterAction::Fund) {
     783             :         // make sure fee calculation works
     784         278 :         ptx.vchSig.resize(65);
     785         278 :     }
     786             : 
     787         479 :     CTxDestination fundDest = payoutDest;
     788         479 :     if (!request.params[paramIdx + 6].isNull()) {
     789         479 :         fundDest = DecodeDestination(request.params[paramIdx + 6].get_str());
     790         479 :         if (!IsValidDestination(fundDest))
     791           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[paramIdx + 6].get_str());
     792         479 :     }
     793             : 
     794         479 :     bool fSubmit{true};
     795         479 :     if ((action == ProTxRegisterAction::External || action == ProTxRegisterAction::Fund) && !request.params[paramIdx + 7].isNull()) {
     796         479 :         fSubmit = ParseBoolV(request.params[paramIdx + 7], "submit");
     797         479 :     }
     798             : 
     799         479 :     if (action == ProTxRegisterAction::Fund) {
     800         201 :         FundSpecialTx(*pwallet, tx, ptx, fundDest);
     801         201 :         UpdateSpecialTxInputsHash(tx, ptx);
     802         201 :         CAmount fundCollateral = GetMnType(mnType).collat_amount;
     803         201 :         uint32_t collateralIndex = (uint32_t) -1;
     804         402 :         for (uint32_t i = 0; i < tx.vout.size(); i++) {
     805         402 :             if (tx.vout[i].nValue == fundCollateral) {
     806         201 :                 collateralIndex = i;
     807         201 :                 break;
     808             :             }
     809         201 :         }
     810         201 :         CHECK_NONFATAL(collateralIndex != (uint32_t) -1);
     811         201 :         ptx.collateralOutpoint.n = collateralIndex;
     812             : 
     813         201 :         SetTxPayload(tx, ptx);
     814         201 :         return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit);
     815             :     } else {
     816             :         // referencing external collateral
     817             : 
     818         556 :         const bool unlockOnError = [&]() {
     819         395 :             if (LOCK(pwallet->cs_wallet); !pwallet->IsLockedCoin(ptx.collateralOutpoint)) {
     820         117 :                 pwallet->LockCoin(ptx.collateralOutpoint);
     821         117 :                 return true;
     822             :             }
     823         161 :             return false;
     824         278 :         }();
     825             :         try {
     826         278 :             FundSpecialTx(*pwallet, tx, ptx, fundDest);
     827         278 :             UpdateSpecialTxInputsHash(tx, ptx);
     828         278 :             Coin coin;
     829         278 :             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         278 :             CTxDestination txDest;
     833         278 :             ExtractDestination(coin.out.scriptPubKey, txDest);
     834         278 :             const PKHash* pkhash = std::get_if<PKHash>(&txDest);
     835         278 :             if (!pkhash) {
     836           0 :                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("collateral type not supported: %s", ptx.collateralOutpoint.ToStringShort()));
     837             :             }
     838             : 
     839         278 :             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         278 :                     LOCK(pwallet->cs_wallet);
     852             :                     // lets prove we own the collateral
     853         278 :                     CScript scriptPubKey = GetScriptForDestination(txDest);
     854         278 :                     std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
     855             : 
     856         278 :                     std::string signed_payload;
     857         278 :                     SigningResult err = pwallet->SignMessage(ptx.MakeSignString(), *pkhash, signed_payload);
     858         278 :                     if (err == SigningResult::SIGNING_FAILED) {
     859           0 :                         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, SigningResultString(err));
     860         278 :                     } else if (err != SigningResult::OK){
     861           0 :                         throw JSONRPCError(RPC_WALLET_ERROR, SigningResultString(err));
     862             :                     }
     863         278 :                     auto opt_vchSig = DecodeBase64(signed_payload);
     864         278 :                     if (!opt_vchSig.has_value()) throw JSONRPCError(RPC_INTERNAL_ERROR, "failed to decode base64 ready signature for protx");
     865         278 :                     ptx.vchSig = opt_vchSig.value();
     866         278 :                 } // cs_wallet
     867         278 :                 SetTxPayload(tx, ptx);
     868         278 :                 return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit);
     869             :             }
     870         278 :         } catch (...) {
     871          10 :             if (unlockOnError) {
     872          20 :                 WITH_LOCK(pwallet->cs_wallet, pwallet->UnlockCoin(ptx.collateralOutpoint));
     873          10 :             }
     874          10 :             throw;
     875          10 :         }
     876             :     }
     877         585 : }
     878             : 
     879        2872 : static RPCHelpMan protx_register_submit()
     880             : {
     881        5744 :     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        2872 :         + HELP_REQUIRING_PASSPHRASE,
     886        8616 :         {
     887        2872 :             {"tx", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The serialized unsigned ProTx in hex format."},
     888        2872 :             {"sig", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature signed with the collateral key. Must be in base64 format."},
     889             :         },
     890        2872 :         RPCResult{
     891        2872 :             RPCResult::Type::STR_HEX, "txid", "The transaction id"
     892             :         },
     893        2872 :         RPCExamples{
     894        2872 :             HelpExampleCli("protx", "register_submit \"tx\" \"sig\"")
     895             :         },
     896        2872 :         [&](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        3214 : static RPCHelpMan protx_update_service()
     938             : {
     939        6428 :     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        3214 :         + HELP_REQUIRING_PASSPHRASE,
     944       22498 :         {
     945        3214 :             GetRpcArg("proTxHash"),
     946        3214 :             GetRpcArg("coreP2PAddrs_update"),
     947        3214 :             GetRpcArg("operatorKey"),
     948        3214 :             GetRpcArg("operatorPayoutAddress"),
     949        3214 :             GetRpcArg("feeSourceAddress"),
     950        3214 :             GetRpcArg("submit"),
     951             :         },
     952        9642 :         {
     953        6428 :             RPCResult{"if \"submit\" is not set or set to true",
     954        3214 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
     955        6428 :             RPCResult{"if \"submit\" is set to false",
     956        3214 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
     957             :         },
     958        3214 :         RPCExamples{
     959        3214 :             HelpExampleCli("protx", "update_service \"0123456701234567012345670123456701234567012345670123456701234567\" \"1.2.3.4:1234\" 5a2e15982e62f1e0b7cf9783c64cf7e3af3f90a52d6c40f6f95d624c0b1621cd")
     960             :         },
     961        3556 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     962             : {
     963         342 :     return protx_update_service_common_wrapper(request, MnType::Regular);
     964             : },
     965             :     };
     966           0 : }
     967             : 
     968        2920 : static RPCHelpMan protx_update_service_evo()
     969             : {
     970        2920 :     const std::string command_name{"protx update_service_evo"};
     971        2920 :     return RPCHelpMan{
     972        2920 :         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        2920 :         "If this is done for an EvoNode that got PoSe-banned, the ProUpServTx will also revive this EvoNode.\n" +
     976             :             HELP_REQUIRING_PASSPHRASE,
     977       29200 :         {
     978        2920 :             GetRpcArg("proTxHash"),
     979        2920 :             GetRpcArg("coreP2PAddrs_update"),
     980        2920 :             GetRpcArg("operatorKey"),
     981        2920 :             GetRpcArg("platformNodeID"),
     982        2920 :             GetRpcArg("platformP2PAddrs_update"),
     983        2920 :             GetRpcArg("platformHTTPSAddrs_update"),
     984        2920 :             GetRpcArg("operatorPayoutAddress"),
     985        2920 :             GetRpcArg("feeSourceAddress"),
     986        2920 :             GetRpcArg("submit"),
     987             :         },
     988        8760 :         {
     989        5840 :             RPCResult{"if \"submit\" is not set or set to true",
     990        2920 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
     991        5840 :             RPCResult{"if \"submit\" is set to false",
     992        2920 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
     993             :         },
     994        2920 :         RPCExamples{
     995        2920 :             HelpExampleCli("protx", "update_service_evo \"0123456701234567012345670123456701234567012345670123456701234567\" \"1.2.3.4:1234\" \"5a2e15982e62f1e0b7cf9783c64cf7e3af3f90a52d6c40f6f95d624c0b1621cd\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")},
     996        2968 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     997             : {
     998          48 :     return protx_update_service_common_wrapper(request, MnType::Evo);
     999             : },
    1000             :     };
    1001        2920 : }
    1002             : 
    1003         390 : static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& request, const MnType mnType)
    1004             : {
    1005         390 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1006         390 :     const ChainstateManager& chainman = EnsureChainman(node);
    1007             : 
    1008         390 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1009         390 :     CChainstateHelper& chain_helper = *CHECK_NONFATAL(node.chain_helper);
    1010             : 
    1011         390 :     const bool isEvoRequested = mnType == MnType::Evo;
    1012         390 :     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    1013         390 :     if (!wallet) return UniValue::VNULL;
    1014             : 
    1015         390 :     EnsureWalletIsUnlocked(*wallet);
    1016             : 
    1017         390 :     CProUpServTx ptx;
    1018         390 :     ptx.proTxHash = ParseHashV(request.params[0], "proTxHash");
    1019         390 :     auto dmn = dmnman.GetListAtChainTip().GetMN(ptx.proTxHash);
    1020         390 :     if (!dmn) {
    1021           0 :         throw std::runtime_error(strprintf("masternode with proTxHash %s not found", ptx.proTxHash.ToString()));
    1022             :     }
    1023             : 
    1024         390 :     ptx.nType = mnType;
    1025         390 :     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         780 :     ptx.nVersion = ProTxVersion::GetMaxFromDeployment<CProUpServTx>(WITH_LOCK(::cs_main,
    1030             :                                                                               return chainman.ActiveChain().Tip()),
    1031         390 :                                                                     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         390 :     if (dmn->pdmnState->nVersion == ProTxVersion::LegacyBLS && ptx.nVersion > ProTxVersion::BasicBLS) {
    1036           0 :         ptx.nVersion = ProTxVersion::BasicBLS;
    1037           0 :     }
    1038             : 
    1039         390 :     ptx.netInfo = NetInfoInterface::MakeNetInfo(ptx.nVersion);
    1040             : 
    1041         390 :     ProcessNetInfoCore(ptx, request.params[1], /*optional=*/false);
    1042             : 
    1043         386 :     CBLSSecretKey keyOperator = ParseBLSSecretKey(request.params[2].get_str(), "operatorKey");
    1044             : 
    1045         386 :     size_t paramIdx = 3;
    1046         386 :     if (isEvoRequested) {
    1047          44 :         if (!IsHex(request.params[paramIdx].get_str())) {
    1048           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "platformNodeID must be hexadecimal string");
    1049             :         }
    1050          44 :         ptx.platformNodeID.SetHex(request.params[paramIdx].get_str());
    1051             : 
    1052          44 :         ProcessNetInfoPlatform(ptx, request.params[paramIdx + 1], request.params[paramIdx + 2], /*optional=*/false);
    1053             : 
    1054          44 :         paramIdx += 3;
    1055          44 :     }
    1056             : 
    1057         386 :     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         386 :     CMutableTransaction tx;
    1062         386 :     tx.nVersion = 3;
    1063         386 :     tx.nType = TRANSACTION_PROVIDER_UPDATE_SERVICE;
    1064             : 
    1065             :     // param operatorPayoutAddress
    1066         386 :     if (!request.params[paramIdx].isNull()) {
    1067         386 :         if (request.params[paramIdx].get_str().empty()) {
    1068         137 :             ptx.scriptOperatorPayout = dmn->pdmnState->scriptOperatorPayout;
    1069         137 :         } else {
    1070         249 :             CTxDestination payoutDest = DecodeDestination(request.params[paramIdx].get_str());
    1071         249 :             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         249 :             ptx.scriptOperatorPayout = GetScriptForDestination(payoutDest);
    1075             :         }
    1076         386 :     } else {
    1077           0 :         ptx.scriptOperatorPayout = dmn->pdmnState->scriptOperatorPayout;
    1078             :     }
    1079             : 
    1080         386 :     CTxDestination feeSource;
    1081             : 
    1082             :     // param feeSourceAddress
    1083         386 :     if (!request.params[paramIdx + 1].isNull()) {
    1084         386 :         feeSource = DecodeDestination(request.params[paramIdx + 1].get_str());
    1085         386 :         if (!IsValidDestination(feeSource))
    1086           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[paramIdx + 1].get_str());
    1087         386 :     } 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         386 :     bool fSubmit{true};
    1098         386 :     if (!request.params[paramIdx + 2].isNull()) {
    1099         386 :         fSubmit = ParseBoolV(request.params[paramIdx + 2], "submit");
    1100         386 :     }
    1101             : 
    1102         386 :     FundSpecialTx(*wallet, tx, ptx, feeSource);
    1103             : 
    1104         386 :     SignSpecialTxPayloadByHash(tx, ptx, keyOperator, /*use_legacy=*/ptx.nVersion == ProTxVersion::LegacyBLS);
    1105         386 :     SetTxPayload(tx, ptx);
    1106             : 
    1107         386 :     return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit);
    1108         390 : }
    1109             : 
    1110        5752 : static RPCHelpMan protx_update_registrar_wrapper(const bool specific_legacy_bls_scheme)
    1111             : {
    1112        5752 :     std::string rpc_name = specific_legacy_bls_scheme ? "update_registrar_legacy" : "update_registrar";
    1113        5752 :     std::string rpc_full_name = std::string("protx ").append(rpc_name);
    1114        5752 :     std::string pubkey_operator = specific_legacy_bls_scheme ? "\"0532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"" : "\"8532646990082f4fd639f90387b1551f2c7c39d37392cb9055a06a7e85c1d23692db8f87f827886310bccc1e29db9aee\"";
    1115        5752 :     std::string rpc_example = rpc_name.append(" \"0123456701234567012345670123456701234567012345670123456701234567\" ").append(pubkey_operator).append(" \"" + EXAMPLE_ADDRESS[1] + "\"");
    1116       11504 :     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        5752 :         + 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        5752 :         + HELP_REQUIRING_PASSPHRASE,
    1122       40264 :         {
    1123        5752 :             GetRpcArg("proTxHash"),
    1124        5752 :             specific_legacy_bls_scheme ? GetRpcArg("operatorPubKey_update_legacy") : GetRpcArg("operatorPubKey_update"),
    1125        5752 :             GetRpcArg("votingAddress_update"),
    1126        5752 :             GetRpcArg("payoutAddress_update"),
    1127        5752 :             GetRpcArg("feeSourceAddress"),
    1128        5752 :             GetRpcArg("submit"),
    1129             :         },
    1130       17256 :         {
    1131       11504 :             RPCResult{"if \"submit\" is not set or set to true",
    1132        5752 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
    1133       11504 :             RPCResult{"if \"submit\" is set to false",
    1134        5752 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
    1135             :         },
    1136        5752 :         RPCExamples{
    1137        5752 :             HelpExampleCli("protx", rpc_example)
    1138             :         },
    1139        5760 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1140             : {
    1141           8 :     const bool use_legacy{self.m_name == "protx update_registrar_legacy"};
    1142           8 :     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           8 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1147           8 :     const ChainstateManager& chainman = EnsureChainman(node);
    1148             : 
    1149           8 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1150           8 :     CChainstateHelper& chain_helper = *CHECK_NONFATAL(node.chain_helper);
    1151             : 
    1152           8 :     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    1153           8 :     if (!wallet) return UniValue::VNULL;
    1154             : 
    1155           8 :     EnsureWalletIsUnlocked(*wallet);
    1156             : 
    1157           8 :     CProUpRegTx ptx;
    1158          24 :     ptx.nVersion = ProTxVersion::GetMaxFromDeployment<CProUpRegTx>(WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()),
    1159           8 :                                                                    chainman, /*is_basic_override=*/!use_legacy);
    1160             : 
    1161           8 :     ptx.proTxHash = ParseHashV(request.params[0], "proTxHash");
    1162           8 :     auto dmn = dmnman.GetListAtChainTip().GetMN(ptx.proTxHash);
    1163           8 :     if (!dmn) {
    1164           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("masternode %s not found", ptx.proTxHash.ToString()));
    1165             :     }
    1166             : 
    1167           8 :     ptx.keyIDVoting = dmn->pdmnState->keyIDVoting;
    1168           8 :     ptx.scriptPayout = dmn->pdmnState->scriptPayout;
    1169             : 
    1170           8 :     if (!request.params[1].get_str().empty()) {
    1171             :         // new pubkey
    1172           8 :         ptx.pubKeyOperator.Set(ParseBLSPubKey(request.params[1].get_str(), "operator BLS address", use_legacy), use_legacy);
    1173           8 :     } else {
    1174             :         // same pubkey, reuse as is
    1175           0 :         ptx.pubKeyOperator = dmn->pdmnState->pubKeyOperator;
    1176             :     }
    1177             : 
    1178           8 :     CHECK_NONFATAL(ptx.pubKeyOperator.IsLegacy() == (ptx.nVersion == ProTxVersion::LegacyBLS));
    1179             : 
    1180           8 :     if (!request.params[2].get_str().empty()) {
    1181           8 :         ptx.keyIDVoting = ParsePubKeyIDFromAddress(request.params[2].get_str(), "voting address");
    1182           8 :     }
    1183             : 
    1184           8 :     CTxDestination payoutDest;
    1185           8 :     ExtractDestination(ptx.scriptPayout, payoutDest);
    1186           8 :     if (!request.params[3].get_str().empty()) {
    1187           8 :         payoutDest = DecodeDestination(request.params[3].get_str());
    1188           8 :         if (!IsValidDestination(payoutDest)) {
    1189           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[3].get_str()));
    1190             :         }
    1191           8 :         ptx.scriptPayout = GetScriptForDestination(payoutDest);
    1192           8 :     }
    1193             : 
    1194             :     {
    1195           8 :         const auto pkhash{PKHash(dmn->pdmnState->keyIDOwner)};
    1196           8 :         LOCK(wallet->cs_wallet);
    1197           8 :         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           8 :     }
    1201             : 
    1202           8 :     CMutableTransaction tx;
    1203           8 :     tx.nVersion = 3;
    1204           8 :     tx.nType = TRANSACTION_PROVIDER_UPDATE_REGISTRAR;
    1205             : 
    1206             :     // make sure we get anough fees added
    1207           8 :     ptx.vchSig.resize(65);
    1208             : 
    1209           8 :     CTxDestination feeSourceDest = payoutDest;
    1210           8 :     if (!request.params[4].isNull()) {
    1211           4 :         feeSourceDest = DecodeDestination(request.params[4].get_str());
    1212           4 :         if (!IsValidDestination(feeSourceDest))
    1213           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[4].get_str());
    1214           4 :     }
    1215             : 
    1216           8 :     bool fSubmit{true};
    1217           8 :     if (!request.params[5].isNull()) {
    1218           4 :         fSubmit = ParseBoolV(request.params[5], "submit");
    1219           4 :     }
    1220             : 
    1221           8 :     FundSpecialTx(*wallet, tx, ptx, feeSourceDest);
    1222           8 :     SignSpecialTxPayloadByHash(tx, ptx, dmn->pdmnState->keyIDOwner, *wallet);
    1223           8 :     SetTxPayload(tx, ptx);
    1224             : 
    1225           8 :     return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit);
    1226           8 : },
    1227             :     };
    1228        5752 : }
    1229             : 
    1230        2880 : static RPCHelpMan protx_update_registrar()
    1231             : {
    1232        2880 :     return protx_update_registrar_wrapper(false);
    1233             : }
    1234             : 
    1235        2872 : static RPCHelpMan protx_update_registrar_legacy()
    1236             : {
    1237        2872 :     return protx_update_registrar_wrapper(true);
    1238             : }
    1239             : 
    1240        2878 : static RPCHelpMan protx_revoke()
    1241             : {
    1242        5756 :     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        2878 :         + HELP_REQUIRING_PASSPHRASE,
    1248       17268 :         {
    1249        2878 :             GetRpcArg("proTxHash"),
    1250        2878 :             GetRpcArg("operatorKey"),
    1251        2878 :             GetRpcArg("reason"),
    1252        2878 :             GetRpcArg("feeSourceAddress"),
    1253        2878 :             GetRpcArg("submit"),
    1254             :         },
    1255        8634 :         {
    1256        5756 :             RPCResult{"if \"submit\" is not set or set to true",
    1257        2878 :                 RPCResult::Type::STR_HEX, "txid", "The transaction id"},
    1258        5756 :             RPCResult{"if \"submit\" is set to false",
    1259        2878 :                 RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"},
    1260             :         },
    1261        2878 :         RPCExamples{
    1262        2878 :             HelpExampleCli("protx", "revoke \"0123456701234567012345670123456701234567012345670123456701234567\" \"072f36a77261cdd5d64c32d97bac417540eddca1d5612f416feb07ff75a8e240\"")
    1263             :         },
    1264        2884 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1265             : {
    1266           6 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1267           6 :     const ChainstateManager& chainman = EnsureChainman(node);
    1268             : 
    1269           6 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1270           6 :     CChainstateHelper& chain_helper = *CHECK_NONFATAL(node.chain_helper);
    1271             : 
    1272           6 :     std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
    1273           6 :     if (!pwallet) return UniValue::VNULL;
    1274             : 
    1275           6 :     EnsureWalletIsUnlocked(*pwallet);
    1276             : 
    1277           6 :     CProUpRevTx ptx;
    1278           6 :     ptx.proTxHash = ParseHashV(request.params[0], "proTxHash");
    1279             : 
    1280           6 :     auto dmn = dmnman.GetListAtChainTip().GetMN(ptx.proTxHash);
    1281           6 :     if (!dmn) {
    1282           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("masternode %s not found", ptx.proTxHash.ToString()));
    1283             :     }
    1284             : 
    1285          12 :     ptx.nVersion = ProTxVersion::GetMaxFromDeployment<CProUpRevTx>(WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()),
    1286           6 :                                                                    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           6 :     if (dmn->pdmnState->nVersion == ProTxVersion::LegacyBLS && ptx.nVersion > ProTxVersion::BasicBLS) {
    1291           0 :         ptx.nVersion = ProTxVersion::BasicBLS;
    1292           0 :     }
    1293             : 
    1294           6 :     CBLSSecretKey keyOperator = ParseBLSSecretKey(request.params[1].get_str(), "operatorKey");
    1295             : 
    1296           6 :     if (!request.params[2].isNull()) {
    1297           6 :         int32_t nReason = request.params[2].getInt<int>();
    1298           6 :         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           6 :         ptx.nReason = (uint16_t)nReason;
    1302           6 :     }
    1303             : 
    1304           6 :     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           6 :     CMutableTransaction tx;
    1309           6 :     tx.nVersion = 3;
    1310           6 :     tx.nType = TRANSACTION_PROVIDER_UPDATE_REVOKE;
    1311             : 
    1312           6 :     if (!request.params[3].isNull()) {
    1313           6 :         CTxDestination feeSourceDest = DecodeDestination(request.params[3].get_str());
    1314           6 :         if (!IsValidDestination(feeSourceDest))
    1315           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[3].get_str());
    1316           6 :         FundSpecialTx(*pwallet, tx, ptx, feeSourceDest);
    1317           6 :     } 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           6 :     bool fSubmit{true};
    1332           6 :     if (!request.params[4].isNull()) {
    1333           6 :         fSubmit = ParseBoolV(request.params[4], "submit");
    1334           6 :     }
    1335             : 
    1336           6 :     SignSpecialTxPayloadByHash(tx, ptx, keyOperator, /*use_legacy=*/ptx.nVersion == ProTxVersion::LegacyBLS);
    1337           6 :     SetTxPayload(tx, ptx);
    1338             : 
    1339           6 :     return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit);
    1340           6 : },
    1341             :     };
    1342           0 : }
    1343             : 
    1344             : #endif//ENABLE_WALLET
    1345             : 
    1346             : #ifdef ENABLE_WALLET
    1347       16030 : static bool CheckWalletOwnsScript(const CWallet* const pwallet, const CScript& script) {
    1348       16030 :     if (!pwallet) {
    1349        4140 :         return false;
    1350             :     }
    1351       23780 :     return WITH_LOCK(pwallet->cs_wallet, return pwallet->IsMine(script)) == isminetype::ISMINE_SPENDABLE;
    1352       16030 : }
    1353             : 
    1354        7516 : static bool CheckWalletOwnsKey(const CWallet* const pwallet, const CKeyID& keyID) {
    1355        7516 :     return CheckWalletOwnsScript(pwallet, GetScriptForDestination(PKHash(keyID)));
    1356           0 : }
    1357             : #endif
    1358             : 
    1359        3758 : static UniValue BuildDMNListEntry(const CWallet* const pwallet, const CDeterministicMN& dmn, CMasternodeMetaMan& mn_metaman, bool detailed, const ChainstateManager& chainman, const CBlockIndex* pindex = nullptr)
    1360             : {
    1361        3758 :     if (!detailed) {
    1362           0 :         return dmn.proTxHash.ToString();
    1363             :     }
    1364             : 
    1365        3758 :     UniValue o = dmn.ToJson();
    1366             : 
    1367        3758 :     CTransactionRef collateralTx{nullptr};
    1368        3758 :     int confirmations = GetUTXOConfirmations(chainman.ActiveChainstate(), dmn.collateralOutpoint);
    1369             : 
    1370        3758 :     if (pindex != nullptr) {
    1371        3108 :         if (confirmations > -1) {
    1372        6192 :             confirmations -= WITH_LOCK(::cs_main, return chainman.ActiveChain().Height()) - pindex->nHeight;
    1373        3096 :         } else {
    1374          12 :             uint256 minedBlockHash;
    1375          12 :             collateralTx = GetTransaction(/* pindex */ nullptr, /* mempool */ nullptr, dmn.collateralOutpoint.hash, Params().GetConsensus(), minedBlockHash);
    1376          24 :             const CBlockIndex* const pindexMined = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(minedBlockHash));
    1377          12 :             CHECK_NONFATAL(pindexMined != nullptr);
    1378          12 :             CHECK_NONFATAL(pindex->GetAncestor(pindexMined->nHeight) == pindexMined);
    1379          12 :             confirmations = pindex->nHeight - pindexMined->nHeight + 1;
    1380             :         }
    1381        3108 :     }
    1382        3758 :     o.pushKV("confirmations", confirmations);
    1383             : 
    1384             : #ifdef ENABLE_WALLET
    1385        3758 :     bool hasOwnerKey = CheckWalletOwnsKey(pwallet, dmn.pdmnState->keyIDOwner);
    1386        3758 :     bool hasVotingKey = CheckWalletOwnsKey(pwallet, dmn.pdmnState->keyIDVoting);
    1387             : 
    1388        3758 :     bool ownsCollateral = false;
    1389        7516 :     if (Coin coin; GetUTXOCoin(chainman.ActiveChainstate(), dmn.collateralOutpoint, coin)) {
    1390        3746 :         ownsCollateral = CheckWalletOwnsScript(pwallet, coin.out.scriptPubKey);
    1391        3758 :     } else if (collateralTx != nullptr) {
    1392          12 :         ownsCollateral = CheckWalletOwnsScript(pwallet, collateralTx->vout[dmn.collateralOutpoint.n].scriptPubKey);
    1393          12 :     }
    1394             : 
    1395        3758 :     if (pwallet) {
    1396        2378 :         UniValue walletObj(UniValue::VOBJ);
    1397        2378 :         walletObj.pushKV("hasOwnerKey", hasOwnerKey);
    1398        2378 :         walletObj.pushKV("hasOperatorKey", false);
    1399        2378 :         walletObj.pushKV("hasVotingKey", hasVotingKey);
    1400        2378 :         walletObj.pushKV("ownsCollateral", ownsCollateral);
    1401        2378 :         walletObj.pushKV("ownsPayeeScript", CheckWalletOwnsScript(pwallet, dmn.pdmnState->scriptPayout));
    1402        2378 :         walletObj.pushKV("ownsOperatorRewardScript", CheckWalletOwnsScript(pwallet, dmn.pdmnState->scriptOperatorPayout));
    1403        2378 :         o.pushKV("wallet", walletObj);
    1404        2378 :     }
    1405             : #endif
    1406             : 
    1407        3758 :     o.pushKV("metaInfo", mn_metaman.GetInfo(dmn.proTxHash).ToJson());
    1408             : 
    1409        3758 :     return o;
    1410        7516 : }
    1411             : 
    1412        9103 : static RPCHelpMan protx_list()
    1413             : {
    1414       18206 :     return RPCHelpMan{"protx list",
    1415        9103 :         "\nLists all ProTxs in your wallet or on-chain, depending on the given type.\n",
    1416       36412 :         {
    1417        9103 :             {"type", RPCArg::Type::STR, RPCArg::Default{"registered"},
    1418        9103 :                 "\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        9103 :             {"detailed", RPCArg::Type::BOOL, RPCArg::Default{false}, "If not specified, only the hashes of the ProTx will be returned."},
    1429        9103 :             {"height", RPCArg::Type::NUM, RPCArg::DefaultHint{"current chain-tip"}, ""},
    1430             :         },
    1431        9103 :         RPCResult{
    1432        9103 :             RPCResult::Type::ARR, "", "List of masternodes",
    1433       27309 :             {
    1434        9103 :                 RPCResult{"when detailed=false", RPCResult::Type::STR, "", "ProTx hash"},
    1435       18206 :                 RPCResult{"when detailed=true", RPCResult::Type::OBJ, "", "",
    1436       18206 :                     {
    1437             :                         // TODO: document fields of the detailed entry
    1438        9103 :                         {RPCResult::Type::ELISION, "", ""}
    1439             :                     }},
    1440             :             }},
    1441        9103 :         RPCExamples{""},
    1442        9196 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1443             : {
    1444          93 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1445          93 :     const ChainstateManager& chainman = EnsureChainman(node);
    1446             : 
    1447          93 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1448          93 :     CMasternodeMetaMan& mn_metaman = *CHECK_NONFATAL(node.mn_metaman);
    1449             : 
    1450          93 :     std::shared_ptr<CWallet> wallet{nullptr};
    1451             : #ifdef ENABLE_WALLET
    1452             :     try {
    1453          93 :         wallet = GetWalletForJSONRPCRequest(request);
    1454          93 :     } catch (...) {
    1455          89 :     }
    1456             : #endif
    1457             : 
    1458          93 :     std::string type = "registered";
    1459          93 :     if (!request.params[0].isNull()) {
    1460          93 :         type = request.params[0].get_str();
    1461          93 :     }
    1462             : 
    1463          93 :     UniValue ret(UniValue::VARR);
    1464             : 
    1465          93 :     if (g_txindex) {
    1466          93 :         g_txindex->BlockUntilSyncedToCurrentChain();
    1467          93 :     }
    1468             : 
    1469          93 :     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          93 :     } else if (type == "valid" || type == "registered" || type == "evo") {
    1504          93 :         if (request.params.size() > 3) {
    1505           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "Too many arguments");
    1506             :         }
    1507             : 
    1508          93 :         bool detailed = !request.params[1].isNull() ? ParseBoolV(request.params[1], "detailed") : false;
    1509             : 
    1510             : #ifdef ENABLE_WALLET
    1511          73 :         LOCK2(wallet ? wallet->cs_wallet : ::cs_main, ::cs_main);
    1512             : #else
    1513             :         LOCK(::cs_main);
    1514             : #endif
    1515          73 :         int height = !request.params[2].isNull() ? request.params[2].getInt<int>() : chainman.ActiveChain().Height();
    1516          73 :         if (height < 1 || height > chainman.ActiveChain().Height()) {
    1517           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid height specified");
    1518             :         }
    1519             : 
    1520          73 :         CDeterministicMNList mnList = dmnman.GetListForBlock(chainman.ActiveChain()[height]);
    1521          73 :         bool onlyValid = type == "valid";
    1522          73 :         bool onlyEvoNodes = type == "evo";
    1523         723 :         mnList.ForEachMN(onlyValid, [&](const auto& dmn) {
    1524         650 :             if (onlyEvoNodes && dmn.nType != MnType::Evo) return;
    1525         650 :             ret.push_back(BuildDMNListEntry(wallet.get(), dmn, mn_metaman, detailed, chainman));
    1526         650 :         });
    1527          73 :     } else {
    1528           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid type specified");
    1529             :     }
    1530             : 
    1531          73 :     return ret;
    1532         202 : },
    1533             :     };
    1534           0 : }
    1535             : 
    1536       12118 : static RPCHelpMan protx_info()
    1537             : {
    1538       24236 :     return RPCHelpMan{"protx info",
    1539       12118 :         "\nReturns detailed information about a deterministic masternode.\n",
    1540       36354 :         {
    1541       12118 :             GetRpcArg("proTxHash"),
    1542       12118 :             {"blockHash", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"(chain tip)"}, "The hash of the block to get deterministic masternode state at"},
    1543             :         },
    1544       12118 :         RPCResult{
    1545       12118 :             RPCResult::Type::OBJ, "", "Details about a specific deterministic masternode",
    1546       24236 :             {
    1547             :                 // TODO: implement proper doc for protx info
    1548       12118 :                 {RPCResult::Type::ELISION, "", ""}
    1549             :             }
    1550             :         },
    1551       12118 :         RPCExamples{
    1552       12118 :             HelpExampleCli("protx", "info \"0123456701234567012345670123456701234567012345670123456701234567\"")
    1553             :         },
    1554       15226 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1555             : {
    1556        3108 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1557        3108 :     const ChainstateManager& chainman = EnsureChainman(node);
    1558             : 
    1559        3108 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1560        3108 :     CMasternodeMetaMan& mn_metaman = *CHECK_NONFATAL(node.mn_metaman);
    1561             : 
    1562        3108 :     std::shared_ptr<CWallet> wallet{nullptr};
    1563             : #ifdef ENABLE_WALLET
    1564             :     try {
    1565        3108 :         wallet = GetWalletForJSONRPCRequest(request);
    1566        3108 :     } catch (...) {
    1567         750 :     }
    1568             : #endif
    1569             : 
    1570        3108 :     if (g_txindex) {
    1571        3108 :         g_txindex->BlockUntilSyncedToCurrentChain();
    1572        3108 :     }
    1573             : 
    1574        3108 :     const CBlockIndex* pindex{nullptr};
    1575             : 
    1576        3108 :     uint256 proTxHash(ParseHashV(request.params[0], "proTxHash"));
    1577             : 
    1578        3108 :     if (request.params[1].isNull()) {
    1579        3096 :         LOCK(::cs_main);
    1580        3096 :         pindex = chainman.ActiveChain().Tip();
    1581        3096 :     } else {
    1582          12 :         LOCK(::cs_main);
    1583          12 :         uint256 blockHash(ParseHashV(request.params[1], "blockHash"));
    1584          12 :         pindex = chainman.m_blockman.LookupBlockIndex(blockHash);
    1585          12 :         if (pindex == nullptr) {
    1586           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
    1587             :         }
    1588          12 :     }
    1589             : 
    1590        3108 :     auto mnList = dmnman.GetListForBlock(pindex);
    1591        3108 :     auto dmn = mnList.GetMN(proTxHash);
    1592        3108 :     if (!dmn) {
    1593           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s not found", proTxHash.ToString()));
    1594             :     }
    1595        3108 :     return BuildDMNListEntry(wallet.get(), *dmn, mn_metaman, true, chainman, pindex);
    1596        3858 : },
    1597             :     };
    1598           0 : }
    1599             : 
    1600         162 : static uint256 ParseBlock(const UniValue& v, const ChainstateManager& chainman, const std::string& strName) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
    1601             : {
    1602         162 :     AssertLockHeld(::cs_main);
    1603             : 
    1604             :     try {
    1605         162 :         return ParseHashV(v, strName);
    1606          64 :     } catch (...) {
    1607          64 :         bool fail{false}; int32_t h{0};
    1608          64 :         if (v.isNum()) {
    1609          64 :             h = v.getInt<int>();
    1610          64 :         } else if (!ParseInt32(v.get_str(), &h)) {
    1611           0 :             fail = true;
    1612           0 :         }
    1613          64 :         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          64 :         return *chainman.ActiveChain()[h]->phashBlock;
    1617          64 :     }
    1618         226 : }
    1619             : 
    1620          32 : static const CBlockIndex* ParseBlockIndex(const UniValue& v, const ChainstateManager& chainman, const std::string& strName) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
    1621             : {
    1622          32 :     AssertLockHeld(::cs_main);
    1623             : 
    1624             :     try {
    1625          32 :         const auto hash{ParseBlock(v, chainman, strName)};
    1626          32 :         const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
    1627          32 :         if (!pindex) {
    1628           0 :             throw std::runtime_error(strprintf("Block %s with hash %s not found", strName, v.getValStr()));
    1629             :         }
    1630          32 :         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        6203 : static RPCHelpMan protx_diff()
    1638             : {
    1639       12406 :     return RPCHelpMan{"protx diff",
    1640        6203 :         "\nCalculates a diff between two deterministic masternode lists. The result also contains proof data.\n",
    1641       24812 :         {
    1642        6203 :             {"baseBlock", RPCArg::Type::STR, RPCArg::Optional::NO, "The starting block hash or height."},
    1643        6203 :             {"block", RPCArg::Type::STR, RPCArg::Optional::NO, "The ending block hash or height."},
    1644        6203 :             {"extended", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Show additional fields."},
    1645             :         },
    1646        6203 :         CSimplifiedMNListDiff::GetJsonHelp(/*key=*/"", /*optional=*/false),
    1647        6203 :         RPCExamples{""},
    1648        6268 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1649             : {
    1650          65 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1651          65 :     const ChainstateManager& chainman = EnsureChainman(node);
    1652             : 
    1653          65 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1654          65 :     const LLMQContext& llmq_ctx = *CHECK_NONFATAL(node.llmq_ctx);
    1655             : 
    1656          65 :     LOCK(::cs_main);
    1657          65 :     uint256 baseBlockHash = ParseBlock(request.params[0], chainman, "baseBlock");
    1658          65 :     uint256 blockHash = ParseBlock(request.params[1], chainman, "block");
    1659          65 :     bool extended = false;
    1660          65 :     if (!request.params[2].isNull()) {
    1661           0 :         extended = ParseBoolV(request.params[2], "extended");
    1662           0 :     }
    1663             : 
    1664          65 :     CSimplifiedMNListDiff mnListDiff;
    1665          65 :     std::string strError;
    1666             : 
    1667         130 :     if (!BuildSimplifiedMNListDiff(dmnman, chainman, *llmq_ctx.quorum_block_processor, *llmq_ctx.qman, baseBlockHash,
    1668          65 :                                    blockHash, mnListDiff, strError, extended))
    1669             :     {
    1670           0 :         throw std::runtime_error(strError);
    1671             :     }
    1672             : 
    1673          65 :     return mnListDiff.ToJson(extended);
    1674          65 : },
    1675             :     };
    1676           0 : }
    1677             : 
    1678        6154 : static RPCHelpMan protx_listdiff()
    1679             : {
    1680       12308 :     return RPCHelpMan{"protx listdiff",
    1681        6154 :                "\nCalculate a full MN list diff between two masternode lists.\n",
    1682       18462 :                {
    1683        6154 :                        {"baseBlock", RPCArg::Type::STR, RPCArg::Optional::NO, "The starting block hash or height."},
    1684        6154 :                        {"block", RPCArg::Type::STR, RPCArg::Optional::NO, "The ending block hash or height."},
    1685             :                },
    1686        6154 :                 RPCResult {
    1687        6154 :                     RPCResult::Type::OBJ, "", "",
    1688       36924 :                     {
    1689        6154 :                         {RPCResult::Type::NUM, "baseHeight", "Height of base (starting) block"},
    1690        6154 :                         {RPCResult::Type::NUM, "blockHeight", "Height of target (ending) block"},
    1691       12308 :                         {RPCResult::Type::ARR, "addedMNs", "Added masternodes",
    1692        6154 :                             {CDeterministicMN::GetJsonHelp(/*key=*/"", /*optional=*/false)}},
    1693       12308 :                         {RPCResult::Type::ARR, "removedMns", "Removed masternodes",
    1694        6154 :                             {{RPCResult::Type::STR_HEX, "protx", "ProTx of removed masternode"}}},
    1695       12308 :                         {RPCResult::Type::ARR, "updatedMNs", "Updated masternodes",
    1696       12308 :                             {{RPCResult::Type::OBJ, "<protx_hash>", "",
    1697        6154 :                                 {CDeterministicMNStateDiff::GetJsonHelp(/*key=*/"", /*optional=*/false)}}}},
    1698             :                     },
    1699             :                 },
    1700        6154 :                 RPCExamples{""},
    1701        6170 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1702             : {
    1703          16 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
    1704          16 :     const ChainstateManager& chainman = EnsureChainman(node);
    1705             : 
    1706          16 :     CDeterministicMNManager& dmnman = *CHECK_NONFATAL(node.dmnman);
    1707             : 
    1708          16 :     LOCK(::cs_main);
    1709          16 :     UniValue ret(UniValue::VOBJ);
    1710             : 
    1711          16 :     const CBlockIndex* pBaseBlockIndex = ParseBlockIndex(request.params[0], chainman, "baseBlock");
    1712          16 :     const CBlockIndex* pTargetBlockIndex = ParseBlockIndex(request.params[1], chainman, "block");
    1713             : 
    1714          16 :     if (pBaseBlockIndex == nullptr) {
    1715           0 :         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Base block not found");
    1716             :     }
    1717             : 
    1718          16 :     if (pTargetBlockIndex == nullptr) {
    1719           0 :         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
    1720             :     }
    1721             : 
    1722          16 :     ret.pushKV("baseHeight", pBaseBlockIndex->nHeight);
    1723          16 :     ret.pushKV("blockHeight", pTargetBlockIndex->nHeight);
    1724             : 
    1725          16 :     auto baseBlockMNList = dmnman.GetListForBlock(pBaseBlockIndex);
    1726          16 :     auto blockMNList = dmnman.GetListForBlock(pTargetBlockIndex);
    1727             : 
    1728          16 :     auto mnDiff = baseBlockMNList.BuildDiff(blockMNList);
    1729             : 
    1730          16 :     UniValue jaddedMNs(UniValue::VARR);
    1731         112 :     for(const auto& mn : mnDiff.addedMNs) {
    1732          96 :         jaddedMNs.push_back(mn->ToJson());
    1733             :     }
    1734          16 :     ret.pushKV("addedMNs", jaddedMNs);
    1735             : 
    1736          16 :     UniValue jremovedMNs(UniValue::VARR);
    1737          16 :     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          16 :     ret.pushKV("removedMNs", jremovedMNs);
    1745             : 
    1746          16 :     UniValue jupdatedMNs(UniValue::VARR);
    1747          32 :     for(const auto& [internal_id, stateDiff] : mnDiff.updatedMNs) {
    1748           8 :         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           8 :         CHECK_NONFATAL(dmn);
    1752           8 :         UniValue obj(UniValue::VOBJ);
    1753           8 :         obj.pushKV(dmn->proTxHash.ToString(), stateDiff.ToJson(dmn->nType));
    1754           8 :         jupdatedMNs.push_back(obj);
    1755           8 :     }
    1756          16 :     ret.pushKV("updatedMNs", jupdatedMNs);
    1757             : 
    1758          16 :     return ret;
    1759          16 : },
    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        6138 : static RPCHelpMan evodb_verify()
    1852             : {
    1853       12276 :     return RPCHelpMan{"evodb verify",
    1854        6138 :         "\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       18414 :         {
    1859        6138 :             {"startBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The starting block hash or height (defaults to DIP0003 activation height)."},
    1860        6138 :             {"stopBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The ending block hash or height (defaults to current chain tip)."},
    1861             :         },
    1862        6138 :         RPCResult{
    1863        6138 :             RPCResult::Type::OBJ, "", "",
    1864       36828 :             {
    1865        6138 :                 {RPCResult::Type::NUM, "startHeight", "Actual starting block height (may differ from input if clamped to DIP0003 activation)"},
    1866        6138 :                 {RPCResult::Type::NUM, "stopHeight", "Ending block height"},
    1867        6138 :                 {RPCResult::Type::NUM, "diffsRecalculated", "Number of diffs recalculated (always 0 for verify-only mode)"},
    1868        6138 :                 {RPCResult::Type::NUM, "snapshotsVerified", "Number of snapshot pairs that passed verification"},
    1869       12276 :                 {RPCResult::Type::ARR, "verificationErrors", "List of verification errors (empty if verification passed)",
    1870       12276 :                     {
    1871        6138 :                         {RPCResult::Type::STR, "", "Error message"},
    1872             :                     }
    1873             :                 },
    1874             :             }
    1875             :         },
    1876        6138 :         RPCExamples{
    1877        6138 :             HelpExampleCli("evodb verify", "")
    1878        6138 :             + HelpExampleCli("evodb verify", "1000 2000")
    1879        6138 :             + HelpExampleRpc("evodb", "\"verify\"")
    1880        6138 :             + HelpExampleRpc("evodb", "\"verify\", 1000, 2000")
    1881             :         },
    1882        6138 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1883             : {
    1884           0 :     return evodb_verify_or_repair_impl(request, false);
    1885             : },
    1886             :     };
    1887           0 : }
    1888             : 
    1889        6138 : static RPCHelpMan evodb_repair()
    1890             : {
    1891       12276 :     return RPCHelpMan{"evodb repair",
    1892        6138 :         "\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       18414 :         {
    1897        6138 :             {"startBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The starting block hash or height (defaults to DIP0003 activation height)."},
    1898        6138 :             {"stopBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The ending block hash or height (defaults to current chain tip)."},
    1899             :         },
    1900        6138 :         RPCResult{
    1901        6138 :             RPCResult::Type::OBJ, "", "",
    1902       42966 :             {
    1903        6138 :                 {RPCResult::Type::NUM, "startHeight", "Actual starting block height (may differ from input if clamped to DIP0003 activation)"},
    1904        6138 :                 {RPCResult::Type::NUM, "stopHeight", "Ending block height"},
    1905        6138 :                 {RPCResult::Type::NUM, "diffsRecalculated", "Number of diffs successfully recalculated and written to database"},
    1906        6138 :                 {RPCResult::Type::NUM, "snapshotsVerified", "Number of snapshot pairs that passed verification"},
    1907       12276 :                 {RPCResult::Type::ARR, "verificationErrors", "Errors encountered during verification phase (empty if verification passed)",
    1908       12276 :                     {
    1909        6138 :                         {RPCResult::Type::STR, "", "Error message"},
    1910             :                     }
    1911             :                 },
    1912       12276 :                 {RPCResult::Type::ARR, "repairErrors", "Critical errors encountered during repair phase (non-empty means full reindex required)",
    1913       12276 :                     {
    1914        6138 :                         {RPCResult::Type::STR, "", "Error message"},
    1915             :                     }
    1916             :                 },
    1917             :             }
    1918             :         },
    1919        6138 :         RPCExamples{
    1920        6138 :             HelpExampleCli("evodb repair", "")
    1921        6138 :             + HelpExampleCli("evodb repair", "1000 2000")
    1922        6138 :             + HelpExampleRpc("evodb", "\"repair\"")
    1923        6138 :             + HelpExampleRpc("evodb", "\"repair\", 1000, 2000")
    1924             :         },
    1925        6138 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1926             : {
    1927           0 :     return evodb_verify_or_repair_impl(request, true);
    1928             : },
    1929             :     };
    1930           0 : }
    1931             : 
    1932        6156 : static RPCHelpMan protx_help()
    1933             : {
    1934        6156 :     return RPCHelpMan{
    1935        6156 :         "protx",
    1936        6156 :         "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       12312 :         {
    1963        6156 :             {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to execute"},
    1964             :         },
    1965        6156 :         RPCResult{RPCResult::Type::NONE, "", ""},
    1966        6156 :         RPCExamples{""},
    1967        6156 :         [&](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        6594 : static RPCHelpMan bls_generate()
    1975             : {
    1976        6594 :     return RPCHelpMan{
    1977        6594 :         "bls generate",
    1978        6594 :         "\nReturns a BLS secret/public key pair.\n",
    1979       13188 :         {
    1980        6594 :             {"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        6594 :         RPCResult{RPCResult::Type::OBJ,
    1983        6594 :                   "",
    1984        6594 :                   "",
    1985       19782 :                   {{RPCResult::Type::STR_HEX, "secret", "BLS secret key"},
    1986        6594 :                    {RPCResult::Type::STR_HEX, "public", "BLS public key"},
    1987        6594 :                    {RPCResult::Type::STR_HEX, "scheme", "BLS scheme (valid schemes: legacy, basic)"}}},
    1988        6594 :         RPCExamples{HelpExampleCli("bls generate", "")},
    1989        7050 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
    1990         456 :             CBLSSecretKey sk;
    1991         456 :             sk.MakeNewKey();
    1992         456 :             bool bls_legacy_scheme{false};
    1993         456 :             if (!request.params[0].isNull()) {
    1994          20 :                 if (!IsDeprecatedRPCEnabled("legacy_mn")) {
    1995           0 :                     throw std::runtime_error("DEPRECATED: Pass config option -deprecatedrpc=legacy_mn to set this argument");
    1996             :                 }
    1997          20 :                 bls_legacy_scheme = ParseBoolV(request.params[0], "bls_legacy_scheme");
    1998          20 :             }
    1999         456 :             UniValue ret(UniValue::VOBJ);
    2000         456 :             ret.pushKV("secret", sk.ToString());
    2001         456 :             ret.pushKV("public", sk.GetPublicKey().ToString(bls_legacy_scheme));
    2002         456 :             std::string bls_scheme_str = bls_legacy_scheme ? "legacy" : "basic";
    2003         456 :             ret.pushKV("scheme", bls_scheme_str);
    2004         456 :             return ret;
    2005         456 :         },
    2006             :     };
    2007           0 : }
    2008             : 
    2009        6145 : static RPCHelpMan bls_fromsecret()
    2010             : {
    2011        6145 :     return RPCHelpMan{
    2012        6145 :         "bls fromsecret",
    2013        6145 :         "\nParses a BLS secret key and returns the secret/public key pair.\n",
    2014       18435 :         {
    2015        6145 :             {"secret", RPCArg::Type::STR, RPCArg::Optional::NO, "The BLS secret key"},
    2016        6145 :             {"legacy", RPCArg::Type::BOOL, RPCArg::Default{false}, "Pass true if you need in legacy scheme"},
    2017             :         },
    2018        6145 :         RPCResult{RPCResult::Type::OBJ,
    2019        6145 :                   "",
    2020        6145 :                   "",
    2021       24580 :                   {
    2022        6145 :                       {RPCResult::Type::STR_HEX, "secret", "BLS secret key"},
    2023        6145 :                       {RPCResult::Type::STR_HEX, "public", "BLS public key"},
    2024        6145 :                       {RPCResult::Type::STR_HEX, "scheme", "BLS scheme (valid schemes: legacy, basic)"},
    2025             :                   }},
    2026        6145 :         RPCExamples{
    2027        6145 :             HelpExampleCli("bls fromsecret", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")},
    2028        6152 :         [&](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        6156 : static RPCHelpMan bls_help()
    2045             : {
    2046       12312 :     return RPCHelpMan{"bls",
    2047        6156 :         "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       12312 :         {
    2053        6156 :             {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to execute"},
    2054             :         },
    2055        6156 :         RPCResult{RPCResult::Type::NONE, "", ""},
    2056        6156 :         RPCExamples{""},
    2057        6156 :         [&](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        1436 : Span<const CRPCCommand> GetWalletEvoRPCCommands()
    2066             : {
    2067       25848 :     static const CRPCCommand commands[]{
    2068        1436 :         {"evo", &protx_list},
    2069        1436 :         {"evo", &protx_info},
    2070        1436 :         {"evo", &protx_register},
    2071        1436 :         {"evo", &protx_register_evo},
    2072        1436 :         {"evo", &protx_register_fund},
    2073        1436 :         {"evo", &protx_register_fund_evo},
    2074        1436 :         {"evo", &protx_register_prepare},
    2075        1436 :         {"evo", &protx_register_prepare_evo},
    2076        1436 :         {"evo", &protx_update_service},
    2077        1436 :         {"evo", &protx_update_service_evo},
    2078        1436 :         {"evo", &protx_register_submit},
    2079        1436 :         {"evo", &protx_update_registrar},
    2080        1436 :         {"evo", &protx_revoke},
    2081        1436 :         {"hidden", &protx_register_legacy},
    2082        1436 :         {"hidden", &protx_register_fund_legacy},
    2083        1436 :         {"hidden", &protx_register_prepare_legacy},
    2084        1436 :         {"hidden", &protx_update_registrar_legacy},
    2085             :     };
    2086        1436 :     return commands;
    2087           0 : }
    2088             : #endif // ENABLE_WALLET
    2089             : 
    2090        3201 : void RegisterEvoRPCCommands(CRPCTable& tableRPC)
    2091             : {
    2092       27753 :     static const CRPCCommand commands[]{
    2093        3069 :         {"evo", &bls_help},
    2094        3069 :         {"evo", &bls_generate},
    2095        3069 :         {"evo", &bls_fromsecret},
    2096        3069 :         {"evo", &protx_help},
    2097        3069 :         {"evo", &protx_diff},
    2098        3069 :         {"evo", &protx_listdiff},
    2099        3069 :         {"hidden", &evodb_verify},
    2100        3069 :         {"hidden", &evodb_repair},
    2101             :     };
    2102        9339 :     static const CRPCCommand commands_wallet[]{
    2103        3069 :         {"evo", &protx_list},
    2104        3069 :         {"evo", &protx_info},
    2105             :     };
    2106       28809 :     for (const auto& command : commands) {
    2107       25608 :         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        6402 :     if (!g_wallet_init_interface.HasWalletSupport()
    2116             : #ifdef ENABLE_WALLET
    2117        3201 :         || gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)
    2118             : #endif // ENABLE_WALLET
    2119             :     ) {
    2120        4761 :         for (const auto& command : commands_wallet) {
    2121        3174 :             tableRPC.appendCommand(command.name, &command);
    2122             :         }
    2123        1587 :     }
    2124        3201 : }

Generated by: LCOV version 1.16