/ src / rpc / server.cpp
server.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 <rpc/server.h>
  9  
 10  #include <common/args.h>
 11  #include <common/system.h>
 12  #include <logging.h>
 13  #include <node/context.h>
 14  #include <node/kernel_notifications.h>
 15  #include <rpc/server_util.h>
 16  #include <rpc/util.h>
 17  #include <sync.h>
 18  #include <util/signalinterrupt.h>
 19  #include <util/strencodings.h>
 20  #include <util/string.h>
 21  #include <util/time.h>
 22  #include <validation.h>
 23  
 24  #include <algorithm>
 25  #include <cassert>
 26  #include <chrono>
 27  #include <memory>
 28  #include <mutex>
 29  #include <string_view>
 30  #include <unordered_map>
 31  
 32  using util::SplitString;
 33  
 34  static GlobalMutex g_rpc_warmup_mutex;
 35  static std::atomic<bool> g_rpc_running{false};
 36  static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true;
 37  static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started";
 38  static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler);
 39  
 40  struct RPCCommandExecutionInfo
 41  {
 42      std::string method;
 43      SteadyClock::time_point start;
 44  };
 45  
 46  struct RPCServerInfo
 47  {
 48      Mutex mutex;
 49      std::list<RPCCommandExecutionInfo> active_commands GUARDED_BY(mutex);
 50  };
 51  
 52  static RPCServerInfo g_rpc_server_info;
 53  
 54  struct RPCCommandExecution
 55  {
 56      std::list<RPCCommandExecutionInfo>::iterator it;
 57      explicit RPCCommandExecution(const std::string& method)
 58      {
 59          LOCK(g_rpc_server_info.mutex);
 60          it = g_rpc_server_info.active_commands.insert(g_rpc_server_info.active_commands.end(), {method, SteadyClock::now()});
 61      }
 62      ~RPCCommandExecution()
 63      {
 64          LOCK(g_rpc_server_info.mutex);
 65          g_rpc_server_info.active_commands.erase(it);
 66      }
 67  };
 68  
 69  std::string CRPCTable::help(std::string_view strCommand, const JSONRPCRequest& helpreq) const
 70  {
 71      std::string strRet;
 72      std::string category;
 73      std::set<intptr_t> setDone;
 74      std::vector<std::pair<std::string, const CRPCCommand*> > vCommands;
 75      vCommands.reserve(mapCommands.size());
 76  
 77      for (const auto& entry : mapCommands)
 78          vCommands.emplace_back(entry.second.front()->category + entry.first, entry.second.front());
 79      std::ranges::sort(vCommands);
 80  
 81      JSONRPCRequest jreq = helpreq;
 82      jreq.mode = JSONRPCRequest::GET_HELP;
 83      jreq.params = UniValue();
 84  
 85      for (const auto& [_, pcmd] : vCommands) {
 86          std::string strMethod = pcmd->name;
 87          if ((strCommand != "" || pcmd->category == "hidden") && strMethod != strCommand)
 88              continue;
 89          jreq.strMethod = strMethod;
 90          try
 91          {
 92              UniValue unused_result;
 93              if (setDone.insert(pcmd->unique_id).second)
 94                  pcmd->actor(jreq, unused_result, /*last_handler=*/true);
 95          } catch (const HelpResult& e) {
 96              std::string strHelp{e.what()};
 97              if (strCommand == "")
 98              {
 99                  if (strHelp.find('\n') != std::string::npos)
100                      strHelp = strHelp.substr(0, strHelp.find('\n'));
101  
102                  if (category != pcmd->category)
103                  {
104                      if (!category.empty())
105                          strRet += "\n";
106                      category = pcmd->category;
107                      strRet += "== " + Capitalize(category) + " ==\n";
108                  }
109              }
110              strRet += strHelp + "\n";
111          }
112      }
113      if (strRet == "")
114          strRet = strprintf("help: unknown command: %s\n", strCommand);
115      strRet = strRet.substr(0,strRet.size()-1);
116      return strRet;
117  }
118  
119  static RPCHelpMan help()
120  {
121      return RPCHelpMan{
122          "help",
123          "List all commands, or get help for a specified command.\n",
124                  {
125                      {"command", RPCArg::Type::STR, RPCArg::DefaultHint{"all commands"}, "The command to get help on"},
126                  },
127                  {
128                      RPCResult{RPCResult::Type::STR, "", "The help text"},
129                      RPCResult{RPCResult::Type::ANY, "", ""},
130                  },
131                  RPCExamples{""},
132          [&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue
133  {
134      auto command{self.MaybeArg<std::string_view>("command")};
135      if (command == "dump_all_command_conversions") {
136          // Used for testing only, undocumented
137          return tableRPC.dumpArgMap(jsonRequest);
138      }
139  
140      return tableRPC.help(command.value_or(""), jsonRequest);
141  },
142      };
143  }
144  
145  static RPCHelpMan stop()
146  {
147      static const std::string RESULT{CLIENT_NAME " stopping"};
148      return RPCHelpMan{
149          "stop",
150      // Also accept the hidden 'wait' integer argument (milliseconds)
151      // For instance, 'stop 1000' makes the call wait 1 second before returning
152      // to the client (intended for testing)
153          "Request a graceful shutdown of " CLIENT_NAME ".",
154                  {
155                      {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "how long to wait in ms", RPCArgOptions{.hidden=true}},
156                  },
157                  RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"},
158                  RPCExamples{""},
159          [&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue
160  {
161      // Event loop will exit after current HTTP requests have been handled, so
162      // this reply will get back to the client.
163      CHECK_NONFATAL((CHECK_NONFATAL(EnsureAnyNodeContext(jsonRequest.context).shutdown_request))());
164      if (jsonRequest.params[0].isNum()) {
165          UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].getInt<int>()});
166      }
167      return RESULT;
168  },
169      };
170  }
171  
172  static RPCHelpMan uptime()
173  {
174      return RPCHelpMan{
175          "uptime",
176          "Returns the total uptime of the server.\n",
177                              {},
178                              RPCResult{
179                                  RPCResult::Type::NUM, "", "The number of seconds that the server has been running"
180                              },
181                  RPCExamples{
182                      HelpExampleCli("uptime", "")
183                  + HelpExampleRpc("uptime", "")
184                  },
185          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
186  {
187      return TicksSeconds(GetUptime());
188  }
189      };
190  }
191  
192  static RPCHelpMan getrpcinfo()
193  {
194      return RPCHelpMan{
195          "getrpcinfo",
196          "Returns details of the RPC server.\n",
197                  {},
198                  RPCResult{
199                      RPCResult::Type::OBJ, "", "",
200                      {
201                          {RPCResult::Type::ARR, "active_commands", "All active commands",
202                          {
203                              {RPCResult::Type::OBJ, "", "Information about an active command",
204                              {
205                                   {RPCResult::Type::STR, "method", "The name of the RPC command"},
206                                   {RPCResult::Type::NUM, "duration", "The running time in microseconds"},
207                              }},
208                          }},
209                          {RPCResult::Type::STR, "logpath", "The complete file path to the debug log"},
210                      }
211                  },
212                  RPCExamples{
213                      HelpExampleCli("getrpcinfo", "")
214                  + HelpExampleRpc("getrpcinfo", "")},
215          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
216  {
217      LOCK(g_rpc_server_info.mutex);
218      UniValue active_commands(UniValue::VARR);
219      for (const RPCCommandExecutionInfo& info : g_rpc_server_info.active_commands) {
220          UniValue entry(UniValue::VOBJ);
221          entry.pushKV("method", info.method);
222          entry.pushKV("duration", Ticks<std::chrono::microseconds>(SteadyClock::now() - info.start));
223          active_commands.push_back(std::move(entry));
224      }
225  
226      UniValue result(UniValue::VOBJ);
227      result.pushKV("active_commands", std::move(active_commands));
228  
229      const std::string path = LogInstance().m_file_path.utf8string();
230      UniValue log_path(UniValue::VSTR, path);
231      result.pushKV("logpath", std::move(log_path));
232  
233      return result;
234  }
235      };
236  }
237  
238  static const CRPCCommand vRPCCommands[]{
239      /* Overall control/query calls */
240      {"control", &getrpcinfo},
241      {"control", &help},
242      {"control", &stop},
243      {"control", &uptime},
244  };
245  
246  CRPCTable::CRPCTable()
247  {
248      for (const auto& c : vRPCCommands) {
249          appendCommand(c.name, &c);
250      }
251  }
252  
253  void CRPCTable::appendCommand(const std::string& name, const CRPCCommand* pcmd)
254  {
255      CHECK_NONFATAL(!IsRPCRunning()); // Only add commands before rpc is running
256  
257      mapCommands[name].push_back(pcmd);
258  }
259  
260  bool CRPCTable::removeCommand(const std::string& name, const CRPCCommand* pcmd)
261  {
262      auto it = mapCommands.find(name);
263      if (it != mapCommands.end()) {
264          auto new_end = std::remove(it->second.begin(), it->second.end(), pcmd);
265          if (it->second.end() != new_end) {
266              it->second.erase(new_end, it->second.end());
267              return true;
268          }
269      }
270      return false;
271  }
272  
273  void StartRPC()
274  {
275      LogDebug(BCLog::RPC, "Starting RPC\n");
276      g_rpc_running = true;
277  }
278  
279  void InterruptRPC()
280  {
281      static std::once_flag g_rpc_interrupt_flag;
282      // This function could be called twice if the GUI has been started with -server=1.
283      std::call_once(g_rpc_interrupt_flag, []() {
284          LogDebug(BCLog::RPC, "Interrupting RPC\n");
285          // Interrupt e.g. running longpolls
286          g_rpc_running = false;
287      });
288  }
289  
290  void StopRPC()
291  {
292      static std::once_flag g_rpc_stop_flag;
293      // This function could be called twice if the GUI has been started with -server=1.
294      assert(!g_rpc_running);
295      std::call_once(g_rpc_stop_flag, [&]() {
296          LogDebug(BCLog::RPC, "Stopping RPC\n");
297          DeleteAuthCookie();
298          LogDebug(BCLog::RPC, "RPC stopped.\n");
299      });
300  }
301  
302  bool IsRPCRunning()
303  {
304      return g_rpc_running;
305  }
306  
307  void RpcInterruptionPoint()
308  {
309      if (!IsRPCRunning()) throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down");
310  }
311  
312  void SetRPCWarmupStatus(const std::string& newStatus)
313  {
314      LOCK(g_rpc_warmup_mutex);
315      rpcWarmupStatus = newStatus;
316  }
317  
318  void SetRPCWarmupStarting()
319  {
320      LOCK(g_rpc_warmup_mutex);
321      fRPCInWarmup = true;
322  }
323  
324  void SetRPCWarmupFinished()
325  {
326      LOCK(g_rpc_warmup_mutex);
327      assert(fRPCInWarmup);
328      fRPCInWarmup = false;
329  }
330  
331  bool RPCIsInWarmup(std::string *outStatus)
332  {
333      LOCK(g_rpc_warmup_mutex);
334      if (outStatus)
335          *outStatus = rpcWarmupStatus;
336      return fRPCInWarmup;
337  }
338  
339  bool IsDeprecatedRPCEnabled(const std::string& method)
340  {
341      const std::vector<std::string> enabled_methods = gArgs.GetArgs("-deprecatedrpc");
342  
343      return find(enabled_methods.begin(), enabled_methods.end(), method) != enabled_methods.end();
344  }
345  
346  UniValue JSONRPCExec(const JSONRPCRequest& jreq, bool catch_errors)
347  {
348      UniValue result;
349      if (catch_errors) {
350          try {
351              result = tableRPC.execute(jreq);
352          } catch (UniValue& e) {
353              return JSONRPCReplyObj(NullUniValue, std::move(e), jreq.id, jreq.m_json_version);
354          } catch (const std::exception& e) {
355              return JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_MISC_ERROR, e.what()), jreq.id, jreq.m_json_version);
356          }
357      } else {
358          result = tableRPC.execute(jreq);
359      }
360  
361      return JSONRPCReplyObj(std::move(result), NullUniValue, jreq.id, jreq.m_json_version);
362  }
363  
364  /**
365   * Process named arguments into a vector of positional arguments, based on the
366   * passed-in specification for the RPC call's arguments.
367   */
368  static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, const std::vector<std::pair<std::string, bool>>& argNames)
369  {
370      JSONRPCRequest out = in;
371      out.params = UniValue(UniValue::VARR);
372      // Build a map of parameters, and remove ones that have been processed, so that we can throw a focused error if
373      // there is an unknown one.
374      const std::vector<std::string>& keys = in.params.getKeys();
375      const std::vector<UniValue>& values = in.params.getValues();
376      std::unordered_map<std::string, const UniValue*> argsIn;
377      for (size_t i=0; i<keys.size(); ++i) {
378          auto [_, inserted] = argsIn.emplace(keys[i], &values[i]);
379          if (!inserted) {
380              throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + keys[i] + " specified multiple times");
381          }
382      }
383      // Process expected parameters. If any parameters were left unspecified in
384      // the request before a parameter that was specified, null values need to be
385      // inserted at the unspecified parameter positions, and the "hole" variable
386      // below tracks the number of null values that need to be inserted.
387      // The "initial_hole_size" variable stores the size of the initial hole,
388      // i.e. how many initial positional arguments were left unspecified. This is
389      // used after the for-loop to add initial positional arguments from the
390      // "args" parameter, if present.
391      int hole = 0;
392      int initial_hole_size = 0;
393      const std::string* initial_param = nullptr;
394      UniValue options{UniValue::VOBJ};
395      for (const auto& [argNamePattern, named_only]: argNames) {
396          std::vector<std::string> vargNames = SplitString(argNamePattern, '|');
397          auto fr = argsIn.end();
398          for (const std::string & argName : vargNames) {
399              fr = argsIn.find(argName);
400              if (fr != argsIn.end()) {
401                  break;
402              }
403          }
404  
405          // Handle named-only parameters by pushing them into a temporary options
406          // object, and then pushing the accumulated options as the next
407          // positional argument.
408          if (named_only) {
409              if (fr != argsIn.end()) {
410                  if (options.exists(fr->first)) {
411                      throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + fr->first + " specified multiple times");
412                  }
413                  options.pushKVEnd(fr->first, *fr->second);
414                  argsIn.erase(fr);
415              }
416              continue;
417          }
418  
419          if (!options.empty() || fr != argsIn.end()) {
420              for (int i = 0; i < hole; ++i) {
421                  // Fill hole between specified parameters with JSON nulls,
422                  // but not at the end (for backwards compatibility with calls
423                  // that act based on number of specified parameters).
424                  out.params.push_back(UniValue());
425              }
426              hole = 0;
427              if (!initial_param) initial_param = &argNamePattern;
428          } else {
429              hole += 1;
430              if (out.params.empty()) initial_hole_size = hole;
431          }
432  
433          // If named input parameter "fr" is present, push it onto out.params. If
434          // options are present, push them onto out.params. If both are present,
435          // throw an error.
436          if (fr != argsIn.end()) {
437              if (!options.empty()) {
438                  throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + fr->first + " conflicts with parameter " + options.getKeys().front());
439              }
440              out.params.push_back(*fr->second);
441              argsIn.erase(fr);
442          }
443          if (!options.empty()) {
444              out.params.push_back(std::move(options));
445              options = UniValue{UniValue::VOBJ};
446          }
447      }
448      // If leftover "args" param was found, use it as a source of positional
449      // arguments and add named arguments after. This is a convenience for
450      // clients that want to pass a combination of named and positional
451      // arguments as described in doc/JSON-RPC-interface.md#parameter-passing
452      auto positional_args{argsIn.extract("args")};
453      if (positional_args && positional_args.mapped()->isArray()) {
454          if (initial_hole_size < (int)positional_args.mapped()->size() && initial_param) {
455              throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + *initial_param + " specified twice both as positional and named argument");
456          }
457          // Assign positional_args to out.params and append named_args after.
458          UniValue named_args{std::move(out.params)};
459          out.params = *positional_args.mapped();
460          for (size_t i{out.params.size()}; i < named_args.size(); ++i) {
461              out.params.push_back(named_args[i]);
462          }
463      }
464      // If there are still arguments in the argsIn map, this is an error.
465      if (!argsIn.empty()) {
466          throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown named parameter " + argsIn.begin()->first);
467      }
468      // Return request with named arguments transformed to positional arguments
469      return out;
470  }
471  
472  static bool ExecuteCommands(const std::vector<const CRPCCommand*>& commands, const JSONRPCRequest& request, UniValue& result)
473  {
474      for (const auto& command : commands) {
475          if (ExecuteCommand(*command, request, result, &command == &commands.back())) {
476              return true;
477          }
478      }
479      return false;
480  }
481  
482  UniValue CRPCTable::execute(const JSONRPCRequest &request) const
483  {
484      // Return immediately if in warmup
485      {
486          LOCK(g_rpc_warmup_mutex);
487          if (fRPCInWarmup)
488              throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus);
489      }
490  
491      // Find method
492      auto it = mapCommands.find(request.strMethod);
493      if (it != mapCommands.end()) {
494          UniValue result;
495          if (ExecuteCommands(it->second, request, result)) {
496              return result;
497          }
498      }
499      throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found");
500  }
501  
502  static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler)
503  {
504      try {
505          RPCCommandExecution execution(request.strMethod);
506          // Execute, convert arguments to array if necessary
507          if (request.params.isObject()) {
508              return command.actor(transformNamedArguments(request, command.argNames), result, last_handler);
509          } else {
510              return command.actor(request, result, last_handler);
511          }
512      } catch (const UniValue::type_error& e) {
513          throw JSONRPCError(RPC_TYPE_ERROR, e.what());
514      } catch (const std::exception& e) {
515          throw JSONRPCError(RPC_MISC_ERROR, e.what());
516      }
517  }
518  
519  std::vector<std::string> CRPCTable::listCommands() const
520  {
521      std::vector<std::string> commandList;
522      commandList.reserve(mapCommands.size());
523      for (const auto& i : mapCommands) commandList.emplace_back(i.first);
524      return commandList;
525  }
526  
527  UniValue CRPCTable::dumpArgMap(const JSONRPCRequest& args_request) const
528  {
529      JSONRPCRequest request = args_request;
530      request.mode = JSONRPCRequest::GET_ARGS;
531  
532      UniValue ret{UniValue::VARR};
533      for (const auto& cmd : mapCommands) {
534          UniValue result;
535          if (ExecuteCommands(cmd.second, request, result)) {
536              for (const auto& values : result.getValues()) {
537                  ret.push_back(values);
538              }
539          }
540      }
541      return ret;
542  }
543  
544  CRPCTable tableRPC;