mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-05 14:06:27 -05:00
Merge bitcoin/bitcoin#31212: util: Improve documentation and negation of args
95a0104f2e
test: Add tests for directories in place of config files (Hodlinator)e85abe92c7
args: Catch directories in place of config files (Hodlinator)e4b6b1822c
test: Add tests for -noconf (Hodlinator)483f0dacc4
args: Properly support -noconf (Hodlinator)312ec64cc0
test refactor: feature_config_args.py - Stop nodes at the end of tests, not at the beginning (Hodlinator)7402658bc2
test: -norpccookiefile (Hodlinator)39cbd4f37c
args: Support -norpccookiefile for bitcoind and bitcoin-cli (Hodlinator)e82ad88452
logs: Use correct path and more appropriate macros in cookie-related code (Hodlinator)6e28c76907
test: Harden testing of cookie file existence (Hodlinator)75bacabb55
test: combine_logs.py - Output debug.log paths on error (Hodlinator)bffd92f00f
args: Support -nopid (Hodlinator)12f8d848fd
args: Disallow -nodatadir (Hodlinator)6ff9662760
scripted-diff: Avoid printing version information for -noversion (Hodlinator)e8a2054edc
doc args: Document narrow scope of -color (Hodlinator) Pull request description: - Document `-color` as only applying to `-getinfo`, to be less confusing for bitcoin-cli users. - No longer print version information when getting passed `-noversion`. - Disallow `-nodatadir` as we cannot run without one. It was previously interpreted as a mix of unset and as a relative path of "0". - Support `-norpccookiefile` - Support `-nopid` - Properly support `-noconf` (instead of working by accident). Also detect when directories are specified instead of files. Prompted by investigation in https://github.com/bitcoin/bitcoin/pull/16545#pullrequestreview-2316714013. ACKs for top commit: l0rinc: utACK95a0104f2e
achow101: ACK95a0104f2e
ryanofsky: Code review ACK95a0104f2e
. Looks good! Thanks for all your work on this breaking the changes down and making them simple. Tree-SHA512: 5174251e6b9196a9c6d135eddcb94130295c551bcfccc78e633d9e118ff91523b1be0d72828fb49603ceae312e6e1f8ee2651c6a2b9e0f195603a73a9a622785
This commit is contained in:
commit
11f68cc810
16 changed files with 212 additions and 75 deletions
|
@ -82,7 +82,7 @@ static void SetupCliArgs(ArgsManager& argsman)
|
||||||
|
|
||||||
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-generate",
|
argsman.AddArg("-generate",
|
||||||
strprintf("Generate blocks, equivalent to RPC getnewaddress followed by RPC generatetoaddress. Optional positional integer "
|
strprintf("Generate blocks, equivalent to RPC getnewaddress followed by RPC generatetoaddress. Optional positional integer "
|
||||||
"arguments are number of blocks to generate (default: %s) and maximum iterations to try (default: %s), equivalent to "
|
"arguments are number of blocks to generate (default: %s) and maximum iterations to try (default: %s), equivalent to "
|
||||||
|
@ -94,7 +94,7 @@ static void SetupCliArgs(ArgsManager& argsman)
|
||||||
argsman.AddArg("-netinfo", strprintf("Get network peer connection information from the remote server. An optional argument from 0 to %d can be passed for different peers listings (default: 0). If a non-zero value is passed, an additional \"outonly\" (or \"o\") argument can be passed to see outbound peers only. Pass \"help\" (or \"h\") for detailed help documentation.", NETINFO_MAX_LEVEL), ArgsManager::ALLOW_ANY, OptionsCategory::CLI_COMMANDS);
|
argsman.AddArg("-netinfo", strprintf("Get network peer connection information from the remote server. An optional argument from 0 to %d can be passed for different peers listings (default: 0). If a non-zero value is passed, an additional \"outonly\" (or \"o\") argument can be passed to see outbound peers only. Pass \"help\" (or \"h\") for detailed help documentation.", NETINFO_MAX_LEVEL), ArgsManager::ALLOW_ANY, OptionsCategory::CLI_COMMANDS);
|
||||||
|
|
||||||
SetupChainParamsBaseOptions(argsman);
|
SetupChainParamsBaseOptions(argsman);
|
||||||
argsman.AddArg("-color=<when>", strprintf("Color setting for CLI output (default: %s). Valid values: always, auto (add color codes when standard output is connected to a terminal and OS is not WIN32), never.", DEFAULT_COLOR_SETTING), ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
|
argsman.AddArg("-color=<when>", strprintf("Color setting for CLI output (default: %s). Valid values: always, auto (add color codes when standard output is connected to a terminal and OS is not WIN32), never. Only applies to the output of -getinfo.", DEFAULT_COLOR_SETTING), ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-rpcclienttimeout=<n>", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-rpcclienttimeout=<n>", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
|
@ -145,10 +145,10 @@ static int AppInitRPC(int argc, char* argv[])
|
||||||
tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error);
|
tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error);
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
|
if (argc < 2 || HelpRequested(gArgs) || gArgs.GetBoolArg("-version", false)) {
|
||||||
std::string strUsage = CLIENT_NAME " RPC client version " + FormatFullVersion() + "\n";
|
std::string strUsage = CLIENT_NAME " RPC client version " + FormatFullVersion() + "\n";
|
||||||
|
|
||||||
if (gArgs.IsArgSet("-version")) {
|
if (gArgs.GetBoolArg("-version", false)) {
|
||||||
strUsage += FormatParagraph(LicenseInfo());
|
strUsage += FormatParagraph(LicenseInfo());
|
||||||
} else {
|
} else {
|
||||||
strUsage += "\n"
|
strUsage += "\n"
|
||||||
|
|
|
@ -105,11 +105,11 @@ static int AppInitRawTx(int argc, char* argv[])
|
||||||
|
|
||||||
fCreateBlank = gArgs.GetBoolArg("-create", false);
|
fCreateBlank = gArgs.GetBoolArg("-create", false);
|
||||||
|
|
||||||
if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
|
if (argc < 2 || HelpRequested(gArgs) || gArgs.GetBoolArg("-version", false)) {
|
||||||
// First part of help message is specific to this utility
|
// First part of help message is specific to this utility
|
||||||
std::string strUsage = CLIENT_NAME " bitcoin-tx utility version " + FormatFullVersion() + "\n";
|
std::string strUsage = CLIENT_NAME " bitcoin-tx utility version " + FormatFullVersion() + "\n";
|
||||||
|
|
||||||
if (gArgs.IsArgSet("-version")) {
|
if (gArgs.GetBoolArg("-version", false)) {
|
||||||
strUsage += FormatParagraph(LicenseInfo());
|
strUsage += FormatParagraph(LicenseInfo());
|
||||||
} else {
|
} else {
|
||||||
strUsage += "\n"
|
strUsage += "\n"
|
||||||
|
|
|
@ -50,11 +50,11 @@ static int AppInitUtil(ArgsManager& args, int argc, char* argv[])
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HelpRequested(args) || args.IsArgSet("-version")) {
|
if (HelpRequested(args) || args.GetBoolArg("-version", false)) {
|
||||||
// First part of help message is specific to this utility
|
// First part of help message is specific to this utility
|
||||||
std::string strUsage = CLIENT_NAME " bitcoin-util utility version " + FormatFullVersion() + "\n";
|
std::string strUsage = CLIENT_NAME " bitcoin-util utility version " + FormatFullVersion() + "\n";
|
||||||
|
|
||||||
if (args.IsArgSet("-version")) {
|
if (args.GetBoolArg("-version", false)) {
|
||||||
strUsage += FormatParagraph(LicenseInfo());
|
strUsage += FormatParagraph(LicenseInfo());
|
||||||
} else {
|
} else {
|
||||||
strUsage += "\n"
|
strUsage += "\n"
|
||||||
|
|
|
@ -34,7 +34,7 @@ static void SetupWalletToolArgs(ArgsManager& argsman)
|
||||||
SetupChainParamsBaseOptions(argsman);
|
SetupChainParamsBaseOptions(argsman);
|
||||||
|
|
||||||
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-wallet=<wallet-name>", "Specify wallet name", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-wallet=<wallet-name>", "Specify wallet name", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-dumpfile=<file name>", "When used with 'dump', writes out the records to this file. When used with 'createfromdump', loads the records into a new wallet.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
|
argsman.AddArg("-dumpfile=<file name>", "When used with 'dump', writes out the records to this file. When used with 'createfromdump', loads the records into a new wallet.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
|
argsman.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
|
||||||
|
@ -60,10 +60,10 @@ static std::optional<int> WalletAppInit(ArgsManager& args, int argc, char* argv[
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
const bool missing_args{argc < 2};
|
const bool missing_args{argc < 2};
|
||||||
if (missing_args || HelpRequested(args) || args.IsArgSet("-version")) {
|
if (missing_args || HelpRequested(args) || args.GetBoolArg("-version", false)) {
|
||||||
std::string strUsage = strprintf("%s bitcoin-wallet utility version", CLIENT_NAME) + " " + FormatFullVersion() + "\n";
|
std::string strUsage = strprintf("%s bitcoin-wallet utility version", CLIENT_NAME) + " " + FormatFullVersion() + "\n";
|
||||||
|
|
||||||
if (args.IsArgSet("-version")) {
|
if (args.GetBoolArg("-version", false)) {
|
||||||
strUsage += FormatParagraph(LicenseInfo());
|
strUsage += FormatParagraph(LicenseInfo());
|
||||||
} else {
|
} else {
|
||||||
strUsage += "\n"
|
strUsage += "\n"
|
||||||
|
|
|
@ -135,10 +135,10 @@ static bool ParseArgs(NodeContext& node, int argc, char* argv[])
|
||||||
static bool ProcessInitCommands(ArgsManager& args)
|
static bool ProcessInitCommands(ArgsManager& args)
|
||||||
{
|
{
|
||||||
// Process help and version before taking care about datadir
|
// Process help and version before taking care about datadir
|
||||||
if (HelpRequested(args) || args.IsArgSet("-version")) {
|
if (HelpRequested(args) || args.GetBoolArg("-version", false)) {
|
||||||
std::string strUsage = CLIENT_NAME " daemon version " + FormatFullVersion() + "\n";
|
std::string strUsage = CLIENT_NAME " daemon version " + FormatFullVersion() + "\n";
|
||||||
|
|
||||||
if (args.IsArgSet("-version")) {
|
if (args.GetBoolArg("-version", false)) {
|
||||||
strUsage += FormatParagraph(LicenseInfo());
|
strUsage += FormatParagraph(LicenseInfo());
|
||||||
} else {
|
} else {
|
||||||
strUsage += "\n"
|
strUsage += "\n"
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
@ -128,13 +129,19 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto conf_path{GetConfigFilePath()};
|
const auto conf_path{GetConfigFilePath()};
|
||||||
std::ifstream stream{conf_path};
|
std::ifstream stream;
|
||||||
|
if (!conf_path.empty()) { // path is empty when -noconf is specified
|
||||||
// not ok to have a config file specified that cannot be opened
|
if (fs::is_directory(conf_path)) {
|
||||||
|
error = strprintf("Config file \"%s\" is a directory.", fs::PathToString(conf_path));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stream = std::ifstream{conf_path};
|
||||||
|
// If the file is explicitly specified, it must be readable
|
||||||
if (IsArgSet("-conf") && !stream.good()) {
|
if (IsArgSet("-conf") && !stream.good()) {
|
||||||
error = strprintf("specified config file \"%s\" could not be opened.", fs::PathToString(conf_path));
|
error = strprintf("specified config file \"%s\" could not be opened.", fs::PathToString(conf_path));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// ok to not have a config file
|
// ok to not have a config file
|
||||||
if (stream.good()) {
|
if (stream.good()) {
|
||||||
if (!ReadConfigStream(stream, fs::PathToString(conf_path), error, ignore_invalid_keys)) {
|
if (!ReadConfigStream(stream, fs::PathToString(conf_path), error, ignore_invalid_keys)) {
|
||||||
|
@ -175,7 +182,12 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
|
||||||
const size_t default_includes = add_includes({});
|
const size_t default_includes = add_includes({});
|
||||||
|
|
||||||
for (const std::string& conf_file_name : conf_file_names) {
|
for (const std::string& conf_file_name : conf_file_names) {
|
||||||
std::ifstream conf_file_stream{AbsPathForConfigVal(*this, fs::PathFromString(conf_file_name), /*net_specific=*/false)};
|
const auto include_conf_path{AbsPathForConfigVal(*this, fs::PathFromString(conf_file_name), /*net_specific=*/false)};
|
||||||
|
if (fs::is_directory(include_conf_path)) {
|
||||||
|
error = strprintf("Included config file \"%s\" is a directory.", fs::PathToString(include_conf_path));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::ifstream conf_file_stream{include_conf_path};
|
||||||
if (conf_file_stream.good()) {
|
if (conf_file_stream.good()) {
|
||||||
if (!ReadConfigStream(conf_file_stream, conf_file_name, error, ignore_invalid_keys)) {
|
if (!ReadConfigStream(conf_file_stream, conf_file_name, error, ignore_invalid_keys)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -213,7 +225,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
|
||||||
|
|
||||||
fs::path AbsPathForConfigVal(const ArgsManager& args, const fs::path& path, bool net_specific)
|
fs::path AbsPathForConfigVal(const ArgsManager& args, const fs::path& path, bool net_specific)
|
||||||
{
|
{
|
||||||
if (path.is_absolute()) {
|
if (path.is_absolute() || path.empty()) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
return fsbridge::AbsPathJoin(net_specific ? args.GetDataDirNet() : args.GetDataDirBase(), path);
|
return fsbridge::AbsPathJoin(net_specific ? args.GetDataDirNet() : args.GetDataDirBase(), path);
|
||||||
|
|
|
@ -62,31 +62,38 @@ std::optional<ConfigError> InitConfig(ArgsManager& args, SettingsAbortFn setting
|
||||||
fs::create_directories(net_path / "wallets");
|
fs::create_directories(net_path / "wallets");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show an error or warning if there is a bitcoin.conf file in the
|
// Show an error or warn/log if there is a bitcoin.conf file in the
|
||||||
// datadir that is being ignored.
|
// datadir that is being ignored.
|
||||||
const fs::path base_config_path = base_path / BITCOIN_CONF_FILENAME;
|
const fs::path base_config_path = base_path / BITCOIN_CONF_FILENAME;
|
||||||
if (fs::exists(base_config_path) && !fs::equivalent(orig_config_path, base_config_path)) {
|
if (fs::exists(base_config_path)) {
|
||||||
|
if (orig_config_path.empty()) {
|
||||||
|
LogInfo(
|
||||||
|
"Data directory %s contains a %s file which is explicitly ignored using -noconf.",
|
||||||
|
fs::quoted(fs::PathToString(base_path)),
|
||||||
|
fs::quoted(BITCOIN_CONF_FILENAME));
|
||||||
|
} else if (!fs::equivalent(orig_config_path, base_config_path)) {
|
||||||
const std::string cli_config_path = args.GetArg("-conf", "");
|
const std::string cli_config_path = args.GetArg("-conf", "");
|
||||||
const std::string config_source = cli_config_path.empty()
|
const std::string config_source = cli_config_path.empty()
|
||||||
? strprintf("data directory %s", fs::quoted(fs::PathToString(orig_datadir_path)))
|
? strprintf("data directory %s", fs::quoted(fs::PathToString(orig_datadir_path)))
|
||||||
: strprintf("command line argument %s", fs::quoted("-conf=" + cli_config_path));
|
: strprintf("command line argument %s", fs::quoted("-conf=" + cli_config_path));
|
||||||
const std::string error = strprintf(
|
std::string error = strprintf(
|
||||||
"Data directory %1$s contains a %2$s file which is ignored, because a different configuration file "
|
"Data directory %1$s contains a %2$s file which is ignored, because a different configuration file "
|
||||||
"%3$s from %4$s is being used instead. Possible ways to address this would be to:\n"
|
"%3$s from %4$s is being used instead. Possible ways to address this would be to:\n"
|
||||||
"- Delete or rename the %2$s file in data directory %1$s.\n"
|
"- Delete or rename the %2$s file in data directory %1$s.\n"
|
||||||
"- Change datadir= or conf= options to specify one configuration file, not two, and use "
|
"- Change datadir= or conf= options to specify one configuration file, not two, and use "
|
||||||
"includeconf= to include any other configuration files.\n"
|
"includeconf= to include any other configuration files.",
|
||||||
"- Set allowignoredconf=1 option to treat this condition as a warning, not an error.",
|
|
||||||
fs::quoted(fs::PathToString(base_path)),
|
fs::quoted(fs::PathToString(base_path)),
|
||||||
fs::quoted(BITCOIN_CONF_FILENAME),
|
fs::quoted(BITCOIN_CONF_FILENAME),
|
||||||
fs::quoted(fs::PathToString(orig_config_path)),
|
fs::quoted(fs::PathToString(orig_config_path)),
|
||||||
config_source);
|
config_source);
|
||||||
if (args.GetBoolArg("-allowignoredconf", false)) {
|
if (args.GetBoolArg("-allowignoredconf", false)) {
|
||||||
LogPrintf("Warning: %s\n", error);
|
LogWarning("%s", error);
|
||||||
} else {
|
} else {
|
||||||
|
error += "\n- Set allowignoredconf=1 option to treat this condition as a warning, not an error.";
|
||||||
return ConfigError{ConfigStatus::FAILED, Untranslated(error)};
|
return ConfigError{ConfigStatus::FAILED, Untranslated(error)};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create settings.json if -nosettings was not specified.
|
// Create settings.json if -nosettings was not specified.
|
||||||
if (args.GetSettingsPath()) {
|
if (args.GetSettingsPath()) {
|
||||||
|
|
|
@ -134,8 +134,6 @@ static bool multiUserAuthorized(std::string strUserPass)
|
||||||
|
|
||||||
static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUsernameOut)
|
static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUsernameOut)
|
||||||
{
|
{
|
||||||
if (strRPCUserColonPass.empty()) // Belt-and-suspenders measure if InitRPCAuthentication was not called
|
|
||||||
return false;
|
|
||||||
if (strAuth.substr(0, 6) != "Basic ")
|
if (strAuth.substr(0, 6) != "Basic ")
|
||||||
return false;
|
return false;
|
||||||
std::string_view strUserPass64 = TrimStringView(std::string_view{strAuth}.substr(6));
|
std::string_view strUserPass64 = TrimStringView(std::string_view{strAuth}.substr(6));
|
||||||
|
@ -147,8 +145,9 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna
|
||||||
if (strUserPass.find(':') != std::string::npos)
|
if (strUserPass.find(':') != std::string::npos)
|
||||||
strAuthUsernameOut = strUserPass.substr(0, strUserPass.find(':'));
|
strAuthUsernameOut = strUserPass.substr(0, strUserPass.find(':'));
|
||||||
|
|
||||||
//Check if authorized under single-user field
|
// Check if authorized under single-user field.
|
||||||
if (TimingResistantEqual(strUserPass, strRPCUserColonPass)) {
|
// (strRPCUserColonPass is empty when -norpccookiefile is specified).
|
||||||
|
if (!strRPCUserColonPass.empty() && TimingResistantEqual(strUserPass, strRPCUserColonPass)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return multiUserAuthorized(strUserPass);
|
return multiUserAuthorized(strUserPass);
|
||||||
|
@ -294,22 +293,26 @@ static bool InitRPCAuthentication()
|
||||||
{
|
{
|
||||||
if (gArgs.GetArg("-rpcpassword", "") == "")
|
if (gArgs.GetArg("-rpcpassword", "") == "")
|
||||||
{
|
{
|
||||||
LogInfo("Using random cookie authentication.\n");
|
|
||||||
|
|
||||||
std::optional<fs::perms> cookie_perms{std::nullopt};
|
std::optional<fs::perms> cookie_perms{std::nullopt};
|
||||||
auto cookie_perms_arg{gArgs.GetArg("-rpccookieperms")};
|
auto cookie_perms_arg{gArgs.GetArg("-rpccookieperms")};
|
||||||
if (cookie_perms_arg) {
|
if (cookie_perms_arg) {
|
||||||
auto perm_opt = InterpretPermString(*cookie_perms_arg);
|
auto perm_opt = InterpretPermString(*cookie_perms_arg);
|
||||||
if (!perm_opt) {
|
if (!perm_opt) {
|
||||||
LogInfo("Invalid -rpccookieperms=%s; must be one of 'owner', 'group', or 'all'.\n", *cookie_perms_arg);
|
LogError("Invalid -rpccookieperms=%s; must be one of 'owner', 'group', or 'all'.", *cookie_perms_arg);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
cookie_perms = *perm_opt;
|
cookie_perms = *perm_opt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(strRPCUserColonPass.empty()); // Only support initializing once
|
||||||
if (!GenerateAuthCookie(&strRPCUserColonPass, cookie_perms)) {
|
if (!GenerateAuthCookie(&strRPCUserColonPass, cookie_perms)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (strRPCUserColonPass.empty()) {
|
||||||
|
LogInfo("RPC authentication cookie file generation is disabled.");
|
||||||
|
} else {
|
||||||
|
LogInfo("Using random cookie authentication.");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LogPrintf("Config options rpcuser and rpcpassword will soon be deprecated. Locally-run instances may remove rpcuser to use cookie-based auth, or may be replaced with rpcauth. Please see share/rpcauth for rpcauth auth generation.\n");
|
LogPrintf("Config options rpcuser and rpcpassword will soon be deprecated. Locally-run instances may remove rpcuser to use cookie-based auth, or may be replaced with rpcauth. Please see share/rpcauth for rpcauth auth generation.\n");
|
||||||
strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
|
strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
|
||||||
|
|
|
@ -175,6 +175,8 @@ static fs::path GetPidFile(const ArgsManager& args)
|
||||||
|
|
||||||
[[nodiscard]] static bool CreatePidFile(const ArgsManager& args)
|
[[nodiscard]] static bool CreatePidFile(const ArgsManager& args)
|
||||||
{
|
{
|
||||||
|
if (args.IsArgNegated("-pid")) return true;
|
||||||
|
|
||||||
std::ofstream file{GetPidFile(args)};
|
std::ofstream file{GetPidFile(args)};
|
||||||
if (file) {
|
if (file) {
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
|
@ -483,7 +485,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
|
||||||
argsman.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Disables automatic broadcast and rebroadcast of transactions, unless the source peer has the 'forcerelay' permission. RPC transactions are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Disables automatic broadcast and rebroadcast of transactions, unless the source peer has the 'forcerelay' permission. RPC transactions are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-coinstatsindex", strprintf("Maintain coinstats index used by the gettxoutsetinfo RPC (default: %u)", DEFAULT_COINSTATSINDEX), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-coinstatsindex", strprintf("Maintain coinstats index used by the gettxoutsetinfo RPC (default: %u)", DEFAULT_COINSTATSINDEX), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-conf=<file>", strprintf("Specify path to read-only configuration file. Relative paths will be prefixed by datadir location (only useable from command line, not configuration file) (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-conf=<file>", strprintf("Specify path to read-only configuration file. Relative paths will be prefixed by datadir location (only useable from command line, not configuration file) (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-dbcache=<n>", strprintf("Maximum database cache size <n> MiB (minimum %d, default: %d). Make sure you have enough RAM. In addition, unused memory allocated to the mempool is shared with this cache (see -maxmempool).", nMinDbCache, nDefaultDbCache), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-dbcache=<n>", strprintf("Maximum database cache size <n> MiB (minimum %d, default: %d). Make sure you have enough RAM. In addition, unused memory allocated to the mempool is shared with this cache (see -maxmempool).", nMinDbCache, nDefaultDbCache), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-includeconf=<file>", "Specify additional configuration file, relative to the -datadir path (only useable from configuration file, not command line)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-includeconf=<file>", "Specify additional configuration file, relative to the -datadir path (only useable from configuration file, not command line)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <util/translation.h>
|
#include <util/translation.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -122,10 +123,13 @@ bool StartLogging(const ArgsManager& args)
|
||||||
|
|
||||||
// Only log conf file usage message if conf file actually exists.
|
// Only log conf file usage message if conf file actually exists.
|
||||||
fs::path config_file_path = args.GetConfigFilePath();
|
fs::path config_file_path = args.GetConfigFilePath();
|
||||||
if (fs::exists(config_file_path)) {
|
if (args.IsArgNegated("-conf")) {
|
||||||
|
LogInfo("Config file: <disabled>");
|
||||||
|
} else if (fs::is_directory(config_file_path)) {
|
||||||
|
LogWarning("Config file: %s (is directory, not file)", fs::PathToString(config_file_path));
|
||||||
|
} else if (fs::exists(config_file_path)) {
|
||||||
LogPrintf("Config file: %s\n", fs::PathToString(config_file_path));
|
LogPrintf("Config file: %s\n", fs::PathToString(config_file_path));
|
||||||
} else if (args.IsArgSet("-conf")) {
|
} else if (args.IsArgSet("-conf")) {
|
||||||
// Warn if no conf file exists at path provided by user
|
|
||||||
InitWarning(strprintf(_("The specified config file %s does not exist"), fs::PathToString(config_file_path)));
|
InitWarning(strprintf(_("The specified config file %s does not exist"), fs::PathToString(config_file_path)));
|
||||||
} else {
|
} else {
|
||||||
// Not categorizing as "Warning" because it's the default behavior
|
// Not categorizing as "Warning" because it's the default behavior
|
||||||
|
|
|
@ -582,8 +582,8 @@ int GuiMain(int argc, char* argv[])
|
||||||
|
|
||||||
// Show help message immediately after parsing command-line options (for "-lang") and setting locale,
|
// Show help message immediately after parsing command-line options (for "-lang") and setting locale,
|
||||||
// but before showing splash screen.
|
// but before showing splash screen.
|
||||||
if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
|
if (HelpRequested(gArgs) || gArgs.GetBoolArg("-version", false)) {
|
||||||
HelpMessageDialog help(nullptr, gArgs.IsArgSet("-version"));
|
HelpMessageDialog help(nullptr, gArgs.GetBoolArg("-version", false));
|
||||||
help.showOrPrint();
|
help.showOrPrint();
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,9 @@ static const char* const COOKIEAUTH_FILE = ".cookie";
|
||||||
static fs::path GetAuthCookieFile(bool temp=false)
|
static fs::path GetAuthCookieFile(bool temp=false)
|
||||||
{
|
{
|
||||||
fs::path arg = gArgs.GetPathArg("-rpccookiefile", COOKIEAUTH_FILE);
|
fs::path arg = gArgs.GetPathArg("-rpccookiefile", COOKIEAUTH_FILE);
|
||||||
|
if (arg.empty()) {
|
||||||
|
return {}; // -norpccookiefile was specified
|
||||||
|
}
|
||||||
if (temp) {
|
if (temp) {
|
||||||
arg += ".tmp";
|
arg += ".tmp";
|
||||||
}
|
}
|
||||||
|
@ -106,9 +109,12 @@ bool GenerateAuthCookie(std::string* cookie_out, std::optional<fs::perms> cookie
|
||||||
*/
|
*/
|
||||||
std::ofstream file;
|
std::ofstream file;
|
||||||
fs::path filepath_tmp = GetAuthCookieFile(true);
|
fs::path filepath_tmp = GetAuthCookieFile(true);
|
||||||
|
if (filepath_tmp.empty()) {
|
||||||
|
return true; // -norpccookiefile
|
||||||
|
}
|
||||||
file.open(filepath_tmp);
|
file.open(filepath_tmp);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
LogInfo("Unable to open cookie authentication file %s for writing\n", fs::PathToString(filepath_tmp));
|
LogWarning("Unable to open cookie authentication file %s for writing", fs::PathToString(filepath_tmp));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
file << cookie;
|
file << cookie;
|
||||||
|
@ -116,14 +122,14 @@ bool GenerateAuthCookie(std::string* cookie_out, std::optional<fs::perms> cookie
|
||||||
|
|
||||||
fs::path filepath = GetAuthCookieFile(false);
|
fs::path filepath = GetAuthCookieFile(false);
|
||||||
if (!RenameOver(filepath_tmp, filepath)) {
|
if (!RenameOver(filepath_tmp, filepath)) {
|
||||||
LogInfo("Unable to rename cookie authentication file %s to %s\n", fs::PathToString(filepath_tmp), fs::PathToString(filepath));
|
LogWarning("Unable to rename cookie authentication file %s to %s", fs::PathToString(filepath_tmp), fs::PathToString(filepath));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (cookie_perms) {
|
if (cookie_perms) {
|
||||||
std::error_code code;
|
std::error_code code;
|
||||||
fs::permissions(filepath, cookie_perms.value(), fs::perm_options::replace, code);
|
fs::permissions(filepath, cookie_perms.value(), fs::perm_options::replace, code);
|
||||||
if (code) {
|
if (code) {
|
||||||
LogInfo("Unable to set permissions on cookie authentication file %s\n", fs::PathToString(filepath_tmp));
|
LogWarning("Unable to set permissions on cookie authentication file %s", fs::PathToString(filepath));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,6 +148,9 @@ bool GetAuthCookie(std::string *cookie_out)
|
||||||
std::ifstream file;
|
std::ifstream file;
|
||||||
std::string cookie;
|
std::string cookie;
|
||||||
fs::path filepath = GetAuthCookieFile();
|
fs::path filepath = GetAuthCookieFile();
|
||||||
|
if (filepath.empty()) {
|
||||||
|
return true; // -norpccookiefile
|
||||||
|
}
|
||||||
file.open(filepath);
|
file.open(filepath);
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -79,11 +79,12 @@ def read_logs(tmp_dir):
|
||||||
Delegates to generator function get_log_events() to provide individual log events
|
Delegates to generator function get_log_events() to provide individual log events
|
||||||
for each of the input log files."""
|
for each of the input log files."""
|
||||||
|
|
||||||
# Find out what the folder is called that holds the debug.log file
|
# Find out what the folder is called that holds node 0's debug.log file
|
||||||
glob = pathlib.Path(tmp_dir).glob('node0/**/debug.log')
|
debug_logs = list(pathlib.Path(tmp_dir).glob('node0/**/debug.log'))
|
||||||
path = next(glob, None)
|
if len(debug_logs) > 0:
|
||||||
if path:
|
assert len(debug_logs) < 2, 'Max one debug.log is supported, ' \
|
||||||
assert next(glob, None) is None # more than one debug.log, should never happen
|
'found several:\n\t' + '\n\t'.join([str(f) for f in debug_logs])
|
||||||
|
path = debug_logs[0]
|
||||||
chain = re.search(r'node0/(.+?)/debug\.log$', path.as_posix()).group(1) # extract the chain name
|
chain = re.search(r'node0/(.+?)/debug\.log$', path.as_posix()).group(1) # extract the chain name
|
||||||
else:
|
else:
|
||||||
chain = 'regtest' # fallback to regtest (should only happen when none exists)
|
chain = 'regtest' # fallback to regtest (should only happen when none exists)
|
||||||
|
|
|
@ -27,9 +27,74 @@ class ConfArgsTest(BitcoinTestFramework):
|
||||||
self.wallet_names = []
|
self.wallet_names = []
|
||||||
self.disable_autoconnect = False
|
self.disable_autoconnect = False
|
||||||
|
|
||||||
|
# Overridden to avoid attempt to sync not yet started nodes.
|
||||||
|
def setup_network(self):
|
||||||
|
self.setup_nodes()
|
||||||
|
|
||||||
|
# Overriden to not start nodes automatically - doing so is the
|
||||||
|
# responsibility of each test function.
|
||||||
|
def setup_nodes(self):
|
||||||
|
self.add_nodes(self.num_nodes, self.extra_args)
|
||||||
|
# Ensure a log file exists as TestNode.assert_debug_log() expects it.
|
||||||
|
self.nodes[0].debug_log_path.parent.mkdir()
|
||||||
|
self.nodes[0].debug_log_path.touch()
|
||||||
|
|
||||||
|
def test_dir_config(self):
|
||||||
|
self.log.info('Error should be emitted if config file is a directory')
|
||||||
|
conf_path = self.nodes[0].datadir_path / 'bitcoin.conf'
|
||||||
|
os.rename(conf_path, conf_path.with_suffix('.confbkp'))
|
||||||
|
conf_path.mkdir()
|
||||||
|
self.stop_node(0)
|
||||||
|
self.nodes[0].assert_start_raises_init_error(
|
||||||
|
extra_args=['-regtest'],
|
||||||
|
expected_msg=f'Error: Error reading configuration file: Config file "{conf_path}" is a directory.',
|
||||||
|
)
|
||||||
|
conf_path.rmdir()
|
||||||
|
os.rename(conf_path.with_suffix('.confbkp'), conf_path)
|
||||||
|
|
||||||
|
self.log.debug('Verifying includeconf directive pointing to directory is caught')
|
||||||
|
with open(conf_path, 'a', encoding='utf-8') as conf:
|
||||||
|
conf.write(f'includeconf={self.nodes[0].datadir_path}\n')
|
||||||
|
self.nodes[0].assert_start_raises_init_error(
|
||||||
|
extra_args=['-regtest'],
|
||||||
|
expected_msg=f'Error: Error reading configuration file: Included config file "{self.nodes[0].datadir_path}" is a directory.',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.nodes[0].replace_in_config([(f'includeconf={self.nodes[0].datadir_path}', '')])
|
||||||
|
|
||||||
|
def test_negated_config(self):
|
||||||
|
self.log.info('Disabling configuration via -noconf')
|
||||||
|
|
||||||
|
conf_path = self.nodes[0].datadir_path / 'bitcoin.conf'
|
||||||
|
with open(conf_path, encoding='utf-8') as conf:
|
||||||
|
settings = [f'-{line.rstrip()}' for line in conf if len(line) > 1 and line[0] != '[']
|
||||||
|
os.rename(conf_path, conf_path.with_suffix('.confbkp'))
|
||||||
|
|
||||||
|
self.log.debug('Verifying garbage in config can be detected')
|
||||||
|
with open(conf_path, 'a', encoding='utf-8') as conf:
|
||||||
|
conf.write(f'garbage\n')
|
||||||
|
self.nodes[0].assert_start_raises_init_error(
|
||||||
|
extra_args=['-regtest'],
|
||||||
|
expected_msg='Error: Error reading configuration file: parse error on line 1: garbage',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.log.debug('Verifying that disabling of the config file means garbage inside of it does ' \
|
||||||
|
'not prevent the node from starting, and message about existing config file is logged')
|
||||||
|
ignored_file_message = [f'[InitConfig] Data directory "{self.nodes[0].datadir_path}" contains a "bitcoin.conf" file which is explicitly ignored using -noconf.']
|
||||||
|
with self.nodes[0].assert_debug_log(timeout=60, expected_msgs=ignored_file_message):
|
||||||
|
self.start_node(0, extra_args=settings + ['-noconf'])
|
||||||
|
self.stop_node(0)
|
||||||
|
|
||||||
|
self.log.debug('Verifying no message appears when removing config file')
|
||||||
|
os.remove(conf_path)
|
||||||
|
with self.nodes[0].assert_debug_log(timeout=60, expected_msgs=[], unexpected_msgs=ignored_file_message):
|
||||||
|
self.start_node(0, extra_args=settings + ['-noconf'])
|
||||||
|
self.stop_node(0)
|
||||||
|
|
||||||
|
os.rename(conf_path.with_suffix('.confbkp'), conf_path)
|
||||||
|
|
||||||
def test_config_file_parser(self):
|
def test_config_file_parser(self):
|
||||||
self.log.info('Test config file parser')
|
self.log.info('Test config file parser')
|
||||||
self.stop_node(0)
|
|
||||||
|
|
||||||
# Check that startup fails if conf= is set in bitcoin.conf or in an included conf file
|
# Check that startup fails if conf= is set in bitcoin.conf or in an included conf file
|
||||||
bad_conf_file_path = self.nodes[0].datadir_path / "bitcoin_bad.conf"
|
bad_conf_file_path = self.nodes[0].datadir_path / "bitcoin_bad.conf"
|
||||||
|
@ -162,12 +227,11 @@ class ConfArgsTest(BitcoinTestFramework):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_log_buffer(self):
|
def test_log_buffer(self):
|
||||||
self.stop_node(0)
|
|
||||||
with self.nodes[0].assert_debug_log(expected_msgs=['Warning: parsed potentially confusing double-negative -connect=0\n']):
|
with self.nodes[0].assert_debug_log(expected_msgs=['Warning: parsed potentially confusing double-negative -connect=0\n']):
|
||||||
self.start_node(0, extra_args=['-noconnect=0'])
|
self.start_node(0, extra_args=['-noconnect=0'])
|
||||||
|
self.stop_node(0)
|
||||||
|
|
||||||
def test_args_log(self):
|
def test_args_log(self):
|
||||||
self.stop_node(0)
|
|
||||||
self.log.info('Test config args logging')
|
self.log.info('Test config args logging')
|
||||||
with self.nodes[0].assert_debug_log(
|
with self.nodes[0].assert_debug_log(
|
||||||
expected_msgs=[
|
expected_msgs=[
|
||||||
|
@ -196,10 +260,10 @@ class ConfArgsTest(BitcoinTestFramework):
|
||||||
'-rpcuser=secret-rpcuser',
|
'-rpcuser=secret-rpcuser',
|
||||||
'-torpassword=secret-torpassword',
|
'-torpassword=secret-torpassword',
|
||||||
])
|
])
|
||||||
|
self.stop_node(0)
|
||||||
|
|
||||||
def test_networkactive(self):
|
def test_networkactive(self):
|
||||||
self.log.info('Test -networkactive option')
|
self.log.info('Test -networkactive option')
|
||||||
self.stop_node(0)
|
|
||||||
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
|
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
|
||||||
self.start_node(0)
|
self.start_node(0)
|
||||||
|
|
||||||
|
@ -222,16 +286,12 @@ class ConfArgsTest(BitcoinTestFramework):
|
||||||
self.stop_node(0)
|
self.stop_node(0)
|
||||||
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
|
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
|
||||||
self.start_node(0, extra_args=['-nonetworkactive=1'])
|
self.start_node(0, extra_args=['-nonetworkactive=1'])
|
||||||
|
self.stop_node(0)
|
||||||
|
|
||||||
def test_seed_peers(self):
|
def test_seed_peers(self):
|
||||||
self.log.info('Test seed peers')
|
self.log.info('Test seed peers')
|
||||||
default_data_dir = self.nodes[0].datadir_path
|
default_data_dir = self.nodes[0].datadir_path
|
||||||
peer_dat = default_data_dir / 'peers.dat'
|
peer_dat = default_data_dir / 'peers.dat'
|
||||||
# Only regtest has no fixed seeds. To avoid connections to random
|
|
||||||
# nodes, regtest is the only network where it is safe to enable
|
|
||||||
# -fixedseeds in tests
|
|
||||||
util.assert_equal(self.nodes[0].getblockchaininfo()['chain'],'regtest')
|
|
||||||
self.stop_node(0)
|
|
||||||
|
|
||||||
# No peers.dat exists and -dnsseed=1
|
# No peers.dat exists and -dnsseed=1
|
||||||
# We expect the node will use DNS Seeds, but Regtest mode does not have
|
# We expect the node will use DNS Seeds, but Regtest mode does not have
|
||||||
|
@ -248,6 +308,12 @@ class ConfArgsTest(BitcoinTestFramework):
|
||||||
timeout=10,
|
timeout=10,
|
||||||
):
|
):
|
||||||
self.start_node(0, extra_args=['-dnsseed=1', '-fixedseeds=1', f'-mocktime={start}'])
|
self.start_node(0, extra_args=['-dnsseed=1', '-fixedseeds=1', f'-mocktime={start}'])
|
||||||
|
|
||||||
|
# Only regtest has no fixed seeds. To avoid connections to random
|
||||||
|
# nodes, regtest is the only network where it is safe to enable
|
||||||
|
# -fixedseeds in tests
|
||||||
|
util.assert_equal(self.nodes[0].getblockchaininfo()['chain'],'regtest')
|
||||||
|
|
||||||
with self.nodes[0].assert_debug_log(expected_msgs=[
|
with self.nodes[0].assert_debug_log(expected_msgs=[
|
||||||
"Adding fixed seeds as 60 seconds have passed and addrman is empty",
|
"Adding fixed seeds as 60 seconds have passed and addrman is empty",
|
||||||
]):
|
]):
|
||||||
|
@ -294,13 +360,13 @@ class ConfArgsTest(BitcoinTestFramework):
|
||||||
"Adding fixed seeds as 60 seconds have passed and addrman is empty",
|
"Adding fixed seeds as 60 seconds have passed and addrman is empty",
|
||||||
]):
|
]):
|
||||||
self.nodes[0].setmocktime(start + 65)
|
self.nodes[0].setmocktime(start + 65)
|
||||||
|
self.stop_node(0)
|
||||||
|
|
||||||
def test_connect_with_seednode(self):
|
def test_connect_with_seednode(self):
|
||||||
self.log.info('Test -connect with -seednode')
|
self.log.info('Test -connect with -seednode')
|
||||||
seednode_ignored = ['-seednode is ignored when -connect is used\n']
|
seednode_ignored = ['-seednode is ignored when -connect is used\n']
|
||||||
dnsseed_ignored = ['-dnsseed is ignored when -connect is used and -proxy is specified\n']
|
dnsseed_ignored = ['-dnsseed is ignored when -connect is used and -proxy is specified\n']
|
||||||
addcon_thread_started = ['addcon thread start\n']
|
addcon_thread_started = ['addcon thread start\n']
|
||||||
self.stop_node(0)
|
|
||||||
|
|
||||||
# When -connect is supplied, expanding addrman via getaddr calls to ADDR_FETCH(-seednode)
|
# When -connect is supplied, expanding addrman via getaddr calls to ADDR_FETCH(-seednode)
|
||||||
# nodes is irrelevant and -seednode is ignored.
|
# nodes is irrelevant and -seednode is ignored.
|
||||||
|
@ -325,6 +391,7 @@ class ConfArgsTest(BitcoinTestFramework):
|
||||||
with self.nodes[0].assert_debug_log(expected_msgs=addcon_thread_started,
|
with self.nodes[0].assert_debug_log(expected_msgs=addcon_thread_started,
|
||||||
unexpected_msgs=seednode_ignored):
|
unexpected_msgs=seednode_ignored):
|
||||||
self.restart_node(0, extra_args=[connect_arg, '-seednode=fakeaddress2'])
|
self.restart_node(0, extra_args=[connect_arg, '-seednode=fakeaddress2'])
|
||||||
|
self.stop_node(0)
|
||||||
|
|
||||||
def test_ignored_conf(self):
|
def test_ignored_conf(self):
|
||||||
self.log.info('Test error is triggered when the datadir in use contains a bitcoin.conf file that would be ignored '
|
self.log.info('Test error is triggered when the datadir in use contains a bitcoin.conf file that would be ignored '
|
||||||
|
@ -423,6 +490,8 @@ class ConfArgsTest(BitcoinTestFramework):
|
||||||
self.test_networkactive()
|
self.test_networkactive()
|
||||||
self.test_connect_with_seednode()
|
self.test_connect_with_seednode()
|
||||||
|
|
||||||
|
self.test_dir_config()
|
||||||
|
self.test_negated_config()
|
||||||
self.test_config_file_parser()
|
self.test_config_file_parser()
|
||||||
self.test_config_file_log()
|
self.test_config_file_log()
|
||||||
self.test_invalid_command_line_options()
|
self.test_invalid_command_line_options()
|
||||||
|
|
|
@ -164,6 +164,9 @@ class TestBitcoinCli(BitcoinTestFramework):
|
||||||
self.log.info("Test connecting with non-existing RPC cookie file")
|
self.log.info("Test connecting with non-existing RPC cookie file")
|
||||||
assert_raises_process_error(1, "Could not locate RPC credentials", self.nodes[0].cli('-rpccookiefile=does-not-exist', '-rpcpassword=').echo)
|
assert_raises_process_error(1, "Could not locate RPC credentials", self.nodes[0].cli('-rpccookiefile=does-not-exist', '-rpcpassword=').echo)
|
||||||
|
|
||||||
|
self.log.info("Test connecting without RPC cookie file and with password arg")
|
||||||
|
assert_equal(BLOCKS, self.nodes[0].cli('-norpccookiefile', f'-rpcuser={user}', f'-rpcpassword={password}').getblockcount())
|
||||||
|
|
||||||
self.log.info("Test -getinfo with arguments fails")
|
self.log.info("Test -getinfo with arguments fails")
|
||||||
assert_raises_process_error(1, "-getinfo takes no arguments", self.nodes[0].cli('-getinfo').help)
|
assert_raises_process_error(1, "-getinfo takes no arguments", self.nodes[0].cli('-getinfo').help)
|
||||||
|
|
||||||
|
|
|
@ -22,13 +22,13 @@ import sys
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
def call_with_auth(node, user, password):
|
def call_with_auth(node, user, password, method="getbestblockhash"):
|
||||||
url = urllib.parse.urlparse(node.url)
|
url = urllib.parse.urlparse(node.url)
|
||||||
headers = {"Authorization": "Basic " + str_to_b64str('{}:{}'.format(user, password))}
|
headers = {"Authorization": "Basic " + str_to_b64str('{}:{}'.format(user, password))}
|
||||||
|
|
||||||
conn = http.client.HTTPConnection(url.hostname, url.port)
|
conn = http.client.HTTPConnection(url.hostname, url.port)
|
||||||
conn.connect()
|
conn.connect()
|
||||||
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
|
conn.request('POST', '/', f'{{"method": "{method}"}}', headers)
|
||||||
resp = conn.getresponse()
|
resp = conn.getresponse()
|
||||||
conn.close()
|
conn.close()
|
||||||
return resp
|
return resp
|
||||||
|
@ -121,6 +121,25 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
||||||
for perm in ["owner", "group", "all"]:
|
for perm in ["owner", "group", "all"]:
|
||||||
test_perm(perm)
|
test_perm(perm)
|
||||||
|
|
||||||
|
def test_norpccookiefile(self, node0_cookie_path):
|
||||||
|
assert self.nodes[0].is_node_stopped(), "We expect previous test to stopped the node"
|
||||||
|
assert not node0_cookie_path.exists()
|
||||||
|
|
||||||
|
self.log.info('Starting with -norpccookiefile')
|
||||||
|
# Start, but don't wait for RPC connection as TestNode.wait_for_rpc_connection() requires the cookie.
|
||||||
|
with self.nodes[0].busy_wait_for_debug_log([b'init message: Done loading']):
|
||||||
|
self.nodes[0].start(extra_args=["-norpccookiefile"])
|
||||||
|
assert not node0_cookie_path.exists()
|
||||||
|
|
||||||
|
self.log.info('Testing user/password authentication still works without cookie file')
|
||||||
|
assert_equal(200, call_with_auth(self.nodes[0], "rt", self.rtpassword).status)
|
||||||
|
# After confirming that we could log in, check that cookie file does not exist.
|
||||||
|
assert not node0_cookie_path.exists()
|
||||||
|
|
||||||
|
# Need to shut down in slightly unorthodox way since cookie auth can't be used
|
||||||
|
assert_equal(200, call_with_auth(self.nodes[0], "rt", self.rtpassword, method="stop").status)
|
||||||
|
self.nodes[0].wait_until_stopped()
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
self.conf_setup()
|
self.conf_setup()
|
||||||
self.log.info('Check correctness of the rpcauth config option')
|
self.log.info('Check correctness of the rpcauth config option')
|
||||||
|
@ -166,11 +185,19 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
||||||
self.stop_node(0)
|
self.stop_node(0)
|
||||||
|
|
||||||
self.log.info('Check that failure to write cookie file will abort the node gracefully')
|
self.log.info('Check that failure to write cookie file will abort the node gracefully')
|
||||||
(self.nodes[0].chain_path / ".cookie.tmp").mkdir()
|
cookie_path = self.nodes[0].chain_path / ".cookie"
|
||||||
|
cookie_path_tmp = self.nodes[0].chain_path / ".cookie.tmp"
|
||||||
|
cookie_path_tmp.mkdir()
|
||||||
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error)
|
self.nodes[0].assert_start_raises_init_error(expected_msg=init_error)
|
||||||
|
cookie_path_tmp.rmdir()
|
||||||
|
assert not cookie_path.exists()
|
||||||
|
self.restart_node(0)
|
||||||
|
assert cookie_path.exists()
|
||||||
|
self.stop_node(0)
|
||||||
|
|
||||||
self.test_rpccookieperms()
|
self.test_rpccookieperms()
|
||||||
|
|
||||||
|
self.test_norpccookiefile(cookie_path)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
HTTPBasicsTest(__file__).main()
|
HTTPBasicsTest(__file__).main()
|
||||||
|
|
Loading…
Add table
Reference in a new issue