]>
Commit | Line | Data |
---|---|---|
9eace6b1 JG |
1 | // Copyright (c) 2009-2010 Satoshi Nakamoto |
2 | // Copyright (c) 2009-2012 The Bitcoin developers | |
3 | // Distributed under the MIT/X11 software license, see the accompanying | |
3a25a2b9 | 4 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
9eace6b1 JG |
5 | |
6 | #include "walletdb.h" | |
7 | #include "wallet.h" | |
8 | #include <boost/filesystem.hpp> | |
9 | ||
10 | using namespace std; | |
11 | using namespace boost; | |
12 | ||
13 | ||
14 | static uint64 nAccountingEntryNumber = 0; | |
15 | ||
9eace6b1 JG |
16 | // |
17 | // CWalletDB | |
18 | // | |
19 | ||
20 | bool CWalletDB::WriteName(const string& strAddress, const string& strName) | |
21 | { | |
22 | nWalletDBUpdated++; | |
23 | return Write(make_pair(string("name"), strAddress), strName); | |
24 | } | |
25 | ||
26 | bool CWalletDB::EraseName(const string& strAddress) | |
27 | { | |
28 | // This should only be used for sending addresses, never for receiving addresses, | |
29 | // receiving addresses must always have an address book entry if they're not change return. | |
30 | nWalletDBUpdated++; | |
31 | return Erase(make_pair(string("name"), strAddress)); | |
32 | } | |
33 | ||
34 | bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account) | |
35 | { | |
36 | account.SetNull(); | |
37 | return Read(make_pair(string("acc"), strAccount), account); | |
38 | } | |
39 | ||
40 | bool CWalletDB::WriteAccount(const string& strAccount, const CAccount& account) | |
41 | { | |
42 | return Write(make_pair(string("acc"), strAccount), account); | |
43 | } | |
44 | ||
9c7722b7 LD |
45 | bool CWalletDB::WriteAccountingEntry(const uint64 nAccEntryNum, const CAccountingEntry& acentry) |
46 | { | |
47 | return Write(boost::make_tuple(string("acentry"), acentry.strAccount, nAccEntryNum), acentry); | |
48 | } | |
49 | ||
9eace6b1 JG |
50 | bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry) |
51 | { | |
9c7722b7 | 52 | return WriteAccountingEntry(++nAccountingEntryNumber, acentry); |
9eace6b1 JG |
53 | } |
54 | ||
55 | int64 CWalletDB::GetAccountCreditDebit(const string& strAccount) | |
56 | { | |
57 | list<CAccountingEntry> entries; | |
58 | ListAccountCreditDebit(strAccount, entries); | |
59 | ||
60 | int64 nCreditDebit = 0; | |
61 | BOOST_FOREACH (const CAccountingEntry& entry, entries) | |
62 | nCreditDebit += entry.nCreditDebit; | |
63 | ||
64 | return nCreditDebit; | |
65 | } | |
66 | ||
67 | void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountingEntry>& entries) | |
68 | { | |
69 | bool fAllAccounts = (strAccount == "*"); | |
70 | ||
71 | Dbc* pcursor = GetCursor(); | |
72 | if (!pcursor) | |
73 | throw runtime_error("CWalletDB::ListAccountCreditDebit() : cannot create DB cursor"); | |
74 | unsigned int fFlags = DB_SET_RANGE; | |
75 | loop | |
76 | { | |
77 | // Read next record | |
6b6aaa16 | 78 | CDataStream ssKey(SER_DISK, CLIENT_VERSION); |
9eace6b1 JG |
79 | if (fFlags == DB_SET_RANGE) |
80 | ssKey << boost::make_tuple(string("acentry"), (fAllAccounts? string("") : strAccount), uint64(0)); | |
6b6aaa16 | 81 | CDataStream ssValue(SER_DISK, CLIENT_VERSION); |
9eace6b1 JG |
82 | int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags); |
83 | fFlags = DB_NEXT; | |
84 | if (ret == DB_NOTFOUND) | |
85 | break; | |
86 | else if (ret != 0) | |
87 | { | |
88 | pcursor->close(); | |
89 | throw runtime_error("CWalletDB::ListAccountCreditDebit() : error scanning DB"); | |
90 | } | |
91 | ||
92 | // Unserialize | |
93 | string strType; | |
94 | ssKey >> strType; | |
95 | if (strType != "acentry") | |
96 | break; | |
97 | CAccountingEntry acentry; | |
98 | ssKey >> acentry.strAccount; | |
99 | if (!fAllAccounts && acentry.strAccount != strAccount) | |
100 | break; | |
101 | ||
102 | ssValue >> acentry; | |
9c7722b7 | 103 | ssKey >> acentry.nEntryNo; |
9eace6b1 JG |
104 | entries.push_back(acentry); |
105 | } | |
106 | ||
107 | pcursor->close(); | |
108 | } | |
109 | ||
110 | ||
eed1785f | 111 | DBErrors |
9c7722b7 LD |
112 | CWalletDB::ReorderTransactions(CWallet* pwallet) |
113 | { | |
114 | LOCK(pwallet->cs_wallet); | |
115 | // Old wallets didn't have any defined order for transactions | |
116 | // Probably a bad idea to change the output of this | |
117 | ||
118 | // First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap. | |
119 | typedef pair<CWalletTx*, CAccountingEntry*> TxPair; | |
120 | typedef multimap<int64, TxPair > TxItems; | |
121 | TxItems txByTime; | |
122 | ||
123 | for (map<uint256, CWalletTx>::iterator it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it) | |
124 | { | |
125 | CWalletTx* wtx = &((*it).second); | |
126 | txByTime.insert(make_pair(wtx->nTimeReceived, TxPair(wtx, (CAccountingEntry*)0))); | |
127 | } | |
128 | list<CAccountingEntry> acentries; | |
129 | ListAccountCreditDebit("", acentries); | |
130 | BOOST_FOREACH(CAccountingEntry& entry, acentries) | |
131 | { | |
132 | txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry))); | |
133 | } | |
134 | ||
135 | int64& nOrderPosNext = pwallet->nOrderPosNext; | |
136 | nOrderPosNext = 0; | |
137 | std::vector<int64> nOrderPosOffsets; | |
138 | for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it) | |
139 | { | |
140 | CWalletTx *const pwtx = (*it).second.first; | |
141 | CAccountingEntry *const pacentry = (*it).second.second; | |
142 | int64& nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos; | |
143 | ||
144 | if (nOrderPos == -1) | |
145 | { | |
146 | nOrderPos = nOrderPosNext++; | |
147 | nOrderPosOffsets.push_back(nOrderPos); | |
148 | ||
149 | if (pacentry) | |
150 | // Have to write accounting regardless, since we don't keep it in memory | |
151 | if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry)) | |
152 | return DB_LOAD_FAIL; | |
153 | } | |
154 | else | |
155 | { | |
156 | int64 nOrderPosOff = 0; | |
157 | BOOST_FOREACH(const int64& nOffsetStart, nOrderPosOffsets) | |
158 | { | |
159 | if (nOrderPos >= nOffsetStart) | |
160 | ++nOrderPosOff; | |
161 | } | |
162 | nOrderPos += nOrderPosOff; | |
163 | nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1); | |
164 | ||
165 | if (!nOrderPosOff) | |
166 | continue; | |
167 | ||
168 | // Since we're changing the order, write it back | |
169 | if (pwtx) | |
170 | { | |
171 | if (!WriteTx(pwtx->GetHash(), *pwtx)) | |
172 | return DB_LOAD_FAIL; | |
173 | } | |
174 | else | |
175 | if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry)) | |
176 | return DB_LOAD_FAIL; | |
177 | } | |
178 | } | |
179 | ||
180 | return DB_LOAD_OK; | |
181 | } | |
182 | ||
183 | ||
eed1785f GA |
184 | bool |
185 | ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, | |
186 | int& nFileVersion, vector<uint256>& vWalletUpgrade, | |
187 | bool& fIsEncrypted, bool& fAnyUnordered, string& strType, string& strErr) | |
188 | { | |
189 | try { | |
190 | // Unserialize | |
191 | // Taking advantage of the fact that pair serialization | |
192 | // is just the two items serialized one after the other | |
193 | ssKey >> strType; | |
194 | if (strType == "name") | |
195 | { | |
196 | string strAddress; | |
197 | ssKey >> strAddress; | |
198 | ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()]; | |
199 | } | |
200 | else if (strType == "tx") | |
201 | { | |
202 | uint256 hash; | |
203 | ssKey >> hash; | |
204 | CWalletTx& wtx = pwallet->mapWallet[hash]; | |
205 | ssValue >> wtx; | |
ef3988ca PW |
206 | CValidationState state; |
207 | if (wtx.CheckTransaction(state) && (wtx.GetHash() == hash) && state.IsValid()) | |
eed1785f GA |
208 | wtx.BindWallet(pwallet); |
209 | else | |
210 | { | |
211 | pwallet->mapWallet.erase(hash); | |
212 | return false; | |
213 | } | |
214 | ||
215 | // Undo serialize changes in 31600 | |
216 | if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703) | |
217 | { | |
218 | if (!ssValue.empty()) | |
219 | { | |
220 | char fTmp; | |
221 | char fUnused; | |
222 | ssValue >> fTmp >> fUnused >> wtx.strFromAccount; | |
223 | strErr = strprintf("LoadWallet() upgrading tx ver=%d %d '%s' %s", | |
224 | wtx.fTimeReceivedIsTxTime, fTmp, wtx.strFromAccount.c_str(), hash.ToString().c_str()); | |
225 | wtx.fTimeReceivedIsTxTime = fTmp; | |
226 | } | |
227 | else | |
228 | { | |
229 | strErr = strprintf("LoadWallet() repairing tx ver=%d %s", wtx.fTimeReceivedIsTxTime, hash.ToString().c_str()); | |
230 | wtx.fTimeReceivedIsTxTime = 0; | |
231 | } | |
232 | vWalletUpgrade.push_back(hash); | |
233 | } | |
234 | ||
235 | if (wtx.nOrderPos == -1) | |
236 | fAnyUnordered = true; | |
237 | ||
238 | //// debug print | |
239 | //printf("LoadWallet %s\n", wtx.GetHash().ToString().c_str()); | |
240 | //printf(" %12"PRI64d" %s %s %s\n", | |
241 | // wtx.vout[0].nValue, | |
3f964b3c | 242 | // DateTimeStrFormat("%Y-%m-%d %H:%M:%S", wtx.GetBlockTime()).c_str(), |
eed1785f GA |
243 | // wtx.hashBlock.ToString().substr(0,20).c_str(), |
244 | // wtx.mapValue["message"].c_str()); | |
245 | } | |
246 | else if (strType == "acentry") | |
247 | { | |
248 | string strAccount; | |
249 | ssKey >> strAccount; | |
250 | uint64 nNumber; | |
251 | ssKey >> nNumber; | |
252 | if (nNumber > nAccountingEntryNumber) | |
253 | nAccountingEntryNumber = nNumber; | |
254 | ||
255 | if (!fAnyUnordered) | |
256 | { | |
257 | CAccountingEntry acentry; | |
258 | ssValue >> acentry; | |
259 | if (acentry.nOrderPos == -1) | |
260 | fAnyUnordered = true; | |
261 | } | |
262 | } | |
263 | else if (strType == "key" || strType == "wkey") | |
264 | { | |
265 | vector<unsigned char> vchPubKey; | |
266 | ssKey >> vchPubKey; | |
267 | CKey key; | |
268 | if (strType == "key") | |
269 | { | |
270 | CPrivKey pkey; | |
271 | ssValue >> pkey; | |
272 | key.SetPubKey(vchPubKey); | |
273 | if (!key.SetPrivKey(pkey)) | |
274 | { | |
275 | strErr = "Error reading wallet database: CPrivKey corrupt"; | |
276 | return false; | |
277 | } | |
278 | if (key.GetPubKey() != vchPubKey) | |
279 | { | |
280 | strErr = "Error reading wallet database: CPrivKey pubkey inconsistency"; | |
281 | return false; | |
282 | } | |
283 | if (!key.IsValid()) | |
284 | { | |
285 | strErr = "Error reading wallet database: invalid CPrivKey"; | |
286 | return false; | |
287 | } | |
288 | } | |
289 | else | |
290 | { | |
291 | CWalletKey wkey; | |
292 | ssValue >> wkey; | |
293 | key.SetPubKey(vchPubKey); | |
294 | if (!key.SetPrivKey(wkey.vchPrivKey)) | |
295 | { | |
296 | strErr = "Error reading wallet database: CPrivKey corrupt"; | |
297 | return false; | |
298 | } | |
299 | if (key.GetPubKey() != vchPubKey) | |
300 | { | |
301 | strErr = "Error reading wallet database: CWalletKey pubkey inconsistency"; | |
302 | return false; | |
303 | } | |
304 | if (!key.IsValid()) | |
305 | { | |
306 | strErr = "Error reading wallet database: invalid CWalletKey"; | |
307 | return false; | |
308 | } | |
309 | } | |
310 | if (!pwallet->LoadKey(key)) | |
311 | { | |
312 | strErr = "Error reading wallet database: LoadKey failed"; | |
313 | return false; | |
314 | } | |
315 | } | |
316 | else if (strType == "mkey") | |
317 | { | |
318 | unsigned int nID; | |
319 | ssKey >> nID; | |
320 | CMasterKey kMasterKey; | |
321 | ssValue >> kMasterKey; | |
322 | if(pwallet->mapMasterKeys.count(nID) != 0) | |
323 | { | |
324 | strErr = strprintf("Error reading wallet database: duplicate CMasterKey id %u", nID); | |
325 | return false; | |
326 | } | |
327 | pwallet->mapMasterKeys[nID] = kMasterKey; | |
328 | if (pwallet->nMasterKeyMaxID < nID) | |
329 | pwallet->nMasterKeyMaxID = nID; | |
330 | } | |
331 | else if (strType == "ckey") | |
332 | { | |
333 | vector<unsigned char> vchPubKey; | |
334 | ssKey >> vchPubKey; | |
335 | vector<unsigned char> vchPrivKey; | |
336 | ssValue >> vchPrivKey; | |
337 | if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey)) | |
338 | { | |
339 | strErr = "Error reading wallet database: LoadCryptedKey failed"; | |
340 | return false; | |
341 | } | |
342 | fIsEncrypted = true; | |
343 | } | |
344 | else if (strType == "defaultkey") | |
345 | { | |
346 | ssValue >> pwallet->vchDefaultKey; | |
347 | } | |
348 | else if (strType == "pool") | |
349 | { | |
350 | int64 nIndex; | |
351 | ssKey >> nIndex; | |
352 | pwallet->setKeyPool.insert(nIndex); | |
353 | } | |
354 | else if (strType == "version") | |
355 | { | |
356 | ssValue >> nFileVersion; | |
357 | if (nFileVersion == 10300) | |
358 | nFileVersion = 300; | |
359 | } | |
360 | else if (strType == "cscript") | |
361 | { | |
362 | uint160 hash; | |
363 | ssKey >> hash; | |
364 | CScript script; | |
365 | ssValue >> script; | |
366 | if (!pwallet->LoadCScript(script)) | |
367 | { | |
368 | strErr = "Error reading wallet database: LoadCScript failed"; | |
369 | return false; | |
370 | } | |
371 | } | |
372 | else if (strType == "orderposnext") | |
373 | { | |
374 | ssValue >> pwallet->nOrderPosNext; | |
375 | } | |
376 | } catch (...) | |
377 | { | |
378 | return false; | |
379 | } | |
380 | return true; | |
381 | } | |
382 | ||
383 | static bool IsKeyType(string strType) | |
384 | { | |
385 | return (strType== "key" || strType == "wkey" || | |
386 | strType == "mkey" || strType == "ckey"); | |
387 | } | |
388 | ||
389 | DBErrors CWalletDB::LoadWallet(CWallet* pwallet) | |
9eace6b1 | 390 | { |
fd61d6f5 | 391 | pwallet->vchDefaultKey = CPubKey(); |
9eace6b1 JG |
392 | int nFileVersion = 0; |
393 | vector<uint256> vWalletUpgrade; | |
394 | bool fIsEncrypted = false; | |
9c7722b7 | 395 | bool fAnyUnordered = false; |
eed1785f GA |
396 | bool fNoncriticalErrors = false; |
397 | DBErrors result = DB_LOAD_OK; | |
9eace6b1 | 398 | |
eed1785f | 399 | try { |
9eace6b1 JG |
400 | LOCK(pwallet->cs_wallet); |
401 | int nMinVersion = 0; | |
402 | if (Read((string)"minversion", nMinVersion)) | |
403 | { | |
404 | if (nMinVersion > CLIENT_VERSION) | |
405 | return DB_TOO_NEW; | |
406 | pwallet->LoadMinVersion(nMinVersion); | |
407 | } | |
408 | ||
409 | // Get cursor | |
410 | Dbc* pcursor = GetCursor(); | |
411 | if (!pcursor) | |
412 | { | |
413 | printf("Error getting wallet database cursor\n"); | |
414 | return DB_CORRUPT; | |
415 | } | |
416 | ||
417 | loop | |
418 | { | |
419 | // Read next record | |
6b6aaa16 PW |
420 | CDataStream ssKey(SER_DISK, CLIENT_VERSION); |
421 | CDataStream ssValue(SER_DISK, CLIENT_VERSION); | |
9eace6b1 JG |
422 | int ret = ReadAtCursor(pcursor, ssKey, ssValue); |
423 | if (ret == DB_NOTFOUND) | |
424 | break; | |
425 | else if (ret != 0) | |
426 | { | |
427 | printf("Error reading next record from wallet database\n"); | |
428 | return DB_CORRUPT; | |
429 | } | |
430 | ||
eed1785f GA |
431 | // Try to be tolerant of single corrupt records: |
432 | string strType, strErr; | |
433 | if (!ReadKeyValue(pwallet, ssKey, ssValue, nFileVersion, | |
434 | vWalletUpgrade, fIsEncrypted, fAnyUnordered, strType, strErr)) | |
9eace6b1 | 435 | { |
eed1785f GA |
436 | // losing keys is considered a catastrophic error, anything else |
437 | // we assume the user can live with: | |
438 | if (IsKeyType(strType)) | |
439 | result = DB_CORRUPT; | |
9eace6b1 JG |
440 | else |
441 | { | |
eed1785f GA |
442 | // Leave other errors alone, if we try to fix them we might make things worse. |
443 | fNoncriticalErrors = true; // ... but do warn the user there is something wrong. | |
444 | if (strType == "tx") | |
445 | // Rescan if there is a bad transaction record: | |
446 | SoftSetBoolArg("-rescan", true); | |
9eace6b1 | 447 | } |
da7b8c12 | 448 | } |
eed1785f GA |
449 | if (!strErr.empty()) |
450 | printf("%s\n", strErr.c_str()); | |
9eace6b1 JG |
451 | } |
452 | pcursor->close(); | |
453 | } | |
eed1785f GA |
454 | catch (...) |
455 | { | |
456 | result = DB_CORRUPT; | |
457 | } | |
9eace6b1 | 458 | |
eed1785f GA |
459 | if (fNoncriticalErrors && result == DB_LOAD_OK) |
460 | result = DB_NONCRITICAL_ERROR; | |
461 | ||
462 | // Any wallet corruption at all: skip any rewriting or | |
463 | // upgrading, we don't want to make it worse. | |
464 | if (result != DB_LOAD_OK) | |
465 | return result; | |
9eace6b1 JG |
466 | |
467 | printf("nFileVersion = %d\n", nFileVersion); | |
468 | ||
eed1785f GA |
469 | BOOST_FOREACH(uint256 hash, vWalletUpgrade) |
470 | WriteTx(hash, pwallet->mapWallet[hash]); | |
9eace6b1 JG |
471 | |
472 | // Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc: | |
473 | if (fIsEncrypted && (nFileVersion == 40000 || nFileVersion == 50000)) | |
474 | return DB_NEED_REWRITE; | |
475 | ||
476 | if (nFileVersion < CLIENT_VERSION) // Update | |
477 | WriteVersion(CLIENT_VERSION); | |
478 | ||
9c7722b7 | 479 | if (fAnyUnordered) |
eed1785f | 480 | result = ReorderTransactions(pwallet); |
9c7722b7 | 481 | |
eed1785f | 482 | return result; |
9eace6b1 JG |
483 | } |
484 | ||
485 | void ThreadFlushWalletDB(void* parg) | |
486 | { | |
96931d6f GS |
487 | // Make this thread recognisable as the wallet flushing thread |
488 | RenameThread("bitcoin-wallet"); | |
489 | ||
9eace6b1 JG |
490 | const string& strFile = ((const string*)parg)[0]; |
491 | static bool fOneThread; | |
492 | if (fOneThread) | |
493 | return; | |
494 | fOneThread = true; | |
495 | if (!GetBoolArg("-flushwallet", true)) | |
496 | return; | |
497 | ||
498 | unsigned int nLastSeen = nWalletDBUpdated; | |
499 | unsigned int nLastFlushed = nWalletDBUpdated; | |
500 | int64 nLastWalletUpdate = GetTime(); | |
501 | while (!fShutdown) | |
502 | { | |
503 | Sleep(500); | |
504 | ||
505 | if (nLastSeen != nWalletDBUpdated) | |
506 | { | |
507 | nLastSeen = nWalletDBUpdated; | |
508 | nLastWalletUpdate = GetTime(); | |
509 | } | |
510 | ||
511 | if (nLastFlushed != nWalletDBUpdated && GetTime() - nLastWalletUpdate >= 2) | |
512 | { | |
cd9696fc | 513 | TRY_LOCK(bitdb.cs_db,lockDb); |
9eace6b1 JG |
514 | if (lockDb) |
515 | { | |
516 | // Don't do this if any databases are in use | |
517 | int nRefCount = 0; | |
ffe8b77a JG |
518 | map<string, int>::iterator mi = bitdb.mapFileUseCount.begin(); |
519 | while (mi != bitdb.mapFileUseCount.end()) | |
9eace6b1 JG |
520 | { |
521 | nRefCount += (*mi).second; | |
522 | mi++; | |
523 | } | |
524 | ||
525 | if (nRefCount == 0 && !fShutdown) | |
526 | { | |
ffe8b77a JG |
527 | map<string, int>::iterator mi = bitdb.mapFileUseCount.find(strFile); |
528 | if (mi != bitdb.mapFileUseCount.end()) | |
9eace6b1 | 529 | { |
9eace6b1 JG |
530 | printf("Flushing wallet.dat\n"); |
531 | nLastFlushed = nWalletDBUpdated; | |
532 | int64 nStart = GetTimeMillis(); | |
533 | ||
534 | // Flush wallet.dat so it's self contained | |
ffe8b77a | 535 | bitdb.CloseDb(strFile); |
cd9696fc | 536 | bitdb.CheckpointLSN(strFile); |
9eace6b1 | 537 | |
ffe8b77a | 538 | bitdb.mapFileUseCount.erase(mi++); |
9eace6b1 JG |
539 | printf("Flushed wallet.dat %"PRI64d"ms\n", GetTimeMillis() - nStart); |
540 | } | |
541 | } | |
542 | } | |
543 | } | |
544 | } | |
545 | } | |
546 | ||
547 | bool BackupWallet(const CWallet& wallet, const string& strDest) | |
548 | { | |
549 | if (!wallet.fFileBacked) | |
550 | return false; | |
551 | while (!fShutdown) | |
552 | { | |
553 | { | |
cd9696fc | 554 | LOCK(bitdb.cs_db); |
ffe8b77a | 555 | if (!bitdb.mapFileUseCount.count(wallet.strWalletFile) || bitdb.mapFileUseCount[wallet.strWalletFile] == 0) |
9eace6b1 JG |
556 | { |
557 | // Flush log data to the dat file | |
ffe8b77a | 558 | bitdb.CloseDb(wallet.strWalletFile); |
cd9696fc | 559 | bitdb.CheckpointLSN(wallet.strWalletFile); |
ffe8b77a | 560 | bitdb.mapFileUseCount.erase(wallet.strWalletFile); |
9eace6b1 JG |
561 | |
562 | // Copy wallet.dat | |
563 | filesystem::path pathSrc = GetDataDir() / wallet.strWalletFile; | |
564 | filesystem::path pathDest(strDest); | |
565 | if (filesystem::is_directory(pathDest)) | |
566 | pathDest /= wallet.strWalletFile; | |
567 | ||
568 | try { | |
569 | #if BOOST_VERSION >= 104000 | |
570 | filesystem::copy_file(pathSrc, pathDest, filesystem::copy_option::overwrite_if_exists); | |
571 | #else | |
572 | filesystem::copy_file(pathSrc, pathDest); | |
573 | #endif | |
574 | printf("copied wallet.dat to %s\n", pathDest.string().c_str()); | |
575 | return true; | |
576 | } catch(const filesystem::filesystem_error &e) { | |
577 | printf("error copying wallet.dat to %s - %s\n", pathDest.string().c_str(), e.what()); | |
578 | return false; | |
579 | } | |
580 | } | |
581 | } | |
582 | Sleep(100); | |
583 | } | |
584 | return false; | |
585 | } | |
eed1785f GA |
586 | |
587 | // | |
588 | // Try to (very carefully!) recover wallet.dat if there is a problem. | |
589 | // | |
590 | bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys) | |
591 | { | |
592 | // Recovery procedure: | |
593 | // move wallet.dat to wallet.timestamp.bak | |
594 | // Call Salvage with fAggressive=true to | |
595 | // get as much data as possible. | |
596 | // Rewrite salvaged data to wallet.dat | |
597 | // Set -rescan so any missing transactions will be | |
598 | // found. | |
599 | int64 now = GetTime(); | |
600 | std::string newFilename = strprintf("wallet.%"PRI64d".bak", now); | |
601 | ||
602 | int result = dbenv.dbenv.dbrename(NULL, filename.c_str(), NULL, | |
603 | newFilename.c_str(), DB_AUTO_COMMIT); | |
604 | if (result == 0) | |
605 | printf("Renamed %s to %s\n", filename.c_str(), newFilename.c_str()); | |
606 | else | |
607 | { | |
608 | printf("Failed to rename %s to %s\n", filename.c_str(), newFilename.c_str()); | |
609 | return false; | |
610 | } | |
611 | ||
612 | std::vector<CDBEnv::KeyValPair> salvagedData; | |
613 | bool allOK = dbenv.Salvage(newFilename, true, salvagedData); | |
614 | if (salvagedData.empty()) | |
615 | { | |
616 | printf("Salvage(aggressive) found no records in %s.\n", newFilename.c_str()); | |
617 | return false; | |
618 | } | |
619 | printf("Salvage(aggressive) found %"PRIszu" records\n", salvagedData.size()); | |
620 | ||
621 | bool fSuccess = allOK; | |
622 | Db* pdbCopy = new Db(&dbenv.dbenv, 0); | |
623 | int ret = pdbCopy->open(NULL, // Txn pointer | |
624 | filename.c_str(), // Filename | |
625 | "main", // Logical db name | |
626 | DB_BTREE, // Database type | |
627 | DB_CREATE, // Flags | |
628 | 0); | |
629 | if (ret > 0) | |
630 | { | |
631 | printf("Cannot create database file %s\n", filename.c_str()); | |
632 | return false; | |
633 | } | |
634 | CWallet dummyWallet; | |
635 | int nFileVersion = 0; | |
636 | vector<uint256> vWalletUpgrade; | |
637 | bool fIsEncrypted = false; | |
638 | bool fAnyUnordered = false; | |
639 | ||
640 | DbTxn* ptxn = dbenv.TxnBegin(); | |
641 | BOOST_FOREACH(CDBEnv::KeyValPair& row, salvagedData) | |
642 | { | |
643 | if (fOnlyKeys) | |
644 | { | |
645 | CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION); | |
646 | CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); | |
647 | string strType, strErr; | |
648 | bool fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, | |
649 | nFileVersion, vWalletUpgrade, | |
650 | fIsEncrypted, fAnyUnordered, | |
651 | strType, strErr); | |
652 | if (!IsKeyType(strType)) | |
653 | continue; | |
654 | if (!fReadOK) | |
655 | { | |
656 | printf("WARNING: CWalletDB::Recover skipping %s: %s\n", strType.c_str(), strErr.c_str()); | |
657 | continue; | |
658 | } | |
659 | } | |
660 | Dbt datKey(&row.first[0], row.first.size()); | |
661 | Dbt datValue(&row.second[0], row.second.size()); | |
662 | int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE); | |
663 | if (ret2 > 0) | |
664 | fSuccess = false; | |
665 | } | |
666 | ptxn->commit(0); | |
667 | pdbCopy->close(0); | |
668 | delete pdbCopy; | |
669 | ||
670 | return fSuccess; | |
671 | } | |
672 | ||
673 | bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename) | |
674 | { | |
675 | return CWalletDB::Recover(dbenv, filename, false); | |
676 | } |