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