Merge bitcoin/bitcoin#27850: test: Add unit & functional test coverage for blockstore

de8f9123af test: cover read-only blockstore (Matthew Zipkin)
5c2185b3b6 ci: enable chattr +i capability inside containers (Matthew Zipkin)
e573f24202 unit test: add coverage for BlockManager (Matthew Zipkin)

Pull request description:

  This PR adds unit and functional tests to cover the behavior described in #2039. In particular, that bitcoind will crash on startup if a reindex is requested but the `blk` files are read-only. Eventually this behavior can be updated with https://github.com/bitcoin/bitcoin/pull/27039. This PR just commits the test coverage from #27039 as suggested in https://github.com/bitcoin/bitcoin/pull/27039#issuecomment-1584915782

ACKs for top commit:
  jonatack:
    ACK de8f9123af modulo suggestions in https://github.com/bitcoin/bitcoin/pull/27850#discussion_r1319010039, tested on macOS, but not on Linux for the Linux-related change in the last push
  achow101:
    ACK de8f9123af
  MarcoFalke:
    lgtm ACK de8f9123af 📶

Tree-SHA512: b9bd684035dcea11c901b649fc39f397a2155a9a8459f3348e67947e387e45312fddeccb52981aef486f8a31deebb5356a7901c1bb94b78f82c24192a369af73
This commit is contained in:
Andrew Chow
2023-09-14 13:15:24 -04:00
6 changed files with 141 additions and 3 deletions

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
# Copyright (c) 2023-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test running bitcoind with -reindex from a read-only blockstore
- Start a node, generate blocks, then restart with -reindex after setting blk files to read-only
"""
import platform
import stat
import subprocess
from test_framework.test_framework import BitcoinTestFramework
class BlockstoreReindexTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [["-fastprune"]]
def reindex_readonly(self):
self.log.debug("Generate block big enough to start second block file")
fastprune_blockfile_size = 0x10000
opreturn = "6a"
nulldata = fastprune_blockfile_size * "ff"
self.generateblock(self.nodes[0], output=f"raw({opreturn}{nulldata})", transactions=[])
self.stop_node(0)
assert (self.nodes[0].chain_path / "blocks" / "blk00000.dat").exists()
assert (self.nodes[0].chain_path / "blocks" / "blk00001.dat").exists()
self.log.debug("Make the first block file read-only")
filename = self.nodes[0].chain_path / "blocks" / "blk00000.dat"
filename.chmod(stat.S_IREAD)
used_chattr = False
if platform.system() == "Linux":
try:
subprocess.run(['chattr', '+i', filename], capture_output=True, check=True)
used_chattr = True
self.log.info("Made file immutable with chattr")
except subprocess.CalledProcessError as e:
self.log.warning(str(e))
if e.stdout:
self.log.warning(f"stdout: {e.stdout}")
if e.stderr:
self.log.warning(f"stderr: {e.stderr}")
self.log.debug("Attempt to restart and reindex the node with the unwritable block file")
with self.nodes[0].assert_debug_log(expected_msgs=['FlushStateToDisk', 'failed to open file'], unexpected_msgs=[]):
self.nodes[0].assert_start_raises_init_error(extra_args=['-reindex', '-fastprune'],
expected_msg="Error: A fatal internal error occurred, see debug.log for details")
if used_chattr:
subprocess.check_call(['chattr', '-i', filename])
filename.chmod(0o777)
def run_test(self):
self.reindex_readonly()
if __name__ == '__main__':
BlockstoreReindexTest().main()

View File

@@ -162,6 +162,7 @@ BASE_SCRIPTS = [
'wallet_abandonconflict.py --legacy-wallet',
'wallet_abandonconflict.py --descriptors',
'feature_reindex.py',
'feature_reindex_readonly.py',
'wallet_labels.py --legacy-wallet',
'wallet_labels.py --descriptors',
'p2p_compactblocks.py',