0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-22 12:23:34 -05:00

Merge bitcoin/bitcoin#30529: Fix -norpcwhitelist, -norpcallowip, and similar corner case behavior

a85e8c0e61 doc: Add some general documentation about negated options (Ryan Ofsky)
490c8fa178 doc: Add release notes summarizing negated option behavior changes. (Ryan Ofsky)
458ef0a11b refactor: Avoid using IsArgSet() on -connect list option (Ryan Ofsky)
752ab9c3c6 test: Add test to make sure -noconnect disables -dnsseed and -listen by default (Ryan Ofsky)
3c2920ec98 refactor: Avoid using IsArgSet() on -signetseednode and -signetchallenge list options (Ryan Ofsky)
d05668922a refactor: Avoid using IsArgSet() on -debug, -loglevel, and -vbparams list options (Ryan Ofsky)
3d1e8ca53a Normalize inconsistent -noexternalip behavior (Ryan Ofsky)
ecd590d4c1 Normalize inconsistent -noonlynet behavior (Ryan Ofsky)
5544a19f86 Fix nonsensical bitcoin-cli -norpcwallet behavior (Ryan Ofsky)
6e8e7f433f Fix nonsensical -noasmap behavior (Ryan Ofsky)
b6ab350806 Fix nonsensical -notest behavior (Ryan Ofsky)
6768389917 Fix nonsensical -norpcwhitelist behavior (Ryan Ofsky)
e03409c70f Fix nonsensical -norpcbind and -norpcallowip behavior (Ryan Ofsky)
40c4899bc2 Fix nonsensical -nobind and -nowhitebind behavior (Ryan Ofsky)
5453e66fd9 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 a85e8c0e61
  danielabrozzoni:
    re-ACK a85e8c0e61
  hodlinator:
    re-ACK a85e8c0e61

Tree-SHA512: dd4b19faac923aeaa647b1c241d929609ce8242b43e3b7bc32523cc48ec92a83ac0dc5aee79f1eba8794535e0314b96cb151fd04ac973671a1ebb9b52dd16697
This commit is contained in:
Ava Chow 2025-02-14 15:10:09 -08:00
commit e53310c47a
No known key found for this signature in database
GPG key ID: 17565732E08E5E41
13 changed files with 95 additions and 41 deletions

View file

@ -8,6 +8,16 @@ Changes to the configuration file while `bitcoind` or `bitcoin-qt` is running on
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).
## Configuration File Precedence
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.
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.
Command line options also augment or replace `bitcoin.conf` options, and can be useful for scripting and debugging.
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.
## Configuration File Format
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
rpcport=4000
```
### Negated options
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.
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.
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.
## Configuration File Path
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.

View file

@ -0,0 +1,4 @@
Configuration
-------------
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.

View file

@ -110,6 +110,13 @@ static void SetupCliArgs(ArgsManager& argsman)
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);
}
std::optional<std::string> RpcWalletName(const ArgsManager& args)
{
// Check IsArgNegated to return nullopt instead of "0" if -norpcwallet is specified
if (args.IsArgNegated("-rpcwallet")) return std::nullopt;
return args.GetArg("-rpcwallet");
}
/** libevent event log callback */
static void libevent_log_cb(int severity, const char *msg)
{
@ -1183,10 +1190,8 @@ static void ParseGetInfoResult(UniValue& result)
*/
static UniValue GetNewAddress()
{
std::optional<std::string> wallet_name{};
if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
DefaultRequestHandler rh;
return ConnectAndCallRPC(&rh, "getnewaddress", /* args=*/{}, wallet_name);
return ConnectAndCallRPC(&rh, "getnewaddress", /* args=*/{}, RpcWalletName(gArgs));
}
/**
@ -1291,8 +1296,7 @@ static int CommandLineRPC(int argc, char *argv[])
}
if (nRet == 0) {
// Perform RPC call
std::optional<std::string> wallet_name{};
if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
const std::optional<std::string> wallet_name{RpcWalletName(gArgs)};
const UniValue reply = ConnectAndCallRPC(rh.get(), method, args, wallet_name);
// Parse reply
@ -1300,7 +1304,7 @@ static int CommandLineRPC(int argc, char *argv[])
const UniValue& error = reply.find_value("error");
if (error.isNull()) {
if (gArgs.GetBoolArg("-getinfo", false)) {
if (!gArgs.IsArgSet("-rpcwallet")) {
if (!wallet_name) {
GetWalletBalances(result); // fetch multiwallet balances and append to result
}
ParseGetInfoResult(result);

View file

@ -25,10 +25,10 @@ using util::SplitString;
void ReadSigNetArgs(const ArgsManager& args, CChainParams::SigNetOptions& options)
{
if (args.IsArgSet("-signetseednode")) {
if (!args.GetArgs("-signetseednode").empty()) {
options.seeds.emplace(args.GetArgs("-signetseednode"));
}
if (args.IsArgSet("-signetchallenge")) {
if (!args.GetArgs("-signetchallenge").empty()) {
const auto signet_challenge = args.GetArgs("-signetchallenge");
if (signet_challenge.size() != 1) {
throw std::runtime_error("-signetchallenge cannot be multiple values.");
@ -66,8 +66,6 @@ void ReadRegTestArgs(const ArgsManager& args, CChainParams::RegTestOptions& opti
}
}
if (!args.IsArgSet("-vbparams")) return;
for (const std::string& strDeployment : args.GetArgs("-vbparams")) {
std::vector<std::string> vDeploymentParams = SplitString(strDeployment, ':');
if (vDeploymentParams.size() < 3 || 4 < vDeploymentParams.size()) {

View file

@ -334,7 +334,7 @@ static bool InitRPCAuthentication()
}
}
g_rpc_whitelist_default = gArgs.GetBoolArg("-rpcwhitelistdefault", gArgs.IsArgSet("-rpcwhitelist"));
g_rpc_whitelist_default = gArgs.GetBoolArg("-rpcwhitelistdefault", !gArgs.GetArgs("-rpcwhitelist").empty());
for (const std::string& strRPCWhitelist : gArgs.GetArgs("-rpcwhitelist")) {
auto pos = strRPCWhitelist.find(':');
std::string strUser = strRPCWhitelist.substr(0, pos);

View file

@ -362,16 +362,20 @@ static bool HTTPBindAddresses(struct evhttp* http)
std::vector<std::pair<std::string, uint16_t>> endpoints;
// Determine what addresses to bind to
if (!(gArgs.IsArgSet("-rpcallowip") && gArgs.IsArgSet("-rpcbind"))) { // Default to loopback if not allowing external IPs
// To prevent misconfiguration and accidental exposure of the RPC
// interface, require -rpcallowip and -rpcbind to both be specified
// together. If either is missing, ignore both values, bind to localhost
// instead, and log warnings.
if (gArgs.GetArgs("-rpcallowip").empty() || gArgs.GetArgs("-rpcbind").empty()) { // Default to loopback if not allowing external IPs
endpoints.emplace_back("::1", http_port);
endpoints.emplace_back("127.0.0.1", http_port);
if (gArgs.IsArgSet("-rpcallowip")) {
if (!gArgs.GetArgs("-rpcallowip").empty()) {
LogPrintf("WARNING: option -rpcallowip was specified without -rpcbind; this doesn't usually make sense\n");
}
if (gArgs.IsArgSet("-rpcbind")) {
if (!gArgs.GetArgs("-rpcbind").empty()) {
LogPrintf("WARNING: option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect\n");
}
} else if (gArgs.IsArgSet("-rpcbind")) { // Specific bind address
} else { // Specific bind addresses
for (const std::string& strRPCBind : gArgs.GetArgs("-rpcbind")) {
uint16_t port{http_port};
std::string host;

View file

@ -716,17 +716,18 @@ void InitParameterInteraction(ArgsManager& args)
{
// when specifying an explicit binding address, you want to listen on it
// even when -connect or -proxy is specified
if (args.IsArgSet("-bind")) {
if (!args.GetArgs("-bind").empty()) {
if (args.SoftSetBoolArg("-listen", true))
LogInfo("parameter interaction: -bind set -> setting -listen=1\n");
}
if (args.IsArgSet("-whitebind")) {
if (!args.GetArgs("-whitebind").empty()) {
if (args.SoftSetBoolArg("-listen", true))
LogInfo("parameter interaction: -whitebind set -> setting -listen=1\n");
}
if (args.IsArgSet("-connect") || args.GetIntArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS) <= 0) {
if (!args.GetArgs("-connect").empty() || args.IsArgNegated("-connect") || args.GetIntArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS) <= 0) {
// when only connecting to trusted nodes, do not seed via DNS, or listen by default
// do the same when connections are disabled
if (args.SoftSetBoolArg("-dnsseed", false))
LogInfo("parameter interaction: -connect or -maxconnections=0 set -> setting -dnsseed=0\n");
if (args.SoftSetBoolArg("-listen", false))
@ -762,7 +763,7 @@ void InitParameterInteraction(ArgsManager& args)
}
}
if (args.IsArgSet("-externalip")) {
if (!args.GetArgs("-externalip").empty()) {
// if an explicit public IP is specified, do not try to find others
if (args.SoftSetBoolArg("-discover", false))
LogInfo("parameter interaction: -externalip set -> setting -discover=0\n");
@ -782,8 +783,8 @@ void InitParameterInteraction(ArgsManager& args)
if (args.SoftSetBoolArg("-whitelistrelay", true))
LogInfo("parameter interaction: -whitelistforcerelay=1 -> setting -whitelistrelay=1\n");
}
if (args.IsArgSet("-onlynet")) {
const auto onlynets = args.GetArgs("-onlynet");
const auto onlynets = args.GetArgs("-onlynet");
if (!onlynets.empty()) {
bool clearnet_reachable = std::any_of(onlynets.begin(), onlynets.end(), [](const auto& net) {
const auto n = ParseNetwork(net);
return n == NET_IPV4 || n == NET_IPV6;
@ -1044,12 +1045,12 @@ bool AppInitParameterInteraction(const ArgsManager& args)
if (args.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS))
g_local_services = ServiceFlags(g_local_services | NODE_BLOOM);
if (args.IsArgSet("-test")) {
const std::vector<std::string> test_options = args.GetArgs("-test");
if (!test_options.empty()) {
if (chainparams.GetChainType() != ChainType::REGTEST) {
return InitError(Untranslated("-test=<option> can only be used with regtest"));
}
const std::vector<std::string> options = args.GetArgs("-test");
for (const std::string& option : options) {
for (const std::string& option : test_options) {
auto it = std::find_if(TEST_OPTIONS_DOC.begin(), TEST_OPTIONS_DOC.end(), [&option](const std::string& doc_option) {
size_t pos = doc_option.find(" (");
return (pos != std::string::npos) && (doc_option.substr(0, pos) == option);
@ -1433,7 +1434,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// Read asmap file if configured
std::vector<bool> asmap;
if (args.IsArgSet("-asmap")) {
if (args.IsArgSet("-asmap") && !args.IsArgNegated("-asmap")) {
fs::path asmap_path = args.GetPathArg("-asmap", DEFAULT_ASMAP_FILENAME);
if (!asmap_path.is_absolute()) {
asmap_path = args.GetDataDirNet() / asmap_path;
@ -1511,9 +1512,10 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
strSubVersion.size(), MAX_SUBVERSION_LENGTH));
}
if (args.IsArgSet("-onlynet")) {
const auto onlynets = args.GetArgs("-onlynet");
if (!onlynets.empty()) {
g_reachable_nets.RemoveAll();
for (const std::string& snet : args.GetArgs("-onlynet")) {
for (const std::string& snet : onlynets) {
enum Network net = ParseNetwork(snet);
if (net == NET_UNROUTABLE)
return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'"), snet));
@ -1522,7 +1524,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
}
if (!args.IsArgSet("-cjdnsreachable")) {
if (args.IsArgSet("-onlynet") && g_reachable_nets.Contains(NET_CJDNS)) {
if (!onlynets.empty() && g_reachable_nets.Contains(NET_CJDNS)) {
return InitError(
_("Outbound connections restricted to CJDNS (-onlynet=cjdns) but "
"-cjdnsreachable is not provided"));
@ -1573,7 +1575,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
onion_proxy = addrProxy;
}
const bool onlynet_used_with_onion{args.IsArgSet("-onlynet") && g_reachable_nets.Contains(NET_ONION)};
const bool onlynet_used_with_onion{!onlynets.empty() && g_reachable_nets.Contains(NET_ONION)};
// -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses
// -noonion (or -onion=0) disables connecting to .onion entirely
@ -1994,10 +1996,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
connOptions.vSeedNodes = args.GetArgs("-seednode");
// Initiate outbound connections unless connect=0
connOptions.m_use_addrman_outgoing = !args.IsArgSet("-connect");
if (!connOptions.m_use_addrman_outgoing) {
const auto connect = args.GetArgs("-connect");
const auto connect = args.GetArgs("-connect");
if (!connect.empty() || args.IsArgNegated("-connect")) {
// Do not initiate other outgoing connections when connecting to trusted
// nodes, or when -noconnect is specified.
connOptions.m_use_addrman_outgoing = false;
if (connect.size() != 1 || connect[0] != "0") {
connOptions.m_specified_outgoing = connect;
}
@ -2018,7 +2022,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
}
SetProxy(NET_I2P, Proxy{addr.value()});
} else {
if (args.IsArgSet("-onlynet") && g_reachable_nets.Contains(NET_I2P)) {
if (!onlynets.empty() && g_reachable_nets.Contains(NET_I2P)) {
return InitError(
_("Outbound connections restricted to i2p (-onlynet=i2p) but "
"-i2psam is not provided"));

View file

@ -58,7 +58,6 @@ void SetLoggingOptions(const ArgsManager& args)
util::Result<void> SetLoggingLevel(const ArgsManager& args)
{
if (args.IsArgSet("-loglevel")) {
for (const std::string& level_str : args.GetArgs("-loglevel")) {
if (level_str.find_first_of(':', 3) == std::string::npos) {
// user passed a global log level, i.e. -loglevel=<level>
@ -73,13 +72,11 @@ util::Result<void> SetLoggingLevel(const ArgsManager& args)
}
}
}
}
return {};
}
util::Result<void> SetLoggingCategories(const ArgsManager& args)
{
if (args.IsArgSet("-debug")) {
const std::vector<std::string> categories = args.GetArgs("-debug");
// Special-case: Disregard any debugging categories appearing before -debug=0/none
@ -93,7 +90,6 @@ util::Result<void> SetLoggingCategories(const ArgsManager& args)
return util::Error{strprintf(_("Unsupported logging category %s=%s."), "-debug", cat)};
}
}
}
// Now remove the logging categories which were explicitly excluded
for (const std::string& cat : args.GetArgs("-debugexclude")) {

View file

@ -2246,7 +2246,7 @@ void CConnman::ThreadDNSAddressSeed()
{
int outbound_connection_count = 0;
if (gArgs.IsArgSet("-seednode")) {
if (!gArgs.GetArgs("-seednode").empty()) {
auto start = NodeClock::now();
constexpr std::chrono::seconds SEEDNODE_TIMEOUT = 30s;
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
auto next_extra_network_peer{start + rng.rand_exp_duration(EXTRA_NETWORK_PEER_INTERVAL)};
const bool dnsseed = gArgs.GetBoolArg("-dnsseed", DEFAULT_DNSSEED);
bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS);
const bool use_seednodes{gArgs.IsArgSet("-seednode")};
const bool use_seednodes{!gArgs.GetArgs("-seednode").empty()};
auto seed_node_timer = NodeClock::now();
bool add_addr_fetch{addrman.Size() == 0 && !seed_nodes.empty()};

View file

@ -403,7 +403,7 @@ void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlRe
const auto onlynets = gArgs.GetArgs("-onlynet");
const bool onion_allowed_by_onlynet{
!gArgs.IsArgSet("-onlynet") ||
onlynets.empty() ||
std::any_of(onlynets.begin(), onlynets.end(), [](const auto& n) {
return ParseNetwork(n) == NET_ONION;
})};

View file

@ -54,6 +54,12 @@ class AsmapTest(BitcoinTestFramework):
with self.node.assert_debug_log(['Using /16 prefix for IP bucketing']):
self.start_node(0)
def test_noasmap_arg(self):
self.log.info('Test bitcoind with -noasmap arg passed')
self.stop_node(0)
with self.node.assert_debug_log(['Using /16 prefix for IP bucketing']):
self.start_node(0, ["-noasmap"])
def test_asmap_with_absolute_path(self):
self.log.info('Test bitcoind -asmap=<absolute path>')
self.stop_node(0)
@ -137,6 +143,7 @@ class AsmapTest(BitcoinTestFramework):
self.asmap_raw = os.path.join(base_dir, ASMAP)
self.test_without_asmap_arg()
self.test_noasmap_arg()
self.test_asmap_with_absolute_path()
self.test_asmap_with_relative_path()
self.test_default_asmap()

View file

@ -369,6 +369,8 @@ class ConfArgsTest(BitcoinTestFramework):
seednode_ignored = ['-seednode is ignored when -connect is used\n']
dnsseed_ignored = ['-dnsseed is ignored when -connect is used and -proxy is specified\n']
addcon_thread_started = ['addcon thread start\n']
dnsseed_disabled = "parameter interaction: -connect or -maxconnections=0 set -> setting -dnsseed=0"
listen_disabled = "parameter interaction: -connect or -maxconnections=0 set -> setting -listen=0"
# When -connect is supplied, expanding addrman via getaddr calls to ADDR_FETCH(-seednode)
# nodes is irrelevant and -seednode is ignored.
@ -393,6 +395,19 @@ class ConfArgsTest(BitcoinTestFramework):
with self.nodes[0].assert_debug_log(expected_msgs=addcon_thread_started,
unexpected_msgs=seednode_ignored):
self.restart_node(0, extra_args=[connect_arg, '-seednode=fakeaddress2'])
# Make sure -noconnect soft-disables -listen and -dnsseed.
# Need to temporarily remove these settings from the config file in
# order for the two log messages to appear
self.nodes[0].replace_in_config([("bind=", "#bind="), ("dnsseed=", "#dnsseed=")])
with self.nodes[0].assert_debug_log(expected_msgs=[dnsseed_disabled, listen_disabled]):
self.restart_node(0, extra_args=[connect_arg])
self.nodes[0].replace_in_config([("#bind=", "bind="), ("#dnsseed=", "dnsseed=")])
# Make sure -proxy and -noconnect warn about -dnsseed setting being
# ignored, just like -proxy and -connect do.
with self.nodes[0].assert_debug_log(expected_msgs=dnsseed_ignored):
self.restart_node(0, extra_args=[connect_arg, '-dnsseed', '-proxy=localhost:1080'])
self.stop_node(0)
def test_ignored_conf(self):

View file

@ -279,6 +279,10 @@ class TestBitcoinCli(BitcoinTestFramework):
assert_equal(cli_get_info['Wallet'], wallets[1])
assert_equal(Decimal(cli_get_info['Balance']), amounts[1])
self.log.info("Test -getinfo -norpcwallet returns the same as -getinfo")
# Previously there was a bug where -norpcwallet was treated like -rpcwallet=0
assert_equal(self.nodes[0].cli('-getinfo', "-norpcwallet").send_cli(), cli_get_info_string)
self.log.info("Test -getinfo with -rpcwallet=remaining-non-default-wallet returns only its balance")
cli_get_info_string = self.nodes[0].cli('-getinfo', rpcwallet2).send_cli()
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)