Move CWalletDB code to new walletdb module.
[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 license.txt 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
16 extern CCriticalSection cs_db;
17 extern map<string, int> mapFileUseCount;
18 extern void CloseDb(const string& strFile);
19
20 //
21 // CWalletDB
22 //
23
24 bool CWalletDB::WriteName(const string& strAddress, const string& strName)
25 {
26     nWalletDBUpdated++;
27     return Write(make_pair(string("name"), strAddress), strName);
28 }
29
30 bool CWalletDB::EraseName(const string& strAddress)
31 {
32     // This should only be used for sending addresses, never for receiving addresses,
33     // receiving addresses must always have an address book entry if they're not change return.
34     nWalletDBUpdated++;
35     return Erase(make_pair(string("name"), strAddress));
36 }
37
38 bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account)
39 {
40     account.SetNull();
41     return Read(make_pair(string("acc"), strAccount), account);
42 }
43
44 bool CWalletDB::WriteAccount(const string& strAccount, const CAccount& account)
45 {
46     return Write(make_pair(string("acc"), strAccount), account);
47 }
48
49 bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry)
50 {
51     return Write(boost::make_tuple(string("acentry"), acentry.strAccount, ++nAccountingEntryNumber), acentry);
52 }
53
54 int64 CWalletDB::GetAccountCreditDebit(const string& strAccount)
55 {
56     list<CAccountingEntry> entries;
57     ListAccountCreditDebit(strAccount, entries);
58
59     int64 nCreditDebit = 0;
60     BOOST_FOREACH (const CAccountingEntry& entry, entries)
61         nCreditDebit += entry.nCreditDebit;
62
63     return nCreditDebit;
64 }
65
66 void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountingEntry>& entries)
67 {
68     bool fAllAccounts = (strAccount == "*");
69
70     Dbc* pcursor = GetCursor();
71     if (!pcursor)
72         throw runtime_error("CWalletDB::ListAccountCreditDebit() : cannot create DB cursor");
73     unsigned int fFlags = DB_SET_RANGE;
74     loop
75     {
76         // Read next record
77         CDataStream ssKey;
78         if (fFlags == DB_SET_RANGE)
79             ssKey << boost::make_tuple(string("acentry"), (fAllAccounts? string("") : strAccount), uint64(0));
80         CDataStream ssValue;
81         int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
82         fFlags = DB_NEXT;
83         if (ret == DB_NOTFOUND)
84             break;
85         else if (ret != 0)
86         {
87             pcursor->close();
88             throw runtime_error("CWalletDB::ListAccountCreditDebit() : error scanning DB");
89         }
90
91         // Unserialize
92         string strType;
93         ssKey >> strType;
94         if (strType != "acentry")
95             break;
96         CAccountingEntry acentry;
97         ssKey >> acentry.strAccount;
98         if (!fAllAccounts && acentry.strAccount != strAccount)
99             break;
100
101         ssValue >> acentry;
102         entries.push_back(acentry);
103     }
104
105     pcursor->close();
106 }
107
108
109 int CWalletDB::LoadWallet(CWallet* pwallet)
110 {
111     pwallet->vchDefaultKey.clear();
112     int nFileVersion = 0;
113     vector<uint256> vWalletUpgrade;
114     bool fIsEncrypted = false;
115
116     //// todo: shouldn't we catch exceptions and try to recover and continue?
117     {
118         LOCK(pwallet->cs_wallet);
119         int nMinVersion = 0;
120         if (Read((string)"minversion", nMinVersion))
121         {
122             if (nMinVersion > CLIENT_VERSION)
123                 return DB_TOO_NEW;
124             pwallet->LoadMinVersion(nMinVersion);
125         }
126
127         // Get cursor
128         Dbc* pcursor = GetCursor();
129         if (!pcursor)
130         {
131             printf("Error getting wallet database cursor\n");
132             return DB_CORRUPT;
133         }
134
135         loop
136         {
137             // Read next record
138             CDataStream ssKey;
139             CDataStream ssValue;
140             int ret = ReadAtCursor(pcursor, ssKey, ssValue);
141             if (ret == DB_NOTFOUND)
142                 break;
143             else if (ret != 0)
144             {
145                 printf("Error reading next record from wallet database\n");
146                 return DB_CORRUPT;
147             }
148
149             // Unserialize
150             // Taking advantage of the fact that pair serialization
151             // is just the two items serialized one after the other
152             string strType;
153             ssKey >> strType;
154             if (strType == "name")
155             {
156                 string strAddress;
157                 ssKey >> strAddress;
158                 ssValue >> pwallet->mapAddressBook[strAddress];
159             }
160             else if (strType == "tx")
161             {
162                 uint256 hash;
163                 ssKey >> hash;
164                 CWalletTx& wtx = pwallet->mapWallet[hash];
165                 ssValue >> wtx;
166                 wtx.BindWallet(pwallet);
167
168                 if (wtx.GetHash() != hash)
169                     printf("Error in wallet.dat, hash mismatch\n");
170
171                 // Undo serialize changes in 31600
172                 if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703)
173                 {
174                     if (!ssValue.empty())
175                     {
176                         char fTmp;
177                         char fUnused;
178                         ssValue >> fTmp >> fUnused >> wtx.strFromAccount;
179                         printf("LoadWallet() upgrading tx ver=%d %d '%s' %s\n", wtx.fTimeReceivedIsTxTime, fTmp, wtx.strFromAccount.c_str(), hash.ToString().c_str());
180                         wtx.fTimeReceivedIsTxTime = fTmp;
181                     }
182                     else
183                     {
184                         printf("LoadWallet() repairing tx ver=%d %s\n", wtx.fTimeReceivedIsTxTime, hash.ToString().c_str());
185                         wtx.fTimeReceivedIsTxTime = 0;
186                     }
187                     vWalletUpgrade.push_back(hash);
188                 }
189
190                 //// debug print
191                 //printf("LoadWallet  %s\n", wtx.GetHash().ToString().c_str());
192                 //printf(" %12I64d  %s  %s  %s\n",
193                 //    wtx.vout[0].nValue,
194                 //    DateTimeStrFormat("%x %H:%M:%S", wtx.GetBlockTime()).c_str(),
195                 //    wtx.hashBlock.ToString().substr(0,20).c_str(),
196                 //    wtx.mapValue["message"].c_str());
197             }
198             else if (strType == "acentry")
199             {
200                 string strAccount;
201                 ssKey >> strAccount;
202                 uint64 nNumber;
203                 ssKey >> nNumber;
204                 if (nNumber > nAccountingEntryNumber)
205                     nAccountingEntryNumber = nNumber;
206             }
207             else if (strType == "key" || strType == "wkey")
208             {
209                 vector<unsigned char> vchPubKey;
210                 ssKey >> vchPubKey;
211                 CKey key;
212                 if (strType == "key")
213                 {
214                     CPrivKey pkey;
215                     ssValue >> pkey;
216                     key.SetPubKey(vchPubKey);
217                     key.SetPrivKey(pkey);
218                     if (key.GetPubKey() != vchPubKey)
219                     {
220                         printf("Error reading wallet database: CPrivKey pubkey inconsistency\n");
221                         return DB_CORRUPT;
222                     }
223                     if (!key.IsValid())
224                     {
225                         printf("Error reading wallet database: invalid CPrivKey\n");
226                         return DB_CORRUPT;
227                     }
228                 }
229                 else
230                 {
231                     CWalletKey wkey;
232                     ssValue >> wkey;
233                     key.SetPubKey(vchPubKey);
234                     key.SetPrivKey(wkey.vchPrivKey);
235                     if (key.GetPubKey() != vchPubKey)
236                     {
237                         printf("Error reading wallet database: CWalletKey pubkey inconsistency\n");
238                         return DB_CORRUPT;
239                     }
240                     if (!key.IsValid())
241                     {
242                         printf("Error reading wallet database: invalid CWalletKey\n");
243                         return DB_CORRUPT;
244                     }
245                 }
246                 if (!pwallet->LoadKey(key))
247                 {
248                     printf("Error reading wallet database: LoadKey failed\n");
249                     return DB_CORRUPT;
250                 }
251             }
252             else if (strType == "mkey")
253             {
254                 unsigned int nID;
255                 ssKey >> nID;
256                 CMasterKey kMasterKey;
257                 ssValue >> kMasterKey;
258                 if(pwallet->mapMasterKeys.count(nID) != 0)
259                 {
260                     printf("Error reading wallet database: duplicate CMasterKey id %u\n", nID);
261                     return DB_CORRUPT;
262                 }
263                 pwallet->mapMasterKeys[nID] = kMasterKey;
264                 if (pwallet->nMasterKeyMaxID < nID)
265                     pwallet->nMasterKeyMaxID = nID;
266             }
267             else if (strType == "ckey")
268             {
269                 vector<unsigned char> vchPubKey;
270                 ssKey >> vchPubKey;
271                 vector<unsigned char> vchPrivKey;
272                 ssValue >> vchPrivKey;
273                 if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey))
274                 {
275                     printf("Error reading wallet database: LoadCryptedKey failed\n");
276                     return DB_CORRUPT;
277                 }
278                 fIsEncrypted = true;
279             }
280             else if (strType == "defaultkey")
281             {
282                 ssValue >> pwallet->vchDefaultKey;
283             }
284             else if (strType == "pool")
285             {
286                 int64 nIndex;
287                 ssKey >> nIndex;
288                 pwallet->setKeyPool.insert(nIndex);
289             }
290             else if (strType == "version")
291             {
292                 ssValue >> nFileVersion;
293                 if (nFileVersion == 10300)
294                     nFileVersion = 300;
295             }
296             else if (strType == "cscript")
297             {
298                 uint160 hash;
299                 ssKey >> hash;
300                 CScript script;
301                 ssValue >> script;
302                 if (!pwallet->LoadCScript(script))
303                 {
304                     printf("Error reading wallet database: LoadCScript failed\n");
305                     return DB_CORRUPT;
306                 }
307             }
308         }
309         pcursor->close();
310     }
311
312     BOOST_FOREACH(uint256 hash, vWalletUpgrade)
313         WriteTx(hash, pwallet->mapWallet[hash]);
314
315     printf("nFileVersion = %d\n", nFileVersion);
316
317
318     // Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc:
319     if (fIsEncrypted && (nFileVersion == 40000 || nFileVersion == 50000))
320         return DB_NEED_REWRITE;
321
322     if (nFileVersion < CLIENT_VERSION) // Update
323         WriteVersion(CLIENT_VERSION);
324
325     return DB_LOAD_OK;
326 }
327
328 void ThreadFlushWalletDB(void* parg)
329 {
330     const string& strFile = ((const string*)parg)[0];
331     static bool fOneThread;
332     if (fOneThread)
333         return;
334     fOneThread = true;
335     if (!GetBoolArg("-flushwallet", true))
336         return;
337
338     unsigned int nLastSeen = nWalletDBUpdated;
339     unsigned int nLastFlushed = nWalletDBUpdated;
340     int64 nLastWalletUpdate = GetTime();
341     while (!fShutdown)
342     {
343         Sleep(500);
344
345         if (nLastSeen != nWalletDBUpdated)
346         {
347             nLastSeen = nWalletDBUpdated;
348             nLastWalletUpdate = GetTime();
349         }
350
351         if (nLastFlushed != nWalletDBUpdated && GetTime() - nLastWalletUpdate >= 2)
352         {
353             TRY_LOCK(cs_db,lockDb);
354             if (lockDb)
355             {
356                 // Don't do this if any databases are in use
357                 int nRefCount = 0;
358                 map<string, int>::iterator mi = mapFileUseCount.begin();
359                 while (mi != mapFileUseCount.end())
360                 {
361                     nRefCount += (*mi).second;
362                     mi++;
363                 }
364
365                 if (nRefCount == 0 && !fShutdown)
366                 {
367                     map<string, int>::iterator mi = mapFileUseCount.find(strFile);
368                     if (mi != mapFileUseCount.end())
369                     {
370                         printf("%s ", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str());
371                         printf("Flushing wallet.dat\n");
372                         nLastFlushed = nWalletDBUpdated;
373                         int64 nStart = GetTimeMillis();
374
375                         // Flush wallet.dat so it's self contained
376                         CloseDb(strFile);
377                         dbenv.txn_checkpoint(0, 0, 0);
378                         dbenv.lsn_reset(strFile.c_str(), 0);
379
380                         mapFileUseCount.erase(mi++);
381                         printf("Flushed wallet.dat %"PRI64d"ms\n", GetTimeMillis() - nStart);
382                     }
383                 }
384             }
385         }
386     }
387 }
388
389 bool BackupWallet(const CWallet& wallet, const string& strDest)
390 {
391     if (!wallet.fFileBacked)
392         return false;
393     while (!fShutdown)
394     {
395         {
396             LOCK(cs_db);
397             if (!mapFileUseCount.count(wallet.strWalletFile) || mapFileUseCount[wallet.strWalletFile] == 0)
398             {
399                 // Flush log data to the dat file
400                 CloseDb(wallet.strWalletFile);
401                 dbenv.txn_checkpoint(0, 0, 0);
402                 dbenv.lsn_reset(wallet.strWalletFile.c_str(), 0);
403                 mapFileUseCount.erase(wallet.strWalletFile);
404
405                 // Copy wallet.dat
406                 filesystem::path pathSrc = GetDataDir() / wallet.strWalletFile;
407                 filesystem::path pathDest(strDest);
408                 if (filesystem::is_directory(pathDest))
409                     pathDest /= wallet.strWalletFile;
410
411                 try {
412 #if BOOST_VERSION >= 104000
413                     filesystem::copy_file(pathSrc, pathDest, filesystem::copy_option::overwrite_if_exists);
414 #else
415                     filesystem::copy_file(pathSrc, pathDest);
416 #endif
417                     printf("copied wallet.dat to %s\n", pathDest.string().c_str());
418                     return true;
419                 } catch(const filesystem::filesystem_error &e) {
420                     printf("error copying wallet.dat to %s - %s\n", pathDest.string().c_str(), e.what());
421                     return false;
422                 }
423             }
424         }
425         Sleep(100);
426     }
427     return false;
428 }