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;