server.cpp raw

   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 RPCMethod help()
 120  {
 121      return RPCMethod{
 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 RPCMethod& 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 RPCMethod stop()
 146  {
 147      static const std::string RESULT{CLIENT_NAME " stopping"};
 148      return RPCMethod{
 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 RPCMethod& 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 RPCMethod uptime()
 173  {
 174      return RPCMethod{
 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 RPCMethod& self, const JSONRPCRequest& request) -> UniValue
 186  {
 187      return TicksSeconds(GetUptime());
 188  }
 189      };
 190  }
 191  
 192  static RPCMethod getrpcinfo()
 193  {
 194      return RPCMethod{
 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 RPCMethod& 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;
 545