Repository: bitcoin/bitcoin
File: test/functional/feature_reindex.py (Python)

Code before:
            genesis_start = find_block(b, 0)
            assert_equal(genesis_start, 4)
            b2_start = find_block(b, genesis_start)
            b3_start = find_block(b, b2_start)
            b4_start = find_block(b, b3_start)

            # Blocks 2 and 3 should be the same size.
            assert_equal(b3_start-b2_start, b4_start-b3_start)

            # Swap the second and third blocks (don't disturb the genesis block).
            bf.seek(b2_start)
            bf.write(b[b3_start:b4_start])
            bf.write(b[b2_start:b3_start])

        # The reindexing code should detect and accommodate out of order blocks.
        with self.nodes[0].assert_debug_log([
            'LoadExternalBlockFile: Out of order block',
            'LoadExternalBlockFile: Processing out of order child',
        ]):
            extra_args = [["-reindex"]]
            self.start_nodes(extra_args)

        # All blocks should be accepted and processed.
        assert_equal(self.nodes[0].getblockcount(), 12)

    def continue_reindex_after_shutdown(self):
        node = self.nodes[0]
        self.generate(node, 1500)

        # Restart node with reindex and stop reindex as soon as it starts reindexing
        self.log.info("Restarting node while reindexing..")
        node.stop_node()
        with node.busy_wait_for_debug_log([b'initload thread start']):
            node.start(['-blockfilterindex', '-reindex'])
            node.wait_for_rpc_connection(wait_for_import=False)
        node.stop_node()

        # Start node without the reindex flag and verify it does not wipe the indexes data again
        db_path = node.chain_path / 'indexes' / 'blockfilter' / 'basic' / 'db'
        with node.assert_debug_log(expected_msgs=[f'Opening LevelDB in {db_path}'], unexpected_msgs=[f'Wiping LevelDB in {db_path}']):
            node.start(['-blockfilterindex'])
            node.wait_for_rpc_connection(wait_for_import=False)
        node.stop_node()

    def run_test(self):
        self.reindex(False)
        self.reindex(True)
        self.reindex(False)
        self.reindex(True)

        self.out_of_order()

Review comment (bug):
Smaller change if we make `util_xor()` take an offset:
```diff
diff --git a/test/functional/feature_reindex.py b/test/functional/feature_reindex.py
index f079a5e343..9f3f5357ba 100755
--- a/test/functional/feature_reindex.py
+++ b/test/functional/feature_reindex.py
@@ -43,15 +43,15 @@ class ReindexTest(BitcoinTestFramework):
             NUM_XOR_BYTES = 8 # From InitBlocksdirXorKey::xor_key.size()
             xor_dat = xor_f.read(NUM_XOR_BYTES)
 
-        def util_xor(data, key):
+        def util_xor(data, key, offset):
             data = bytearray(data)
             for i in range(len(data)):
-                data[i] ^= key[i % len(key)]
+                data[i] ^= key[(i + offset) % len(key)]
             return bytes(data)
 
         with open(blk0, 'r+b') as bf:
             # Read at least the first few blocks (including genesis)
-            b = util_xor(bf.read(2000), xor_dat)
+            b = util_xor(bf.read(2000), xor_dat, 0)
 
             # Find the offsets of blocks 2, 3, and 4 (the first 3 blocks beyond genesis)
             # by searching for the regtest marker bytes (see pchMessageStart).
@@ -68,12 +68,9 @@ class ReindexTest(BitcoinTestFramework):
             assert_equal(b3_start - b2_start, b4_start - b3_start)
 
             # Swap the second and third blocks (don't disturb the genesis block).
-            b_new = bytearray(b)
-            b_new[b2_start:b3_start] = b[b3_start:b4_start]
-            b_new[b3_start:b4_start] = b[b2_start:b3_start]
-            b_new = util_xor(b_new, xor_dat)
             bf.seek(b2_start)
-            bf.write(b_new[b2_start:b4_start])
+            bf.write(util_xor(b[b3_start:b4_start], xor_dat, b2_start))
+            bf.write(util_xor(b[b2_start:b3_start], xor_dat, b3_start))
 
         # The reindexing code should detect and accommodate out of order blocks.
         with self.nodes[0].assert_debug_log([
```

Code after:

        with open(blk0, 'r+b') as bf:
            # Read at least the first few blocks (including genesis)
            b = util_xor(bf.read(2000), xor_dat, offset=0)

            # Find the offsets of blocks 2, 3, and 4 (the first 3 blocks beyond genesis)
            # by searching for the regtest marker bytes (see pchMessageStart).
            def find_block(b, start):
                return b.find(MAGIC_BYTES["regtest"], start)+4

            genesis_start = find_block(b, 0)
            assert_equal(genesis_start, 4)
            b2_start = find_block(b, genesis_start)
            b3_start = find_block(b, b2_start)
            b4_start = find_block(b, b3_start)

            # Blocks 2 and 3 should be the same size.
            assert_equal(b3_start - b2_start, b4_start - b3_start)

            # Swap the second and third blocks (don't disturb the genesis block).
            bf.seek(b2_start)
            bf.write(util_xor(b[b3_start:b4_start], xor_dat, offset=b2_start))
            bf.write(util_xor(b[b2_start:b3_start], xor_dat, offset=b3_start))

        # The reindexing code should detect and accommodate out of order blocks.
        with self.nodes[0].assert_debug_log([
            'LoadExternalBlockFile: Out of order block',
            'LoadExternalBlockFile: Processing out of order child',
        ]):
            extra_args = [["-reindex"]]
            self.start_nodes(extra_args)

        # All blocks should be accepted and processed.
        assert_equal(self.nodes[0].getblockcount(), 12)

    def continue_reindex_after_shutdown(self):
        node = self.nodes[0]
        self.generate(node, 1500)

        # Restart node with reindex and stop reindex as soon as it starts reindexing
        self.log.info("Restarting node while reindexing..")
        node.stop_node()
        with node.busy_wait_for_debug_log([b'initload thread start']):
            node.start(['-blockfilterindex', '-reindex'])
            node.wait_for_rpc_connection(wait_for_import=False)
        node.stop_node()

        # Start node without the reindex flag and verify it does not wipe the indexes data again
        db_path = node.chain_path / 'indexes' / 'blockfilter' / 'basic' / 'db'
        with node.assert_debug_log(expected_msgs=[f'Opening LevelDB in {db_path}'], unexpected_msgs=[f'Wiping LevelDB in {db_path}']):
            node.start(['-blockfilterindex'])