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