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

Code before:
        # Next cause a bunch of writes by filling the keypool
        wallet.keypoolrefill(wallet.getwalletinfo()["keypoolsize"] + 100)
        # Lastly kill bitcoind so that the LSNs don't get reset
        self.nodes[0].process.kill()
        self.nodes[0].wait_until_stopped(expected_ret_code=1 if platform.system() == "Windows" else -9)
        assert self.nodes[0].is_node_stopped()

        wallet_dump = self.nodes[0].datadir_path / "unclean_lsn.dump"
        self.assert_raises_tool_error("LSNs are not reset, this database is not completely flushed. Please reopen then close the database with a version that has BDB support", "-wallet=unclean_lsn", f"-dumpfile={wallet_dump}", "dump")

        # File can be dumped after reload it normally
        self.start_node(0)
        self.nodes[0].loadwallet("unclean_lsn")
        self.stop_node(0)
        self.assert_tool_output("The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n", "-wallet=unclean_lsn", f"-dumpfile={wallet_dump}", "dump")

    def run_test(self):
        self.wallet_path = self.nodes[0].wallets_path / self.default_wallet_name / self.wallet_data_filename
        self.test_invalid_tool_commands_and_args()
        # Warning: The following tests are order-dependent.
        self.test_tool_wallet_info()
        self.test_tool_wallet_info_after_transaction()
        self.test_tool_wallet_create_on_existing_wallet()
        self.test_getwalletinfo_on_different_wallet()
        if not self.options.descriptors:
            # Salvage is a legacy wallet only thing
            self.test_salvage()
            self.test_dump_endianness()
            self.test_dump_unclean_lsns()
        self.test_dump_createfromdump()
        self.test_chainless_conflicts()
        self.test_dump_very_large_records()

if __name__ == '__main__':
    ToolWalletTest(__file__).main()


Review comment (bug):
In ad0a8ffceacfd5ce37826b2882b0e51a14c5a6bf "test: compare BDB dumps of test framework parser and wallet tool"

In addition to testing overflow pages, we can also test internal pages by generating a bunch of keys with `keypoolrefill`.

Code after:
        self.nodes[0].createwallet("unclean_lsn")
        wallet = self.nodes[0].get_wallet_rpc("unclean_lsn")
        # First unload and load normally to make sure everything is written
        wallet.unloadwallet()
        self.nodes[0].loadwallet("unclean_lsn")
        # Next cause a bunch of writes by filling the keypool
        wallet.keypoolrefill(wallet.getwalletinfo()["keypoolsize"] + 100)
        # Lastly kill bitcoind so that the LSNs don't get reset
        self.nodes[0].process.kill()
        self.nodes[0].wait_until_stopped(expected_ret_code=1 if platform.system() == "Windows" else -9)
        assert self.nodes[0].is_node_stopped()

        wallet_dump = self.nodes[0].datadir_path / "unclean_lsn.dump"
        self.assert_raises_tool_error("LSNs are not reset, this database is not completely flushed. Please reopen then close the database with a version that has BDB support", "-wallet=unclean_lsn", f"-dumpfile={wallet_dump}", "dump")

        # File can be dumped after reload it normally
        self.start_node(0)
        self.nodes[0].loadwallet("unclean_lsn")
        self.stop_node(0)
        self.assert_tool_output("The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n", "-wallet=unclean_lsn", f"-dumpfile={wallet_dump}", "dump")

    def test_compare_legacy_dump_with_framework_bdb_parser(self):
        self.log.info("Verify that legacy wallet database dump matches the one from the test framework's BDB parser")
        wallet_name = "bdb_ro_test"
        self.start_node(0)
        # add some really large labels (above twice the largest valid page size) to create BDB overflow pages
        self.nodes[0].createwallet(wallet_name)
        wallet_rpc = self.nodes[0].get_wallet_rpc(wallet_name)
        generated_labels = {}
        for i in range(10):
            address = getnewdestination()[2]
            large_label = ''.join([random.choice(string.ascii_letters) for _ in range(150000)])
            wallet_rpc.setlabel(address, large_label)
            generated_labels[address] = large_label
        # fill the keypool to create BDB internal pages
        wallet_rpc.keypoolrefill(1000)
        self.stop_node(0)

        wallet_dumpfile = self.nodes[0].datadir_path / "bdb_ro_test.dump"
        self.assert_tool_output("The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n", "-wallet={}".format(wallet_name), "-dumpfile={}".format(wallet_dumpfile), "dump")

        expected_dump = self.read_dump(wallet_dumpfile)
        # remove extra entries from wallet tool dump that are not actual key/value pairs from the database
        del expected_dump['BITCOIN_CORE_WALLET_DUMP']
        del expected_dump['format']
        del expected_dump['checksum']
        bdb_ro_parser_dump_raw = dump_bdb_kv(self.nodes[0].wallets_path / wallet_name / "wallet.dat")
        bdb_ro_parser_dump = OrderedDict()
        assert any([len(bytes.fromhex(value)) >= 150000 for value in expected_dump.values()])
        for key, value in sorted(bdb_ro_parser_dump_raw.items()):
            bdb_ro_parser_dump[key.hex()] = value.hex()