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