/ test / functional / feature_reindex_readonly.py
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()