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