update to 0.4 preview
[novacoin.git] / src / walletdb.cpp
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
4 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
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 extern bool fWalletUnlockMintOnly;
16
17 //
18 // CWalletDB
19 //
20
21 bool CWalletDB::WriteName(const string& strAddress, const string& strName)
22 {
23     nWalletDBUpdated++;
24     return Write(make_pair(string("name"), strAddress), strName);
25 }
26
27 bool CWalletDB::EraseName(const string& strAddress)
28 {
29     // This should only be used for sending addresses, never for receiving addresses,
30     // receiving addresses must always have an address book entry if they're not change return.
31     nWalletDBUpdated++;
32     return Erase(make_pair(string("name"), strAddress));
33 }
34
35 bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account)
36 {
37     account.SetNull();
38     return Read(make_pair(string("acc"), strAccount), account);
39 }
40
41 bool CWalletDB::WriteAccount(const string& strAccount, const CAccount& account)
42 {
43     return Write(make_pair(string("acc"), strAccount), account);
44 }
45
46 bool CWalletDB::WriteAccountingEntry(const uint64 nAccEntryNum, const CAccountingEntry& acentry)
47 {
48     return Write(boost::make_tuple(string("acentry"), acentry.strAccount, nAccEntryNum), acentry);
49 }
50
51 bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry)
52 {
53     return WriteAccountingEntry(++nAccountingEntryNumber, acentry);
54 }
55
56 int64 CWalletDB::GetAccountCreditDebit(const string& strAccount)
57 {
58     list<CAccountingEntry> entries;
59     ListAccountCreditDebit(strAccount, entries);
60
61     int64 nCreditDebit = 0;
62     BOOST_FOREACH (const CAccountingEntry& entry, entries)
63         nCreditDebit += entry.nCreditDebit;
64
65     return nCreditDebit;
66 }
67
68 void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountingEntry>& entries)
69 {
70     bool fAllAccounts = (strAccount == "*");
71
72     Dbc* pcursor = GetCursor();
73     if (!pcursor)
74         throw runtime_error("CWalletDB::ListAccountCreditDebit() : cannot create DB cursor");
75     unsigned int fFlags = DB_SET_RANGE;
76     loop
77     {
78         // Read next record
79         CDataStream ssKey(SER_DISK, CLIENT_VERSION);
80         if (fFlags == DB_SET_RANGE)
81             ssKey << boost::make_tuple(string("acentry"), (fAllAccounts? string("") : strAccount), uint64(0));
82         CDataStream ssValue(SER_DISK, CLIENT_VERSION);
83         int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
84         fFlags = DB_NEXT;
85         if (ret == DB_NOTFOUND)
86             break;
87         else if (ret != 0)
88         {
89             pcursor->close();
90             throw runtime_error("CWalletDB::ListAccountCreditDebit() : error scanning DB");
91         }
92
93         // Unserialize
94         string strType;
95         ssKey >> strType;
96         if (strType != "acentry")
97             break;
98         CAccountingEntry acentry;
99         ssKey >> acentry.strAccount;
100         if (!fAllAccounts && acentry.strAccount != strAccount)
101             break;
102
103         ssValue >> acentry;
104         ssKey >> acentry.nEntryNo;
105         entries.push_back(acentry);
106     }
107
108     pcursor->close();
109 }
110
111
112 DBErrors
113 CWalletDB::ReorderTransactions(CWallet* pwallet)
114 {
115     LOCK(pwallet->cs_wallet);
116     // Old wallets didn't have any defined order for transactions
117     // Probably a bad idea to change the output of this
118
119     // First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap.
120     typedef pair<CWalletTx*, CAccountingEntry*> TxPair;
121     typedef multimap<int64, TxPair > TxItems;
122     TxItems txByTime;
123
124     for (map<uint256, CWalletTx>::iterator it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it)
125     {
126         CWalletTx* wtx = &((*it).second);
127         txByTime.insert(make_pair(wtx->nTimeReceived, TxPair(wtx, (CAccountingEntry*)0)));
128     }
129     list<CAccountingEntry> acentries;
130     ListAccountCreditDebit("", acentries);
131     BOOST_FOREACH(CAccountingEntry& entry, acentries)
132     {
133         txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry)));
134     }
135
136     int64& nOrderPosNext = pwallet->nOrderPosNext;
137     nOrderPosNext = 0;
138     std::vector<int64> nOrderPosOffsets;
139     for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it)
140     {
141         CWalletTx *const pwtx = (*it).second.first;
142         CAccountingEntry *const pacentry = (*it).second.second;
143         int64& nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos;
144
145         if (nOrderPos == -1)
146         {
147             nOrderPos = nOrderPosNext++;
148             nOrderPosOffsets.push_back(nOrderPos);
149
150             if (pacentry)
151                 // Have to write accounting regardless, since we don't keep it in memory
152                 if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
153                     return DB_LOAD_FAIL;
154         }
155         else
156         {
157             int64 nOrderPosOff = 0;
158             BOOST_FOREACH(const int64& nOffsetStart, nOrderPosOffsets)
159             {
160                 if (nOrderPos >= nOffsetStart)
161                     ++nOrderPosOff;
162             }
163             nOrderPos += nOrderPosOff;
164             nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1);
165
166             if (!nOrderPosOff)
167                 continue;
168
169             // Since we're changing the order, write it back
170             if (pwtx)
171             {
172                 if (!WriteTx(pwtx->GetHash(), *pwtx))
173                     return DB_LOAD_FAIL;
174             }
175             else
176                 if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
177                     return DB_LOAD_FAIL;
178         }
179     }
180
181     return DB_LOAD_OK;
182 }
183
184
185 bool
186 ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
187              int& nFileVersion, vector<uint256>& vWalletUpgrade,
188              bool& fIsEncrypted,  bool& fAnyUnordered, string& strType, string& strErr)
189 {
190     try {
191         // Unserialize
192         // Taking advantage of the fact that pair serialization
193         // is just the two items serialized one after the other
194         ssKey >> strType;
195         if (strType == "name")
196         {
197             string strAddress;
198             ssKey >> strAddress;
199             ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()];
200         }
201         else if (strType == "tx")
202         {
203             uint256 hash;
204             ssKey >> hash;
205             CWalletTx& wtx = pwallet->mapWallet[hash];
206             ssValue >> wtx;
207             if (wtx.CheckTransaction() && (wtx.GetHash() == hash))
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,
242             //    DateTimeStrFormat("%x %H:%M:%S", wtx.GetBlockTime()).c_str(),
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)
390 {
391     pwallet->vchDefaultKey = CPubKey();
392     int nFileVersion = 0;
393     vector<uint256> vWalletUpgrade;
394     bool fIsEncrypted = false;
395     bool fAnyUnordered = false;
396     bool fNoncriticalErrors = false;
397     DBErrors result = DB_LOAD_OK;
398
399     try {
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
420             CDataStream ssKey(SER_DISK, CLIENT_VERSION);
421             CDataStream ssValue(SER_DISK, CLIENT_VERSION);
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
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))
435             {
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;
440                 else
441                 {
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);
447                 }
448             }
449             if (!strErr.empty())
450                 printf("%s\n", strErr.c_str());
451         }
452         pcursor->close();
453     }
454     catch (...)
455     {
456         result = DB_CORRUPT;
457     }
458
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;
466
467     printf("nFileVersion = %d\n", nFileVersion);
468
469     BOOST_FOREACH(uint256 hash, vWalletUpgrade)
470         WriteTx(hash, pwallet->mapWallet[hash]);
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
479     if (fAnyUnordered)
480         result = ReorderTransactions(pwallet);
481
482     return result;
483 }
484
485 void ThreadFlushWalletDB(void* parg)
486 {
487     // Make this thread recognisable as the wallet flushing thread
488     RenameThread("bitcoin-wallet");
489
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         {
513             TRY_LOCK(bitdb.cs_db,lockDb);
514             if (lockDb)
515             {
516                 // Don't do this if any databases are in use
517                 int nRefCount = 0;
518                 map<string, int>::iterator mi = bitdb.mapFileUseCount.begin();
519                 while (mi != bitdb.mapFileUseCount.end())
520                 {
521                     nRefCount += (*mi).second;
522                     mi++;
523                 }
524
525                 if (nRefCount == 0 && !fShutdown)
526                 {
527                     map<string, int>::iterator mi = bitdb.mapFileUseCount.find(strFile);
528                     if (mi != bitdb.mapFileUseCount.end())
529                     {
530                         printf("Flushing wallet.dat\n");
531                         nLastFlushed = nWalletDBUpdated;
532                         int64 nStart = GetTimeMillis();
533
534                         // Flush wallet.dat so it's self contained
535                         bitdb.CloseDb(strFile);
536                         bitdb.CheckpointLSN(strFile);
537
538                         bitdb.mapFileUseCount.erase(mi++);
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         {
554             LOCK(bitdb.cs_db);
555             if (!bitdb.mapFileUseCount.count(wallet.strWalletFile) || bitdb.mapFileUseCount[wallet.strWalletFile] == 0)
556             {
557                 // Flush log data to the dat file
558                 bitdb.CloseDb(wallet.strWalletFile);
559                 bitdb.CheckpointLSN(wallet.strWalletFile);
560                 bitdb.mapFileUseCount.erase(wallet.strWalletFile);
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 }
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 }