This commit is contained in:
Ryan Ofsky 2025-04-29 11:49:19 +02:00 committed by GitHub
commit 008cd61fe6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 409 additions and 19 deletions

View file

@ -90,6 +90,7 @@ endif()
#=============================
include(CMakeDependentOption)
# When adding a new option, end the <help_text> with a full stop for consistency.
option(BUILD_BITCOIN_BIN "Build bitcoin executable." ON)
option(BUILD_DAEMON "Build bitcoind executable." ON)
option(BUILD_GUI "Build bitcoin-qt executable." OFF)
option(BUILD_CLI "Build bitcoin-cli executable." ON)
@ -214,6 +215,7 @@ target_link_libraries(core_interface INTERFACE
if(BUILD_FOR_FUZZING)
message(WARNING "BUILD_FOR_FUZZING=ON will disable all other targets and force BUILD_FUZZ_BINARY=ON.")
set(BUILD_BITCOIN_BIN OFF)
set(BUILD_DAEMON OFF)
set(BUILD_CLI OFF)
set(BUILD_TX OFF)
@ -655,6 +657,7 @@ message("\n")
message("Configure summary")
message("=================")
message("Executables:")
message(" bitcoin ............................. ${BUILD_BITCOIN_BIN}")
message(" bitcoind ............................ ${BUILD_DAEMON}")
if(BUILD_DAEMON AND ENABLE_IPC)
set(bitcoin_daemon_status ON)

View file

@ -20,4 +20,4 @@ export BITCOIN_CONFIG="\
-DCMAKE_CXX_COMPILER='clang++;-m32' \
-DAPPEND_CPPFLAGS='-DBOOST_MULTI_INDEX_ENABLE_SAFE_MODE' \
"
export BITCOIND=bitcoin-node # Used in functional tests
export BITCOIN_CMD="bitcoin -m" # Used in functional tests

View file

@ -7,6 +7,7 @@ function(generate_setup_nsi)
set(abs_top_builddir ${PROJECT_BINARY_DIR})
set(CLIENT_URL ${PROJECT_HOMEPAGE_URL})
set(CLIENT_TARNAME "bitcoin")
set(BITCOIN_WRAPPER_NAME "bitcoin")
set(BITCOIN_GUI_NAME "bitcoin-qt")
set(BITCOIN_DAEMON_NAME "bitcoind")
set(BITCOIN_CLI_NAME "bitcoin-cli")

View file

@ -23,7 +23,7 @@ function(add_maintenance_targets)
return()
endif()
foreach(target IN ITEMS bitcoind bitcoin-qt bitcoin-cli bitcoin-tx bitcoin-util bitcoin-wallet test_bitcoin bench_bitcoin)
foreach(target IN ITEMS bitcoin bitcoind bitcoin-qt bitcoin-cli bitcoin-tx bitcoin-util bitcoin-wallet test_bitcoin bench_bitcoin)
if(TARGET ${target})
list(APPEND executables $<TARGET_FILE:${target}>)
endif()
@ -43,7 +43,7 @@ function(add_maintenance_targets)
endfunction()
function(add_windows_deploy_target)
if(MINGW AND TARGET bitcoin-qt AND TARGET bitcoind AND TARGET bitcoin-cli AND TARGET bitcoin-tx AND TARGET bitcoin-wallet AND TARGET bitcoin-util AND TARGET test_bitcoin)
if(MINGW AND TARGET bitcoin AND TARGET bitcoin-qt AND TARGET bitcoind AND TARGET bitcoin-cli AND TARGET bitcoin-tx AND TARGET bitcoin-wallet AND TARGET bitcoin-util AND TARGET test_bitcoin)
find_program(MAKENSIS_EXECUTABLE makensis)
if(NOT MAKENSIS_EXECUTABLE)
add_custom_target(deploy
@ -59,6 +59,7 @@ function(add_windows_deploy_target)
add_custom_command(
OUTPUT ${PROJECT_BINARY_DIR}/bitcoin-win64-setup.exe
COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/release
COMMAND ${CMAKE_STRIP} $<TARGET_FILE:bitcoin> -o ${PROJECT_BINARY_DIR}/release/$<TARGET_FILE_NAME:bitcoin>
COMMAND ${CMAKE_STRIP} $<TARGET_FILE:bitcoin-qt> -o ${PROJECT_BINARY_DIR}/release/$<TARGET_FILE_NAME:bitcoin-qt>
COMMAND ${CMAKE_STRIP} $<TARGET_FILE:bitcoind> -o ${PROJECT_BINARY_DIR}/release/$<TARGET_FILE_NAME:bitcoind>
COMMAND ${CMAKE_STRIP} $<TARGET_FILE:bitcoin-cli> -o ${PROJECT_BINARY_DIR}/release/$<TARGET_FILE_NAME:bitcoin-cli>

View file

@ -122,6 +122,9 @@ def check_ELF_FORTIFY(binary) -> bool:
# bitcoin-util does not currently contain any fortified functions
if 'Bitcoin Core bitcoin-util utility version ' in binary.strings:
return True
# bitcoin wrapper does not currently contain any fortified functions
if '--monolithic' in binary.strings:
return True
chk_funcs = set()

View file

@ -62,6 +62,8 @@ bitcoin-cli -named createwallet wallet_name=mywallet load_on_startup=true
bitcoin-cli -named createwallet mywallet load_on_startup=true
```
`bitcoin rpc` can also be substituted for `bitcoin-cli -named`.
## Versioning
The RPC interface might change from one major version of Bitcoin Core to the

View file

@ -17,6 +17,9 @@ Unpack the files into a directory and run:
- `bin/bitcoin-qt` (GUI) or
- `bin/bitcoind` (headless)
- `bin/bitcoin` (command line interface)
The `bitcoin` command supports subcommands like `bitcoin gui`, `bitcoin daemon`, and `bitcoin rpc` exposing different functionality. Subcommands can be listed with `bitcoin help`.
### Windows

View file

@ -215,6 +215,10 @@ cmake --build build --target deploy
Bitcoin Core should now be available at `./build/bin/bitcoind`.
If you compiled support for the GUI, it should be available at `./build/bin/bitcoin-qt`.
There is also a multifunction command line interface at `./build/bin/bitcoin`
supporting subcommands like `bitcoin daemon`, `bitcoin gui`, `bitcoin rpc`, and
others that can be listed with `bitcoin help`.
The first time you run `bitcoind` or `bitcoin-qt`, it will start downloading the blockchain.
This process could take many hours, or even days on slower than average systems.

View file

@ -203,5 +203,6 @@ This example lists the steps necessary to setup and build a command line only di
cmake --build build
ctest --test-dir build
./build/bin/bitcoind
./build/bin/bitcoin help
If you intend to work with legacy Berkeley DB wallets, see [Berkeley DB](#berkeley-db) section.

View file

@ -113,3 +113,5 @@ To see which CJDNS peers your node is connected to, use `bitcoin-cli -netinfo 4`
or the `getpeerinfo` RPC (i.e. `bitcoin-cli getpeerinfo`).
You can use the `getnodeaddresses` RPC to fetch a number of CJDNS peers known to your node; run `bitcoin-cli help getnodeaddresses` for details.
`bitcoin rpc` can also be substituted for `bitcoin-cli`.

View file

@ -14,6 +14,8 @@ Start Bitcoin Core:
$ bitcoind -signer=../HWI/hwi.py
```
`bitcoin daemon` can also be substituted for `bitcoind`.
### Device setup
Follow the hardware manufacturers instructions for the initial device setup, as well as their instructions for creating a backup. Alternatively, for some devices, you can use the `setup`, `restore` and `backup` commands provided by [HWI](https://github.com/bitcoin-core/HWI).
@ -40,6 +42,8 @@ Create a wallet, this automatically imports the public keys:
$ bitcoin-cli createwallet "hww" true true "" true true true
```
`bitcoin rpc` can also be substituted for `bitcoin-cli`.
### Verify an address
Display an address on the device:

View file

@ -15,6 +15,8 @@ The following command, for example, creates a descriptor wallet. More informatio
$ bitcoin-cli createwallet "wallet-01"
```
`bitcoin rpc` can also be substituted for `bitcoin-cli`.
By default, wallets are created in the `wallets` folder of the data directory, which varies by operating system, as shown below. The user can change the default by using the `-datadir` or `-walletdir` initialization parameters.
| Operating System | Default wallet directory |

View file

@ -25,8 +25,8 @@ make -C depends NO_QT=1 MULTIPROCESS=1
HOST_PLATFORM="x86_64-pc-linux-gnu"
cmake -B build --toolchain=depends/$HOST_PLATFORM/toolchain.cmake
cmake --build build
build/bin/bitcoin-node -regtest -printtoconsole -debug=ipc
BITCOIND=$(pwd)/build/bin/bitcoin-node build/test/functional/test_runner.py
build/bin/bitcoin -m daemon -regtest -printtoconsole -debug=ipc
BITCOIN_CMD="bitcoin -m" build/test/functional/test_runner.py
```
The `cmake` build will pick up settings and library locations from the depends directory, so there is no need to pass `-DENABLE_IPC=ON` as a separate flag when using the depends system (it's controlled by the `MULTIPROCESS=1` option).
@ -41,6 +41,11 @@ By default when `-DENABLE_IPC=ON` is enabled, the libmultiprocess sources at [..
## Usage
`bitcoin-node` is a drop-in replacement for `bitcoind`, and `bitcoin-gui` is a drop-in replacement for `bitcoin-qt`, and there are no differences in use or external behavior between the new and old executables. But internally after [#10102](https://github.com/bitcoin/bitcoin/pull/10102), `bitcoin-gui` will spawn a `bitcoin-node` process to run P2P and RPC code, communicating with it across a socket pair, and `bitcoin-node` will spawn `bitcoin-wallet` to run wallet code, also communicating over a socket pair. This will let node, wallet, and GUI code run in separate address spaces for better isolation, and allow future improvements like being able to start and stop components independently on different machines and environments.
[#19460](https://github.com/bitcoin/bitcoin/pull/19460) also adds a new `bitcoin-node` `-ipcbind` option and a `bitcoind-wallet` `-ipcconnect` option to allow new wallet processes to connect to an existing node process.
And [#19461](https://github.com/bitcoin/bitcoin/pull/19461) adds a new `bitcoin-gui` `-ipcconnect` option to allow new GUI processes to connect to an existing node process.
Recommended way to use multiprocess binaries is to invoke `bitcoin` CLI like `bitcoin -m daemon -debug=ipc` or `bitcoin -m gui -printtoconsole -debug=ipc`.
When the `-m` (`--multiprocess`) option is used the `bitcoin` command will execute multiprocess binaries instead of monolithic ones (`bitcoin-node` instead of `bitcoind`, and `bitcoin-gui` instead of `bitcoin-qt`). The multiprocess binaries can also be invoked directly, but this is not recommended as they may change or be renamed in the future, and they are not installed in the PATH.
The multiprocess binaries currently function the same as the monolithic binaries, except they support an `-ipcbind` option.
In the future, after [#10102](https://github.com/bitcoin/bitcoin/pull/10102) they will have other differences. Specficially `bitcoin-gui` will spawn a `bitcoin-node` process to run P2P and RPC code, communicating with it across a socket pair, and `bitcoin-node` will spawn `bitcoin-wallet` to run wallet code, also communicating over a socket pair. This will let node, wallet, and GUI code run in separate address spaces for better isolation, and allow future improvements like being able to start and stop components independently on different machines and environments. [#19460](https://github.com/bitcoin/bitcoin/pull/19460) also adds a new `bitcoind-wallet -ipcconnect` option to allow new wallet processes to connect to an existing node process.
And [#19461](https://github.com/bitcoin/bitcoin/pull/19461) adds a new `bitcoin-gui -ipcconnect` option to allow new GUI processes to connect to an existing node process.

View file

@ -31,6 +31,8 @@ do
done
```
`bitcoin rpc` can also be substituted for `bitcoin-cli`.
Extract the xpub of each wallet. To do this, the `listdescriptors` RPC is used. By default, Bitcoin Core single-sig wallets are created using path `m/44'/1'/0'` for PKH, `m/84'/1'/0'` for WPKH, `m/49'/1'/0'` for P2WPKH-nested-in-P2SH and `m/86'/1'/0'` for P2TR based accounts. Each of them uses the chain 0 for external addresses and chain 1 for internal ones, as shown in the example below.
```

View file

@ -34,6 +34,8 @@ We are going to first create an `offline_wallet` on the offline host. We will th
}
```
`bitcoin rpc` can also be substituted for `bitcoin-cli`.
> [!NOTE]
> The use of a passphrase is crucial to encrypt the wallet.dat file. This encryption ensures that even if an unauthorized individual gains access to the offline host, they won't be able to access the wallet's contents. Further details about securing your wallet can be found in [Managing the Wallet](https://github.com/bitcoin/bitcoin/blob/master/doc/managing-wallets.md#12-encrypting-the-wallet)

View file

@ -103,7 +103,7 @@ Alice, Bob, and Carol want to create a 2-of-3 multisig address. They're all usin
Bitcoin Core. We assume their wallets only contain the multisig funds. In case
they also have a personal wallet, this can be accomplished through the
multiwallet feature - possibly resulting in a need to add `-rpcwallet=name` to
the command line in case `bitcoin-cli` is used.
the command line in case `bitcoin-cli` or `bitcoin rpc` are used.
Setup:
- All three call `getnewaddress` to create a new address; call these addresses

View file

@ -0,0 +1,11 @@
New command line interface
--------------------------
A new `bitcoin` command line tool has been added to make features more
discoverable and convenient to use. The `bitcoin` tool just calls other
executables and does not implement any functionality on its own. Specifically
`bitcoin daemon` is a synonym for `bitcoind`, `bitcoin gui` is a synonym for
`bitcoin-qt`, and `bitcoin rpc` is a synonym for `bitcoin-cli -named`. Other
commands and options can be listed with `bitcoin help`. The new tool does not
replace other tools, so all existing commands should continue working and there
are no plans to deprecate them.

View file

@ -27,6 +27,8 @@ e.g. for `-onlynet=onion`.
You can use the `getnodeaddresses` RPC to fetch a number of onion peers known to your node; run `bitcoin-cli help getnodeaddresses` for details.
`bitcoin rpc` can also be substituted for `bitcoin-cli`.
## 1. Run Bitcoin Core behind a Tor proxy
The first step is running Bitcoin Core behind a Tor proxy. This will already anonymize all
@ -64,6 +66,8 @@ In a typical situation, this suffices to run behind a Tor proxy:
./bitcoind -proxy=127.0.0.1:9050
`bitcoin daemon` or `bitcoin gui` can also be substituted for `bitcoind`.
## 2. Automatically create a Bitcoin Core onion service
Bitcoin Core makes use of Tor's control socket API to create and destroy

View file

@ -87,6 +87,8 @@ For instance:
-zmqpubrawtx=ipc:///tmp/bitcoind.tx.raw \
-zmqpubhashtxhwm=10000
`bitcoin daemon` or `bitcoin gui` can also be substituted for `bitcoind`.
Notification types correspond to message topics (details in next section). For instance,
for the notification `-zmqpubhashtx` the topic is `hashtx`. These options can also be
provided in bitcoin.conf.

View file

@ -73,6 +73,7 @@ Section -Main SEC0000
SetOutPath $INSTDIR
SetOverwrite on
File @abs_top_builddir@/release/@BITCOIN_GUI_NAME@@EXEEXT@
File @abs_top_builddir@/release/@BITCOIN_WRAPPER_NAME@@EXEEXT@
File /oname=COPYING.txt @abs_top_srcdir@/COPYING
File /oname=readme.txt @abs_top_srcdir@/doc/README_windows.txt
File @abs_top_srcdir@/share/examples/bitcoin.conf

View file

@ -331,6 +331,12 @@ target_link_libraries(bitcoin_node
$<TARGET_NAME_IF_EXISTS:USDT::headers>
)
# Bitcoin wrapper executable that can call other executables.
if(BUILD_BITCOIN_BIN)
add_executable(bitcoin bitcoin.cpp)
target_link_libraries(bitcoin core_interface bitcoin_util)
install_binary_component(bitcoin)
endif()
# Bitcoin Core bitcoind.
if(BUILD_DAEMON)

196
src/bitcoin.cpp Normal file
View file

@ -0,0 +1,196 @@
// Copyright (c) 2024 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <bitcoin-build-config.h> // IWYU pragma: keep
#include <clientversion.h>
#include <util/fs.h>
#include <util/exec.h>
#include <util/strencodings.h>
#include <util/translation.h>
#include <iostream>
#include <string>
#include <tinyformat.h>
#include <vector>
const TranslateFn G_TRANSLATION_FUN{nullptr};
static constexpr auto HELP_USAGE = R"(Usage: %s [OPTIONS] COMMAND...
Options:
-m, --multiprocess Run multiprocess binaries bitcoin-node, bitcoin-gui.
-M, --monolithic Run monolithic binaries bitcoind, bitcoin-qt. (Default behavior)
-v, --version Show version information
-h, --help Show this help message
Commands:
gui [ARGS] Start GUI, equivalent to running 'bitcoin-qt [ARGS]' or 'bitcoin-gui [ARGS]'.
daemon [ARGS] Start daemon, equivalent to running 'bitcoind [ARGS]' or 'bitcoin-node [ARGS]'.
rpc [ARGS] Call RPC method, equivalent to running 'bitcoin-cli -named [ARGS]'.
wallet [ARGS] Call wallet command, equivalent to running 'bitcoin-wallet [ARGS]'.
tx [ARGS] Manipulate hex-encoded transactions, equivalent to running 'bitcoin-tx [ARGS]'.
help [-a] Show this help message. Include -a or --all to show additional internal commands.
)";
static constexpr auto HELP_INTERNAL = R"(
Additional internal commands:
bench [ARGS] Run bench command, equivalent to running 'bench_bitcoin [ARGS]'.
chainstate [ARGS] Run bitcoin kernel chainstate util, equivalent to running 'bitcoin-chainstate [ARGS]'.
test [ARGS] Run unit tests, equivalent to running 'test_bitcoin [ARGS]'.
test-gui [ARGS] Run GUI unit tests, equivalent to running 'test_bitcoin-qt [ARGS]'.
)";
struct CommandLine {
bool use_multiprocess{false};
bool show_version{false};
bool show_help{false};
bool show_help_all{false};
std::string_view command;
std::vector<const char*> args;
};
CommandLine ParseCommandLine(int argc, char* argv[]);
static void ExecCommand(const std::vector<const char*>& args, std::string_view argv0);
int main(int argc, char* argv[])
{
try {
CommandLine cmd{ParseCommandLine(argc, argv)};
if (cmd.show_version) {
tfm::format(std::cout, "%s version %s\n%s", CLIENT_NAME, FormatFullVersion(), FormatParagraph(LicenseInfo()));
return EXIT_SUCCESS;
}
std::vector<const char*> args;
if (cmd.show_help || cmd.command.empty()) {
tfm::format(std::cout, HELP_USAGE, argv[0]);
if (cmd.show_help_all) tfm::format(std::cout, HELP_INTERNAL);
return cmd.show_help ? EXIT_SUCCESS : EXIT_FAILURE;
} else if (cmd.command == "gui") {
args.emplace_back(cmd.use_multiprocess ? "qt/bitcoin-gui" : "qt/bitcoin-qt");
} else if (cmd.command == "daemon") {
args.emplace_back(cmd.use_multiprocess ? "bitcoin-node" : "bitcoind");
} else if (cmd.command == "rpc") {
args.emplace_back("bitcoin-cli");
args.emplace_back("-named");
} else if (cmd.command == "wallet") {
args.emplace_back("bitcoin-wallet");
} else if (cmd.command == "tx") {
args.emplace_back("bitcoin-tx");
} else if (cmd.command == "bench") {
args.emplace_back("bench/bench_bitcoin");
} else if (cmd.command == "chainstate") {
args.emplace_back("bitcoin-chainstate");
} else if (cmd.command == "test") {
args.emplace_back("test/test_bitcoin");
} else if (cmd.command == "test-gui") {
args.emplace_back("qt/test/test_bitcoin-qt");
} else if (cmd.command == "util") {
args.emplace_back("bitcoin-util");
} else {
throw std::runtime_error(strprintf("Unrecognized command: '%s'", cmd.command));
}
if (!args.empty()) {
args.insert(args.end(), cmd.args.begin(), cmd.args.end());
ExecCommand(args, argv[0]);
}
} catch (const std::exception& e) {
tfm::format(std::cerr, "Error: %s\nTry '%s --help' for more information.\n", e.what(), argv[0]);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
CommandLine ParseCommandLine(int argc, char* argv[])
{
CommandLine cmd;
cmd.args.reserve(argc);
for (int i = 1; i < argc; ++i) {
std::string_view arg = argv[i];
if (!cmd.command.empty()) {
cmd.args.emplace_back(argv[i]);
} else if (arg == "-m" || arg == "--multiprocess") {
cmd.use_multiprocess = true;
} else if (arg == "-M" || arg == "--monolithic") {
cmd.use_multiprocess = false;
} else if (arg == "-v" || arg == "--version") {
cmd.show_version = true;
} else if (arg == "-h" || arg == "--help" || arg == "help") {
cmd.show_help = true;
} else if (cmd.show_help && (arg == "-a" || arg == "--all")) {
cmd.show_help_all = true;
} else if (arg.starts_with("-")) {
throw std::runtime_error(strprintf("Unknown option: %s", arg));
} else if (!arg.empty()) {
cmd.command = arg;
}
}
return cmd;
}
//! Execute the specified bitcoind, bitcoin-qt or other command line in `args`
//! using src, bin and libexec directory paths relative to this executable, where
//! the path to this executable is specified in `wrapper_argv0`.
//!
//! @param args Command line arguments to execute, where first argument should
//! be a relative path to a bitcoind, bitcoin-qt or other executable
//! that will be located on the PATH or relative to wrapper_argv0.
//!
//! @param wrapper_argv0 String containing first command line argument passed to
//! main() to run the current executable. This is used to
//! help determine the path to the current executable and
//! how to look for new executables.
//
//! @note This function doesn't currently print anything but can be debugged
//! from the command line using strace or dtrace like:
//!
//! strace -e trace=execve -s 10000 build/src/bitcoin ...
//! dtrace -n 'proc:::exec-success /pid == $target/ { trace(curpsinfo->pr_psargs); }' -c ...
static void ExecCommand(const std::vector<const char*>& args, std::string_view wrapper_argv0)
{
// Construct argument string for execvp
std::vector<const char*> exec_args{args};
exec_args.emplace_back(nullptr);
// Try to call ExecVp with given exe path.
auto try_exec = [&](fs::path exe_path, bool allow_notfound = true) {
std::string exe_path_str{fs::PathToString(exe_path)};
exec_args[0] = exe_path_str.c_str();
if (util::ExecVp(exec_args[0], (char*const*)exec_args.data()) == -1) {
if (allow_notfound && errno == ENOENT) return false;
throw std::system_error(errno, std::system_category(), strprintf("execvp failed to execute '%s'", exec_args[0]));
}
return true; // In practice, this line should not be reached if execvp succeeds
};
// Get the wrapper executable path.
const fs::path wrapper_path{util::GetExePath(wrapper_argv0)};
// Try to resolve any symlinks and figure out the directory containing the wrapper executable.
std::error_code ec;
fs::path wrapper_dir{fs::weakly_canonical(wrapper_path, ec)};
if (wrapper_dir.empty()) wrapper_dir = wrapper_path; // Restore previous path if weakly_canonical failed.
wrapper_dir = wrapper_dir.parent_path();
// Get path of the executable to be invoked.
const fs::path arg0{fs::PathFromString(args[0])};
// Decide whether to fall back to the operating system to search for the
// specified executable. Avoid doing this if it looks like the wrapper
// executable was invoked by path, rather than by search, to avoid
// unintentionally launching system executables in a local build.
// (https://github.com/bitcoin/bitcoin/pull/31375#discussion_r1861814807)
const bool fallback_os_search{!fs::PathFromString(std::string{wrapper_argv0}).has_parent_path()};
// If wrapper is installed in a bin/ directory, look for target executable
// in libexec/
(wrapper_dir.filename() == "bin" && try_exec(fs::path{wrapper_dir.parent_path()} / "libexec" / arg0.filename())) ||
// Otherwise check the "daemon" subdirectory in a windows install.
try_exec(wrapper_dir / "daemon" / arg0.filename()) ||
// Otherwise look for target executable next to current wrapper
try_exec(wrapper_dir / arg0.filename(), fallback_os_search) ||
// Otherwise just look on the system path.
(fallback_os_search && try_exec(arg0.filename(), false));
};

View file

@ -9,6 +9,7 @@ add_library(bitcoin_util STATIC EXCLUDE_FROM_ALL
bytevectorhash.cpp
chaintype.cpp
check.cpp
exec.cpp
exception.cpp
feefrac.cpp
fs.cpp

107
src/util/exec.cpp Normal file
View file

@ -0,0 +1,107 @@
// Copyright (c) 2025 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <util/fs.h>
#include <string>
#include <vector>
#ifdef WIN32
#include <process.h>
#include <windows.h>
#else
#include <unistd.h>
#endif
namespace util {
int ExecVp(const char *file, char *const argv[])
{
#ifndef WIN32
return execvp(file, argv);
#else
std::vector<char*> new_argv;
std::vector<std::string> escaped_args;
for (char* const* arg_ptr{argv}; *arg_ptr; ++arg_ptr) {
std::string_view arg{*arg_ptr};
if (arg.find_first_of(" \t\"") == std::string_view::npos) {
// Argument has no quotes or spaces so escaping not necessary.
new_argv.push_back(*arg_ptr);
} else {
// Add escaping to the command line that the executable being
// invoked will split up using the CommandLineToArgvW function,
// which expects arguments with spaces to be quoted, quote
// characters to be backslash-escaped, and backslashes to also be
// backslash-escaped, but only if they precede a quote character.
std::string escaped{'"'}; // Start with a quote
for (size_t i = 0; i < arg.size(); ++i) {
if (arg[i] == '\\') {
// Count consecutive backslashes
size_t backslash_count = 0;
while (i < arg.size() && arg[i] == '\\') {
++backslash_count;
++i;
}
if (i < arg.size() && arg[i] == '"') {
// Backslashes before a quote need to be doubled
escaped.append(backslash_count * 2 + 1, '\\');
escaped.push_back('"');
} else {
// Otherwise, backslashes remain as-is
escaped.append(backslash_count, '\\');
--i; // Compensate for the outer loop's increment
}
} else if (arg[i] == '"') {
// Escape double quotes with a backslash
escaped.push_back('\\');
escaped.push_back('"');
} else {
escaped.push_back(arg[i]);
}
}
escaped.push_back('"'); // End with a quote
escaped_args.emplace_back(std::move(escaped));
new_argv.push_back((char *)escaped_args.back().c_str());
}
}
new_argv.push_back(nullptr);
return _execvp(file, new_argv.data());
#endif
}
fs::path GetExePath(std::string_view argv0)
{
// Try to figure out where executable is located. This does a simplified
// search that won't work perfectly on every platform and doesn't need to,
// as it is only currently being used in a convenience wrapper binary to try
// to prioritize locally built or installed executables over system
// executables.
const fs::path argv0_path{fs::PathFromString(std::string{argv0})};
fs::path path{argv0_path};
std::error_code ec;
#ifndef WIN32
// If argv0 doesn't contain a path separator, it was invoked from the system
// PATH and can be searched for there.
if (!argv0_path.has_parent_path()) {
if (const char* path_env = std::getenv("PATH")) {
size_t start{0}, end{0};
for (std::string_view paths{path_env}; end != std::string_view::npos; start = end + 1) {
end = paths.find(':', start);
fs::path candidate = fs::path(paths.substr(start, end - start)) / argv0_path;
if (fs::is_regular_file(candidate, ec)) {
path = candidate;
break;
}
}
}
}
#else
wchar_t module_path[MAX_PATH];
if (GetModuleFileNameW(nullptr, module_path, MAX_PATH) > 0) {
path = fs::path{module_path};
}
#endif
return path;
}
} // namespace util

19
src/util/exec.h Normal file
View file

@ -0,0 +1,19 @@
// Copyright (c) 2025 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_UTIL_EXEC_H
#define BITCOIN_UTIL_EXEC_H
#include <util/fs.h>
#include <string_view>
namespace util {
//! Cross-platform wrapper for POSIX execvp function.
int ExecVp(const char *file, char *const argv[]);
//! Return path to current executable assuming it was invoked with argv0.
fs::path GetExePath(std::string_view argv0);
} // namespace util
#endif // BITCOIN_UTIL_EXEC_H

View file

@ -13,6 +13,7 @@ import platform
import pdb
import random
import re
import shlex
import shutil
import subprocess
import sys
@ -74,31 +75,35 @@ class Binaries:
def daemon_argv(self):
"Return argv array that should be used to invoke bitcoind"
return self._argv(self.paths.bitcoind)
return self._argv("daemon", self.paths.bitcoind)
def rpc_argv(self):
"Return argv array that should be used to invoke bitcoin-cli"
return self._argv(self.paths.bitcoincli)
# Add -nonamed because "bitcoin rpc" enables -named by default, but bitcoin-cli doesn't
return self._argv("rpc", self.paths.bitcoincli) + ["-nonamed"]
def util_argv(self):
"Return argv array that should be used to invoke bitcoin-util"
return self._argv(self.paths.bitcoinutil)
return self._argv("util", self.paths.bitcoinutil)
def wallet_argv(self):
"Return argv array that should be used to invoke bitcoin-wallet"
return self._argv(self.paths.bitcoinwallet)
return self._argv("wallet", self.paths.bitcoinwallet)
def chainstate_argv(self):
"Return argv array that should be used to invoke bitcoin-chainstate"
return self._argv(self.paths.bitcoinchainstate)
return self._argv("chainstate", self.paths.bitcoinchainstate)
def _argv(self, bin_path):
"""Return argv array that should be used to invoke the command.
Normally this will return binary paths directly from the paths object,
but when bin_dir is set (by tests calling binaries from previous
releases) it will return paths relative to bin_dir instead."""
def _argv(self, command, bin_path):
"""Return argv array that should be used to invoke the command. It
either uses the bitcoin wrapper executable (if BITCOIN_CMD is set), or
the direct binary path (bitcoind, etc). When bin_dir is set (by tests
calling binaries from previous releases) it always uses the direct
path."""
if self.bin_dir is not None:
return [os.path.join(self.bin_dir, os.path.basename(bin_path))]
elif self.paths.bitcoin_cmd is not None:
return self.paths.bitcoin_cmd + [command]
else:
return [bin_path]
@ -292,6 +297,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
binary + self.config["environment"]["EXEEXT"],
)
setattr(paths, attribute_name, os.getenv(env_variable_name, default=default_filename))
# BITCOIN_CMD environment variable can be specified to invoke bitcoin
# wrapper binary instead of other executables.
paths.bitcoin_cmd = shlex.split(os.getenv("BITCOIN_CMD", "")) or None
return paths
def get_binaries(self, bin_dir=None):