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 if (name.empty()) { 125 tfm::format(std::cerr, "Wallet name cannot be empty\n"); 126 return false; 127 } 128 129 // Get the dumpfile 130 std::string dump_filename = args.GetArg("-dumpfile", ""); 131 if (dump_filename.empty()) { 132 error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided."); 133 return false; 134 } 135 136 fs::path dump_path = fs::PathFromString(dump_filename); 137 dump_path = fs::absolute(dump_path); 138 if (!fs::exists(dump_path)) { 139 error = strprintf(_("Dump file %s does not exist."), fs::PathToString(dump_path)); 140 return false; 141 } 142 std::ifstream dump_file{dump_path.std_path()}; 143 144 // Compute the checksum 145 HashWriter hasher{}; 146 uint256 checksum; 147 148 // Check the magic and version 149 std::string magic_key; 150 std::getline(dump_file, magic_key, ','); 151 std::string version_value; 152 std::getline(dump_file, version_value, '\n'); 153 if (magic_key != DUMP_MAGIC) { 154 error = strprintf(_("Error: Dumpfile identifier record is incorrect. Got \"%s\", expected \"%s\"."), magic_key, DUMP_MAGIC); 155 dump_file.close(); 156 return false; 157 } 158 // Check the version number (value of first record) 159 const auto ver{ToIntegral<uint32_t>(version_value)}; 160 if (!ver) { 161 error = strprintf(_("Error: Unable to parse version %u as a uint32_t"), version_value); 162 dump_file.close(); 163 return false; 164 } 165 if (*ver != DUMP_VERSION) { 166 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 dump_file.close(); 168 return false; 169 } 170 std::string magic_hasher_line = strprintf("%s,%s\n", magic_key, version_value); 171 hasher << std::span{magic_hasher_line}; 172 173 // Get the stored file format 174 std::string format_key; 175 std::getline(dump_file, format_key, ','); 176 std::string format_value; 177 std::getline(dump_file, format_value, '\n'); 178 if (format_key != "format") { 179 error = strprintf(_("Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."), format_key); 180 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 if (format_value != "sqlite") { 188 error = strprintf(_("Error: Dumpfile specifies an unsupported database format (%s). Only sqlite database dumps are supported"), format_value); 189 return false; 190 } 191 std::string format_hasher_line = strprintf("%s,%s\n", format_key, format_value); 192 hasher << std::span{format_hasher_line}; 193 194 DatabaseOptions options; 195 DatabaseStatus status; 196 ReadDatabaseArgs(args, options); 197 options.require_create = true; 198 options.require_format = DatabaseFormat::SQLITE; 199 std::unique_ptr<WalletDatabase> database = MakeDatabase(wallet_path, options, status, error); 200 if (!database) return false; 201 202 // dummy chain interface 203 bool ret = true; 204 std::shared_ptr<CWallet> wallet(new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet); 205 { 206 // Get the database handle 207 WalletDatabase& db = wallet->GetDatabase(); 208 std::unique_ptr<DatabaseBatch> batch = db.MakeBatch(); 209 batch->TxnBegin(); 210 211 // Read the records from the dump file and write them to the database 212 while (dump_file.good()) { 213 std::string key; 214 std::getline(dump_file, key, ','); 215 std::string value; 216 std::getline(dump_file, value, '\n'); 217 218 if (key == "checksum") { 219 std::vector<unsigned char> parsed_checksum = ParseHex(value); 220 if (parsed_checksum.size() != checksum.size()) { 221 error = Untranslated("Error: Checksum is not the correct size"); 222 ret = false; 223 break; 224 } 225 std::copy(parsed_checksum.begin(), parsed_checksum.end(), checksum.begin()); 226 break; 227 } 228 229 std::string line = strprintf("%s,%s\n", key, value); 230 hasher << std::span{line}; 231 232 if (key.empty() || value.empty()) { 233 continue; 234 } 235 236 if (!IsHex(key)) { 237 error = strprintf(_("Error: Got key that was not hex: %s"), key); 238 ret = false; 239 break; 240 } 241 if (!IsHex(value)) { 242 error = strprintf(_("Error: Got value that was not hex: %s"), value); 243 ret = false; 244 break; 245 } 246 247 std::vector<unsigned char> k = ParseHex(key); 248 std::vector<unsigned char> v = ParseHex(value); 249 if (!batch->Write(std::span{k}, std::span{v})) { 250 error = strprintf(_("Error: Unable to write record to new wallet")); 251 ret = false; 252 break; 253 } 254 } 255 256 if (ret) { 257 uint256 comp_checksum = hasher.GetHash(); 258 if (checksum.IsNull()) { 259 error = _("Error: Missing checksum"); 260 ret = false; 261 } else if (checksum != comp_checksum) { 262 error = strprintf(_("Error: Dumpfile checksum does not match. Computed %s, expected %s"), HexStr(comp_checksum), HexStr(checksum)); 263 ret = false; 264 } 265 } 266 267 if (ret) { 268 batch->TxnCommit(); 269 } else { 270 batch->TxnAbort(); 271 } 272 273 batch.reset(); 274 275 dump_file.close(); 276 } 277 // On failure, gather the paths to remove 278 std::vector<fs::path> paths_to_remove = wallet->GetDatabase().Files(); 279 if (!name.empty()) paths_to_remove.push_back(wallet_path); 280 281 wallet.reset(); // The pointer deleter will close the wallet for us. 282 283 // Remove the wallet dir if we have a failure 284 if (!ret) { 285 for (const auto& p : paths_to_remove) { 286 fs::remove(p); 287 } 288 } 289 290 return ret; 291 } 292 } // namespace wallet