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