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 : }
|