Line data Source code
1 : // Copyright (c) 2010 Satoshi Nakamoto
2 : // Copyright (c) 2009-2021 The Bitcoin Core developers
3 : // Copyright (c) 2014-2025 The Dash Core developers
4 : // Distributed under the MIT software license, see the accompanying
5 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
6 :
7 : #include <chainparams.h>
8 : #include <consensus/consensus.h>
9 : #include <evo/mnauth.h>
10 : #include <httpserver.h>
11 : #include <index/addressindex.h>
12 : #include <index/blockfilterindex.h>
13 : #include <index/coinstatsindex.h>
14 : #include <index/spentindex.h>
15 : #include <index/timestampindex.h>
16 : #include <index/txindex.h>
17 : #include <init.h>
18 : #include <interfaces/chain.h>
19 : #include <interfaces/echo.h>
20 : #include <interfaces/init.h>
21 : #include <interfaces/ipc.h>
22 : #include <key_io.h>
23 : #include <net.h>
24 : #include <net_processing.h>
25 : #include <node/context.h>
26 : #include <rpc/server.h>
27 : #include <rpc/server_util.h>
28 : #include <rpc/util.h>
29 : #include <scheduler.h>
30 : #include <txmempool.h>
31 : #include <univalue.h>
32 : #include <util/check.h>
33 : #include <util/system.h>
34 : #include <validation.h>
35 :
36 : #include <masternode/sync.h>
37 : #include <spork.h>
38 :
39 : #include <stdint.h>
40 : #ifdef HAVE_MALLOC_INFO
41 : #include <malloc.h>
42 : #endif
43 :
44 : using node::NodeContext;
45 :
46 92 : static RPCHelpMan debug()
47 : {
48 184 : return RPCHelpMan{"debug",
49 : "Change debug category on the fly. Specify single category or use '+' to specify many.\n"
50 92 : "The valid logging categories are: " + LogInstance().LogCategoriesString() + ".\n"
51 : "libevent logging is configured on startup and cannot be modified by this RPC during runtime.\n"
52 : "There are also a few meta-categories:\n"
53 : " - \"all\", \"1\" and \"\" activate all categories at once;\n"
54 : " - \"dash\" activates all Dash-specific categories at once;\n"
55 : " - \"none\" (or \"0\") deactivates all categories at once.\n"
56 : "Note: If specified category doesn't match any of the above, no error is thrown.\n"
57 : "Note: Consider using 'logging' RPC which has more features.\n"
58 : " - For 'debug all': logging [\\\"all\\\"]\n"
59 : " - For 'debug none': logging []\n"
60 : " - For 'debug X+Y': logging '[\"X\", \"Y\"]'",
61 184 : {
62 92 : {"category", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the debug category to turn on."},
63 : },
64 92 : RPCResult{
65 92 : RPCResult::Type::STR, "result", "\"Debug mode: \" followed by the specified category",
66 : },
67 92 : RPCExamples {
68 92 : HelpExampleCli("debug", "dash")
69 92 : + HelpExampleRpc("debug", "dash+net")
70 : },
71 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
72 : {
73 :
74 0 : std::string strMode = request.params[0].get_str();
75 0 : LogInstance().DisableCategory(BCLog::ALL);
76 :
77 0 : std::vector<std::string> categories = SplitString(strMode, '+');
78 0 : if (std::find(categories.begin(), categories.end(), std::string("0")) == categories.end()) {
79 0 : for (const auto& cat : categories) {
80 0 : LogInstance().EnableCategory(cat);
81 : }
82 0 : }
83 :
84 0 : return "Debug mode: " + LogInstance().LogCategoriesString(/*enabled_only=*/true);
85 0 : },
86 : };
87 0 : }
88 :
89 92 : static RPCHelpMan mnsync()
90 : {
91 184 : return RPCHelpMan{"mnsync",
92 92 : "Returns the sync status, updates to the next step or resets it entirely.\n",
93 184 : {
94 92 : {"mode", RPCArg::Type::STR, RPCArg::Optional::NO, "[status|next|reset]"},
95 : },
96 276 : {
97 184 : RPCResult{"for mode = status",
98 92 : RPCResult::Type::OBJ, "", "",
99 644 : {
100 92 : {RPCResult::Type::NUM, "AssetID", "The asset ID"},
101 92 : {RPCResult::Type::STR, "AssetName", "The asset name"},
102 92 : {RPCResult::Type::NUM, "AssetStartTime", "The asset start time"},
103 92 : {RPCResult::Type::NUM, "Attempt", "The attempt"},
104 92 : {RPCResult::Type::BOOL, "IsBlockchainSynced", "true if the blockchain synced"},
105 92 : {RPCResult::Type::BOOL, "IsSynced", "true if synced"},
106 : }},
107 184 : RPCResult{"for mode = next|reset",
108 92 : RPCResult::Type::STR, "", ""},
109 : },
110 92 : RPCExamples{""},
111 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
112 : {
113 :
114 0 : std::string strMode = request.params[0].get_str();
115 :
116 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
117 0 : CHECK_NONFATAL(node.mn_sync);
118 0 : auto& mn_sync = *node.mn_sync;
119 :
120 0 : if(strMode == "status") {
121 0 : UniValue objStatus(UniValue::VOBJ);
122 0 : objStatus.pushKV("AssetID", mn_sync.GetAssetID());
123 0 : objStatus.pushKV("AssetName", mn_sync.GetAssetName());
124 0 : objStatus.pushKV("AssetStartTime", mn_sync.GetAssetStartTime());
125 0 : objStatus.pushKV("Attempt", mn_sync.GetAttempt());
126 0 : objStatus.pushKV("IsBlockchainSynced", mn_sync.IsBlockchainSynced());
127 0 : objStatus.pushKV("IsSynced", mn_sync.IsSynced());
128 0 : return objStatus;
129 0 : }
130 :
131 0 : if(strMode == "next")
132 : {
133 0 : mn_sync.SwitchToNextAsset();
134 0 : return "sync updated to " + mn_sync.GetAssetName();
135 : }
136 :
137 0 : if(strMode == "reset")
138 : {
139 0 : mn_sync.Reset(true);
140 0 : return "success";
141 : }
142 0 : return "failure";
143 0 : },
144 : };
145 0 : }
146 :
147 : /*
148 : Used for updating/reading spork settings on the network
149 : */
150 92 : static RPCHelpMan spork()
151 : {
152 : // default help, for basic mode
153 184 : return RPCHelpMan{"spork",
154 92 : "\nShows information about current state of sporks\n",
155 184 : {
156 92 : {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "'show' to show all current spork values, 'active' to show which sporks are active"},
157 : },
158 276 : {
159 184 : RPCResult{"For 'show'",
160 92 : RPCResult::Type::OBJ_DYN, "", "keys are the sporks, and values indicates its value",
161 184 : {
162 92 : {RPCResult::Type::NUM, "SPORK_NAME", "The value of the specific spork with the name SPORK_NAME"},
163 : }},
164 184 : RPCResult{"For 'active'",
165 92 : RPCResult::Type::OBJ_DYN, "", "keys are the sporks, and values indicates its status",
166 184 : {
167 92 : {RPCResult::Type::BOOL, "SPORK_NAME", "'true' for time-based sporks if spork is active and 'false' otherwise"},
168 : }},
169 : },
170 92 : RPCExamples {
171 92 : HelpExampleCli("spork", "show")
172 92 : + HelpExampleRpc("spork", "\"show\"")
173 : },
174 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
175 : {
176 :
177 : // basic mode, show info
178 0 : std:: string strCommand = request.params[0].get_str();
179 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
180 0 : CHECK_NONFATAL(node.sporkman);
181 0 : if (strCommand == "show") {
182 0 : UniValue ret(UniValue::VOBJ);
183 0 : for (const auto& sporkDef : sporkDefs) {
184 0 : ret.pushKV(std::string(sporkDef.name), node.sporkman->GetSporkValue(sporkDef.sporkId));
185 : }
186 0 : return ret;
187 0 : } else if(strCommand == "active"){
188 0 : UniValue ret(UniValue::VOBJ);
189 0 : for (const auto& sporkDef : sporkDefs) {
190 0 : ret.pushKV(std::string(sporkDef.name), node.sporkman->IsSporkActive(sporkDef.sporkId));
191 : }
192 0 : return ret;
193 0 : }
194 :
195 0 : return UniValue::VNULL;
196 0 : },
197 : };
198 0 : }
199 :
200 92 : static RPCHelpMan sporkupdate()
201 : {
202 184 : return RPCHelpMan{"sporkupdate",
203 92 : "\nUpdate the value of the specific spork. Requires \"-sporkkey\" to be set to sign the message.\n",
204 276 : {
205 92 : {"name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the spork to update"},
206 92 : {"value", RPCArg::Type::NUM, RPCArg::Optional::NO, "The new desired value of the spork"},
207 : },
208 92 : RPCResult{
209 92 : RPCResult::Type::STR, "result", "\"success\" if spork value was updated or this help otherwise"
210 : },
211 92 : RPCExamples{
212 92 : HelpExampleCli("sporkupdate", "SPORK_2_INSTANTSEND_ENABLED 4070908800")
213 92 : + HelpExampleRpc("sporkupdate", "\"SPORK_2_INSTANTSEND_ENABLED\", 4070908800")
214 : },
215 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
216 : {
217 :
218 : // advanced mode, update spork values
219 0 : SporkId nSporkID = CSporkManager::GetSporkIDByName(request.params[0].get_str());
220 0 : if (nSporkID == SPORK_INVALID) {
221 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid spork name");
222 : }
223 :
224 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
225 0 : CHECK_NONFATAL(node.sporkman);
226 :
227 : // SPORK VALUE
228 0 : int64_t nValue = request.params[1].getInt<int64_t>();
229 :
230 0 : auto inv{node.sporkman->UpdateSpork(nSporkID, nValue)};
231 0 : if (inv.has_value()) {
232 0 : PeerManager& peerman = EnsurePeerman(node);
233 0 : peerman.RelayInv(inv.value());
234 0 : return "success";
235 : }
236 :
237 0 : return UniValue::VNULL;
238 0 : },
239 : };
240 0 : }
241 :
242 92 : static RPCHelpMan setmocktime()
243 : {
244 184 : return RPCHelpMan{"setmocktime",
245 92 : "\nSet the local time to given timestamp (-regtest only)\n",
246 184 : {
247 92 : {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, UNIX_EPOCH_TIME + "\n"
248 : "Pass 0 to go back to using the system time."},
249 : },
250 92 : RPCResult{RPCResult::Type::NONE, "", ""},
251 92 : RPCExamples{""},
252 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
253 : {
254 :
255 0 : if (!Params().IsMockableChain()) {
256 0 : throw std::runtime_error("setmocktime is for regression testing (-regtest mode) only");
257 : }
258 :
259 : // For now, don't change mocktime if we're in the middle of validation, as
260 : // this could have an effect on mempool time-based eviction, as well as
261 : // IsCurrentForFeeEstimation() and IsInitialBlockDownload().
262 : // TODO: figure out the right way to synchronize around mocktime, and
263 : // ensure all call sites of GetTime() are accessing this safely.
264 0 : LOCK(cs_main);
265 :
266 0 : RPCTypeCheck(request.params, {UniValue::VNUM});
267 0 : const int64_t time{request.params[0].getInt<int64_t>()};
268 0 : if (time < 0) {
269 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time));
270 : }
271 0 : SetMockTime(time);
272 0 : if (auto* node_context = GetContext<NodeContext>(request.context)) {
273 0 : for (const auto& chain_client : node_context->chain_clients) {
274 0 : chain_client->setMockTime(time);
275 : }
276 0 : }
277 :
278 0 : return UniValue::VNULL;
279 0 : },
280 : };
281 0 : }
282 :
283 92 : static RPCHelpMan mnauth()
284 : {
285 184 : return RPCHelpMan{"mnauth",
286 92 : "\nOverride MNAUTH processing results for the specified node with a user provided data (-regtest only).\n",
287 368 : {
288 92 : {"nodeId", RPCArg::Type::NUM, RPCArg::Optional::NO, "Internal peer id of the node the mock data gets added to."},
289 92 : {"proTxHash", RPCArg::Type::STR, RPCArg::Optional::NO, "The authenticated proTxHash as hex string."},
290 92 : {"publicKey", RPCArg::Type::STR, RPCArg::Optional::NO, "The authenticated public key as hex string."},
291 : },
292 92 : RPCResult{
293 92 : RPCResult::Type::BOOL, "result", "true, if the node was updated"
294 : },
295 92 : RPCExamples{""},
296 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
297 : {
298 :
299 0 : if (!Params().MineBlocksOnDemand())
300 0 : throw std::runtime_error("mnauth for regression testing (-regtest mode) only");
301 :
302 0 : int64_t nodeId = request.params[0].getInt<int64_t>();
303 0 : uint256 proTxHash = ParseHashV(request.params[1], "proTxHash");
304 0 : if (proTxHash.IsNull()) {
305 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "proTxHash invalid");
306 : }
307 :
308 0 : CBLSPublicKey publicKey;
309 0 : publicKey.SetHexStr(request.params[2].get_str(), /*specificLegacyScheme=*/false);
310 0 : if (!publicKey.IsValid()) {
311 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "publicKey invalid");
312 : }
313 :
314 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
315 0 : bool fSuccess = node.connman->ForNode(nodeId, CConnman::AllNodes, [&](CNode* pNode){
316 0 : pNode->SetVerifiedProRegTxHash(proTxHash);
317 0 : pNode->SetVerifiedPubKeyHash(publicKey.GetHash());
318 0 : return true;
319 : });
320 :
321 0 : return fSuccess;
322 0 : },
323 : };
324 0 : }
325 :
326 0 : static bool getAddressFromIndex(const AddressType& type, const uint160 &hash, std::string &address)
327 : {
328 0 : if (type == AddressType::P2SH) {
329 0 : address = EncodeDestination(ScriptHash(hash));
330 0 : } else if (type == AddressType::P2PK_OR_P2PKH) {
331 0 : address = EncodeDestination(PKHash(hash));
332 0 : } else {
333 0 : return false;
334 : }
335 0 : return true;
336 0 : }
337 :
338 0 : static bool getIndexKey(const std::string& str, uint160& hashBytes, AddressType& type)
339 : {
340 0 : CTxDestination dest = DecodeDestination(str);
341 0 : if (!IsValidDestination(dest)) {
342 0 : type = AddressType::UNKNOWN;
343 0 : return false;
344 : }
345 0 : const PKHash *pkhash = std::get_if<PKHash>(&dest);
346 0 : const ScriptHash *scriptID = std::get_if<ScriptHash>(&dest);
347 0 : type = pkhash ? AddressType::P2PK_OR_P2PKH : AddressType::P2SH;
348 0 : hashBytes = pkhash ? uint160(*pkhash) : uint160(*scriptID);
349 0 : return true;
350 0 : }
351 :
352 0 : static bool getAddressesFromParams(const UniValue& params, std::vector<std::pair<uint160, AddressType> > &addresses)
353 : {
354 0 : if (params[0].isStr()) {
355 0 : uint160 hashBytes;
356 0 : AddressType type{AddressType::UNKNOWN};
357 0 : if (!getIndexKey(params[0].get_str(), hashBytes, type)) {
358 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
359 : }
360 0 : addresses.push_back(std::make_pair(hashBytes, type));
361 0 : } else if (params[0].isObject()) {
362 :
363 0 : UniValue addressValues = params[0].get_obj().find_value("addresses");
364 0 : if (!addressValues.isArray()) {
365 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Addresses is expected to be an array");
366 : }
367 :
368 0 : for (const auto& address : addressValues.getValues()) {
369 0 : uint160 hashBytes;
370 0 : AddressType type{AddressType::UNKNOWN};
371 0 : if (!getIndexKey(address.get_str(), hashBytes, type)) {
372 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
373 : }
374 0 : addresses.push_back(std::make_pair(hashBytes, type));
375 : }
376 0 : } else {
377 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
378 : }
379 :
380 0 : return true;
381 0 : }
382 :
383 92 : static RPCHelpMan getaddressmempool()
384 : {
385 184 : return RPCHelpMan{"getaddressmempool",
386 92 : "\nReturns all mempool deltas for an address (requires addressindex to be enabled).\n",
387 184 : {
388 184 : {"addresses", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "",
389 184 : {
390 92 : {"address", RPCArg::Type::STR, RPCArg::Default{""}, "The base58check encoded address"},
391 : },
392 : },
393 : },
394 92 : RPCResult{
395 92 : RPCResult::Type::ARR, "", "",
396 184 : {
397 184 : {RPCResult::Type::OBJ, "", "",
398 736 : {
399 92 : {RPCResult::Type::STR, "address", "The base58check encoded address"},
400 92 : {RPCResult::Type::STR_HEX, "txid", "The related txid"},
401 92 : {RPCResult::Type::NUM, "index", "The related input or output index"},
402 92 : {RPCResult::Type::NUM, "satoshis", "The difference of duffs"},
403 92 : {RPCResult::Type::NUM_TIME, "timestamp", "The time the transaction entered the mempool (seconds)"},
404 92 : {RPCResult::Type::STR_HEX, "prevtxid", "The previous txid (if spending)"},
405 92 : {RPCResult::Type::NUM, "prevout", "The previous transaction output index (if spending)"},
406 : }},
407 : }},
408 92 : RPCExamples{
409 92 : HelpExampleCli("getaddressmempool", "'{\"addresses\": [\"" + EXAMPLE_ADDRESS[0] + "\"]}'")
410 92 : + HelpExampleRpc("getaddressmempool", "{\"addresses\": [\"" + EXAMPLE_ADDRESS[0] + "\"]}")
411 : },
412 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
413 : {
414 0 : if (!g_addressindex) {
415 0 : throw JSONRPCError(RPC_MISC_ERROR, "Address index is not enabled. Start with -addressindex to enable.");
416 : }
417 :
418 0 : CTxMemPool& mempool = EnsureAnyMemPool(request.context);
419 :
420 0 : std::vector<std::pair<uint160, AddressType>> addresses;
421 0 : if (!getAddressesFromParams(request.params, addresses)) {
422 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
423 : }
424 :
425 0 : std::vector<CMempoolAddressDeltaKey> input_addresses;
426 0 : std::vector<CMempoolAddressDeltaEntry> indexes;
427 0 : for (const auto& [hash, type] : addresses) {
428 0 : input_addresses.push_back({type, hash});
429 : }
430 :
431 0 : mempool.getAddressIndex(input_addresses, indexes);
432 :
433 0 : std::sort(indexes.begin(), indexes.end(),
434 0 : [](const CMempoolAddressDeltaEntry &a, const CMempoolAddressDeltaEntry &b) {
435 0 : return a.second.m_time < b.second.m_time;
436 : });
437 :
438 0 : UniValue result(UniValue::VARR);
439 :
440 0 : for (const auto& [mempoolAddressKey, mempoolAddressDelta] : indexes) {
441 0 : std::string address;
442 0 : if (!getAddressFromIndex(mempoolAddressKey.m_address_type, mempoolAddressKey.m_address_bytes, address)) {
443 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown address type");
444 : }
445 :
446 0 : UniValue delta(UniValue::VOBJ);
447 0 : delta.pushKV("address", address);
448 0 : delta.pushKV("txid", mempoolAddressKey.m_tx_hash.GetHex());
449 0 : delta.pushKV("index", mempoolAddressKey.m_tx_index);
450 0 : delta.pushKV("satoshis", mempoolAddressDelta.m_amount);
451 0 : delta.pushKV("timestamp", count_seconds(mempoolAddressDelta.m_time));
452 0 : if (mempoolAddressDelta.m_amount < 0) {
453 0 : delta.pushKV("prevtxid", mempoolAddressDelta.m_prev_hash.GetHex());
454 0 : delta.pushKV("prevout", mempoolAddressDelta.m_prev_out);
455 0 : }
456 0 : result.push_back(delta);
457 0 : }
458 :
459 0 : return result;
460 0 : },
461 : };
462 0 : }
463 :
464 92 : static RPCHelpMan getaddressutxos()
465 : {
466 184 : return RPCHelpMan{"getaddressutxos",
467 92 : "\nReturns all unspent outputs for an address (requires addressindex to be enabled).\n",
468 184 : {
469 184 : {"addresses", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "",
470 184 : {
471 92 : {"address", RPCArg::Type::STR, RPCArg::Default{""}, "The base58check encoded address"},
472 : },
473 : },
474 : },
475 92 : RPCResult{
476 92 : RPCResult::Type::ARR, "", "",
477 184 : {
478 184 : {RPCResult::Type::OBJ, "", "",
479 644 : {
480 92 : {RPCResult::Type::STR, "address", "The address base58check encoded"},
481 92 : {RPCResult::Type::STR_HEX, "txid", "The output txid"},
482 92 : {RPCResult::Type::NUM, "index", "The output index"},
483 92 : {RPCResult::Type::STR_HEX, "script", "The script hex-encoded"},
484 92 : {RPCResult::Type::NUM, "satoshis", "The number of duffs of the output"},
485 92 : {RPCResult::Type::NUM, "height", "The block height"},
486 : }},
487 : }},
488 92 : RPCExamples{
489 92 : HelpExampleCli("getaddressutxos", "'{\"addresses\": [\"" + EXAMPLE_ADDRESS[0] + "\"]}'")
490 92 : + HelpExampleRpc("getaddressutxos", "{\"addresses\": [\"" + EXAMPLE_ADDRESS[0] + "\"]}")
491 : },
492 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
493 : {
494 0 : std::vector<std::pair<uint160, AddressType> > addresses;
495 0 : if (!getAddressesFromParams(request.params, addresses)) {
496 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
497 : }
498 :
499 0 : if (!g_addressindex) {
500 0 : throw JSONRPCError(RPC_MISC_ERROR, "Address index is not enabled. Start with -addressindex to enable.");
501 : }
502 :
503 0 : if (!g_addressindex->BlockUntilSyncedToCurrentChain()) {
504 0 : throw JSONRPCError(RPC_MISC_ERROR, strprintf("Address index is syncing. Current height: %d", g_addressindex->GetSummary().best_block_height));
505 : }
506 :
507 0 : std::vector<CAddressUnspentIndexEntry> unspentOutputs;
508 :
509 0 : for (const auto& address : addresses) {
510 0 : if (!g_addressindex->GetAddressUnspentIndex(address.first, address.second, unspentOutputs,
511 : /* height_sort = */ true)) {
512 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address");
513 : }
514 : }
515 :
516 0 : UniValue result(UniValue::VARR);
517 :
518 0 : for (const auto& [unspentKey, unspentValue] : unspentOutputs) {
519 0 : UniValue output(UniValue::VOBJ);
520 0 : std::string address;
521 0 : if (!getAddressFromIndex(unspentKey.m_address_type, unspentKey.m_address_bytes, address)) {
522 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown address type");
523 : }
524 :
525 0 : output.pushKV("address", address);
526 0 : output.pushKV("txid", unspentKey.m_tx_hash.GetHex());
527 0 : output.pushKV("outputIndex", unspentKey.m_tx_index);
528 0 : output.pushKV("script", HexStr(unspentValue.m_tx_script));
529 0 : output.pushKV("satoshis", unspentValue.m_amount);
530 0 : output.pushKV("height", unspentValue.m_block_height);
531 0 : result.push_back(output);
532 0 : }
533 :
534 0 : return result;
535 0 : },
536 : };
537 0 : }
538 :
539 92 : static RPCHelpMan getaddressdeltas()
540 : {
541 184 : return RPCHelpMan{"getaddressdeltas",
542 92 : "\nReturns all changes for an address (requires addressindex to be enabled).\n",
543 184 : {
544 184 : {"addresses", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "",
545 184 : {
546 92 : {"address", RPCArg::Type::STR, RPCArg::Default{""}, "The base58check encoded address"},
547 : },
548 : },
549 : },
550 92 : RPCResult{
551 92 : RPCResult::Type::ARR, "", "",
552 184 : {
553 184 : {RPCResult::Type::OBJ, "", "",
554 644 : {
555 92 : {RPCResult::Type::NUM, "satoshis", "The difference of duffs"},
556 92 : {RPCResult::Type::STR_HEX, "txid", "The related txid"},
557 92 : {RPCResult::Type::NUM, "index", "The related input or output index"},
558 92 : {RPCResult::Type::NUM, "blockindex", "The related block index"},
559 92 : {RPCResult::Type::NUM, "height", "The block height"},
560 92 : {RPCResult::Type::STR, "address", "The base58check encoded address"},
561 : }},
562 : }},
563 92 : RPCExamples{
564 92 : HelpExampleCli("getaddressdeltas", "'{\"addresses\": [\"" + EXAMPLE_ADDRESS[0] + "\"]}'")
565 92 : + HelpExampleRpc("getaddressdeltas", "{\"addresses\": [\"" + EXAMPLE_ADDRESS[0] + "\"]}")
566 : },
567 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
568 : {
569 0 : UniValue startValue = request.params[0].get_obj().find_value("start");
570 0 : UniValue endValue = request.params[0].get_obj().find_value("end");
571 :
572 0 : int start = 0;
573 0 : int end = 0;
574 :
575 0 : if (startValue.isNum() && endValue.isNum()) {
576 0 : start = startValue.getInt<int>();
577 0 : end = endValue.getInt<int>();
578 0 : if (end < start) {
579 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "End value is expected to be greater than start");
580 : }
581 0 : }
582 :
583 0 : std::vector<std::pair<uint160, AddressType> > addresses;
584 :
585 0 : if (!getAddressesFromParams(request.params, addresses)) {
586 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
587 : }
588 :
589 0 : std::vector<CAddressIndexEntry> addressIndex;
590 :
591 0 : if (!g_addressindex) {
592 0 : throw JSONRPCError(RPC_MISC_ERROR, "Address index is not enabled. Start with -addressindex to enable.");
593 : }
594 :
595 0 : if (!g_addressindex->BlockUntilSyncedToCurrentChain()) {
596 0 : throw JSONRPCError(RPC_MISC_ERROR, strprintf("Address index is syncing. Current height: %d", g_addressindex->GetSummary().best_block_height));
597 : }
598 :
599 0 : for (const auto& address : addresses) {
600 0 : if (start <= 0 || end <= 0) { start = 0; end = 0; }
601 0 : if (!g_addressindex->GetAddressIndex(address.first, address.second,
602 0 : addressIndex, start, end)) {
603 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address");
604 : }
605 : }
606 :
607 0 : UniValue result(UniValue::VARR);
608 :
609 0 : for (const auto& [indexKey, indexDelta] : addressIndex) {
610 0 : std::string address;
611 0 : if (!getAddressFromIndex(indexKey.m_address_type, indexKey.m_address_bytes, address)) {
612 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown address type");
613 : }
614 :
615 0 : UniValue delta(UniValue::VOBJ);
616 0 : delta.pushKV("satoshis", indexDelta);
617 0 : delta.pushKV("txid", indexKey.m_tx_hash.GetHex());
618 0 : delta.pushKV("index", indexKey.m_tx_index);
619 0 : delta.pushKV("blockindex", indexKey.m_block_tx_pos);
620 0 : delta.pushKV("height", indexKey.m_block_height);
621 0 : delta.pushKV("address", address);
622 0 : result.push_back(delta);
623 0 : }
624 :
625 0 : return result;
626 0 : },
627 : };
628 0 : }
629 :
630 92 : static RPCHelpMan getaddressbalance()
631 : {
632 184 : return RPCHelpMan{"getaddressbalance",
633 92 : "\nReturns the balance for an address(es) (requires addressindex to be enabled).\n",
634 184 : {
635 184 : {"addresses", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "",
636 184 : {
637 92 : {"address", RPCArg::Type::STR, RPCArg::Default{""}, "The base58check encoded address"},
638 : },
639 : },
640 : },
641 92 : RPCResult{
642 92 : RPCResult::Type::OBJ, "", "",
643 460 : {
644 92 : {RPCResult::Type::NUM, "balance", "The current total balance in duffs"},
645 92 : {RPCResult::Type::NUM, "balance_immature", "The current immature balance in duffs"},
646 92 : {RPCResult::Type::NUM, "balance_spendable", "The current spendable balance in duffs"},
647 92 : {RPCResult::Type::NUM, "received", "The total number of duffs received (including change)"},
648 : }},
649 92 : RPCExamples{
650 92 : HelpExampleCli("getaddressbalance", "'{\"addresses\": [\"" + EXAMPLE_ADDRESS[0] + "\"]}'")
651 92 : + HelpExampleRpc("getaddressbalance", "{\"addresses\": [\"" + EXAMPLE_ADDRESS[0] + "\"]}")
652 : },
653 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
654 : {
655 0 : std::vector<std::pair<uint160, AddressType> > addresses;
656 :
657 0 : if (!getAddressesFromParams(request.params, addresses)) {
658 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
659 : }
660 :
661 0 : std::vector<CAddressIndexEntry> addressIndex;
662 :
663 0 : if (!g_addressindex) {
664 0 : throw JSONRPCError(RPC_MISC_ERROR, "Address index is not enabled. Start with -addressindex to enable.");
665 : }
666 :
667 0 : if (!g_addressindex->BlockUntilSyncedToCurrentChain()) {
668 0 : throw JSONRPCError(RPC_MISC_ERROR, strprintf("Address index is syncing. Current height: %d", g_addressindex->GetSummary().best_block_height));
669 : }
670 :
671 : int nHeight;
672 : {
673 0 : LOCK(::cs_main);
674 0 : for (const auto& address : addresses) {
675 0 : if (!g_addressindex->GetAddressIndex(address.first, address.second, addressIndex,
676 : /*start=*/0, /*end=*/0)) {
677 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address");
678 : }
679 : }
680 0 : nHeight = g_addressindex->GetSummary().best_block_height;
681 0 : }
682 :
683 :
684 0 : CAmount balance = 0;
685 0 : CAmount balance_spendable = 0;
686 0 : CAmount balance_immature = 0;
687 0 : CAmount received = 0;
688 :
689 0 : for (const auto& [indexKey, indexDelta] : addressIndex) {
690 0 : if (indexDelta > 0) {
691 0 : received += indexDelta;
692 0 : }
693 0 : if (indexKey.m_block_tx_pos == 0 && nHeight - indexKey.m_block_height < COINBASE_MATURITY) {
694 0 : balance_immature += indexDelta;
695 0 : } else {
696 0 : balance_spendable += indexDelta;
697 : }
698 0 : balance += indexDelta;
699 : }
700 :
701 0 : UniValue result(UniValue::VOBJ);
702 0 : result.pushKV("balance", balance);
703 0 : result.pushKV("balance_immature", balance_immature);
704 0 : result.pushKV("balance_spendable", balance_spendable);
705 0 : result.pushKV("received", received);
706 :
707 0 : return result;
708 :
709 0 : },
710 : };
711 0 : }
712 :
713 92 : static RPCHelpMan getaddresstxids()
714 : {
715 184 : return RPCHelpMan{"getaddresstxids",
716 92 : "\nReturns the txids for an address(es) (requires addressindex to be enabled).\n",
717 184 : {
718 184 : {"addresses", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "",
719 184 : {
720 92 : {"address", RPCArg::Type::STR, RPCArg::Default{""}, "The base58check encoded address"},
721 : },
722 : },
723 : },
724 92 : RPCResult{
725 92 : RPCResult::Type::ARR, "", "",
726 92 : {{RPCResult::Type::STR_HEX, "transactionid", "The transaction id"}}
727 : },
728 92 : RPCExamples{
729 92 : HelpExampleCli("getaddresstxids", "'{\"addresses\": [\"" + EXAMPLE_ADDRESS[0] + "\"]}'")
730 92 : + HelpExampleRpc("getaddresstxids", "{\"addresses\": [\"" + EXAMPLE_ADDRESS[0] + "\"]}")
731 : },
732 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
733 : {
734 0 : std::vector<std::pair<uint160, AddressType> > addresses;
735 :
736 0 : if (!getAddressesFromParams(request.params, addresses)) {
737 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
738 : }
739 :
740 0 : int start = 0;
741 0 : int end = 0;
742 0 : if (request.params[0].isObject()) {
743 0 : UniValue startValue = request.params[0].get_obj().find_value("start");
744 0 : UniValue endValue = request.params[0].get_obj().find_value("end");
745 0 : if (startValue.isNum() && endValue.isNum()) {
746 0 : start = startValue.getInt<int>();
747 0 : end = endValue.getInt<int>();
748 0 : }
749 0 : }
750 :
751 0 : std::vector<CAddressIndexEntry> addressIndex;
752 :
753 0 : if (!g_addressindex) {
754 0 : throw JSONRPCError(RPC_MISC_ERROR, "Address index is not enabled. Start with -addressindex to enable.");
755 : }
756 :
757 0 : if (!g_addressindex->BlockUntilSyncedToCurrentChain()) {
758 0 : throw JSONRPCError(RPC_MISC_ERROR, strprintf("Address index is syncing. Current height: %d", g_addressindex->GetSummary().best_block_height));
759 : }
760 :
761 0 : for (const auto& address : addresses) {
762 0 : if (start <= 0 || end <= 0) { start = 0; end = 0; }
763 0 : if (!g_addressindex->GetAddressIndex(address.first, address.second,
764 0 : addressIndex, start, end)) {
765 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available for address");
766 : }
767 : }
768 :
769 0 : std::set<std::pair<int, std::string> > txids;
770 0 : UniValue result(UniValue::VARR);
771 :
772 0 : for (const auto& [indexKey, _]: addressIndex) {
773 0 : int height = indexKey.m_block_height;
774 0 : std::string txid = indexKey.m_tx_hash.GetHex();
775 :
776 0 : if (addresses.size() > 1) {
777 0 : txids.insert(std::make_pair(height, txid));
778 0 : } else {
779 0 : if (txids.insert(std::make_pair(height, txid)).second) {
780 0 : result.push_back(txid);
781 0 : }
782 : }
783 0 : }
784 :
785 0 : if (addresses.size() > 1) {
786 0 : for (const auto& tx : txids) {
787 0 : result.push_back(tx.second);
788 : }
789 0 : }
790 :
791 0 : return result;
792 :
793 0 : },
794 : };
795 0 : }
796 :
797 92 : static RPCHelpMan getspentinfo()
798 : {
799 184 : return RPCHelpMan{"getspentinfo",
800 92 : "\nReturns the txid and index where an output is spent.\n",
801 184 : {
802 184 : {"request", RPCArg::Type::OBJ, RPCArg::Default{UniValue::VOBJ}, "",
803 276 : {
804 92 : {"txid", RPCArg::Type::STR_HEX, RPCArg::Default{""}, "The hex string of the txid"},
805 92 : {"index", RPCArg::Type::NUM, RPCArg::Default{0}, "The start block height"},
806 : },
807 : },
808 : },
809 92 : RPCResult{
810 92 : RPCResult::Type::OBJ, "", "",
811 276 : {
812 92 : {RPCResult::Type::STR_HEX, "txid", "The transaction id"},
813 92 : {RPCResult::Type::NUM, "index", "The spending input index"},
814 : }},
815 92 : RPCExamples{
816 92 : HelpExampleCli("getspentinfo", "'{\"txid\": \"0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9\", \"index\": 0}'")
817 92 : + HelpExampleRpc("getspentinfo", "{\"txid\": \"0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9\", \"index\": 0}")
818 : },
819 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
820 : {
821 0 : UniValue txidValue = request.params[0].get_obj().find_value("txid");
822 0 : UniValue indexValue = request.params[0].get_obj().find_value("index");
823 :
824 0 : if (!txidValue.isStr() || !indexValue.isNum()) {
825 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid txid or index");
826 : }
827 :
828 0 : uint256 txid = ParseHashV(txidValue, "txid");
829 0 : int outputIndex = indexValue.getInt<int>();
830 :
831 0 : if (outputIndex < 0) {
832 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid index (must be non-negative)");
833 : }
834 :
835 0 : if (!g_spentindex) {
836 0 : throw JSONRPCError(RPC_MISC_ERROR, "Spent index is not enabled. Start with -spentindex to enable.");
837 : }
838 :
839 0 : CSpentIndexKey key(txid, outputIndex);
840 0 : CSpentIndexValue value;
841 0 : bool found = false;
842 :
843 : // Sync the index to the current chain tip before querying.
844 : // We don't fail here if not synced — the result may still come from mempool below.
845 0 : g_spentindex->BlockUntilSyncedToCurrentChain();
846 :
847 0 : if (g_spentindex->GetSpentInfo(key, value)) {
848 0 : found = true;
849 0 : }
850 :
851 : // Check mempool for unconfirmed spends.
852 : //
853 : // Mempool entry overwrites index entry if present.
854 : // During reorg, mempool may have newer state than index (which updates asynchronously).
855 0 : CTxMemPool& mempool = EnsureAnyMemPool(request.context);
856 : {
857 0 : LOCK(mempool.cs);
858 0 : CSpentIndexValue mempoolValue;
859 0 : if (mempool.getSpentIndex(key, mempoolValue)) {
860 0 : value = mempoolValue; // Overwrite with mempool entry (current state)
861 0 : found = true;
862 0 : }
863 0 : }
864 :
865 0 : if (!found) {
866 0 : const IndexSummary summary = g_spentindex->GetSummary();
867 0 : if (!summary.synced) {
868 0 : throw JSONRPCError(RPC_MISC_ERROR, strprintf("Unable to get spent info. Spent index is syncing, current height: %d", summary.best_block_height));
869 : }
870 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to get spent info");
871 0 : }
872 :
873 0 : UniValue obj(UniValue::VOBJ);
874 0 : obj.pushKV("txid", value.m_tx_hash.GetHex());
875 0 : obj.pushKV("index", (int)value.m_tx_index);
876 0 : obj.pushKV("height", value.m_block_height);
877 :
878 0 : return obj;
879 0 : },
880 : };
881 0 : }
882 :
883 92 : static RPCHelpMan mockscheduler()
884 : {
885 184 : return RPCHelpMan{"mockscheduler",
886 92 : "\nBump the scheduler into the future (-regtest only)\n",
887 184 : {
888 92 : {"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." },
889 : },
890 92 : RPCResult{RPCResult::Type::NONE, "", ""},
891 92 : RPCExamples{""},
892 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
893 : {
894 0 : if (!Params().IsMockableChain()) {
895 0 : throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only");
896 : }
897 :
898 : // check params are valid values
899 0 : RPCTypeCheck(request.params, {UniValue::VNUM});
900 0 : int64_t delta_seconds = request.params[0].getInt<int64_t>();
901 0 : if (delta_seconds <= 0 || delta_seconds > 3600) {
902 0 : throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)");
903 : }
904 :
905 0 : auto* node_context = CHECK_NONFATAL(GetContext<NodeContext>(request.context));
906 : // protect against null pointer dereference
907 0 : CHECK_NONFATAL(node_context->scheduler);
908 0 : node_context->scheduler->MockForward(std::chrono::seconds(delta_seconds));
909 :
910 0 : return UniValue::VNULL;
911 0 : },
912 : };
913 0 : }
914 :
915 0 : static UniValue RPCLockedMemoryInfo()
916 : {
917 0 : LockedPool::Stats stats = LockedPoolManager::Instance().stats();
918 0 : UniValue obj(UniValue::VOBJ);
919 0 : obj.pushKV("used", uint64_t(stats.used));
920 0 : obj.pushKV("free", uint64_t(stats.free));
921 0 : obj.pushKV("total", uint64_t(stats.total));
922 0 : obj.pushKV("locked", uint64_t(stats.locked));
923 0 : obj.pushKV("chunks_used", uint64_t(stats.chunks_used));
924 0 : obj.pushKV("chunks_free", uint64_t(stats.chunks_free));
925 0 : return obj;
926 0 : }
927 :
928 : #ifdef HAVE_MALLOC_INFO
929 : static std::string RPCMallocInfo()
930 : {
931 : char *ptr = nullptr;
932 : size_t size = 0;
933 : FILE *f = open_memstream(&ptr, &size);
934 : if (f) {
935 : malloc_info(0, f);
936 : fclose(f);
937 : if (ptr) {
938 : std::string rv(ptr, size);
939 : free(ptr);
940 : return rv;
941 : }
942 : }
943 : return "";
944 : }
945 : #endif
946 :
947 92 : static RPCHelpMan getmemoryinfo()
948 : {
949 : /* Please, avoid using the word "pool" here in the RPC interface or help,
950 : * as users will undoubtedly confuse it with the other "memory pool"
951 : */
952 184 : return RPCHelpMan{"getmemoryinfo",
953 92 : "Returns an object containing information about memory usage.\n",
954 184 : {
955 92 : {"mode", RPCArg::Type::STR, RPCArg::Default{"stats"}, "determines what kind of information is returned.\n"
956 : " - \"stats\" returns general statistics about memory usage in the daemon.\n"
957 : " - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc)."},
958 : },
959 276 : {
960 184 : RPCResult{"mode \"stats\"",
961 92 : RPCResult::Type::OBJ, "", "",
962 184 : {
963 184 : {RPCResult::Type::OBJ, "locked", "Information about locked memory manager",
964 644 : {
965 92 : {RPCResult::Type::NUM, "used", "Number of bytes used"},
966 92 : {RPCResult::Type::NUM, "free", "Number of bytes available in current arenas"},
967 92 : {RPCResult::Type::NUM, "total", "Total number of bytes managed"},
968 92 : {RPCResult::Type::NUM, "locked", "Amount of bytes that succeeded locking. If this number is smaller than total, locking pages failed at some point and key data could be swapped to disk."},
969 92 : {RPCResult::Type::NUM, "chunks_used", "Number allocated chunks"},
970 92 : {RPCResult::Type::NUM, "chunks_free", "Number unused chunks"},
971 : }},
972 : }
973 : },
974 184 : RPCResult{"mode \"mallocinfo\"",
975 92 : RPCResult::Type::STR, "", "\"<malloc version=\"1\">...\""
976 : },
977 : },
978 92 : RPCExamples{
979 92 : HelpExampleCli("getmemoryinfo", "")
980 92 : + HelpExampleRpc("getmemoryinfo", "")
981 : },
982 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
983 : {
984 :
985 0 : std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str();
986 0 : if (mode == "stats") {
987 0 : UniValue obj(UniValue::VOBJ);
988 0 : obj.pushKV("locked", RPCLockedMemoryInfo());
989 0 : return obj;
990 0 : } else if (mode == "mallocinfo") {
991 : #ifdef HAVE_MALLOC_INFO
992 : return RPCMallocInfo();
993 : #else
994 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo mode not available");
995 : #endif
996 : } else {
997 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode);
998 : }
999 0 : },
1000 : };
1001 0 : }
1002 :
1003 0 : static void EnableOrDisableLogCategories(UniValue cats, bool enable) {
1004 0 : cats = cats.get_array();
1005 0 : for (unsigned int i = 0; i < cats.size(); ++i) {
1006 0 : std::string cat = cats[i].get_str();
1007 :
1008 : bool success;
1009 0 : if (enable) {
1010 0 : success = LogInstance().EnableCategory(cat);
1011 0 : } else {
1012 0 : success = LogInstance().DisableCategory(cat);
1013 : }
1014 :
1015 0 : if (!success) {
1016 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat);
1017 : }
1018 0 : }
1019 0 : }
1020 :
1021 92 : static RPCHelpMan logging()
1022 : {
1023 184 : return RPCHelpMan{"logging",
1024 : "Gets and sets the logging configuration.\n"
1025 : "When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n"
1026 : "When called with arguments, adds or removes categories from debug logging and return the lists above.\n"
1027 : "The arguments are evaluated in order \"include\", \"exclude\".\n"
1028 : "If an item is both included and excluded, it will thus end up being excluded.\n"
1029 92 : "The valid logging categories are: " + LogInstance().LogCategoriesString() + "\n"
1030 : "In addition, the following are available as category names with special meanings:\n"
1031 : " - \"all\", \"1\" : represent all logging categories.\n"
1032 : " - \"dash\" activates all Dash-specific categories at once.\n"
1033 : "To deactivate all categories at once you can specify \"all\" in <exclude>.\n"
1034 : " - \"none\", \"0\" : even if other logging categories are specified, ignore all of them.\n"
1035 : ,
1036 276 : {
1037 184 : {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The of categories to add to debug logging",
1038 184 : {
1039 92 : {"include_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
1040 : }},
1041 184 : {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to remove from debug logging",
1042 184 : {
1043 92 : {"exclude_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
1044 : }},
1045 : },
1046 92 : RPCResult{
1047 92 : RPCResult::Type::OBJ_DYN, "", "keys are the logging categories, and values indicates its status",
1048 184 : {
1049 92 : {RPCResult::Type::BOOL, "category", "if being debug logged or not. false:inactive, true:active"},
1050 : }
1051 : },
1052 92 : RPCExamples{
1053 92 : HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"")
1054 92 : + HelpExampleCli("logging", "'[\"dash\"]' '[\"llmq\",\"zmq\"]'")
1055 92 : + HelpExampleRpc("logging", "[\"all\"], \"[libevent]\"")
1056 : },
1057 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1058 : {
1059 :
1060 0 : uint64_t original_log_categories = LogInstance().GetCategoryMask();
1061 0 : if (request.params[0].isArray()) {
1062 0 : EnableOrDisableLogCategories(request.params[0], true);
1063 0 : }
1064 0 : if (request.params[1].isArray()) {
1065 0 : EnableOrDisableLogCategories(request.params[1], false);
1066 0 : }
1067 0 : uint64_t updated_log_categories = LogInstance().GetCategoryMask();
1068 0 : uint64_t changed_log_categories = original_log_categories ^ updated_log_categories;
1069 :
1070 : // Update libevent logging if BCLog::LIBEVENT has changed.
1071 0 : if (changed_log_categories & BCLog::LIBEVENT) {
1072 0 : UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT));
1073 0 : }
1074 :
1075 0 : UniValue result(UniValue::VOBJ);
1076 0 : for (const auto& logCatActive : LogInstance().LogCategoriesList()) {
1077 0 : result.pushKV(logCatActive.category, logCatActive.active);
1078 : }
1079 :
1080 0 : return result;
1081 0 : },
1082 : };
1083 0 : }
1084 :
1085 184 : static RPCHelpMan echo(const std::string& name)
1086 : {
1087 368 : return RPCHelpMan{name,
1088 184 : "\nSimply echo back the input arguments. This command is for testing.\n"
1089 : "\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\n"
1090 : "\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in "
1091 : "dash-cli and the GUI. There is no server-side difference.",
1092 2024 : {
1093 184 : {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
1094 184 : {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
1095 184 : {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
1096 184 : {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
1097 184 : {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
1098 184 : {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
1099 184 : {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
1100 184 : {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
1101 184 : {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
1102 184 : {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
1103 : },
1104 184 : RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"},
1105 184 : RPCExamples{""},
1106 184 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1107 : {
1108 0 : if (request.params[9].isStr()) {
1109 0 : CHECK_NONFATAL(request.params[9].get_str() != "trigger_internal_bug");
1110 0 : }
1111 :
1112 0 : return request.params;
1113 : },
1114 : };
1115 0 : }
1116 :
1117 92 : static RPCHelpMan echo() { return echo("echo"); }
1118 92 : static RPCHelpMan echojson() { return echo("echojson"); }
1119 :
1120 92 : static RPCHelpMan echoipc()
1121 : {
1122 92 : return RPCHelpMan{
1123 92 : "echoipc",
1124 92 : "\nEcho back the input argument, passing it through a spawned process in a multiprocess build.\n"
1125 : "This command is for testing.\n",
1126 92 : {{"arg", RPCArg::Type::STR, RPCArg::Optional::NO, "The string to echo",}},
1127 92 : RPCResult{RPCResult::Type::STR, "echo", "The echoed string."},
1128 184 : RPCExamples{HelpExampleCli("echo", "\"Hello world\"") +
1129 92 : HelpExampleRpc("echo", "\"Hello world\"")},
1130 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
1131 0 : interfaces::Init& local_init = *EnsureAnyNodeContext(request.context).init;
1132 0 : std::unique_ptr<interfaces::Echo> echo;
1133 0 : if (interfaces::Ipc* ipc = local_init.ipc()) {
1134 : // Spawn a new bitcoin-node process and call makeEcho to get a
1135 : // client pointer to a interfaces::Echo instance running in
1136 : // that process. This is just for testing. A slightly more
1137 : // realistic test spawning a different executable instead of
1138 : // the same executable would add a new bitcoin-echo executable,
1139 : // and spawn bitcoin-echo below instead of bitcoin-node. But
1140 : // using bitcoin-node avoids the need to build and install a
1141 : // new executable just for this one test.
1142 0 : auto init = ipc->spawnProcess("dash-node");
1143 0 : echo = init->makeEcho();
1144 0 : ipc->addCleanup(*echo, [init = init.release()] { delete init; });
1145 0 : } else {
1146 : // IPC support is not available because this is a bitcoind
1147 : // process not a bitcoind-node process, so just create a local
1148 : // interfaces::Echo object and return it so the `echoipc` RPC
1149 : // method will work, and the python test calling `echoipc`
1150 : // can expect the same result.
1151 0 : echo = local_init.makeEcho();
1152 : }
1153 0 : return echo->echo(request.params[0].get_str());
1154 0 : },
1155 : };
1156 0 : }
1157 :
1158 0 : static UniValue SummaryToJSON(const IndexSummary&& summary, std::string index_name)
1159 : {
1160 0 : UniValue ret_summary(UniValue::VOBJ);
1161 0 : if (!index_name.empty() && index_name != summary.name) return ret_summary;
1162 :
1163 0 : UniValue entry(UniValue::VOBJ);
1164 0 : entry.pushKV("synced", summary.synced);
1165 0 : entry.pushKV("best_block_height", summary.best_block_height);
1166 0 : ret_summary.pushKV(summary.name, entry);
1167 0 : return ret_summary;
1168 0 : }
1169 :
1170 92 : static RPCHelpMan getindexinfo()
1171 : {
1172 184 : return RPCHelpMan{"getindexinfo",
1173 92 : "\nReturns the status of one or all available indices currently running in the node.\n",
1174 184 : {
1175 92 : {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Filter results for an index with a specific name."},
1176 : },
1177 92 : RPCResult{
1178 184 : RPCResult::Type::OBJ_DYN, "", "", {
1179 92 : {
1180 92 : RPCResult::Type::OBJ, "name", "The name of the index",
1181 276 : {
1182 92 : {RPCResult::Type::BOOL, "synced", "Whether the index is synced or not"},
1183 92 : {RPCResult::Type::NUM, "best_block_height", "The block height to which the index is synced"},
1184 : }
1185 : },
1186 : },
1187 : },
1188 92 : RPCExamples{
1189 92 : HelpExampleCli("getindexinfo", "")
1190 92 : + HelpExampleRpc("getindexinfo", "")
1191 92 : + HelpExampleCli("getindexinfo", "txindex")
1192 92 : + HelpExampleRpc("getindexinfo", "txindex")
1193 : },
1194 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1195 : {
1196 0 : UniValue result(UniValue::VOBJ);
1197 0 : const std::string index_name = request.params[0].isNull() ? "" : request.params[0].get_str();
1198 :
1199 0 : if (g_txindex) {
1200 0 : result.pushKVs(SummaryToJSON(g_txindex->GetSummary(), index_name));
1201 0 : }
1202 :
1203 0 : if (g_coin_stats_index) {
1204 0 : result.pushKVs(SummaryToJSON(g_coin_stats_index->GetSummary(), index_name));
1205 0 : }
1206 :
1207 0 : ForEachBlockFilterIndex([&result, &index_name](const BlockFilterIndex& index) {
1208 0 : result.pushKVs(SummaryToJSON(index.GetSummary(), index_name));
1209 0 : });
1210 :
1211 0 : if (g_addressindex) {
1212 0 : result.pushKVs(SummaryToJSON(g_addressindex->GetSummary(), index_name));
1213 0 : }
1214 :
1215 0 : if (g_timestampindex) {
1216 0 : result.pushKVs(SummaryToJSON(g_timestampindex->GetSummary(), index_name));
1217 0 : }
1218 :
1219 0 : if (g_spentindex) {
1220 0 : result.pushKVs(SummaryToJSON(g_spentindex->GetSummary(), index_name));
1221 0 : }
1222 :
1223 0 : return result;
1224 0 : },
1225 : };
1226 0 : }
1227 :
1228 178 : void RegisterNodeRPCCommands(CRPCTable &t)
1229 : {
1230 224 : static const CRPCCommand commands[] =
1231 920 : { // category actor (function)
1232 : // --------------------- ------------------------
1233 46 : { "control", &debug, },
1234 46 : { "control", &getmemoryinfo, },
1235 46 : { "control", &logging, },
1236 46 : { "util", &getindexinfo, },
1237 46 : { "blockchain", &getspentinfo, },
1238 :
1239 : /* Address index */
1240 46 : { "addressindex", &getaddressmempool, },
1241 46 : { "addressindex", &getaddressutxos, },
1242 46 : { "addressindex", &getaddressdeltas, },
1243 46 : { "addressindex", &getaddresstxids, },
1244 46 : { "addressindex", &getaddressbalance, },
1245 :
1246 : /* Dash features */
1247 46 : { "dash", &mnsync, },
1248 46 : { "dash", &spork, },
1249 46 : { "dash", &sporkupdate, },
1250 :
1251 : /* Not shown in help */
1252 46 : { "hidden", &setmocktime, },
1253 46 : { "hidden", &mockscheduler, },
1254 46 : { "hidden", &echo, },
1255 46 : { "hidden", &echojson, },
1256 46 : { "hidden", &echoipc, },
1257 46 : { "hidden", &mnauth, },
1258 : };
1259 : // clang-format on
1260 3560 : for (const auto& c : commands) {
1261 3382 : t.appendCommand(c.name, &c);
1262 : }
1263 178 : }
|