LCOV - code coverage report
Current view: top level - src/wallet - dump.cpp (source / functions) Coverage Total Hit
Test: fuzz_coverage.info Lines: 25.3 % 170 43
Test Date: 2025-05-07 04:08:43 Functions: 33.3 % 3 1
Branches: 13.9 % 288 40

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

Generated by: LCOV version 2.0-1