feature_reindex_readonly.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2023-present The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """Test running bitcoind with -reindex from a read-only blockstore
6 - Start a node, generate blocks, then restart with -reindex after setting blk files to read-only
7 """
8
9 import os
10 import stat
11 import subprocess
12 from test_framework.test_framework import BitcoinTestFramework
13 from test_framework.util import assert_equal
14
15
16 class BlockstoreReindexTest(BitcoinTestFramework):
17 def set_test_params(self):
18 self.setup_clean_chain = True
19 self.num_nodes = 1
20 self.extra_args = [["-fastprune"]]
21
22 def reindex_readonly(self):
23 self.log.debug("Generate block big enough to start second block file")
24 fastprune_blockfile_size = 0x10000
25 opreturn = "6a"
26 nulldata = fastprune_blockfile_size * "ff"
27 self.generateblock(self.nodes[0], output=f"raw({opreturn}{nulldata})", transactions=[])
28 block_count = self.nodes[0].getblockcount()
29 self.stop_node(0)
30
31 assert (self.nodes[0].chain_path / "blocks" / "blk00000.dat").exists()
32 assert (self.nodes[0].chain_path / "blocks" / "blk00001.dat").exists()
33
34 self.log.debug("Make the first block file read-only")
35 filename = self.nodes[0].chain_path / "blocks" / "blk00000.dat"
36 filename.chmod(stat.S_IREAD)
37
38 undo_immutable = lambda: None
39 # Linux
40 try:
41 subprocess.run(['chattr'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
42 try:
43 subprocess.run(['chattr', '+i', filename], capture_output=True, check=True)
44 undo_immutable = lambda: subprocess.check_call(['chattr', '-i', filename])
45 self.log.info("Made file immutable with chattr")
46 except subprocess.CalledProcessError as e:
47 self.log.warning(str(e))
48 if e.stdout:
49 self.log.warning(f"stdout: {e.stdout}")
50 if e.stderr:
51 self.log.warning(f"stderr: {e.stderr}")
52 if os.getuid() == 0:
53 self.log.warning("Return early on Linux under root, because chattr failed.")
54 self.log.warning("This should only happen due to missing capabilities in a container.")
55 self.log.warning("Make sure to --cap-add LINUX_IMMUTABLE if you want to run this test.")
56 undo_immutable = False
57 except Exception:
58 # macOS, and *BSD
59 try:
60 subprocess.run(['chflags'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
61 try:
62 subprocess.run(['chflags', 'uchg', filename], capture_output=True, check=True)
63 undo_immutable = lambda: subprocess.check_call(['chflags', 'nouchg', filename])
64 self.log.info("Made file immutable with chflags")
65 except subprocess.CalledProcessError as e:
66 self.log.warning(str(e))
67 if e.stdout:
68 self.log.warning(f"stdout: {e.stdout}")
69 if e.stderr:
70 self.log.warning(f"stderr: {e.stderr}")
71 if os.getuid() == 0:
72 self.log.warning("Return early on BSD under root, because chflags failed.")
73 undo_immutable = False
74 except Exception:
75 pass
76
77 if undo_immutable:
78 self.log.debug("Attempt to restart and reindex the node with the unwritable block file")
79 with self.nodes[0].assert_debug_log(["Reindexing finished"], timeout=60):
80 self.start_node(0, extra_args=['-reindex', '-fastprune'])
81 assert_equal(block_count, self.nodes[0].getblockcount())
82 undo_immutable()
83
84 filename.chmod(0o777)
85
86 def run_test(self):
87 self.reindex_readonly()
88
89
90 if __name__ == '__main__':
91 BlockstoreReindexTest(__file__).main()
92