/root/bitcoin/src/rpc/node.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2010 Satoshi Nakamoto |
2 | | // Copyright (c) 2009-2022 The Bitcoin Core developers |
3 | | // Distributed under the MIT software license, see the accompanying |
4 | | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
5 | | |
6 | | #include <bitcoin-build-config.h> // IWYU pragma: keep |
7 | | |
8 | | #include <chainparams.h> |
9 | | #include <httpserver.h> |
10 | | #include <index/blockfilterindex.h> |
11 | | #include <index/coinstatsindex.h> |
12 | | #include <index/txindex.h> |
13 | | #include <interfaces/chain.h> |
14 | | #include <interfaces/echo.h> |
15 | | #include <interfaces/init.h> |
16 | | #include <interfaces/ipc.h> |
17 | | #include <kernel/cs_main.h> |
18 | | #include <logging.h> |
19 | | #include <node/context.h> |
20 | | #include <rpc/server.h> |
21 | | #include <rpc/server_util.h> |
22 | | #include <rpc/util.h> |
23 | | #include <scheduler.h> |
24 | | #include <univalue.h> |
25 | | #include <util/any.h> |
26 | | #include <util/check.h> |
27 | | #include <util/time.h> |
28 | | |
29 | | #include <stdint.h> |
30 | | #ifdef HAVE_MALLOC_INFO |
31 | | #include <malloc.h> |
32 | | #endif |
33 | | |
34 | | using node::NodeContext; |
35 | | |
36 | | static RPCHelpMan setmocktime() |
37 | 0 | { |
38 | 0 | return RPCHelpMan{"setmocktime", |
39 | 0 | "\nSet the local time to given timestamp (-regtest only)\n", |
40 | 0 | { |
41 | 0 | {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, UNIX_EPOCH_TIME + "\n" |
42 | 0 | "Pass 0 to go back to using the system time."}, |
43 | 0 | }, |
44 | 0 | RPCResult{RPCResult::Type::NONE, "", ""}, |
45 | 0 | RPCExamples{""}, |
46 | 0 | [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue |
47 | 0 | { |
48 | 0 | if (!Params().IsMockableChain()) { |
49 | 0 | throw std::runtime_error("setmocktime is for regression testing (-regtest mode) only"); |
50 | 0 | } |
51 | | |
52 | | // For now, don't change mocktime if we're in the middle of validation, as |
53 | | // this could have an effect on mempool time-based eviction, as well as |
54 | | // IsCurrentForFeeEstimation() and IsInitialBlockDownload(). |
55 | | // TODO: figure out the right way to synchronize around mocktime, and |
56 | | // ensure all call sites of GetTime() are accessing this safely. |
57 | 0 | LOCK(cs_main); |
58 | |
|
59 | 0 | const int64_t time{request.params[0].getInt<int64_t>()}; |
60 | 0 | constexpr int64_t max_time{Ticks<std::chrono::seconds>(std::chrono::nanoseconds::max())}; |
61 | 0 | if (time < 0 || time > max_time) { |
62 | 0 | throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime must be in the range [0, %s], not %s.", max_time, time)); |
63 | 0 | } |
64 | | |
65 | 0 | SetMockTime(time); |
66 | 0 | const NodeContext& node_context{EnsureAnyNodeContext(request.context)}; |
67 | 0 | for (const auto& chain_client : node_context.chain_clients) { |
68 | 0 | chain_client->setMockTime(time); |
69 | 0 | } |
70 | |
|
71 | 0 | return UniValue::VNULL; |
72 | 0 | }, |
73 | 0 | }; |
74 | 0 | } |
75 | | |
76 | | static RPCHelpMan mockscheduler() |
77 | 0 | { |
78 | 0 | return RPCHelpMan{"mockscheduler", |
79 | 0 | "\nBump the scheduler into the future (-regtest only)\n", |
80 | 0 | { |
81 | 0 | {"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." }, |
82 | 0 | }, |
83 | 0 | RPCResult{RPCResult::Type::NONE, "", ""}, |
84 | 0 | RPCExamples{""}, |
85 | 0 | [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue |
86 | 0 | { |
87 | 0 | if (!Params().IsMockableChain()) { |
88 | 0 | throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only"); |
89 | 0 | } |
90 | | |
91 | 0 | int64_t delta_seconds = request.params[0].getInt<int64_t>(); |
92 | 0 | if (delta_seconds <= 0 || delta_seconds > 3600) { |
93 | 0 | throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)"); |
94 | 0 | } |
95 | | |
96 | 0 | const NodeContext& node_context{EnsureAnyNodeContext(request.context)}; |
97 | 0 | CHECK_NONFATAL(node_context.scheduler)->MockForward(std::chrono::seconds{delta_seconds}); |
98 | 0 | CHECK_NONFATAL(node_context.validation_signals)->SyncWithValidationInterfaceQueue(); |
99 | 0 | for (const auto& chain_client : node_context.chain_clients) { |
100 | 0 | chain_client->schedulerMockForward(std::chrono::seconds(delta_seconds)); |
101 | 0 | } |
102 | |
|
103 | 0 | return UniValue::VNULL; |
104 | 0 | }, |
105 | 0 | }; |
106 | 0 | } |
107 | | |
108 | | static UniValue RPCLockedMemoryInfo() |
109 | 0 | { |
110 | 0 | LockedPool::Stats stats = LockedPoolManager::Instance().stats(); |
111 | 0 | UniValue obj(UniValue::VOBJ); |
112 | 0 | obj.pushKV("used", uint64_t(stats.used)); |
113 | 0 | obj.pushKV("free", uint64_t(stats.free)); |
114 | 0 | obj.pushKV("total", uint64_t(stats.total)); |
115 | 0 | obj.pushKV("locked", uint64_t(stats.locked)); |
116 | 0 | obj.pushKV("chunks_used", uint64_t(stats.chunks_used)); |
117 | 0 | obj.pushKV("chunks_free", uint64_t(stats.chunks_free)); |
118 | 0 | return obj; |
119 | 0 | } |
120 | | |
121 | | #ifdef HAVE_MALLOC_INFO |
122 | | static std::string RPCMallocInfo() |
123 | 0 | { |
124 | 0 | char *ptr = nullptr; |
125 | 0 | size_t size = 0; |
126 | 0 | FILE *f = open_memstream(&ptr, &size); |
127 | 0 | if (f) { |
128 | 0 | malloc_info(0, f); |
129 | 0 | fclose(f); |
130 | 0 | if (ptr) { |
131 | 0 | std::string rv(ptr, size); |
132 | 0 | free(ptr); |
133 | 0 | return rv; |
134 | 0 | } |
135 | 0 | } |
136 | 0 | return ""; |
137 | 0 | } |
138 | | #endif |
139 | | |
140 | | static RPCHelpMan getmemoryinfo() |
141 | 0 | { |
142 | | /* Please, avoid using the word "pool" here in the RPC interface or help, |
143 | | * as users will undoubtedly confuse it with the other "memory pool" |
144 | | */ |
145 | 0 | return RPCHelpMan{"getmemoryinfo", |
146 | 0 | "Returns an object containing information about memory usage.\n", |
147 | 0 | { |
148 | 0 | {"mode", RPCArg::Type::STR, RPCArg::Default{"stats"}, "determines what kind of information is returned.\n" |
149 | 0 | " - \"stats\" returns general statistics about memory usage in the daemon.\n" |
150 | 0 | " - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc)."}, |
151 | 0 | }, |
152 | 0 | { |
153 | 0 | RPCResult{"mode \"stats\"", |
154 | 0 | RPCResult::Type::OBJ, "", "", |
155 | 0 | { |
156 | 0 | {RPCResult::Type::OBJ, "locked", "Information about locked memory manager", |
157 | 0 | { |
158 | 0 | {RPCResult::Type::NUM, "used", "Number of bytes used"}, |
159 | 0 | {RPCResult::Type::NUM, "free", "Number of bytes available in current arenas"}, |
160 | 0 | {RPCResult::Type::NUM, "total", "Total number of bytes managed"}, |
161 | 0 | {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."}, |
162 | 0 | {RPCResult::Type::NUM, "chunks_used", "Number allocated chunks"}, |
163 | 0 | {RPCResult::Type::NUM, "chunks_free", "Number unused chunks"}, |
164 | 0 | }}, |
165 | 0 | } |
166 | 0 | }, |
167 | 0 | RPCResult{"mode \"mallocinfo\"", |
168 | 0 | RPCResult::Type::STR, "", "\"<malloc version=\"1\">...\"" |
169 | 0 | }, |
170 | 0 | }, |
171 | 0 | RPCExamples{ |
172 | 0 | HelpExampleCli("getmemoryinfo", "") |
173 | 0 | + HelpExampleRpc("getmemoryinfo", "") |
174 | 0 | }, |
175 | 0 | [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue |
176 | 0 | { |
177 | 0 | std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str(); |
178 | 0 | if (mode == "stats") { |
179 | 0 | UniValue obj(UniValue::VOBJ); |
180 | 0 | obj.pushKV("locked", RPCLockedMemoryInfo()); |
181 | 0 | return obj; |
182 | 0 | } else if (mode == "mallocinfo") { |
183 | 0 | #ifdef HAVE_MALLOC_INFO |
184 | 0 | return RPCMallocInfo(); |
185 | | #else |
186 | | throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo mode not available"); |
187 | | #endif |
188 | 0 | } else { |
189 | 0 | throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode); |
190 | 0 | } |
191 | 0 | }, |
192 | 0 | }; |
193 | 0 | } |
194 | | |
195 | 0 | static void EnableOrDisableLogCategories(UniValue cats, bool enable) { |
196 | 0 | cats = cats.get_array(); |
197 | 0 | for (unsigned int i = 0; i < cats.size(); ++i) { |
198 | 0 | std::string cat = cats[i].get_str(); |
199 | |
|
200 | 0 | bool success; |
201 | 0 | if (enable) { |
202 | 0 | success = LogInstance().EnableCategory(cat); |
203 | 0 | } else { |
204 | 0 | success = LogInstance().DisableCategory(cat); |
205 | 0 | } |
206 | |
|
207 | 0 | if (!success) { |
208 | 0 | throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat); |
209 | 0 | } |
210 | 0 | } |
211 | 0 | } |
212 | | |
213 | | static RPCHelpMan logging() |
214 | 0 | { |
215 | 0 | return RPCHelpMan{"logging", |
216 | 0 | "Gets and sets the logging configuration.\n" |
217 | 0 | "When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n" |
218 | 0 | "When called with arguments, adds or removes categories from debug logging and return the lists above.\n" |
219 | 0 | "The arguments are evaluated in order \"include\", \"exclude\".\n" |
220 | 0 | "If an item is both included and excluded, it will thus end up being excluded.\n" |
221 | 0 | "The valid logging categories are: " + LogInstance().LogCategoriesString() + "\n" |
222 | 0 | "In addition, the following are available as category names with special meanings:\n" |
223 | 0 | " - \"all\", \"1\" : represent all logging categories.\n" |
224 | 0 | , |
225 | 0 | { |
226 | 0 | {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The categories to add to debug logging", |
227 | 0 | { |
228 | 0 | {"include_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"}, |
229 | 0 | }}, |
230 | 0 | {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The categories to remove from debug logging", |
231 | 0 | { |
232 | 0 | {"exclude_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"}, |
233 | 0 | }}, |
234 | 0 | }, |
235 | 0 | RPCResult{ |
236 | 0 | RPCResult::Type::OBJ_DYN, "", "keys are the logging categories, and values indicates its status", |
237 | 0 | { |
238 | 0 | {RPCResult::Type::BOOL, "category", "if being debug logged or not. false:inactive, true:active"}, |
239 | 0 | } |
240 | 0 | }, |
241 | 0 | RPCExamples{ |
242 | 0 | HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"") |
243 | 0 | + HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]") |
244 | 0 | }, |
245 | 0 | [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue |
246 | 0 | { |
247 | 0 | BCLog::CategoryMask original_log_categories = LogInstance().GetCategoryMask(); |
248 | 0 | if (request.params[0].isArray()) { |
249 | 0 | EnableOrDisableLogCategories(request.params[0], true); |
250 | 0 | } |
251 | 0 | if (request.params[1].isArray()) { |
252 | 0 | EnableOrDisableLogCategories(request.params[1], false); |
253 | 0 | } |
254 | 0 | BCLog::CategoryMask updated_log_categories = LogInstance().GetCategoryMask(); |
255 | 0 | BCLog::CategoryMask changed_log_categories = original_log_categories ^ updated_log_categories; |
256 | | |
257 | | // Update libevent logging if BCLog::LIBEVENT has changed. |
258 | 0 | if (changed_log_categories & BCLog::LIBEVENT) { |
259 | 0 | UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT)); |
260 | 0 | } |
261 | |
|
262 | 0 | UniValue result(UniValue::VOBJ); |
263 | 0 | for (const auto& logCatActive : LogInstance().LogCategoriesList()) { |
264 | 0 | result.pushKV(logCatActive.category, logCatActive.active); |
265 | 0 | } |
266 | |
|
267 | 0 | return result; |
268 | 0 | }, |
269 | 0 | }; |
270 | 0 | } |
271 | | |
272 | | static RPCHelpMan echo(const std::string& name) |
273 | 0 | { |
274 | 0 | return RPCHelpMan{name, |
275 | 0 | "\nSimply echo back the input arguments. This command is for testing.\n" |
276 | 0 | "\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\n" |
277 | 0 | "\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in " |
278 | 0 | "bitcoin-cli and the GUI. There is no server-side difference.", |
279 | 0 | { |
280 | 0 | {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, |
281 | 0 | {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, |
282 | 0 | {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, |
283 | 0 | {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, |
284 | 0 | {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, |
285 | 0 | {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, |
286 | 0 | {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, |
287 | 0 | {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, |
288 | 0 | {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, |
289 | 0 | {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}}, |
290 | 0 | }, |
291 | 0 | RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"}, |
292 | 0 | RPCExamples{""}, |
293 | 0 | [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue |
294 | 0 | { |
295 | 0 | if (request.params[9].isStr()) { |
296 | 0 | CHECK_NONFATAL(request.params[9].get_str() != "trigger_internal_bug"); |
297 | 0 | } |
298 | |
|
299 | 0 | return request.params; |
300 | 0 | }, |
301 | 0 | }; |
302 | 0 | } |
303 | | |
304 | 0 | static RPCHelpMan echo() { return echo("echo"); } |
305 | 0 | static RPCHelpMan echojson() { return echo("echojson"); } |
306 | | |
307 | | static RPCHelpMan echoipc() |
308 | 0 | { |
309 | 0 | return RPCHelpMan{ |
310 | 0 | "echoipc", |
311 | 0 | "\nEcho back the input argument, passing it through a spawned process in a multiprocess build.\n" |
312 | 0 | "This command is for testing.\n", |
313 | 0 | {{"arg", RPCArg::Type::STR, RPCArg::Optional::NO, "The string to echo",}}, |
314 | 0 | RPCResult{RPCResult::Type::STR, "echo", "The echoed string."}, |
315 | 0 | RPCExamples{HelpExampleCli("echo", "\"Hello world\"") + |
316 | 0 | HelpExampleRpc("echo", "\"Hello world\"")}, |
317 | 0 | [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { |
318 | 0 | interfaces::Init& local_init = *EnsureAnyNodeContext(request.context).init; |
319 | 0 | std::unique_ptr<interfaces::Echo> echo; |
320 | 0 | if (interfaces::Ipc* ipc = local_init.ipc()) { |
321 | | // Spawn a new bitcoin-node process and call makeEcho to get a |
322 | | // client pointer to a interfaces::Echo instance running in |
323 | | // that process. This is just for testing. A slightly more |
324 | | // realistic test spawning a different executable instead of |
325 | | // the same executable would add a new bitcoin-echo executable, |
326 | | // and spawn bitcoin-echo below instead of bitcoin-node. But |
327 | | // using bitcoin-node avoids the need to build and install a |
328 | | // new executable just for this one test. |
329 | 0 | auto init = ipc->spawnProcess("bitcoin-node"); |
330 | 0 | echo = init->makeEcho(); |
331 | 0 | ipc->addCleanup(*echo, [init = init.release()] { delete init; }); |
332 | 0 | } else { |
333 | | // IPC support is not available because this is a bitcoind |
334 | | // process not a bitcoind-node process, so just create a local |
335 | | // interfaces::Echo object and return it so the `echoipc` RPC |
336 | | // method will work, and the python test calling `echoipc` |
337 | | // can expect the same result. |
338 | 0 | echo = local_init.makeEcho(); |
339 | 0 | } |
340 | 0 | return echo->echo(request.params[0].get_str()); |
341 | 0 | }, |
342 | 0 | }; |
343 | 0 | } |
344 | | |
345 | | static UniValue SummaryToJSON(const IndexSummary&& summary, std::string index_name) |
346 | 0 | { |
347 | 0 | UniValue ret_summary(UniValue::VOBJ); |
348 | 0 | if (!index_name.empty() && index_name != summary.name) return ret_summary; |
349 | | |
350 | 0 | UniValue entry(UniValue::VOBJ); |
351 | 0 | entry.pushKV("synced", summary.synced); |
352 | 0 | entry.pushKV("best_block_height", summary.best_block_height); |
353 | 0 | ret_summary.pushKV(summary.name, std::move(entry)); |
354 | 0 | return ret_summary; |
355 | 0 | } |
356 | | |
357 | | static RPCHelpMan getindexinfo() |
358 | 0 | { |
359 | 0 | return RPCHelpMan{"getindexinfo", |
360 | 0 | "\nReturns the status of one or all available indices currently running in the node.\n", |
361 | 0 | { |
362 | 0 | {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Filter results for an index with a specific name."}, |
363 | 0 | }, |
364 | 0 | RPCResult{ |
365 | 0 | RPCResult::Type::OBJ_DYN, "", "", { |
366 | 0 | { |
367 | 0 | RPCResult::Type::OBJ, "name", "The name of the index", |
368 | 0 | { |
369 | 0 | {RPCResult::Type::BOOL, "synced", "Whether the index is synced or not"}, |
370 | 0 | {RPCResult::Type::NUM, "best_block_height", "The block height to which the index is synced"}, |
371 | 0 | } |
372 | 0 | }, |
373 | 0 | }, |
374 | 0 | }, |
375 | 0 | RPCExamples{ |
376 | 0 | HelpExampleCli("getindexinfo", "") |
377 | 0 | + HelpExampleRpc("getindexinfo", "") |
378 | 0 | + HelpExampleCli("getindexinfo", "txindex") |
379 | 0 | + HelpExampleRpc("getindexinfo", "txindex") |
380 | 0 | }, |
381 | 0 | [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue |
382 | 0 | { |
383 | 0 | UniValue result(UniValue::VOBJ); |
384 | 0 | const std::string index_name = request.params[0].isNull() ? "" : request.params[0].get_str(); |
385 | |
|
386 | 0 | if (g_txindex) { |
387 | 0 | result.pushKVs(SummaryToJSON(g_txindex->GetSummary(), index_name)); |
388 | 0 | } |
389 | |
|
390 | 0 | if (g_coin_stats_index) { |
391 | 0 | result.pushKVs(SummaryToJSON(g_coin_stats_index->GetSummary(), index_name)); |
392 | 0 | } |
393 | |
|
394 | 0 | ForEachBlockFilterIndex([&result, &index_name](const BlockFilterIndex& index) { |
395 | 0 | result.pushKVs(SummaryToJSON(index.GetSummary(), index_name)); |
396 | 0 | }); |
397 | |
|
398 | 0 | return result; |
399 | 0 | }, |
400 | 0 | }; |
401 | 0 | } |
402 | | |
403 | | void RegisterNodeRPCCommands(CRPCTable& t) |
404 | 0 | { |
405 | 0 | static const CRPCCommand commands[]{ |
406 | 0 | {"control", &getmemoryinfo}, |
407 | 0 | {"control", &logging}, |
408 | 0 | {"util", &getindexinfo}, |
409 | 0 | {"hidden", &setmocktime}, |
410 | 0 | {"hidden", &mockscheduler}, |
411 | 0 | {"hidden", &echo}, |
412 | 0 | {"hidden", &echojson}, |
413 | 0 | {"hidden", &echoipc}, |
414 | 0 | }; |
415 | 0 | for (const auto& c : commands) { |
416 | 0 | t.appendCommand(c.name, &c); |
417 | 0 | } |
418 | 0 | } |