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:
    utACK 95a0104f2e
  achow101:
    ACK 95a0104f2e
  ryanofsky:
    Code review ACK 95a0104f2e. 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:
Ava Chow
2024-12-04 13:20:46 -05:00
16 changed files with 212 additions and 75 deletions

View File

@@ -79,11 +79,12 @@ def read_logs(tmp_dir):
Delegates to generator function get_log_events() to provide individual log events
for each of the input log files."""
# Find out what the folder is called that holds the debug.log file
glob = pathlib.Path(tmp_dir).glob('node0/**/debug.log')
path = next(glob, None)
if path:
assert next(glob, None) is None # more than one debug.log, should never happen
# Find out what the folder is called that holds node 0's debug.log file
debug_logs = list(pathlib.Path(tmp_dir).glob('node0/**/debug.log'))
if len(debug_logs) > 0:
assert len(debug_logs) < 2, 'Max one debug.log is supported, ' \
'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
else:
chain = 'regtest' # fallback to regtest (should only happen when none exists)

View File

@@ -27,9 +27,74 @@ class ConfArgsTest(BitcoinTestFramework):
self.wallet_names = []
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):
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
bad_conf_file_path = self.nodes[0].datadir_path / "bitcoin_bad.conf"
@@ -162,12 +227,11 @@ class ConfArgsTest(BitcoinTestFramework):
)
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']):
self.start_node(0, extra_args=['-noconnect=0'])
self.stop_node(0)
def test_args_log(self):
self.stop_node(0)
self.log.info('Test config args logging')
with self.nodes[0].assert_debug_log(
expected_msgs=[
@@ -196,10 +260,10 @@ class ConfArgsTest(BitcoinTestFramework):
'-rpcuser=secret-rpcuser',
'-torpassword=secret-torpassword',
])
self.stop_node(0)
def test_networkactive(self):
self.log.info('Test -networkactive option')
self.stop_node(0)
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
self.start_node(0)
@@ -222,16 +286,12 @@ class ConfArgsTest(BitcoinTestFramework):
self.stop_node(0)
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
self.start_node(0, extra_args=['-nonetworkactive=1'])
self.stop_node(0)
def test_seed_peers(self):
self.log.info('Test seed peers')
default_data_dir = self.nodes[0].datadir_path
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
# We expect the node will use DNS Seeds, but Regtest mode does not have
@@ -248,6 +308,12 @@ class ConfArgsTest(BitcoinTestFramework):
timeout=10,
):
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=[
"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",
]):
self.nodes[0].setmocktime(start + 65)
self.stop_node(0)
def test_connect_with_seednode(self):
self.log.info('Test -connect with -seednode')
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']
self.stop_node(0)
# When -connect is supplied, expanding addrman via getaddr calls to ADDR_FETCH(-seednode)
# 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,
unexpected_msgs=seednode_ignored):
self.restart_node(0, extra_args=[connect_arg, '-seednode=fakeaddress2'])
self.stop_node(0)
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 '
@@ -423,6 +490,8 @@ class ConfArgsTest(BitcoinTestFramework):
self.test_networkactive()
self.test_connect_with_seednode()
self.test_dir_config()
self.test_negated_config()
self.test_config_file_parser()
self.test_config_file_log()
self.test_invalid_command_line_options()

View File

@@ -164,6 +164,9 @@ class TestBitcoinCli(BitcoinTestFramework):
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)
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")
assert_raises_process_error(1, "-getinfo takes no arguments", self.nodes[0].cli('-getinfo').help)

View File

@@ -22,13 +22,13 @@ import sys
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)
headers = {"Authorization": "Basic " + str_to_b64str('{}:{}'.format(user, password))}
conn = http.client.HTTPConnection(url.hostname, url.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
conn.request('POST', '/', f'{{"method": "{method}"}}', headers)
resp = conn.getresponse()
conn.close()
return resp
@@ -121,6 +121,25 @@ class HTTPBasicsTest(BitcoinTestFramework):
for perm in ["owner", "group", "all"]:
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):
self.conf_setup()
self.log.info('Check correctness of the rpcauth config option')
@@ -166,11 +185,19 @@ class HTTPBasicsTest(BitcoinTestFramework):
self.stop_node(0)
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)
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_norpccookiefile(cookie_path)
if __name__ == '__main__':
HTTPBasicsTest(__file__).main()