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

Code before:
        n0.createwallet('w')
        w = n0.get_wallet_rpc("w")
        w_address = w.getnewaddress()

        # Create another wallet and backup now (before snapshot height)
        n0.createwallet('w2')
        w2 = n0.get_wallet_rpc("w2")
        w2_address = w2.getnewaddress()
        w2.backupwallet("backup_w2.dat")

        # Generate a series of blocks that `n0` will have in the snapshot,
        # but that n1 doesn't yet see. In order for the snapshot to activate,
        # though, we have to ferry over the new headers to n1 so that it
        # isn't waiting forever to see the header of the snapshot's base block
        # while disconnected from n0.
        for i in range(100):
            if i % 3 == 0:
                self.mini_wallet.send_self_transfer(from_node=n0)
            self.generate(n0, nblocks=1, sync_fun=self.no_op)
            newblock = n0.getblock(n0.getbestblockhash(), 0)

            # make n1 aware of the new header, but don't give it the block.
            n1.submitheader(newblock)
            n2.submitheader(newblock)

        # Ensure everyone is seeing the same headers.
        for n in self.nodes:
            assert_equal(n.getblockchaininfo()[
                         "headers"], SNAPSHOT_BASE_HEIGHT)

        # This backup is created at the snapshot height, so it's
        # not part of the background sync anymore
        w.backupwallet("backup_w.dat")

        self.log.info("-- Testing assumeutxo")

        assert_equal(n0.getblockcount(), SNAPSHOT_BASE_HEIGHT)
        assert_equal(n1.getblockcount(), START_HEIGHT)

        self.log.info(
            f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}")
        dump_output = n0.dumptxoutset('utxos.dat', "latest")

        assert_equal(
            dump_output['txoutset_hash'],
            "d2b051ff5e8eef46520350776f4100dd710a63447a8e01d917e92e79751a63e2")
        assert_equal(dump_output["nchaintx"], 334)
        assert_equal(n0.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)

        # Mine more blocks on top of the snapshot that n1 hasn't yet seen. This
        # will allow us to test n1's sync-to-tip on top of a snapshot.

Review comment (bug):
There is a small mismatch to the error message returned from the RPC, might be a silent merge conflict.

```suggestion
        error_message = "Wallet loading failed. Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of a pruned node)"
```

Code after:
        self.wait_until(lambda: len(node.getchainstates()['chainstates']) == 1)

    def test_backup_during_background_sync_pruned_node(self, n3, dump_output, expected_error_message):
        self.log.info("Backup from the snapshot height can be loaded during background sync (pruned node)")
        loaded = n3.loadtxoutset(dump_output['path'])
        assert_greater_than(n3.pruneblockchain(START_HEIGHT), 0)
        self.validate_snapshot_import(n3, loaded, dump_output['base_hash'])
        n3.restorewallet("w", "backup_w.dat")
        # Balance of w wallet is still 0 because n3 has not synced yet
        assert_equal(n3.getbalance(), 0)

        self.log.info("Backup from before the snapshot height can't be loaded during background sync (pruned node)")
        assert_raises_rpc_error(-4, expected_error_message, n3.restorewallet, "w2", "backup_w2.dat")

    def test_restore_wallet_pruneheight(self, n3):
        self.log.info("Ensuring wallet can't be restored from a backup that was created before the pruneheight (pruned node)")
        self.complete_background_validation(n3)
        # After background sync, pruneheight is reset to 0, so mine 500 blocks
        # and prune the chain again
        self.generate(n3, nblocks=500, sync_fun=self.no_op)
        assert_equal(n3.pruneblockchain(FINAL_HEIGHT), 298)  # 298 is the height of the last block pruned (pruneheight 299)
        error_message = "Wallet loading failed. Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of a pruned node)"
        # This backup (backup_w2.dat) was created at height 199, so it can't be restored in a node with a pruneheight of 299
        assert_raises_rpc_error(-4, error_message, n3.restorewallet, "w2", "backup_w2.dat")

        self.log.info("Ensuring wallet can be restored from a backup that was created at the pruneheight (pruned node)")
        # This backup (backup_w.dat) was created at height 299, so it can be restored in a node with a pruneheight of 299
        n3.restorewallet("w_alt", "backup_w.dat")
        # Check balance of w_alt wallet
        w_alt = n3.get_wallet_rpc("w_alt")
        assert_equal(w_alt.getbalance(), 34)

    def run_test(self):
        """
        Bring up four (disconnected) nodes:
        - n0: mine some blocks and create a UTXO snapshot
        - n1: load the snapshot and test loading a wallet backup and descriptors during and after background sync
        - n2: load the snapshot and check the wallet balance during background sync
        - n3: load the snapshot, prune the chain, and test loading a wallet backup during and after background sync
        """
        n0 = self.nodes[0]
        n1 = self.nodes[1]
        n2 = self.nodes[2]
        n3 = self.nodes[3]

        self.mini_wallet = MiniWallet(n0)

        # Mock time for a deterministic chain
        for n in self.nodes:
            n.setmocktime(n.getblockheader(n.getbestblockhash())['time'])