dump.cpp
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 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.std_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 << std::span{line}; 61 62 // Write out the file format 63 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 if (format == "bdb_ro") { 67 format = "bdb"; 68 } 69 line = strprintf("%s,%s\n", "format", format); 70 dump_file.write(line.data(), line.size()); 71 hasher << std::span{line}; 72 73 if (ret) { 74 75 // Read the records 76 while (true) { 77 DataStream ss_key{}; 78 DataStream ss_value{}; 79 DatabaseCursor::Status status = cursor->Next(ss_key, ss_value); 80 if (status == DatabaseCursor::Status::DONE) { 81 ret = true; 82 break; 83 } else if (status == DatabaseCursor::Status::FAIL) { 84 error = _("Error reading next record from wallet database"); 85 ret = false; 86 break; 87 } 88 std::string key_str = HexStr(ss_key); 89 std::string value_str = HexStr(ss_value); 90 line = strprintf("%s,%s\n", key_str, value_str); 91 dump_file.write(line.data(), line.size()); 92 hasher << std::span{line}; 93 } 94 } 95 96 cursor.reset(); 97 batch.reset(); 98 99 if (ret) { 100 // Write the hash 101 tfm::format(dump_file, "checksum,%s\n", HexStr(hasher.GetHash())); 102 dump_file.close(); 103 } else { 104 // Remove the dumpfile on failure 105 dump_file.close(); 106 fs::remove(path); 107 } 108 109 return ret; 110 } 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 static void WalletToolReleaseWallet(CWallet* wallet) 116 { 117 wallet->WalletLogPrintf("Releasing wallet\n"); 118 wallet->Close(); 119 delete wallet; 120 } 121 122 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 std::string dump_filename = args.GetArg("-dumpfile", ""); 126 if (dump_filename.empty()) { 127 error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided."); 128 return false; 129 } 130 131 fs::path dump_path = fs::PathFromString(dump_filename); 132 dump_path = fs::absolute(dump_path); 133 if (!fs::exists(dump_path)) { 134 error = strprintf(_("Dump file %s does not exist."), fs::PathToString(dump_path)); 135 return false; 136 } 137 std::ifstream dump_file{dump_path.std_path()}; 138 139 // Compute the checksum 140 HashWriter hasher{}; 141 uint256 checksum; 142 143 // Check the magic and version 144 std::string magic_key; 145 std::getline(dump_file, magic_key, ','); 146 std::string version_value; 147 std::getline(dump_file, version_value, '\n'); 148 if (magic_key != DUMP_MAGIC) { 149 error = strprintf(_("Error: Dumpfile identifier record is incorrect. Got \"%s\", expected \"%s\"."), magic_key, DUMP_MAGIC); 150 dump_file.close(); 151 return false; 152 } 153 // Check the version number (value of first record) 154 const auto ver{ToIntegral<uint32_t>(version_value)}; 155 if (!ver) { 156 error = strprintf(_("Error: Unable to parse version %u as a uint32_t"), version_value); 157 dump_file.close(); 158 return false; 159 } 160 if (*ver != DUMP_VERSION) { 161 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 dump_file.close(); 163 return false; 164 } 165 std::string magic_hasher_line = strprintf("%s,%s\n", magic_key, version_value); 166 hasher << std::span{magic_hasher_line}; 167 168 // Get the stored file format 169 std::string format_key; 170 std::getline(dump_file, format_key, ','); 171 std::string format_value; 172 std::getline(dump_file, format_value, '\n'); 173 if (format_key != "format") { 174 error = strprintf(_("Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."), format_key); 175 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 if (format_value != "sqlite") { 183 error = strprintf(_("Error: Dumpfile specifies an unsupported database format (%s). Only sqlite database dumps are supported"), format_value); 184 return false; 185 } 186 std::string format_hasher_line = strprintf("%s,%s\n", format_key, format_value); 187 hasher << std::span{format_hasher_line}; 188 189 DatabaseOptions options; 190 DatabaseStatus status; 191 ReadDatabaseArgs(args, options); 192 options.require_create = true; 193 options.require_format = DatabaseFormat::SQLITE; 194 std::unique_ptr<WalletDatabase> database = MakeDatabase(wallet_path, options, status, error); 195 if (!database) return false; 196 197 // dummy chain interface 198 bool ret = true; 199 std::shared_ptr<CWallet> wallet(new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet); 200 { 201 LOCK(wallet->cs_wallet); 202 DBErrors load_wallet_ret = wallet->LoadWallet(); 203 if (load_wallet_ret != DBErrors::LOAD_OK) { 204 error = strprintf(_("Error creating %s"), name); 205 return false; 206 } 207 208 // Get the database handle 209 WalletDatabase& db = wallet->GetDatabase(); 210 std::unique_ptr<DatabaseBatch> batch = db.MakeBatch(); 211 batch->TxnBegin(); 212 213 // Read the records from the dump file and write them to the database 214 while (dump_file.good()) { 215 std::string key; 216 std::getline(dump_file, key, ','); 217 std::string value; 218 std::getline(dump_file, value, '\n'); 219 220 if (key == "checksum") { 221 std::vector<unsigned char> parsed_checksum = ParseHex(value); 222 if (parsed_checksum.size() != checksum.size()) { 223 error = Untranslated("Error: Checksum is not the correct size"); 224 ret = false; 225 break; 226 } 227 std::copy(parsed_checksum.begin(), parsed_checksum.end(), checksum.begin()); 228 break; 229 } 230 231 std::string line = strprintf("%s,%s\n", key, value); 232 hasher << std::span{line}; 233 234 if (key.empty() || value.empty()) { 235 continue; 236 } 237 238 if (!IsHex(key)) { 239 error = strprintf(_("Error: Got key that was not hex: %s"), key); 240 ret = false; 241 break; 242 } 243 if (!IsHex(value)) { 244 error = strprintf(_("Error: Got value that was not hex: %s"), value); 245 ret = false; 246 break; 247 } 248 249 std::vector<unsigned char> k = ParseHex(key); 250 std::vector<unsigned char> v = ParseHex(value); 251 if (!batch->Write(std::span{k}, std::span{v})) { 252 error = strprintf(_("Error: Unable to write record to new wallet")); 253 ret = false; 254 break; 255 } 256 } 257 258 if (ret) { 259 uint256 comp_checksum = hasher.GetHash(); 260 if (checksum.IsNull()) { 261 error = _("Error: Missing checksum"); 262 ret = false; 263 } else if (checksum != comp_checksum) { 264 error = strprintf(_("Error: Dumpfile checksum does not match. Computed %s, expected %s"), HexStr(comp_checksum), HexStr(checksum)); 265 ret = false; 266 } 267 } 268 269 if (ret) { 270 batch->TxnCommit(); 271 } else { 272 batch->TxnAbort(); 273 } 274 275 batch.reset(); 276 277 dump_file.close(); 278 } 279 wallet.reset(); // The pointer deleter will close the wallet for us. 280 281 // Remove the wallet dir if we have a failure 282 if (!ret) { 283 fs::remove_all(wallet_path); 284 } 285 286 return ret; 287 } 288 } // namespace wallet