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

Code before:

        # listsinceblock(nodes1_last_blockhash) should now include tx as seen from nodes[0]
        # and return the block height which listsinceblock now exposes since a5e7795.
        transactions = self.nodes[0].listsinceblock(nodes1_last_blockhash)['transactions']
        found = next(tx for tx in transactions if tx['txid'] == senttx)
        assert_equal(found['blockheight'], self.nodes[0].getblockheader(nodes2_first_blockhash)['height'])

    def test_double_spend(self):
        '''
        This tests the case where the same UTXO is spent twice on two separate
        blocks as part of a reorg.

             ab0
          /       \
        aa1 [tx1]   bb1 [tx2]
         |           |
        aa2         bb2
         |           |
        aa3         bb3
                     |
                    bb4

        Problematic case:

        1. User 1 receives BTC in tx1 from utxo1 in block aa1.
        2. User 2 receives BTC in tx2 from utxo1 (same) in block bb1
        3. User 1 sees 2 confirmations at block aa3.
        4. Reorg into bb chain.
        5. User 1 asks `listsinceblock aa3` and does not see that tx1 is now
           invalidated.

        Currently the solution to this is to detect that a reorg'd block is
        asked for in listsinceblock, and to iterate back over existing blocks up
        until the fork point, and to include all transactions that relate to the
        node wallet.
        '''
        self.log.info("Test double spend")

        self.sync_all()

        # share utxo between nodes[1] and nodes[2]
        privkey, pubkey = generate_keypair(wif=True)
        address = key_to_p2wpkh(pubkey)
        self.nodes[2].sendtoaddress(address, 10)
        self.generate(self.nodes[2], 6)
        self.nodes[2].importprivkey(privkey)
        utxos = self.nodes[2].listunspent()
        utxo = [u for u in utxos if u["address"] == address][0]
        self.nodes[1].importprivkey(privkey)

        # Split network into two

Review comment (suggestion):
nit:

This `self.nodes[0].blocks_path / "blk..." / ...` is a [`Path`](https://docs.python.org/3/library/pathlib.html) so you don't need to `import os` to call `os.rename` to rename it, you can instead:

```suggestion
        (self.nodes[0].blocks_path / "blk00000.dat").rename("blk00000.dat.copy")
```

Most of the things you could do to files/directories with `os` can also be done with `Path`.

(The `.rename` is aware of which bit of the path is the file so you don't need to give it the `self.nodes[0].blocks_path` for the rename to work correctly)

Code after:
        self.join_network()

        # listsinceblock(nodes1_last_blockhash) should now include tx as seen from nodes[0]
        # and return the block height which listsinceblock now exposes since a5e7795.
        transactions = self.nodes[0].listsinceblock(nodes1_last_blockhash)['transactions']
        found = next(tx for tx in transactions if tx['txid'] == senttx)
        assert_equal(found['blockheight'], self.nodes[0].getblockheader(nodes2_first_blockhash)['height'])

    def test_cant_read_block(self):
        self.log.info('Test the RPC error "Can\'t read block from disk"')

        # Split network into two
        self.split_network()

        # generate on both sides
        nodes1_last_blockhash = self.generate(self.nodes[1], 6, sync_fun=lambda: self.sync_all(self.nodes[:2]))[-1]
        self.generate(self.nodes[2], 7, sync_fun=lambda: self.sync_all(self.nodes[2:]))[0]

        self.join_network()

        # Renaming the block file to induce unsuccessful block read
        blk_dat = (self.nodes[0].blocks_path / "blk00000.dat")
        blk_dat_moved = blk_dat.rename(self.nodes[0].blocks_path / "blk00000.dat.moved")
        assert not blk_dat.exists()

        # listsinceblock(nodes1_last_blockhash) should now fail as blocks are not accessible
        assert_raises_rpc_error(-32603, "Can't read block from disk",
            self.nodes[0].listsinceblock, nodes1_last_blockhash)

        # Restoring block file
        blk_dat_moved.rename(self.nodes[0].blocks_path / "blk00000.dat")
        assert blk_dat.exists()

    def test_double_spend(self):
        '''
        This tests the case where the same UTXO is spent twice on two separate
        blocks as part of a reorg.

             ab0
          /       \
        aa1 [tx1]   bb1 [tx2]
         |           |
        aa2         bb2
         |           |
        aa3         bb3
                     |
                    bb4

        Problematic case:

        1. User 1 receives BTC in tx1 from utxo1 in block aa1.