Add bitcoin wrapper executable (interfaces)

https://github.com/bitcoin/bitcoin/pull/31375

Host: ryanofsky  -  PR author: ryanofsky

Notes

Motivation & context

  • For years Bitcoin Core has shipped five separate user‑facing binaries. The upcoming multiprocess work would add at least two more (bitcoin‑node, bitcoin‑gui). Reviewers feared an explosion of filenames and user confusion.
  • The PR introduces a single command‑line front‑end called bitcoin that does no consensus or wallet work itself – it simply chooses and exec()’s the appropriate helper binary:

    bitcoin sub‑command Traditional binary Multiprocess binary (-m)
    bitcoin gui … bitcoin‑qt bitcoin‑gui
    bitcoin daemon … bitcoind bitcoin‑node
    bitcoin rpc … bitcoin‑cli -named … bitcoin‑cli -named …
    bitcoin wallet … bitcoin‑wallet bitcoin-wallet
    bitcoin tx … bitcoin‑tx bitcoin-tx

    The bitcoin wrapper therefore accomplishes the “side‑binaries + unified entry point” idea discussed in issue #30983.

New util helpers

  • util::ExecVp() – thin, cross‑platform execvp replacement.
    • POSIX: directly forwards to execvp.
    • Windows: builds a quoted & escaped command line that CommandLineToArgvW in the child process will parse identically to POSIX argv rules.
    • Escaping rules follow the MSVCRT specification: backslashes are doubled only when they precede a quote, and every internal quote is back‑slash‑escaped.
  • util::GetExePath() – attempts to resolve argv[0] into the executable file path.
    • On Unix: uses either the literal argv[0] (if it contains a slash) or searches each element of $PATH until a regular file is found.
    • On Windows: uses GetModuleFileNameW(nullptr, …).

Wrapper lookup logic (ExecCommand)

  1. Determine the directory of the wrapper itself (resolves symlinks).
  2. Try possible candidate paths for the target binary, in descending priority:
    • libexec dir – ${prefix}/libexec/<target> if wrapper is in ${prefix}/bin/
    • Windows installer “daemon” sub‑dir ${wrapper_dir}/daemon/<target>
    • Sibling${wrapper_dir}/<target>
    • Finally, rely on the system PATH only if the wrapper itself was invoked via PATH search (mitigates accidentally running an old system bitcoind while testing a local build).
  3. Call util::ExecVp() with each candidate, moving onto the next candidate if it returns ENOENT (“No such file or directory”) and raising an exception if a different error is returned or if there is no next candidate.

Build‑system & test changes

  • CMake option BUILD_BITCOIN_BIN (ON by default) builds/installs the wrapper.
  • Functional test framework understands BITCOIN_CMD="bitcoin -m" so the entire suite can be driven through the new CLI.
  • CI jobs for the multiprocess build now export that variable.
  • Static‑analysis suppression: the wrapper intentionally contains no FORTIFY functions; security-check.py is taught to ignore it.

Documentation updates

Numerous docs now mention that bitcoin rpc, bitcoin daemon, etc. are synonyms for the traditional commands, improving discoverability for new users while remaining fully backwards‑compatible.


Questions

  1. Did you review the PR? Concept ACK, approach ACK, tested ACK, or NACK? What was your review approach?

  2. Review approach – did you test the wrapper? Did you try both monolithic (bitcoin daemon) and multiprocess (bitcoin -m daemon) modes? (requires -DENABLE_IPC=ON cmake option). Attempt to run one of the strace or dtrace tracing commands suggested in bitcoin.cpp? Any cross‑platform checks?

  3. From issue #30983, four packaging strategies were listed. Which specific drawbacks of the “side‑binaries” approach does this PR address?

  4. In util::ExecVp() (Windows branch) why is a second std::vector escaped_args needed instead of modifying argv in‑place?

  5. Walk through the escaping algorithm in util::ExecVp for the argument C:\Program Files\Bitcoin\bitcoin-qt. What exact string is passed to _execvp()?

  6. GetExePath() does not use readlink("/proc/self/exe") on Linux even though it would be more direct. What advantages does the current implementation have? What corner cases might it miss?

  7. In ExecCommand, explain the purpose of the fallback_os_search Boolean. Under what circumstances is it better to avoid letting the OS search for the binary on the PATH?

  8. The wrapper searches ${prefix}/libexec only when it detects that it is running from an installed bin/ directory. Why not always search libexec?

  9. The functional test layer now conditionally prepends bitcoin -m to every command. How does this interact with backwards‑compatibility testing where older releases are run in the same test suite?

  10. The PR adds an exemption in security-check.py because the wrapper contains no fortified glibc calls. Why does it not contain them, and would adding a trivial printf to bitcoin.cpp break reproducible builds under the current rules?

  11. Discuss an alternative design: linking a static table of sub‑commands to absolute paths at build time instead of computing them at run time. What trade‑offs (deployment, relocatability, reproducibility) influenced the chosen design?

  12. Suppose a user installs only bitcoin (wrapper) and forgets to install bitcoin-cli. Describe the failure mode when they run bitcoin rpc getblockcount. Would it be better for the wrapper to pre‑check the availability of the target binary?

  13. (Forward‑looking) Once bitcoin-gui actually spawns bitcoin-node automatically (after #10102 lands), what additional command‑line options or UX changes might the wrapper need?

  14. Typing bitcoin --version prints wrapper metadata, not bitcoind’s or bitcoin‑qt’s. Is that the right UX? Propose a mechanism for the wrapper to forward --version and --help to the underlying sub‑command when one is specified (e.g. bitcoin --version daemon).

  15. The wrapper is agnostic to options such as -ipcbind passed down to bitcoin‑node. Should the wrapper eventually enforce a policy (e.g. refuse to forward -ipcconnect unless -m is given)? What might go wrong if a user mixes monolithic binaries with IPC flags?

  16. BITCOIN_CMD="bitcoin -m" is parsed with shlex; spaces inside quotes are preserved. Should the framework use an explicit list instead of shell parsing?

  17. Would it ever make sense to ship only the wrapper in bin/ and relocate all other executables to libexec/ to tidy PATH?