mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-17 13:22:03 +01:00
Merge 623c0f0df878968dabbaa271a7654c638603f369 into db2c57ae9eebdb75c58cd165ac929919969c19a9
This commit is contained in:
commit
2beb0bcc62
@ -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)
|
||||
@ -627,6 +629,7 @@ message("\n")
|
||||
message("Configure summary")
|
||||
message("=================")
|
||||
message("Executables:")
|
||||
message(" bitcoin ............................. ${BUILD_BITCOIN_BIN}")
|
||||
message(" bitcoind ............................ ${BUILD_DAEMON}")
|
||||
if(BUILD_DAEMON AND WITH_MULTIPROCESS)
|
||||
set(bitcoin_daemon_status ON)
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
# TODO: Consider replacing this code with the CPack NSIS Generator.
|
||||
# See https://cmake.org/cmake/help/latest/cpack_gen/nsis.html
|
||||
include(GenerateSetupNsi)
|
||||
@ -51,6 +51,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>
|
||||
|
@ -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()
|
||||
|
||||
|
@ -9,7 +9,7 @@ import logging
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import shlex
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
@ -86,7 +86,7 @@ def finish_block(block, signet_solution, grind_cmd):
|
||||
block.solve()
|
||||
else:
|
||||
headhex = CBlockHeader.serialize(block).hex()
|
||||
cmd = grind_cmd.split(" ") + [headhex]
|
||||
cmd = shlex.split(grind_cmd) + [headhex]
|
||||
newheadhex = subprocess.run(cmd, stdout=subprocess.PIPE, input=b"", check=True).stdout.strip()
|
||||
newhead = from_hex(CBlockHeader(), newheadhex.decode('utf8'))
|
||||
block.nNonce = newhead.nNonce
|
||||
@ -479,7 +479,7 @@ def do_calibrate(args):
|
||||
header.nTime = i
|
||||
header.nNonce = 0
|
||||
headhex = header.serialize().hex()
|
||||
cmd = args.grind_cmd.split(" ") + [headhex]
|
||||
cmd = shlex.split(args.grind_cmd) + [headhex]
|
||||
newheadhex = subprocess.run(cmd, stdout=subprocess.PIPE, input=b"", check=True).stdout.strip()
|
||||
|
||||
avg = (time.time() - start) * 1.0 / TRIALS
|
||||
@ -549,7 +549,7 @@ def main():
|
||||
|
||||
args = parser.parse_args(sys.argv[1:])
|
||||
|
||||
args.bcli = lambda *a, input=b"", **kwargs: bitcoin_cli(args.cli.split(" "), list(a), input=input, **kwargs)
|
||||
args.bcli = lambda *a, input=b"", **kwargs: bitcoin_cli(shlex.split(args.cli), list(a), input=input, **kwargs)
|
||||
|
||||
if hasattr(args, "address") and hasattr(args, "descriptor"):
|
||||
args.derived_addresses = {}
|
||||
|
@ -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
|
||||
|
@ -326,6 +326,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
196
src/bitcoin.cpp
Normal 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]'.
|
||||
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 == "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 in a CMake build tree, first look for target executable
|
||||
// relative to it.
|
||||
(wrapper_dir.filename() == "src" && try_exec(wrapper_dir / arg0)) ||
|
||||
// Otherwise 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));
|
||||
};
|
@ -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
107
src/util/exec.cpp
Normal 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
19
src/util/exec.h
Normal 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
|
@ -13,11 +13,13 @@ import platform
|
||||
import pdb
|
||||
import random
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import types
|
||||
|
||||
from .address import create_deterministic_address_bcrt1_p2tr_op_true
|
||||
from .authproxy import JSONRPCException
|
||||
@ -56,6 +58,52 @@ class SkipTest(Exception):
|
||||
self.message = message
|
||||
|
||||
|
||||
class Binaries:
|
||||
"""Helper class to provide information about bitcoin binaries
|
||||
|
||||
Attributes:
|
||||
paths: Object returned from get_binary_paths() containing information
|
||||
which binaries and command lines to use from environment variables and
|
||||
the config file.
|
||||
bin_dir: An optional string containing a directory path to look for
|
||||
binaries, which takes precedence over the paths above, if specified.
|
||||
This is used by tests calling binaries from previous releases.
|
||||
"""
|
||||
def __init__(self, paths, bin_dir):
|
||||
self.paths = paths
|
||||
self.bin_dir = bin_dir
|
||||
|
||||
def daemon_argv(self):
|
||||
"Return argv array that should be used to invoke bitcoind"
|
||||
return self._argv("daemon", self.paths.bitcoind)
|
||||
|
||||
def rpc_argv(self):
|
||||
"Return argv array that should be used to invoke bitcoin-cli"
|
||||
# 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("util", self.paths.bitcoinutil)
|
||||
|
||||
def wallet_argv(self):
|
||||
"Return argv array that should be used to invoke bitcoin-wallet"
|
||||
return self._argv("wallet", self.paths.bitcoinwallet)
|
||||
|
||||
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]
|
||||
|
||||
|
||||
class BitcoinTestMetaClass(type):
|
||||
"""Metaclass for BitcoinTestFramework.
|
||||
|
||||
@ -220,6 +268,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
config = configparser.ConfigParser()
|
||||
config.read_file(open(self.options.configfile))
|
||||
self.config = config
|
||||
self.binary_paths = self.get_binary_paths()
|
||||
if self.options.v1transport:
|
||||
self.options.v2transport=False
|
||||
|
||||
@ -239,9 +288,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
|
||||
PortSeed.n = self.options.port_seed
|
||||
|
||||
def set_binary_paths(self):
|
||||
"""Update self.options with the paths of all binaries from environment variables or their default values"""
|
||||
def get_binary_paths(self):
|
||||
"""Get paths of all binaries from environment variables or their default values"""
|
||||
|
||||
paths = types.SimpleNamespace()
|
||||
binaries = {
|
||||
"bitcoind": ("bitcoind", "BITCOIND"),
|
||||
"bitcoin-cli": ("bitcoincli", "BITCOINCLI"),
|
||||
@ -254,7 +304,14 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
"bin",
|
||||
binary + self.config["environment"]["EXEEXT"],
|
||||
)
|
||||
setattr(self.options, attribute_name, os.getenv(env_variable_name, default=default_filename))
|
||||
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):
|
||||
return Binaries(self.binary_paths, bin_dir)
|
||||
|
||||
def setup(self):
|
||||
"""Call this method to start up the test framework object with options set."""
|
||||
@ -265,8 +322,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
|
||||
config = self.config
|
||||
|
||||
self.set_binary_paths()
|
||||
|
||||
os.environ['PATH'] = os.pathsep.join([
|
||||
os.path.join(config['environment']['BUILDDIR'], 'bin'),
|
||||
os.environ['PATH']
|
||||
@ -473,14 +528,14 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
group.add_argument("--legacy-wallet", action='store_const', const=False, **kwargs,
|
||||
help="Run test using legacy wallets", dest='descriptors')
|
||||
|
||||
def add_nodes(self, num_nodes: int, extra_args=None, *, rpchost=None, binary=None, binary_cli=None, versions=None):
|
||||
def add_nodes(self, num_nodes: int, extra_args=None, *, rpchost=None, versions=None):
|
||||
"""Instantiate TestNode objects.
|
||||
|
||||
Should only be called once after the nodes have been specified in
|
||||
set_test_params()."""
|
||||
def get_bin_from_version(version, bin_name, bin_default):
|
||||
def bin_dir_from_version(version):
|
||||
if not version:
|
||||
return bin_default
|
||||
return None
|
||||
if version > 219999:
|
||||
# Starting at client version 220000 the first two digits represent
|
||||
# the major version, e.g. v22.0 instead of v0.22.0.
|
||||
@ -498,7 +553,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
),
|
||||
),
|
||||
'bin',
|
||||
bin_name,
|
||||
)
|
||||
|
||||
if self.bind_to_localhost_only:
|
||||
@ -513,13 +567,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
extra_args[i] = extra_args[i] + ["-whitelist=noban,in,out@127.0.0.1"]
|
||||
if versions is None:
|
||||
versions = [None] * num_nodes
|
||||
if binary is None:
|
||||
binary = [get_bin_from_version(v, 'bitcoind', self.options.bitcoind) for v in versions]
|
||||
if binary_cli is None:
|
||||
binary_cli = [get_bin_from_version(v, 'bitcoin-cli', self.options.bitcoincli) for v in versions]
|
||||
bin_dirs = [bin_dir_from_version(v) for v in versions]
|
||||
# Fail test if any of the needed release binaries is missing
|
||||
bins_missing = False
|
||||
for bin_path in binary + binary_cli:
|
||||
for bin_path in (argv[0] for bin_dir in bin_dirs
|
||||
for binaries in (self.get_binaries(bin_dir),)
|
||||
for argv in (binaries.daemon_argv(), binaries.rpc_argv())):
|
||||
if shutil.which(bin_path) is None:
|
||||
self.log.error(f"Binary not found: {bin_path}")
|
||||
bins_missing = True
|
||||
@ -529,8 +582,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
assert_equal(len(extra_confs), num_nodes)
|
||||
assert_equal(len(extra_args), num_nodes)
|
||||
assert_equal(len(versions), num_nodes)
|
||||
assert_equal(len(binary), num_nodes)
|
||||
assert_equal(len(binary_cli), num_nodes)
|
||||
assert_equal(len(bin_dirs), num_nodes)
|
||||
for i in range(num_nodes):
|
||||
args = list(extra_args[i])
|
||||
test_node_i = TestNode(
|
||||
@ -540,8 +592,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
rpchost=rpchost,
|
||||
timewait=self.rpc_timeout,
|
||||
timeout_factor=self.options.timeout_factor,
|
||||
bitcoind=binary[i],
|
||||
bitcoin_cli=binary_cli[i],
|
||||
binaries=self.get_binaries(bin_dirs[i]),
|
||||
version=versions[i],
|
||||
coverage_dir=self.options.coveragedir,
|
||||
cwd=self.options.tmpdir,
|
||||
@ -852,8 +903,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
rpchost=None,
|
||||
timewait=self.rpc_timeout,
|
||||
timeout_factor=self.options.timeout_factor,
|
||||
bitcoind=self.options.bitcoind,
|
||||
bitcoin_cli=self.options.bitcoincli,
|
||||
binaries=self.get_binaries(),
|
||||
coverage_dir=None,
|
||||
cwd=self.options.tmpdir,
|
||||
descriptors=self.options.descriptors,
|
||||
|
@ -76,7 +76,7 @@ class TestNode():
|
||||
To make things easier for the test writer, any unrecognised messages will
|
||||
be dispatched to the RPC connection."""
|
||||
|
||||
def __init__(self, i, datadir_path, *, chain, rpchost, timewait, timeout_factor, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False, v2transport=False):
|
||||
def __init__(self, i, datadir_path, *, chain, rpchost, timewait, timeout_factor, binaries, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False, v2transport=False):
|
||||
"""
|
||||
Kwargs:
|
||||
start_perf (bool): If True, begin profiling the node with `perf` as soon as
|
||||
@ -92,7 +92,7 @@ class TestNode():
|
||||
self.chain = chain
|
||||
self.rpchost = rpchost
|
||||
self.rpc_timeout = timewait
|
||||
self.binary = bitcoind
|
||||
self.binaries = binaries
|
||||
self.coverage_dir = coverage_dir
|
||||
self.cwd = cwd
|
||||
self.descriptors = descriptors
|
||||
@ -109,8 +109,7 @@ class TestNode():
|
||||
# Configuration for logging is set as command-line args rather than in the bitcoin.conf file.
|
||||
# This means that starting a bitcoind using the temp dir to debug a failed test won't
|
||||
# spam debug.log.
|
||||
self.args = [
|
||||
self.binary,
|
||||
self.args = self.binaries.daemon_argv() + [
|
||||
f"-datadir={self.datadir_path}",
|
||||
"-logtimemicros",
|
||||
"-debug",
|
||||
@ -149,7 +148,7 @@ class TestNode():
|
||||
self.args.append("-v2transport=0")
|
||||
# if v2transport is requested via global flag but not supported for node version, ignore it
|
||||
|
||||
self.cli = TestNodeCLI(bitcoin_cli, self.datadir_path)
|
||||
self.cli = TestNodeCLI(binaries, self.datadir_path)
|
||||
self.use_cli = use_cli
|
||||
self.start_perf = start_perf
|
||||
|
||||
@ -870,16 +869,16 @@ def arg_to_cli(arg):
|
||||
|
||||
class TestNodeCLI():
|
||||
"""Interface to bitcoin-cli for an individual node"""
|
||||
def __init__(self, binary, datadir):
|
||||
def __init__(self, binaries, datadir):
|
||||
self.options = []
|
||||
self.binary = binary
|
||||
self.binaries = binaries
|
||||
self.datadir = datadir
|
||||
self.input = None
|
||||
self.log = logging.getLogger('TestFramework.bitcoincli')
|
||||
|
||||
def __call__(self, *options, input=None):
|
||||
# TestNodeCLI is callable with bitcoin-cli command-line options
|
||||
cli = TestNodeCLI(self.binary, self.datadir)
|
||||
cli = TestNodeCLI(self.binaries, self.datadir)
|
||||
cli.options = [str(o) for o in options]
|
||||
cli.input = input
|
||||
return cli
|
||||
@ -900,7 +899,7 @@ class TestNodeCLI():
|
||||
"""Run bitcoin-cli command. Deserializes returned string as python object."""
|
||||
pos_args = [arg_to_cli(arg) for arg in args]
|
||||
named_args = [str(key) + "=" + arg_to_cli(value) for (key, value) in kwargs.items()]
|
||||
p_args = [self.binary, f"-datadir={self.datadir}"] + self.options
|
||||
p_args = self.binaries.rpc_argv() + [f"-datadir={self.datadir}"] + self.options
|
||||
if named_args:
|
||||
p_args += ["-named"]
|
||||
if clicommand is not None:
|
||||
@ -916,7 +915,7 @@ class TestNodeCLI():
|
||||
code, message = match.groups()
|
||||
raise JSONRPCException(dict(code=int(code), message=message))
|
||||
# Ignore cli_stdout, raise with cli_stderr
|
||||
raise subprocess.CalledProcessError(returncode, self.binary, output=cli_stderr)
|
||||
raise subprocess.CalledProcessError(returncode, p_args, output=cli_stderr)
|
||||
try:
|
||||
return json.loads(cli_stdout, parse_float=decimal.Decimal)
|
||||
except (json.JSONDecodeError, decimal.InvalidOperation):
|
||||
|
@ -5,6 +5,7 @@
|
||||
"""Test signet miner tool"""
|
||||
|
||||
import os.path
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
@ -49,13 +50,15 @@ class SignetMinerTest(BitcoinTestFramework):
|
||||
# generate block with signet miner tool
|
||||
base_dir = self.config["environment"]["SRCDIR"]
|
||||
signet_miner_path = os.path.join(base_dir, "contrib", "signet", "miner")
|
||||
rpc_argv = node.binaries.rpc_argv() + [f"-datadir={node.cli.datadir}"]
|
||||
util_argv = node.binaries.util_argv() + ["grind"]
|
||||
subprocess.run([
|
||||
sys.executable,
|
||||
signet_miner_path,
|
||||
f'--cli={node.cli.binary} -datadir={node.cli.datadir}',
|
||||
f'--cli={shlex.join(rpc_argv)}',
|
||||
'generate',
|
||||
f'--address={node.getnewaddress()}',
|
||||
f'--grind-cmd={self.options.bitcoinutil} grind',
|
||||
f'--grind-cmd={shlex.join(util_argv)}',
|
||||
f'--nbits={DIFF_1_N_BITS:08x}',
|
||||
f'--set-block-time={int(time.time())}',
|
||||
'--poolnum=99',
|
||||
|
@ -48,7 +48,7 @@ class ToolWalletTest(BitcoinTestFramework):
|
||||
if "dump" in args and self.options.bdbro:
|
||||
default_args.append("-withinternalbdb")
|
||||
|
||||
return subprocess.Popen([self.options.bitcoinwallet] + default_args + list(args), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
return subprocess.Popen(self.get_binaries().wallet_argv() + default_args + list(args), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
def assert_raises_tool_error(self, error, *args):
|
||||
p = self.bitcoin_wallet_process(*args)
|
||||
|
@ -112,7 +112,7 @@ class WalletEncryptionTest(BitcoinTestFramework):
|
||||
|
||||
def do_wallet_tool(*args):
|
||||
proc = subprocess.Popen(
|
||||
[self.options.bitcoinwallet, f"-datadir={self.nodes[0].datadir_path}", f"-chain={self.chain}"] + list(args),
|
||||
self.get_binaries().wallet_argv() + [f"-datadir={self.nodes[0].datadir_path}", f"-chain={self.chain}"] + list(args),
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
|
Loading…
x
Reference in New Issue
Block a user