feature_reindex_readonly.py
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 14 15 class BlockstoreReindexTest(BitcoinTestFramework): 16 def set_test_params(self): 17 self.setup_clean_chain = True 18 self.num_nodes = 1 19 self.extra_args = [["-fastprune"]] 20 21 def reindex_readonly(self): 22 self.log.debug("Generate block big enough to start second block file") 23 fastprune_blockfile_size = 0x10000 24 opreturn = "6a" 25 nulldata = fastprune_blockfile_size * "ff" 26 self.generateblock(self.nodes[0], output=f"raw({opreturn}{nulldata})", transactions=[]) 27 block_count = self.nodes[0].getblockcount() 28 self.stop_node(0) 29 30 assert (self.nodes[0].chain_path / "blocks" / "blk00000.dat").exists() 31 assert (self.nodes[0].chain_path / "blocks" / "blk00001.dat").exists() 32 33 self.log.debug("Make the first block file read-only") 34 filename = self.nodes[0].chain_path / "blocks" / "blk00000.dat" 35 filename.chmod(stat.S_IREAD) 36 37 undo_immutable = lambda: None 38 # Linux 39 try: 40 subprocess.run(['chattr'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 41 try: 42 subprocess.run(['chattr', '+i', filename], capture_output=True, check=True) 43 undo_immutable = lambda: subprocess.check_call(['chattr', '-i', filename]) 44 self.log.info("Made file immutable with chattr") 45 except subprocess.CalledProcessError as e: 46 self.log.warning(str(e)) 47 if e.stdout: 48 self.log.warning(f"stdout: {e.stdout}") 49 if e.stderr: 50 self.log.warning(f"stderr: {e.stderr}") 51 if os.getuid() == 0: 52 self.log.warning("Return early on Linux under root, because chattr failed.") 53 self.log.warning("This should only happen due to missing capabilities in a container.") 54 self.log.warning("Make sure to --cap-add LINUX_IMMUTABLE if you want to run this test.") 55 undo_immutable = False 56 except Exception: 57 # macOS, and *BSD 58 try: 59 subprocess.run(['chflags'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 60 try: 61 subprocess.run(['chflags', 'uchg', filename], capture_output=True, check=True) 62 undo_immutable = lambda: subprocess.check_call(['chflags', 'nouchg', filename]) 63 self.log.info("Made file immutable with chflags") 64 except subprocess.CalledProcessError as e: 65 self.log.warning(str(e)) 66 if e.stdout: 67 self.log.warning(f"stdout: {e.stdout}") 68 if e.stderr: 69 self.log.warning(f"stderr: {e.stderr}") 70 if os.getuid() == 0: 71 self.log.warning("Return early on BSD under root, because chflags failed.") 72 undo_immutable = False 73 except Exception: 74 pass 75 76 if undo_immutable: 77 self.log.debug("Attempt to restart and reindex the node with the unwritable block file") 78 with self.nodes[0].assert_debug_log(["Reindexing finished"], timeout=60): 79 self.start_node(0, extra_args=['-reindex', '-fastprune']) 80 assert block_count == self.nodes[0].getblockcount() 81 undo_immutable() 82 83 filename.chmod(0o777) 84 85 def run_test(self): 86 self.reindex_readonly() 87 88 89 if __name__ == '__main__': 90 BlockstoreReindexTest(__file__).main()