LCOV - code coverage report
Current view: top level - src/wallet - dump.cpp (source / functions) Coverage Total Hit
Test: total_coverage.info Lines: 84.7 % 177 150
Test Date: 2026-02-04 05:05:50 Functions: 100.0 % 3 3
Branches: 50.0 % 334 167

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2020-present The Bitcoin Core developers
       2                 :             : // Distributed under the MIT software license, see the accompanying
       3                 :             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4                 :             : 
       5                 :             : #include <wallet/dump.h>
       6                 :             : 
       7                 :             : #include <common/args.h>
       8                 :             : #include <util/fs.h>
       9                 :             : #include <util/translation.h>
      10                 :             : #include <wallet/wallet.h>
      11                 :             : #include <wallet/walletdb.h>
      12                 :             : 
      13                 :             : #include <algorithm>
      14                 :             : #include <fstream>
      15                 :             : #include <memory>
      16                 :             : #include <string>
      17                 :             : #include <utility>
      18                 :             : #include <vector>
      19                 :             : 
      20                 :             : namespace wallet {
      21                 :             : static const std::string DUMP_MAGIC = "BITCOIN_CORE_WALLET_DUMP";
      22                 :             : uint32_t DUMP_VERSION = 1;
      23                 :             : 
      24                 :           7 : bool DumpWallet(const ArgsManager& args, WalletDatabase& db, bilingual_str& error)
      25                 :             : {
      26                 :             :     // Get the dumpfile
      27   [ +  -  +  - ]:          14 :     std::string dump_filename = args.GetArg("-dumpfile", "");
      28         [ +  + ]:           7 :     if (dump_filename.empty()) {
      29         [ +  - ]:           1 :         error = _("No dump file provided. To use dump, -dumpfile=<filename> must be provided.");
      30                 :           1 :         return false;
      31                 :             :     }
      32                 :             : 
      33         [ +  - ]:           6 :     fs::path path = fs::PathFromString(dump_filename);
      34         [ +  - ]:          12 :     path = fs::absolute(path);
      35   [ +  -  +  + ]:           6 :     if (fs::exists(path)) {
      36   [ -  +  +  - ]:           3 :         error = strprintf(_("File %s already exists. If you are sure this is what you want, move it out of the way first."), fs::PathToString(path));
      37                 :           1 :         return false;
      38                 :             :     }
      39         [ +  - ]:           5 :     std::ofstream dump_file;
      40         [ +  - ]:           5 :     dump_file.open(path.std_path());
      41         [ -  + ]:           5 :     if (dump_file.fail()) {
      42   [ #  #  #  # ]:           0 :         error = strprintf(_("Unable to open %s for writing"), fs::PathToString(path));
      43                 :           0 :         return false;
      44                 :             :     }
      45                 :             : 
      46         [ +  - ]:           5 :     HashWriter hasher{};
      47                 :             : 
      48         [ +  - ]:           5 :     std::unique_ptr<DatabaseBatch> batch = db.MakeBatch();
      49                 :             : 
      50                 :           5 :     bool ret = true;
      51         [ +  - ]:           5 :     std::unique_ptr<DatabaseCursor> cursor = batch->GetNewCursor();
      52         [ -  + ]:           5 :     if (!cursor) {
      53         [ #  # ]:           0 :         error = _("Error: Couldn't create cursor into database");
      54                 :           0 :         ret = false;
      55                 :             :     }
      56                 :             : 
      57                 :             :     // Write out a magic string with version
      58         [ +  - ]:           5 :     std::string line = strprintf("%s,%u\n", DUMP_MAGIC, DUMP_VERSION);
      59   [ -  +  +  - ]:           5 :     dump_file.write(line.data(), line.size());
      60   [ -  +  +  - ]:           5 :     hasher << std::span{line};
      61                 :             : 
      62                 :             :     // Write out the file format
      63         [ +  - ]:           5 :     std::string format = db.Format();
      64                 :             :     // BDB files that are opened using BerkeleyRODatabase have its format as "bdb_ro"
      65                 :             :     // We want to override that format back to "bdb"
      66         [ -  + ]:           5 :     if (format == "bdb_ro") {
      67         [ #  # ]:           0 :         format = "bdb";
      68                 :             :     }
      69         [ +  - ]:           5 :     line = strprintf("%s,%s\n", "format", format);
      70   [ -  +  +  - ]:           5 :     dump_file.write(line.data(), line.size());
      71   [ -  +  +  - ]:           5 :     hasher << std::span{line};
      72                 :             : 
      73         [ +  - ]:           5 :     if (ret) {
      74                 :             : 
      75                 :             :         // Read the records
      76                 :        2291 :         while (true) {
      77                 :        1148 :             DataStream ss_key{};
      78                 :        1148 :             DataStream ss_value{};
      79         [ +  - ]:        1148 :             DatabaseCursor::Status status = cursor->Next(ss_key, ss_value);
      80         [ +  + ]:        1148 :             if (status == DatabaseCursor::Status::DONE) {
      81                 :             :                 ret = true;
      82                 :             :                 break;
      83         [ -  + ]:        1143 :             } else if (status == DatabaseCursor::Status::FAIL) {
      84         [ #  # ]:           0 :                 error = _("Error reading next record from wallet database");
      85                 :           0 :                 ret = false;
      86                 :           0 :                 break;
      87                 :             :             }
      88   [ -  +  +  - ]:        1143 :             std::string key_str = HexStr(ss_key);
      89   [ -  +  +  - ]:        1143 :             std::string value_str = HexStr(ss_value);
      90         [ +  - ]:        1143 :             line = strprintf("%s,%s\n", key_str, value_str);
      91   [ -  +  +  - ]:        1143 :             dump_file.write(line.data(), line.size());
      92   [ -  +  +  - ]:        2286 :             hasher << std::span{line};
      93                 :        1148 :         }
      94                 :             :     }
      95                 :             : 
      96         [ +  - ]:           5 :     cursor.reset();
      97         [ +  - ]:           5 :     batch.reset();
      98                 :             : 
      99         [ +  - ]:           5 :     if (ret) {
     100                 :             :         // Write the hash
     101   [ +  -  +  -  :           5 :         tfm::format(dump_file, "checksum,%s\n", HexStr(hasher.GetHash()));
                   +  - ]
     102         [ +  - ]:           5 :         dump_file.close();
     103                 :             :     } else {
     104                 :             :         // Remove the dumpfile on failure
     105         [ #  # ]:           0 :         dump_file.close();
     106         [ #  # ]:           0 :         fs::remove(path);
     107                 :             :     }
     108                 :             : 
     109                 :           5 :     return ret;
     110                 :          18 : }
     111                 :             : 
     112                 :             : // The standard wallet deleter function blocks on the validation interface
     113                 :             : // queue, which doesn't exist for the bitcoin-wallet. Define our own
     114                 :             : // deleter here.
     115                 :           6 : static void WalletToolReleaseWallet(CWallet* wallet)
     116                 :             : {
     117                 :           6 :     wallet->WalletLogPrintf("Releasing wallet\n");
     118                 :           6 :     wallet->Close();
     119         [ +  - ]:           6 :     delete wallet;
     120                 :           6 : }
     121                 :             : 
     122                 :          13 : bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
     123                 :             : {
     124         [ +  + ]:          13 :     if (name.empty()) {
     125                 :           1 :         tfm::format(std::cerr, "Wallet name cannot be empty\n");
     126                 :           1 :         return false;
     127                 :             :     }
     128                 :             : 
     129                 :             :     // Get the dumpfile
     130   [ +  -  +  - ]:          24 :     std::string dump_filename = args.GetArg("-dumpfile", "");
     131         [ +  + ]:          12 :     if (dump_filename.empty()) {
     132         [ +  - ]:           1 :         error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided.");
     133                 :           1 :         return false;
     134                 :             :     }
     135                 :             : 
     136         [ +  - ]:          11 :     fs::path dump_path = fs::PathFromString(dump_filename);
     137         [ +  - ]:          22 :     dump_path = fs::absolute(dump_path);
     138   [ +  -  +  + ]:          11 :     if (!fs::exists(dump_path)) {
     139   [ -  +  +  - ]:           3 :         error = strprintf(_("Dump file %s does not exist."), fs::PathToString(dump_path));
     140                 :           1 :         return false;
     141                 :             :     }
     142         [ +  - ]:          10 :     std::ifstream dump_file{dump_path.std_path()};
     143                 :             : 
     144                 :             :     // Compute the checksum
     145         [ +  - ]:          10 :     HashWriter hasher{};
     146                 :          10 :     uint256 checksum;
     147                 :             : 
     148                 :             :     // Check the magic and version
     149         [ +  - ]:          10 :     std::string magic_key;
     150         [ +  - ]:          10 :     std::getline(dump_file, magic_key, ',');
     151         [ +  - ]:          10 :     std::string version_value;
     152         [ +  - ]:          10 :     std::getline(dump_file, version_value, '\n');
     153         [ +  + ]:          10 :     if (magic_key != DUMP_MAGIC) {
     154         [ +  - ]:           1 :         error = strprintf(_("Error: Dumpfile identifier record is incorrect. Got \"%s\", expected \"%s\"."), magic_key, DUMP_MAGIC);
     155         [ +  - ]:           1 :         dump_file.close();
     156                 :             :         return false;
     157                 :             :     }
     158                 :             :     // Check the version number (value of first record)
     159         [ -  + ]:           9 :     const auto ver{ToIntegral<uint32_t>(version_value)};
     160         [ -  + ]:           9 :     if (!ver) {
     161         [ #  # ]:           0 :         error = strprintf(_("Error: Unable to parse version %u as a uint32_t"), version_value);
     162         [ #  # ]:           0 :         dump_file.close();
     163                 :             :         return false;
     164                 :             :     }
     165         [ +  + ]:           9 :     if (*ver != DUMP_VERSION) {
     166         [ +  - ]:           2 :         error = strprintf(_("Error: Dumpfile version is not supported. This version of bitcoin-wallet only supports version 1 dumpfiles. Got dumpfile with version %s"), version_value);
     167         [ +  - ]:           2 :         dump_file.close();
     168                 :             :         return false;
     169                 :             :     }
     170         [ +  - ]:           7 :     std::string magic_hasher_line = strprintf("%s,%s\n", magic_key, version_value);
     171   [ -  +  +  - ]:           7 :     hasher << std::span{magic_hasher_line};
     172                 :             : 
     173                 :             :     // Get the stored file format
     174         [ +  - ]:           7 :     std::string format_key;
     175         [ +  - ]:           7 :     std::getline(dump_file, format_key, ',');
     176         [ +  - ]:           7 :     std::string format_value;
     177         [ +  - ]:           7 :     std::getline(dump_file, format_value, '\n');
     178         [ -  + ]:           7 :     if (format_key != "format") {
     179         [ #  # ]:           0 :         error = strprintf(_("Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."), format_key);
     180         [ #  # ]:           0 :         dump_file.close();
     181                 :             :         return false;
     182                 :             :     }
     183                 :             :     // Make sure that the dump was created from a sqlite database only as that is the only
     184                 :             :     // type of database that we still support.
     185                 :             :     // Other formats such as BDB should not be loaded into a sqlite database since they also
     186                 :             :     // use a different type of wallet entirely which is no longer compatible with this software.
     187         [ -  + ]:           7 :     if (format_value != "sqlite") {
     188         [ #  # ]:           0 :         error = strprintf(_("Error: Dumpfile specifies an unsupported database format (%s). Only sqlite database dumps are supported"), format_value);
     189                 :           0 :         return false;
     190                 :             :     }
     191         [ +  - ]:           7 :     std::string format_hasher_line = strprintf("%s,%s\n", format_key, format_value);
     192   [ -  +  +  - ]:           7 :     hasher << std::span{format_hasher_line};
     193                 :             : 
     194         [ +  - ]:           7 :     DatabaseOptions options;
     195                 :           7 :     DatabaseStatus status;
     196         [ +  - ]:           7 :     ReadDatabaseArgs(args, options);
     197                 :           7 :     options.require_create = true;
     198                 :           7 :     options.require_format = DatabaseFormat::SQLITE;
     199         [ +  - ]:           7 :     std::unique_ptr<WalletDatabase> database = MakeDatabase(wallet_path, options, status, error);
     200         [ +  + ]:           7 :     if (!database) return false;
     201                 :             : 
     202                 :             :     // dummy chain interface
     203                 :           6 :     bool ret = true;
     204   [ +  -  +  -  :          12 :     std::shared_ptr<CWallet> wallet(new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet);
             +  -  -  - ]
     205                 :           6 :     {
     206         [ +  - ]:           6 :         LOCK(wallet->cs_wallet);
     207         [ +  - ]:           6 :         DBErrors load_wallet_ret = wallet->LoadWallet();
     208         [ -  + ]:           6 :         if (load_wallet_ret != DBErrors::LOAD_OK) {
     209         [ #  # ]:           0 :             error = strprintf(_("Error creating %s"), name);
     210         [ #  # ]:           0 :             return false;
     211                 :             :         }
     212                 :             : 
     213                 :             :         // Get the database handle
     214                 :           6 :         WalletDatabase& db = wallet->GetDatabase();
     215         [ +  - ]:           6 :         std::unique_ptr<DatabaseBatch> batch = db.MakeBatch();
     216         [ +  - ]:           6 :         batch->TxnBegin();
     217                 :             : 
     218                 :             :         // Read the records from the dump file and write them to the database
     219         [ +  + ]:         232 :         while (dump_file.good()) {
     220         [ +  - ]:         231 :             std::string key;
     221         [ +  - ]:         231 :             std::getline(dump_file, key, ',');
     222         [ +  - ]:         231 :             std::string value;
     223         [ +  - ]:         231 :             std::getline(dump_file, value, '\n');
     224                 :             : 
     225         [ +  + ]:         231 :             if (key == "checksum") {
     226   [ -  +  +  - ]:           5 :                 std::vector<unsigned char> parsed_checksum = ParseHex(value);
     227   [ -  +  +  + ]:           5 :                 if (parsed_checksum.size() != checksum.size()) {
     228   [ +  -  +  - ]:           4 :                     error = Untranslated("Error: Checksum is not the correct size");
     229                 :           2 :                     ret = false;
     230                 :           2 :                     break;
     231                 :             :                 }
     232                 :           3 :                 std::copy(parsed_checksum.begin(), parsed_checksum.end(), checksum.begin());
     233                 :             :                 break;
     234                 :           5 :             }
     235                 :             : 
     236         [ +  - ]:         226 :             std::string line = strprintf("%s,%s\n", key, value);
     237   [ -  +  +  - ]:         226 :             hasher << std::span{line};
     238                 :             : 
     239   [ +  +  -  + ]:         226 :             if (key.empty() || value.empty()) {
     240                 :           1 :                 continue;
     241                 :             :             }
     242                 :             : 
     243   [ -  +  +  -  :         225 :             if (!IsHex(key)) {
                   -  + ]
     244         [ #  # ]:           0 :                 error = strprintf(_("Error: Got key that was not hex: %s"), key);
     245                 :           0 :                 ret = false;
     246                 :           0 :                 break;
     247                 :             :             }
     248   [ -  +  +  -  :         225 :             if (!IsHex(value)) {
                   -  + ]
     249         [ #  # ]:           0 :                 error = strprintf(_("Error: Got value that was not hex: %s"), value);
     250                 :           0 :                 ret = false;
     251                 :           0 :                 break;
     252                 :             :             }
     253                 :             : 
     254   [ -  +  +  - ]:         225 :             std::vector<unsigned char> k = ParseHex(key);
     255   [ -  +  +  - ]:         225 :             std::vector<unsigned char> v = ParseHex(value);
     256   [ -  +  -  +  :         225 :             if (!batch->Write(std::span{k}, std::span{v})) {
             +  -  -  + ]
     257         [ #  # ]:           0 :                 error = strprintf(_("Error: Unable to write record to new wallet"));
     258                 :           0 :                 ret = false;
     259                 :           0 :                 break;
     260                 :             :             }
     261                 :         231 :         }
     262                 :             : 
     263         [ +  + ]:           6 :         if (ret) {
     264         [ +  - ]:           4 :             uint256 comp_checksum = hasher.GetHash();
     265         [ +  + ]:           4 :             if (checksum.IsNull()) {
     266         [ +  - ]:           1 :                 error = _("Error: Missing checksum");
     267                 :           1 :                 ret = false;
     268         [ +  + ]:           3 :             } else if (checksum != comp_checksum) {
     269   [ +  -  +  -  :           2 :                 error = strprintf(_("Error: Dumpfile checksum does not match. Computed %s, expected %s"), HexStr(comp_checksum), HexStr(checksum));
                   +  - ]
     270                 :           1 :                 ret = false;
     271                 :             :             }
     272                 :             :         }
     273                 :             : 
     274                 :           2 :         if (ret) {
     275         [ +  - ]:           2 :             batch->TxnCommit();
     276                 :             :         } else {
     277         [ +  - ]:           4 :             batch->TxnAbort();
     278                 :             :         }
     279                 :             : 
     280         [ +  - ]:           6 :         batch.reset();
     281                 :             : 
     282         [ +  - ]:           6 :         dump_file.close();
     283         [ +  - ]:           6 :     }
     284                 :             :     // On failure, gather the paths to remove
     285         [ +  - ]:           6 :     std::vector<fs::path> paths_to_remove = wallet->GetDatabase().Files();
     286   [ +  -  +  - ]:           6 :     if (!name.empty()) paths_to_remove.push_back(wallet_path);
     287                 :             : 
     288                 :           6 :     wallet.reset(); // The pointer deleter will close the wallet for us.
     289                 :             : 
     290                 :             :     // Remove the wallet dir if we have a failure
     291         [ +  + ]:           6 :     if (!ret) {
     292         [ +  + ]:          16 :         for (const auto& p : paths_to_remove) {
     293         [ +  - ]:          12 :             fs::remove(p);
     294                 :             :         }
     295                 :             :     }
     296                 :             : 
     297                 :           6 :     return ret;
     298                 :          53 : }
     299                 :             : } // namespace wallet
        

Generated by: LCOV version 2.0-1