bitcoin.cpp raw

   1  // Copyright (c) 2025-present The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <bitcoin-build-config.h> // IWYU pragma: keep
   6  
   7  #include <clientversion.h>
   8  #include <common/args.h>
   9  #include <common/license_info.h>
  10  #include <common/system.h>
  11  #include <util/fs.h>
  12  #include <util/exec.h>
  13  #include <util/strencodings.h>
  14  #include <util/translation.h>
  15  
  16  #include <iostream>
  17  #include <string>
  18  #include <tinyformat.h>
  19  #include <vector>
  20  
  21  const TranslateFn G_TRANSLATION_FUN{nullptr};
  22  
  23  static constexpr auto HELP_USAGE = R"(Usage: %s [OPTIONS] COMMAND...
  24  
  25  Options:
  26    -m, --multiprocess     Run multiprocess binaries bitcoin-node, bitcoin-gui.
  27    -M, --monolithic       Run monolithic binaries bitcoind, bitcoin-qt. (Default behavior)
  28    -v, --version          Show version information
  29    -h, --help             Show full help message
  30  
  31  Commands:
  32    gui [ARGS]     Start GUI, equivalent to running 'bitcoin-qt [ARGS]' or 'bitcoin-gui [ARGS]'.
  33    node [ARGS]    Start node, equivalent to running 'bitcoind [ARGS]' or 'bitcoin-node [ARGS]'.
  34    rpc [ARGS]     Call RPC method, equivalent to running 'bitcoin-cli -named [ARGS]'.
  35    wallet [ARGS]  Call wallet command, equivalent to running 'bitcoin-wallet [ARGS]'.
  36    tx [ARGS]      Manipulate hex-encoded transactions, equivalent to running 'bitcoin-tx [ARGS]'.
  37    help           Show full help message.
  38  )";
  39  
  40  static constexpr auto HELP_FULL = R"(
  41  Additional less commonly used commands:
  42    bench [ARGS]      Run bench command, equivalent to running 'bench_bitcoin [ARGS]'.
  43    chainstate [ARGS] Run bitcoin kernel chainstate util, equivalent to running 'bitcoin-chainstate [ARGS]'.
  44    test [ARGS]       Run unit tests, equivalent to running 'test_bitcoin [ARGS]'.
  45    test-gui [ARGS]   Run GUI unit tests, equivalent to running 'test_bitcoin-qt [ARGS]'.
  46  )";
  47  
  48  static constexpr auto HELP_SHORT = R"(
  49  Run '%s help' to see additional commands (e.g. for testing and debugging).
  50  )";
  51  
  52  struct CommandLine {
  53      std::optional<bool> use_multiprocess;
  54      bool show_version{false};
  55      bool show_help{false};
  56      std::string_view command;
  57      std::vector<const char*> args;
  58  };
  59  
  60  CommandLine ParseCommandLine(int argc, char* argv[]);
  61  bool UseMultiprocess(const CommandLine& cmd);
  62  static void ExecCommand(const std::vector<const char*>& args, std::string_view argv0);
  63  
  64  int main(int argc, char* argv[])
  65  {
  66      SetupEnvironment();
  67  
  68      try {
  69          CommandLine cmd{ParseCommandLine(argc, argv)};
  70          if (cmd.show_version) {
  71              tfm::format(std::cout, "%s version %s\n%s", CLIENT_NAME, FormatFullVersion(), FormatParagraph(LicenseInfo()));
  72              return EXIT_SUCCESS;
  73          }
  74  
  75          std::string exe_name{fs::PathToString(fs::PathFromString(argv[0]).filename())};
  76          std::vector<const char*> args;
  77          if (cmd.show_help || cmd.command.empty()) {
  78              tfm::format(std::cout, HELP_USAGE, exe_name);
  79              if (cmd.show_help) {
  80                  tfm::format(std::cout, HELP_FULL);
  81                  return EXIT_SUCCESS;
  82              } else {
  83                  tfm::format(std::cout, HELP_SHORT, exe_name);
  84                  return EXIT_FAILURE;
  85              }
  86          } else if (cmd.command == "gui") {
  87              args.emplace_back(UseMultiprocess(cmd) ? "bitcoin-gui" : "bitcoin-qt");
  88          } else if (cmd.command == "node") {
  89              args.emplace_back(UseMultiprocess(cmd) ? "bitcoin-node" : "bitcoind");
  90          } else if (cmd.command == "rpc") {
  91              args.emplace_back("bitcoin-cli");
  92              // Since "bitcoin rpc" is a new interface that doesn't need to be
  93              // backward compatible, enable -named by default so it is convenient
  94              // for callers to use a mix of named and unnamed parameters. Callers
  95              // can override this by specifying -nonamed, but it handles parameters
  96              // that contain '=' characters, so -nonamed should rarely be needed.
  97              args.emplace_back("-named");
  98          } else if (cmd.command == "wallet") {
  99              args.emplace_back("bitcoin-wallet");
 100          } else if (cmd.command == "tx") {
 101              args.emplace_back("bitcoin-tx");
 102          } else if (cmd.command == "bench") {
 103              args.emplace_back("bench_bitcoin");
 104          } else if (cmd.command == "chainstate") {
 105              args.emplace_back("bitcoin-chainstate");
 106          } else if (cmd.command == "test") {
 107              args.emplace_back("test_bitcoin");
 108          } else if (cmd.command == "test-gui") {
 109              args.emplace_back("test_bitcoin-qt");
 110          } else if (cmd.command == "util") {
 111              args.emplace_back("bitcoin-util");
 112          } else {
 113              throw std::runtime_error(strprintf("Unrecognized command: '%s'", cmd.command));
 114          }
 115          if (!args.empty()) {
 116              args.insert(args.end(), cmd.args.begin(), cmd.args.end());
 117              ExecCommand(args, argv[0]);
 118          }
 119      } catch (const std::exception& e) {
 120          tfm::format(std::cerr, "Error: %s\nTry '%s --help' for more information.\n", e.what(), argv[0]);
 121          return EXIT_FAILURE;
 122      }
 123      return EXIT_SUCCESS;
 124  }
 125  
 126  CommandLine ParseCommandLine(int argc, char* argv[])
 127  {
 128      CommandLine cmd;
 129      cmd.args.reserve(argc);
 130      for (int i = 1; i < argc; ++i) {
 131          std::string_view arg = argv[i];
 132          if (!cmd.command.empty()) {
 133              cmd.args.emplace_back(argv[i]);
 134          } else if (arg == "-m" || arg == "--multiprocess") {
 135              cmd.use_multiprocess = true;
 136          } else if (arg == "-M" || arg == "--monolithic") {
 137              cmd.use_multiprocess = false;
 138          } else if (arg == "-v" || arg == "--version") {
 139              cmd.show_version = true;
 140          } else if (arg == "-h" || arg == "--help" || arg == "help") {
 141              cmd.show_help = true;
 142          } else if (arg.starts_with("-")) {
 143              throw std::runtime_error(strprintf("Unknown option: %s", arg));
 144          } else if (!arg.empty()) {
 145              cmd.command = arg;
 146          }
 147      }
 148      return cmd;
 149  }
 150  
 151  bool UseMultiprocess(const CommandLine& cmd)
 152  {
 153      // If -m or -M options were explicitly specified, there is no need to
 154      // further parse arguments to determine which to use.
 155      if (cmd.use_multiprocess) return *cmd.use_multiprocess;
 156  
 157      ArgsManager args;
 158      args.SetDefaultFlags(ArgsManager::ALLOW_ANY);
 159      std::string error_message;
 160      auto argv{cmd.args};
 161      argv.insert(argv.begin(), nullptr);
 162      if (!args.ParseParameters(argv.size(), argv.data(), error_message)) {
 163          tfm::format(std::cerr, "Warning: failed to parse subcommand command line options: %s\n", error_message);
 164      }
 165      if (!args.ReadConfigFiles(error_message, true)) {
 166          tfm::format(std::cerr, "Warning: failed to parse subcommand config: %s\n", error_message);
 167      }
 168      args.SelectConfigNetwork(args.GetChainTypeString());
 169  
 170      // If any -ipc* options are set these need to be processed by a
 171      // multiprocess-capable binary.
 172      return args.IsArgSet("-ipcbind") || args.IsArgSet("-ipcconnect") || args.IsArgSet("-ipcfd");
 173  }
 174  
 175  //! Execute the specified bitcoind, bitcoin-qt or other command line in `args`
 176  //! using src, bin and libexec directory paths relative to this executable, where
 177  //! the path to this executable is specified in `wrapper_argv0`.
 178  //!
 179  //! @param args Command line arguments to execute, where first argument should
 180  //!             be a relative path to a bitcoind, bitcoin-qt or other executable
 181  //!             that will be located on the PATH or relative to wrapper_argv0.
 182  //!
 183  //! @param wrapper_argv0 String containing first command line argument passed to
 184  //!                      main() to run the current executable. This is used to
 185  //!                      help determine the path to the current executable and
 186  //!                      how to look for new executables.
 187  //
 188  //! @note This function doesn't currently print anything but can be debugged
 189  //! from the command line using strace or dtrace like:
 190  //!
 191  //!     strace -e trace=execve -s 10000 build/bin/bitcoin ...
 192  //!     dtrace -n 'proc:::exec-success  /pid == $target/ { trace(curpsinfo->pr_psargs); }' -c ...
 193  static void ExecCommand(const std::vector<const char*>& args, std::string_view wrapper_argv0)
 194  {
 195      // Construct argument string for execvp
 196      std::vector<const char*> exec_args{args};
 197      exec_args.emplace_back(nullptr);
 198  
 199      // Try to call ExecVp with given exe path.
 200      auto try_exec = [&](fs::path exe_path, bool allow_notfound = true) {
 201          std::string exe_path_str{fs::PathToString(exe_path)};
 202          exec_args[0] = exe_path_str.c_str();
 203          if (util::ExecVp(exec_args[0], (char*const*)exec_args.data()) == -1) {
 204              if (allow_notfound && errno == ENOENT) return false;
 205              throw std::system_error(errno, std::system_category(), strprintf("execvp failed to execute '%s'", exec_args[0]));
 206          }
 207          throw std::runtime_error("execvp returned unexpectedly");
 208      };
 209  
 210      // Get the wrapper executable path.
 211      const fs::path wrapper_path{util::GetExePath(wrapper_argv0)};
 212  
 213      // Try to resolve any symlinks and figure out the directory containing the wrapper executable.
 214      std::error_code ec;
 215      auto wrapper_dir{fs::weakly_canonical(wrapper_path, ec)};
 216      if (wrapper_dir.empty()) wrapper_dir = wrapper_path; // Restore previous path if weakly_canonical failed.
 217      wrapper_dir = wrapper_dir.parent_path();
 218  
 219      // Get path of the executable to be invoked.
 220      const fs::path arg0{fs::PathFromString(args[0])};
 221  
 222      // Decide whether to fall back to the operating system to search for the
 223      // specified executable. Avoid doing this if it looks like the wrapper
 224      // executable was invoked by path, rather than by search, to avoid
 225      // unintentionally launching system executables in a local build.
 226      // (https://github.com/bitcoin/bitcoin/pull/31375#discussion_r1861814807)
 227      const bool fallback_os_search{!fs::PathFromString(std::string{wrapper_argv0}).has_parent_path()};
 228  
 229      // If wrapper is installed in a bin/ directory, look for target executable
 230      // in libexec/
 231      (wrapper_dir.filename() == "bin" && try_exec(wrapper_dir.parent_path() / "libexec" / arg0.filename())) ||
 232  #ifdef WIN32
 233      // Otherwise check the "daemon" subdirectory in a windows install.
 234      (!wrapper_dir.empty() && try_exec(wrapper_dir / "daemon" / arg0.filename())) ||
 235  #endif
 236      // Otherwise look for target executable next to current wrapper
 237      (!wrapper_dir.empty() && try_exec(wrapper_dir / arg0.filename(), fallback_os_search)) ||
 238      // Otherwise just look on the system path.
 239      (fallback_os_search && try_exec(arg0.filename(), false));
 240  }
 241