Skip to content

Commit e53310c

Browse files
committed
Merge bitcoin/bitcoin#30529: Fix -norpcwhitelist, -norpcallowip, and similar corner case behavior
a85e8c0 doc: Add some general documentation about negated options (Ryan Ofsky) 490c8fa doc: Add release notes summarizing negated option behavior changes. (Ryan Ofsky) 458ef0a refactor: Avoid using IsArgSet() on -connect list option (Ryan Ofsky) 752ab9c test: Add test to make sure -noconnect disables -dnsseed and -listen by default (Ryan Ofsky) 3c2920e refactor: Avoid using IsArgSet() on -signetseednode and -signetchallenge list options (Ryan Ofsky) d056689 refactor: Avoid using IsArgSet() on -debug, -loglevel, and -vbparams list options (Ryan Ofsky) 3d1e8ca Normalize inconsistent -noexternalip behavior (Ryan Ofsky) ecd590d Normalize inconsistent -noonlynet behavior (Ryan Ofsky) 5544a19 Fix nonsensical bitcoin-cli -norpcwallet behavior (Ryan Ofsky) 6e8e7f4 Fix nonsensical -noasmap behavior (Ryan Ofsky) b6ab350 Fix nonsensical -notest behavior (Ryan Ofsky) 6768389 Fix nonsensical -norpcwhitelist behavior (Ryan Ofsky) e03409c Fix nonsensical -norpcbind and -norpcallowip behavior (Ryan Ofsky) 40c4899 Fix nonsensical -nobind and -nowhitebind behavior (Ryan Ofsky) 5453e66 Fix nonsensical -noseednode behavior (Ryan Ofsky) Pull request description: The PR changes behavior of negated `-noseednode`, `-nobind`, `-nowhitebind`, `-norpcbind`, `-norpcallowip`, `-norpcwhitelist`, `-notest`, `-noasmap`, `-norpcwallet`, `-noonlynet`, and `-noexternalip` options, so negating these options just clears previously specified values doesn't have other side effects. Negating options on the command line can be a useful way of resetting options that may have been set earlier in the command line or config file. But before this change, negating these options wouldn't fully reset them, and would have confusing and undocumented side effects (see commit descriptions for details). Now, negating these options just resets them and behaves the same as not specifying them. Motivation for this PR is to fix confusing behaviors and also to remove incorrect usages of the `IsArgSet()` function. Using `IsArgSet()` tends to lead to negated option bugs in general, but it especially causes bugs when used with list settings returned by `GetArgs()`, because when these settings are negated, `IsArgSet()` will return true but `GetArgs()` will return an empty list. This PR eliminates all uses of `IsArgSet()` and `GetArgs()` together, and followup PR #17783 makes it an error to use `IsArgSet()` on list settings, since calling `IsArgSet()` is never actually necessary. Most of the changes here were originally made in #17783 and then moved here to be easier to review and avoid a dependency on #16545. ACKs for top commit: achow101: ACK a85e8c0 danielabrozzoni: re-ACK a85e8c0 hodlinator: re-ACK a85e8c0 Tree-SHA512: dd4b19faac923aeaa647b1c241d929609ce8242b43e3b7bc32523cc48ec92a83ac0dc5aee79f1eba8794535e0314b96cb151fd04ac973671a1ebb9b52dd16697
2 parents 254fd89 + a85e8c0 commit e53310c

13 files changed

+95
-41
lines changed

doc/bitcoin-conf.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ Changes to the configuration file while `bitcoind` or `bitcoin-qt` is running on
88

99
Users should never make any configuration changes which they do not understand. Furthermore, users should always be wary of accepting any configuration changes provided to them by another source (even if they believe that they do understand them).
1010

11+
## Configuration File Precedence
12+
13+
Options specified in the configuration file can be overridden by options in the [`settings.json` file](files.md) and by options specified on the command line.
14+
15+
The `settings.json` file contains dynamic settings that are set by the Bitcoin Core GUI and RPCs at runtime, and augment or replace the static settings specified in the `bitcoin.conf` file.
16+
17+
Command line options also augment or replace `bitcoin.conf` options, and can be useful for scripting and debugging.
18+
19+
It is possible to see which setting values are in use by checking `debug.log` output. Any unrecognized options that are found in `bitcoin.conf` also show up as warnings in `debug.log` output.
20+
1121
## Configuration File Format
1222

1323
The configuration file is a plain text file and consists of `option=value` entries, one per line. Leading and trailing whitespaces are removed.
@@ -49,6 +59,14 @@ regtest.rpcport=3000
4959
rpcport=4000
5060
```
5161

62+
### Negated options
63+
64+
Almost all options can be negated by being specified with a `no` prefix. For example an option `-foo` could be negated by writing `nofoo=1` or `nofoo=` in the configuration file or `-nofoo=1` or `-nofoo` on the command line.
65+
66+
In general, negating an option is like setting it to `0` if it is a boolean or integer option, and setting it to an empty string or path or list if it is a string or path or list option.
67+
68+
However, there are exceptions to this general rule. For example, it is an error to negate some options (e.g. `-nodatadir` is disallowed), and some negated strings are treated like `"0"` instead of `""` (e.g. `-noproxy` is treated like `-proxy=0`), and some negating some lists can have side effects in addition to clearing the lists (e.g. `-noconnect` disables automatic connections in addition to dropping any manual connections specified previously with `-connect=<host>`). When there are exceptions to the rule, they should either be obvious from context, or should be mentioned in usage documentation. Nonobvious, undocumented exceptions should be reported as bugs.
69+
5270
## Configuration File Path
5371

5472
The configuration file is not automatically created; you can create it using your favorite text editor. By default, the configuration file name is `bitcoin.conf` and it is located in the Bitcoin data directory, but both the Bitcoin data directory and the configuration file path may be changed using the `-datadir` and `-conf` command-line options.

doc/release-notes-30529.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Configuration
2+
-------------
3+
4+
Handling of negated `-noseednode`, `-nobind`, `-nowhitebind`, `-norpcbind`, `-norpcallowip`, `-norpcwhitelist`, `-notest`, `-noasmap`, `-norpcwallet`, `-noonlynet`, and `-noexternalip` options has changed. Previously negating these options had various confusing and undocumented side effects. Now negating them just resets the settings and restores default behaviors, as if the options were not specified.

src/bitcoin-cli.cpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ static void SetupCliArgs(ArgsManager& argsman)
110110
argsman.AddArg("-stdinwalletpassphrase", "Read wallet passphrase from standard input as a single line. When combined with -stdin, the first line from standard input is used for the wallet passphrase.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
111111
}
112112

113+
std::optional<std::string> RpcWalletName(const ArgsManager& args)
114+
{
115+
// Check IsArgNegated to return nullopt instead of "0" if -norpcwallet is specified
116+
if (args.IsArgNegated("-rpcwallet")) return std::nullopt;
117+
return args.GetArg("-rpcwallet");
118+
}
119+
113120
/** libevent event log callback */
114121
static void libevent_log_cb(int severity, const char *msg)
115122
{
@@ -1183,10 +1190,8 @@ static void ParseGetInfoResult(UniValue& result)
11831190
*/
11841191
static UniValue GetNewAddress()
11851192
{
1186-
std::optional<std::string> wallet_name{};
1187-
if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
11881193
DefaultRequestHandler rh;
1189-
return ConnectAndCallRPC(&rh, "getnewaddress", /* args=*/{}, wallet_name);
1194+
return ConnectAndCallRPC(&rh, "getnewaddress", /* args=*/{}, RpcWalletName(gArgs));
11901195
}
11911196

11921197
/**
@@ -1291,16 +1296,15 @@ static int CommandLineRPC(int argc, char *argv[])
12911296
}
12921297
if (nRet == 0) {
12931298
// Perform RPC call
1294-
std::optional<std::string> wallet_name{};
1295-
if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
1299+
const std::optional<std::string> wallet_name{RpcWalletName(gArgs)};
12961300
const UniValue reply = ConnectAndCallRPC(rh.get(), method, args, wallet_name);
12971301

12981302
// Parse reply
12991303
UniValue result = reply.find_value("result");
13001304
const UniValue& error = reply.find_value("error");
13011305
if (error.isNull()) {
13021306
if (gArgs.GetBoolArg("-getinfo", false)) {
1303-
if (!gArgs.IsArgSet("-rpcwallet")) {
1307+
if (!wallet_name) {
13041308
GetWalletBalances(result); // fetch multiwallet balances and append to result
13051309
}
13061310
ParseGetInfoResult(result);

src/chainparams.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ using util::SplitString;
2525

2626
void ReadSigNetArgs(const ArgsManager& args, CChainParams::SigNetOptions& options)
2727
{
28-
if (args.IsArgSet("-signetseednode")) {
28+
if (!args.GetArgs("-signetseednode").empty()) {
2929
options.seeds.emplace(args.GetArgs("-signetseednode"));
3030
}
31-
if (args.IsArgSet("-signetchallenge")) {
31+
if (!args.GetArgs("-signetchallenge").empty()) {
3232
const auto signet_challenge = args.GetArgs("-signetchallenge");
3333
if (signet_challenge.size() != 1) {
3434
throw std::runtime_error("-signetchallenge cannot be multiple values.");
@@ -66,8 +66,6 @@ void ReadRegTestArgs(const ArgsManager& args, CChainParams::RegTestOptions& opti
6666
}
6767
}
6868

69-
if (!args.IsArgSet("-vbparams")) return;
70-
7169
for (const std::string& strDeployment : args.GetArgs("-vbparams")) {
7270
std::vector<std::string> vDeploymentParams = SplitString(strDeployment, ':');
7371
if (vDeploymentParams.size() < 3 || 4 < vDeploymentParams.size()) {

src/httprpc.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ static bool InitRPCAuthentication()
334334
}
335335
}
336336

337-
g_rpc_whitelist_default = gArgs.GetBoolArg("-rpcwhitelistdefault", gArgs.IsArgSet("-rpcwhitelist"));
337+
g_rpc_whitelist_default = gArgs.GetBoolArg("-rpcwhitelistdefault", !gArgs.GetArgs("-rpcwhitelist").empty());
338338
for (const std::string& strRPCWhitelist : gArgs.GetArgs("-rpcwhitelist")) {
339339
auto pos = strRPCWhitelist.find(':');
340340
std::string strUser = strRPCWhitelist.substr(0, pos);

src/httpserver.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -362,16 +362,20 @@ static bool HTTPBindAddresses(struct evhttp* http)
362362
std::vector<std::pair<std::string, uint16_t>> endpoints;
363363

364364
// Determine what addresses to bind to
365-
if (!(gArgs.IsArgSet("-rpcallowip") && gArgs.IsArgSet("-rpcbind"))) { // Default to loopback if not allowing external IPs
365+
// To prevent misconfiguration and accidental exposure of the RPC
366+
// interface, require -rpcallowip and -rpcbind to both be specified
367+
// together. If either is missing, ignore both values, bind to localhost
368+
// instead, and log warnings.
369+
if (gArgs.GetArgs("-rpcallowip").empty() || gArgs.GetArgs("-rpcbind").empty()) { // Default to loopback if not allowing external IPs
366370
endpoints.emplace_back("::1", http_port);
367371
endpoints.emplace_back("127.0.0.1", http_port);
368-
if (gArgs.IsArgSet("-rpcallowip")) {
372+
if (!gArgs.GetArgs("-rpcallowip").empty()) {
369373
LogPrintf("WARNING: option -rpcallowip was specified without -rpcbind; this doesn't usually make sense\n");
370374
}
371-
if (gArgs.IsArgSet("-rpcbind")) {
375+
if (!gArgs.GetArgs("-rpcbind").empty()) {
372376
LogPrintf("WARNING: option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect\n");
373377
}
374-
} else if (gArgs.IsArgSet("-rpcbind")) { // Specific bind address
378+
} else { // Specific bind addresses
375379
for (const std::string& strRPCBind : gArgs.GetArgs("-rpcbind")) {
376380
uint16_t port{http_port};
377381
std::string host;

src/init.cpp

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -716,17 +716,18 @@ void InitParameterInteraction(ArgsManager& args)
716716
{
717717
// when specifying an explicit binding address, you want to listen on it
718718
// even when -connect or -proxy is specified
719-
if (args.IsArgSet("-bind")) {
719+
if (!args.GetArgs("-bind").empty()) {
720720
if (args.SoftSetBoolArg("-listen", true))
721721
LogInfo("parameter interaction: -bind set -> setting -listen=1\n");
722722
}
723-
if (args.IsArgSet("-whitebind")) {
723+
if (!args.GetArgs("-whitebind").empty()) {
724724
if (args.SoftSetBoolArg("-listen", true))
725725
LogInfo("parameter interaction: -whitebind set -> setting -listen=1\n");
726726
}
727727

728-
if (args.IsArgSet("-connect") || args.GetIntArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS) <= 0) {
728+
if (!args.GetArgs("-connect").empty() || args.IsArgNegated("-connect") || args.GetIntArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS) <= 0) {
729729
// when only connecting to trusted nodes, do not seed via DNS, or listen by default
730+
// do the same when connections are disabled
730731
if (args.SoftSetBoolArg("-dnsseed", false))
731732
LogInfo("parameter interaction: -connect or -maxconnections=0 set -> setting -dnsseed=0\n");
732733
if (args.SoftSetBoolArg("-listen", false))
@@ -762,7 +763,7 @@ void InitParameterInteraction(ArgsManager& args)
762763
}
763764
}
764765

765-
if (args.IsArgSet("-externalip")) {
766+
if (!args.GetArgs("-externalip").empty()) {
766767
// if an explicit public IP is specified, do not try to find others
767768
if (args.SoftSetBoolArg("-discover", false))
768769
LogInfo("parameter interaction: -externalip set -> setting -discover=0\n");
@@ -782,8 +783,8 @@ void InitParameterInteraction(ArgsManager& args)
782783
if (args.SoftSetBoolArg("-whitelistrelay", true))
783784
LogInfo("parameter interaction: -whitelistforcerelay=1 -> setting -whitelistrelay=1\n");
784785
}
785-
if (args.IsArgSet("-onlynet")) {
786-
const auto onlynets = args.GetArgs("-onlynet");
786+
const auto onlynets = args.GetArgs("-onlynet");
787+
if (!onlynets.empty()) {
787788
bool clearnet_reachable = std::any_of(onlynets.begin(), onlynets.end(), [](const auto& net) {
788789
const auto n = ParseNetwork(net);
789790
return n == NET_IPV4 || n == NET_IPV6;
@@ -1044,12 +1045,12 @@ bool AppInitParameterInteraction(const ArgsManager& args)
10441045
if (args.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS))
10451046
g_local_services = ServiceFlags(g_local_services | NODE_BLOOM);
10461047

1047-
if (args.IsArgSet("-test")) {
1048+
const std::vector<std::string> test_options = args.GetArgs("-test");
1049+
if (!test_options.empty()) {
10481050
if (chainparams.GetChainType() != ChainType::REGTEST) {
10491051
return InitError(Untranslated("-test=<option> can only be used with regtest"));
10501052
}
1051-
const std::vector<std::string> options = args.GetArgs("-test");
1052-
for (const std::string& option : options) {
1053+
for (const std::string& option : test_options) {
10531054
auto it = std::find_if(TEST_OPTIONS_DOC.begin(), TEST_OPTIONS_DOC.end(), [&option](const std::string& doc_option) {
10541055
size_t pos = doc_option.find(" (");
10551056
return (pos != std::string::npos) && (doc_option.substr(0, pos) == option);
@@ -1433,7 +1434,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
14331434

14341435
// Read asmap file if configured
14351436
std::vector<bool> asmap;
1436-
if (args.IsArgSet("-asmap")) {
1437+
if (args.IsArgSet("-asmap") && !args.IsArgNegated("-asmap")) {
14371438
fs::path asmap_path = args.GetPathArg("-asmap", DEFAULT_ASMAP_FILENAME);
14381439
if (!asmap_path.is_absolute()) {
14391440
asmap_path = args.GetDataDirNet() / asmap_path;
@@ -1511,9 +1512,10 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
15111512
strSubVersion.size(), MAX_SUBVERSION_LENGTH));
15121513
}
15131514

1514-
if (args.IsArgSet("-onlynet")) {
1515+
const auto onlynets = args.GetArgs("-onlynet");
1516+
if (!onlynets.empty()) {
15151517
g_reachable_nets.RemoveAll();
1516-
for (const std::string& snet : args.GetArgs("-onlynet")) {
1518+
for (const std::string& snet : onlynets) {
15171519
enum Network net = ParseNetwork(snet);
15181520
if (net == NET_UNROUTABLE)
15191521
return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'"), snet));
@@ -1522,7 +1524,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
15221524
}
15231525

15241526
if (!args.IsArgSet("-cjdnsreachable")) {
1525-
if (args.IsArgSet("-onlynet") && g_reachable_nets.Contains(NET_CJDNS)) {
1527+
if (!onlynets.empty() && g_reachable_nets.Contains(NET_CJDNS)) {
15261528
return InitError(
15271529
_("Outbound connections restricted to CJDNS (-onlynet=cjdns) but "
15281530
"-cjdnsreachable is not provided"));
@@ -1573,7 +1575,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
15731575
onion_proxy = addrProxy;
15741576
}
15751577

1576-
const bool onlynet_used_with_onion{args.IsArgSet("-onlynet") && g_reachable_nets.Contains(NET_ONION)};
1578+
const bool onlynet_used_with_onion{!onlynets.empty() && g_reachable_nets.Contains(NET_ONION)};
15771579

15781580
// -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses
15791581
// -noonion (or -onion=0) disables connecting to .onion entirely
@@ -1994,10 +1996,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
19941996

19951997
connOptions.vSeedNodes = args.GetArgs("-seednode");
19961998

1997-
// Initiate outbound connections unless connect=0
1998-
connOptions.m_use_addrman_outgoing = !args.IsArgSet("-connect");
1999-
if (!connOptions.m_use_addrman_outgoing) {
2000-
const auto connect = args.GetArgs("-connect");
1999+
const auto connect = args.GetArgs("-connect");
2000+
if (!connect.empty() || args.IsArgNegated("-connect")) {
2001+
// Do not initiate other outgoing connections when connecting to trusted
2002+
// nodes, or when -noconnect is specified.
2003+
connOptions.m_use_addrman_outgoing = false;
2004+
20012005
if (connect.size() != 1 || connect[0] != "0") {
20022006
connOptions.m_specified_outgoing = connect;
20032007
}
@@ -2018,7 +2022,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
20182022
}
20192023
SetProxy(NET_I2P, Proxy{addr.value()});
20202024
} else {
2021-
if (args.IsArgSet("-onlynet") && g_reachable_nets.Contains(NET_I2P)) {
2025+
if (!onlynets.empty() && g_reachable_nets.Contains(NET_I2P)) {
20222026
return InitError(
20232027
_("Outbound connections restricted to i2p (-onlynet=i2p) but "
20242028
"-i2psam is not provided"));

src/init/common.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ void SetLoggingOptions(const ArgsManager& args)
5858

5959
util::Result<void> SetLoggingLevel(const ArgsManager& args)
6060
{
61-
if (args.IsArgSet("-loglevel")) {
6261
for (const std::string& level_str : args.GetArgs("-loglevel")) {
6362
if (level_str.find_first_of(':', 3) == std::string::npos) {
6463
// user passed a global log level, i.e. -loglevel=<level>
@@ -73,13 +72,11 @@ util::Result<void> SetLoggingLevel(const ArgsManager& args)
7372
}
7473
}
7574
}
76-
}
7775
return {};
7876
}
7977

8078
util::Result<void> SetLoggingCategories(const ArgsManager& args)
8179
{
82-
if (args.IsArgSet("-debug")) {
8380
const std::vector<std::string> categories = args.GetArgs("-debug");
8481

8582
// Special-case: Disregard any debugging categories appearing before -debug=0/none
@@ -93,7 +90,6 @@ util::Result<void> SetLoggingCategories(const ArgsManager& args)
9390
return util::Error{strprintf(_("Unsupported logging category %s=%s."), "-debug", cat)};
9491
}
9592
}
96-
}
9793

9894
// Now remove the logging categories which were explicitly excluded
9995
for (const std::string& cat : args.GetArgs("-debugexclude")) {

src/net.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2246,7 +2246,7 @@ void CConnman::ThreadDNSAddressSeed()
22462246
{
22472247
int outbound_connection_count = 0;
22482248

2249-
if (gArgs.IsArgSet("-seednode")) {
2249+
if (!gArgs.GetArgs("-seednode").empty()) {
22502250
auto start = NodeClock::now();
22512251
constexpr std::chrono::seconds SEEDNODE_TIMEOUT = 30s;
22522252
LogPrintf("-seednode enabled. Trying the provided seeds for %d seconds before defaulting to the dnsseeds.\n", SEEDNODE_TIMEOUT.count());
@@ -2549,7 +2549,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect, Spa
25492549
auto next_extra_network_peer{start + rng.rand_exp_duration(EXTRA_NETWORK_PEER_INTERVAL)};
25502550
const bool dnsseed = gArgs.GetBoolArg("-dnsseed", DEFAULT_DNSSEED);
25512551
bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS);
2552-
const bool use_seednodes{gArgs.IsArgSet("-seednode")};
2552+
const bool use_seednodes{!gArgs.GetArgs("-seednode").empty()};
25532553

25542554
auto seed_node_timer = NodeClock::now();
25552555
bool add_addr_fetch{addrman.Size() == 0 && !seed_nodes.empty()};

src/torcontrol.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlRe
403403
const auto onlynets = gArgs.GetArgs("-onlynet");
404404

405405
const bool onion_allowed_by_onlynet{
406-
!gArgs.IsArgSet("-onlynet") ||
406+
onlynets.empty() ||
407407
std::any_of(onlynets.begin(), onlynets.end(), [](const auto& n) {
408408
return ParseNetwork(n) == NET_ONION;
409409
})};

0 commit comments

Comments
 (0)