Make addrman.h little compact
[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 "base58.h"
9
10 #include <iostream>
11 #include <fstream>
12
13 #include <boost/version.hpp>
14 #include <boost/filesystem.hpp>
15
16 using namespace std;
17
18 static uint64_t nAccountingEntryNumber = 0;
19 extern bool fWalletUnlockMintOnly;
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 uint64_t nAccEntryNum, const CAccountingEntry& acentry)
51 {
52     return Write(make_tuple(string("acentry"), acentry.strAccount, nAccEntryNum), acentry);
53 }
54
55 bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry)
56 {
57     return WriteAccountingEntry(++nAccountingEntryNumber, acentry);
58 }
59
60 int64_t CWalletDB::GetAccountCreditDebit(const string& strAccount)
61 {
62     list<CAccountingEntry> entries;
63     ListAccountCreditDebit(strAccount, entries);
64
65     int64_t nCreditDebit = 0;
66     for(const auto& entry : entries)
67         nCreditDebit += entry.nCreditDebit;
68
69     return nCreditDebit;
70 }
71
72 void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountingEntry>& entries)
73 {
74     bool fAllAccounts = (strAccount == "*");
75
76     Dbc* pcursor = GetCursor();
77     if (!pcursor)
78         throw runtime_error("CWalletDB::ListAccountCreditDebit() : cannot create DB cursor");
79     unsigned int fFlags = DB_SET_RANGE;
80     for ( ; ; )
81     {
82         // Read next record
83         CDataStream ssKey(SER_DISK, CLIENT_VERSION);
84         if (fFlags == DB_SET_RANGE)
85             ssKey << make_tuple(string("acentry"), (fAllAccounts? string("") : strAccount), uint64_t(0));
86         CDataStream ssValue(SER_DISK, CLIENT_VERSION);
87         int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
88         fFlags = DB_NEXT;
89         if (ret == DB_NOTFOUND)
90             break;
91         else if (ret != 0)
92         {
93             pcursor->close();
94             throw runtime_error("CWalletDB::ListAccountCreditDebit() : error scanning DB");
95         }
96
97         // Unserialize
98         string strType;
99         ssKey >> strType;
100         if (strType != "acentry")
101             break;
102         CAccountingEntry acentry;
103         ssKey >> acentry.strAccount;
104         if (!fAllAccounts && acentry.strAccount != strAccount)
105             break;
106
107         ssValue >> acentry;
108         ssKey >> acentry.nEntryNo;
109         entries.push_back(acentry);
110     }
111
112     pcursor->close();
113 }
114
115
116 DBErrors
117 CWalletDB::ReorderTransactions(CWallet* pwallet)
118 {
119     LOCK(pwallet->cs_wallet);
120     // Old wallets didn't have any defined order for transactions
121     // Probably a bad idea to change the output of this
122
123     // First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap.
124     typedef pair<CWalletTx*, CAccountingEntry*> TxPair;
125     typedef multimap<int64_t, TxPair > TxItems;
126     TxItems txByTime;
127
128     for (auto it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it)
129     {
130         CWalletTx* wtx = &((*it).second);
131         txByTime.insert(make_pair(wtx->nTimeReceived, TxPair(wtx, (CAccountingEntry*)0)));
132     }
133     list<CAccountingEntry> acentries;
134     ListAccountCreditDebit("", acentries);
135     for(auto& entry : acentries)
136     {
137         txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry)));
138     }
139
140     int64_t& nOrderPosNext = pwallet->nOrderPosNext;
141     nOrderPosNext = 0;
142     vector<int64_t> nOrderPosOffsets;
143     for (auto it = txByTime.begin(); it != txByTime.end(); ++it)
144     {
145         CWalletTx *const pwtx = (*it).second.first;
146         CAccountingEntry *const pacentry = (*it).second.second;
147         int64_t& nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos;
148
149         if (nOrderPos == -1)
150         {
151             nOrderPos = nOrderPosNext++;
152             nOrderPosOffsets.push_back(nOrderPos);
153
154             if (pacentry)
155                 // Have to write accounting regardless, since we don't keep it in memory
156                 if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
157                     return DB_LOAD_FAIL;
158         }
159         else
160         {
161             int64_t nOrderPosOff = 0;
162             for(const int64_t& nOffsetStart :  nOrderPosOffsets)
163             {
164                 if (nOrderPos >= nOffsetStart)
165                     ++nOrderPosOff;
166             }
167             nOrderPos += nOrderPosOff;
168             nOrderPosNext = max(nOrderPosNext, nOrderPos + 1);
169
170             if (!nOrderPosOff)
171                 continue;
172
173             // Since we're changing the order, write it back
174             if (pwtx)
175             {
176                 if (!WriteTx(pwtx->GetHash(), *pwtx))
177                     return DB_LOAD_FAIL;
178             }
179             else
180                 if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
181                     return DB_LOAD_FAIL;
182         }
183     }
184
185     return DB_LOAD_OK;
186 }
187
188 class CWalletScanState {
189 public:
190     unsigned int nKeys;
191     unsigned int nCKeys;
192     unsigned int nKeyMeta;
193     bool fIsEncrypted;
194     bool fAnyUnordered;
195     int nFileVersion;
196     vector<uint256> vWalletUpgrade;
197
198     CWalletScanState() {
199         nKeys = nCKeys = nKeyMeta = 0;
200         fIsEncrypted = false;
201         fAnyUnordered = false;
202         nFileVersion = 0;
203     }
204 };
205
206 bool
207 ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
208              CWalletScanState &wss, string& strType, string& strErr)
209 {
210     try {
211         // Unserialize
212         // Taking advantage of the fact that pair serialization
213         // is just the two items serialized one after the other
214         ssKey >> strType;
215
216         if (strType == "name")
217         {
218             string strAddress;
219             ssKey >> strAddress;
220             ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress)];
221         }
222         else if (strType == "tx")
223         {
224             uint256 hash;
225             ssKey >> hash;
226             CWalletTx& wtx = pwallet->mapWallet[hash];
227             ssValue >> wtx;
228             if (wtx.CheckTransaction() && (wtx.GetHash() == hash))
229                 wtx.BindWallet(pwallet);
230             else
231             {
232                 pwallet->mapWallet.erase(hash);
233                 return false;
234             }
235
236             // Undo serialize changes in 31600
237             if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703)
238             {
239                 if (!ssValue.empty())
240                 {
241                     char fTmp;
242                     char fUnused;
243                     ssValue >> fTmp >> fUnused >> wtx.strFromAccount;
244                     strErr = strprintf("LoadWallet() upgrading tx ver=%d %d '%s' %s",
245                                        wtx.fTimeReceivedIsTxTime, fTmp, wtx.strFromAccount.c_str(), hash.ToString().c_str());
246                     wtx.fTimeReceivedIsTxTime = fTmp;
247                 }
248                 else
249                 {
250                     strErr = strprintf("LoadWallet() repairing tx ver=%d %s", wtx.fTimeReceivedIsTxTime, hash.ToString().c_str());
251                     wtx.fTimeReceivedIsTxTime = 0;
252                 }
253                 wss.vWalletUpgrade.push_back(hash);
254             }
255
256             if (wtx.nOrderPos == -1)
257                 wss.fAnyUnordered = true;
258
259             //// debug print
260             //printf("LoadWallet  %s\n", wtx.GetHash().ToString().c_str());
261             //printf(" %12"PRId64"  %s  %s  %s\n",
262             //    wtx.vout[0].nValue,
263             //    DateTimeStrFormat("%x %H:%M:%S", wtx.GetBlockTime()).c_str(),
264             //    wtx.hashBlock.ToString().substr(0,20).c_str(),
265             //    wtx.mapValue["message"].c_str());
266         }
267         else if (strType == "acentry")
268         {
269             string strAccount;
270             ssKey >> strAccount;
271             uint64_t nNumber;
272             ssKey >> nNumber;
273             if (nNumber > nAccountingEntryNumber)
274                 nAccountingEntryNumber = nNumber;
275
276             if (!wss.fAnyUnordered)
277             {
278                 CAccountingEntry acentry;
279                 ssValue >> acentry;
280                 if (acentry.nOrderPos == -1)
281                     wss.fAnyUnordered = true;
282             }
283         }
284         else if (strType == "watchs")
285         {
286             CScript script;
287             ssKey >> script;
288             char fYes;
289             ssValue >> fYes;
290             if (fYes == '1')
291                 pwallet->LoadWatchOnly(script);
292
293             // Watch-only addresses have no birthday information for now,
294             // so set the wallet birthday to the beginning of time.
295             pwallet->nTimeFirstKey = 1;
296         }
297         else if (strType == "malpair")
298         {
299             string strKeyView;
300
301             CSecret vchSecret;
302             ssKey >> strKeyView;
303             ssValue >> vchSecret;
304
305             CMalleableKeyView keyView(strKeyView);
306             if (!pwallet->LoadKey(keyView, vchSecret))
307             {
308                 strErr = "Error reading wallet database: LoadKey failed";
309                 return false;
310             }
311         }
312         else if (strType == "malcpair")
313         {
314             string strKeyView;
315
316             vector<unsigned char> vchCryptedSecret;
317             ssKey >> strKeyView;
318             ssValue >> vchCryptedSecret;
319
320             CMalleableKeyView keyView(strKeyView);
321             if (!pwallet->LoadCryptedKey(keyView, vchCryptedSecret))
322             {
323                 strErr = "Error reading wallet database: LoadCryptedKey failed";
324                 return false;
325             }
326         }
327         else if (strType == "key" || strType == "wkey")
328         {
329             CKey key;
330             CPubKey vchPubKey;
331             ssKey >> vchPubKey;
332             if (strType == "key")
333             {
334                 wss.nKeys++;
335                 CPrivKey pkey;
336                 ssValue >> pkey;
337                 if (!key.SetPrivKey(pkey))
338                 {
339                     strErr = "Error reading wallet database: CPrivKey corrupt";
340                     return false;
341                 }
342                 if (key.GetPubKey() != vchPubKey)
343                 {
344                     strErr = "Error reading wallet database: CPrivKey pubkey inconsistency";
345                     return false;
346                 }
347                 key.SetCompressedPubKey(vchPubKey.IsCompressed());
348                 if (!key.IsValid())
349                 {
350                     strErr = "Error reading wallet database: invalid CPrivKey";
351                     return false;
352                 }
353             }
354             else
355             {
356                 CWalletKey wkey;
357                 ssValue >> wkey;
358                 if (!key.SetPrivKey(wkey.vchPrivKey))
359                 {
360                     strErr = "Error reading wallet database: CPrivKey corrupt";
361                     return false;
362                 }
363                 if (key.GetPubKey() != vchPubKey)
364                 {
365                     strErr = "Error reading wallet database: CWalletKey pubkey inconsistency";
366                     return false;
367                 }
368                 key.SetCompressedPubKey(vchPubKey.IsCompressed());
369                 if (!key.IsValid())
370                 {
371                     strErr = "Error reading wallet database: invalid CWalletKey";
372                     return false;
373                 }
374             }
375             if (!pwallet->LoadKey(key))
376             {
377                 strErr = "Error reading wallet database: LoadKey failed";
378                 return false;
379             }
380         }
381         else if (strType == "mkey")
382         {
383             unsigned int nID;
384             ssKey >> nID;
385             CMasterKey kMasterKey;
386             ssValue >> kMasterKey;
387
388             if(pwallet->mapMasterKeys.count(nID) != 0)
389             {
390                 strErr = strprintf("Error reading wallet database: duplicate CMasterKey id %u", nID);
391                 return false;
392             }
393             pwallet->mapMasterKeys[nID] = kMasterKey;
394             if (pwallet->nMasterKeyMaxID < nID)
395                 pwallet->nMasterKeyMaxID = nID;
396         }
397         else if (strType == "ckey")
398         {
399             wss.nCKeys++;
400             CPubKey vchPubKey;
401             ssKey >> vchPubKey;
402             vector<unsigned char> vchPrivKey;
403             ssValue >> vchPrivKey;
404             if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey))
405             {
406                 strErr = "Error reading wallet database: LoadCryptedKey failed";
407                 return false;
408             }
409             wss.fIsEncrypted = true;
410         }
411         else if (strType == "malmeta")
412         {
413             string strKeyView;
414             ssKey >> strKeyView;
415
416             CMalleableKeyView keyView;
417             keyView.SetString(strKeyView);
418
419             CKeyMetadata keyMeta;
420             ssValue >> keyMeta;
421             wss.nKeyMeta++;
422
423             pwallet->LoadKeyMetadata(keyView, keyMeta);
424         }
425         else if (strType == "keymeta")
426         {
427             CPubKey vchPubKey;
428             ssKey >> vchPubKey;
429             CKeyMetadata keyMeta;
430             ssValue >> keyMeta;
431             wss.nKeyMeta++;
432
433             pwallet->LoadKeyMetadata(vchPubKey, keyMeta);
434
435             // find earliest key creation time, as wallet birthday
436             if (!pwallet->nTimeFirstKey ||
437                 (keyMeta.nCreateTime < pwallet->nTimeFirstKey))
438                 pwallet->nTimeFirstKey = keyMeta.nCreateTime;
439         }
440         else if (strType == "defaultkey")
441         {
442             ssValue >> pwallet->vchDefaultKey;
443         }
444         else if (strType == "pool")
445         {
446             int64_t nIndex;
447             ssKey >> nIndex;
448             CKeyPool keypool;
449             ssValue >> keypool;
450             pwallet->setKeyPool.insert(nIndex);
451
452             // If no metadata exists yet, create a default with the pool key's
453             // creation time. Note that this may be overwritten by actually
454             // stored metadata for that key later, which is fine.
455             CBitcoinAddress addr = CBitcoinAddress(keypool.vchPubKey.GetID());
456             if (pwallet->mapKeyMetadata.count(addr) == 0)
457                 pwallet->mapKeyMetadata[addr] = CKeyMetadata(keypool.nTime);
458
459         }
460         else if (strType == "version")
461         {
462             ssValue >> wss.nFileVersion;
463             if (wss.nFileVersion == 10300)
464                 wss.nFileVersion = 300;
465         }
466         else if (strType == "cscript")
467         {
468             uint160 hash;
469             ssKey >> hash;
470             CScript script;
471             ssValue >> script;
472             if (!pwallet->LoadCScript(script))
473             {
474                 strErr = "Error reading wallet database: LoadCScript failed";
475                 return false;
476             }
477         }
478         else if (strType == "orderposnext")
479         {
480             ssValue >> pwallet->nOrderPosNext;
481         }
482     } catch (...)
483     {
484         return false;
485     }
486     return true;
487 }
488
489 static bool IsKeyType(string strType)
490 {
491     return (strType== "key" || strType == "wkey" ||
492             strType == "mkey" || strType == "ckey" || strType == "malpair" || strType == "malcpair");
493 }
494
495 DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
496 {
497     pwallet->vchDefaultKey = CPubKey();
498     CWalletScanState wss;
499     bool fNoncriticalErrors = false;
500     DBErrors result = DB_LOAD_OK;
501
502     try {
503         LOCK(pwallet->cs_wallet);
504         int nMinVersion = 0;
505         if (Read((string)"minversion", nMinVersion))
506         {
507             if (nMinVersion > CLIENT_VERSION)
508                 return DB_TOO_NEW;
509             pwallet->LoadMinVersion(nMinVersion);
510         }
511
512         // Get cursor
513         Dbc* pcursor = GetCursor();
514         if (!pcursor)
515         {
516             printf("Error getting wallet database cursor\n");
517             return DB_CORRUPT;
518         }
519
520         for ( ; ; )
521         {
522             // Read next record
523             CDataStream ssKey(SER_DISK, CLIENT_VERSION);
524             CDataStream ssValue(SER_DISK, CLIENT_VERSION);
525             int ret = ReadAtCursor(pcursor, ssKey, ssValue);
526             if (ret == DB_NOTFOUND)
527                 break;
528             else if (ret != 0)
529             {
530                 printf("Error reading next record from wallet database\n");
531                 return DB_CORRUPT;
532             }
533
534             // Try to be tolerant of single corrupt records:
535             string strType, strErr;
536             if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr))
537             {
538                 // losing keys is considered a catastrophic error, anything else
539                 // we assume the user can live with:
540                 if (IsKeyType(strType))
541                     result = DB_CORRUPT;
542                 else
543                 {
544                     // Leave other errors alone, if we try to fix them we might make things worse.
545                     fNoncriticalErrors = true; // ... but do warn the user there is something wrong.
546                     if (strType == "tx")
547                         // Rescan if there is a bad transaction record:
548                         SoftSetBoolArg("-rescan", true);
549                 }
550             }
551             if (!strErr.empty())
552                 printf("%s\n", strErr.c_str());
553         }
554         pcursor->close();
555     }
556     catch (...)
557     {
558         result = DB_CORRUPT;
559     }
560
561     if (fNoncriticalErrors && result == DB_LOAD_OK)
562         result = DB_NONCRITICAL_ERROR;
563
564     // Any wallet corruption at all: skip any rewriting or
565     // upgrading, we don't want to make it worse.
566     if (result != DB_LOAD_OK)
567         return result;
568
569     printf("nFileVersion = %d\n", wss.nFileVersion);
570
571     printf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total\n",
572            wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys);
573
574     // nTimeFirstKey is only reliable if all keys have metadata
575     if ((wss.nKeys + wss.nCKeys) != wss.nKeyMeta)
576         pwallet->nTimeFirstKey = 1; // 0 would be considered 'no value'
577
578
579     for(uint256 hash :  wss.vWalletUpgrade)
580         WriteTx(hash, pwallet->mapWallet[hash]);
581
582     // Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc:
583     if (wss.fIsEncrypted && (wss.nFileVersion == 40000 || wss.nFileVersion == 50000))
584         return DB_NEED_REWRITE;
585
586     if (wss.nFileVersion < CLIENT_VERSION) // Update
587         WriteVersion(CLIENT_VERSION);
588
589     if (wss.fAnyUnordered)
590         result = ReorderTransactions(pwallet);
591
592     return result;
593 }
594
595 DBErrors CWalletDB::FindWalletTx(CWallet* pwallet, vector<uint256>& vTxHash)
596 {
597     pwallet->vchDefaultKey = CPubKey();
598     CWalletScanState wss;
599     bool fNoncriticalErrors = false;
600     DBErrors result = DB_LOAD_OK;
601
602     try {
603         LOCK(pwallet->cs_wallet);
604         int nMinVersion = 0;
605         if (Read((string)"minversion", nMinVersion))
606         {
607             if (nMinVersion > CLIENT_VERSION)
608                 return DB_TOO_NEW;
609             pwallet->LoadMinVersion(nMinVersion);
610         }
611
612         // Get cursor
613         Dbc* pcursor = GetCursor();
614         if (!pcursor)
615         {
616             printf("Error getting wallet database cursor\n");
617             return DB_CORRUPT;
618         }
619
620         for ( ; ; )
621         {
622             // Read next record
623             CDataStream ssKey(SER_DISK, CLIENT_VERSION);
624             CDataStream ssValue(SER_DISK, CLIENT_VERSION);
625             int ret = ReadAtCursor(pcursor, ssKey, ssValue);
626             if (ret == DB_NOTFOUND)
627                 break;
628             else if (ret != 0)
629             {
630                 printf("Error reading next record from wallet database\n");
631                 return DB_CORRUPT;
632             }
633
634             string strType;
635             ssKey >> strType;
636             if (strType == "tx") {
637                 uint256 hash;
638                 ssKey >> hash;
639
640                 vTxHash.push_back(hash);
641             }
642         }
643         pcursor->close();
644     }
645     catch (const boost::thread_interrupted&) {
646         throw;
647     }
648     catch (...) {
649         result = DB_CORRUPT;
650     }
651
652     if (fNoncriticalErrors && result == DB_LOAD_OK)
653         result = DB_NONCRITICAL_ERROR;
654
655     return result;
656 }
657
658 DBErrors CWalletDB::ZapWalletTx(CWallet* pwallet)
659 {
660     // build list of wallet TXs
661     vector<uint256> vTxHash;
662     DBErrors err = FindWalletTx(pwallet, vTxHash);
663     if (err != DB_LOAD_OK)
664         return err;
665
666     // erase each wallet TX
667     for(uint256& hash : vTxHash) {
668         if (!EraseTx(hash))
669             return DB_CORRUPT;
670     }
671
672     return DB_LOAD_OK;
673 }
674
675 void ThreadFlushWalletDB(void* parg)
676 {
677     // Make this thread recognisable as the wallet flushing thread
678     RenameThread("novacoin-wallet");
679
680     const string& strFile = ((const string*)parg)[0];
681     static bool fOneThread;
682     if (fOneThread)
683         return;
684     fOneThread = true;
685     if (!GetBoolArg("-flushwallet", true))
686         return;
687
688     unsigned int nLastSeen = nWalletDBUpdated;
689     unsigned int nLastFlushed = nWalletDBUpdated;
690     int64_t nLastWalletUpdate = GetTime();
691     while (!fShutdown)
692     {
693         Sleep(500);
694
695         if (nLastSeen != nWalletDBUpdated)
696         {
697             nLastSeen = nWalletDBUpdated;
698             nLastWalletUpdate = GetTime();
699         }
700
701         if (nLastFlushed != nWalletDBUpdated && GetTime() - nLastWalletUpdate >= 2)
702         {
703             TRY_LOCK(bitdb.cs_db,lockDb);
704             if (lockDb)
705             {
706                 // Don't do this if any databases are in use
707                 int nRefCount = 0;
708                 auto mi = bitdb.mapFileUseCount.begin();
709                 while (mi != bitdb.mapFileUseCount.end())
710                 {
711                     nRefCount += (*mi).second;
712                     mi++;
713                 }
714
715                 if (nRefCount == 0 && !fShutdown)
716                 {
717                     auto mi = bitdb.mapFileUseCount.find(strFile);
718                     if (mi != bitdb.mapFileUseCount.end())
719                     {
720                         printf("Flushing wallet.dat\n");
721                         nLastFlushed = nWalletDBUpdated;
722                         int64_t nStart = GetTimeMillis();
723
724                         // Flush wallet.dat so it's self contained
725                         bitdb.CloseDb(strFile);
726                         bitdb.CheckpointLSN(strFile);
727
728                         bitdb.mapFileUseCount.erase(mi++);
729                         printf("Flushed wallet.dat %" PRId64 "ms\n", GetTimeMillis() - nStart);
730                     }
731                 }
732             }
733         }
734     }
735 }
736
737 bool BackupWallet(const CWallet& wallet, const string& strDest)
738 {
739     if (!wallet.fFileBacked)
740         return false;
741     while (!fShutdown)
742     {
743         {
744             LOCK(bitdb.cs_db);
745             if (!bitdb.mapFileUseCount.count(wallet.strWalletFile) || bitdb.mapFileUseCount[wallet.strWalletFile] == 0)
746             {
747                 // Flush log data to the dat file
748                 bitdb.CloseDb(wallet.strWalletFile);
749                 bitdb.CheckpointLSN(wallet.strWalletFile);
750                 bitdb.mapFileUseCount.erase(wallet.strWalletFile);
751
752                 // Copy wallet.dat
753                 boost::filesystem::path pathSrc = GetDataDir() / wallet.strWalletFile;
754                 boost::filesystem::path pathDest(strDest);
755                 if (boost::filesystem::is_directory(pathDest))
756                     pathDest /= wallet.strWalletFile;
757
758                 try {
759 #if BOOST_VERSION >= 104000
760                     boost::filesystem::copy_file(pathSrc, pathDest, boost::filesystem::copy_option::overwrite_if_exists);
761 #else
762                     boost::filesystem::copy_file(pathSrc, pathDest);
763 #endif
764                     printf("copied wallet.dat to %s\n", pathDest.string().c_str());
765                     return true;
766                 } catch(const boost::filesystem::filesystem_error &e) {
767                     printf("error copying wallet.dat to %s - %s\n", pathDest.string().c_str(), e.what());
768                     return false;
769                 }
770             }
771         }
772         Sleep(100);
773     }
774     return false;
775 }
776
777 bool DumpWallet(CWallet* pwallet, const string& strDest)
778 {
779     if (!pwallet->fFileBacked)
780         return false;
781
782     map<CBitcoinAddress, int64_t> mapAddresses;
783     set<CKeyID> setKeyPool;
784
785     pwallet->GetAddresses(mapAddresses);
786     pwallet->GetAllReserveKeys(setKeyPool);
787
788     // sort time/key pairs
789     vector<pair<int64_t, CBitcoinAddress> > vAddresses;
790     for (auto it = mapAddresses.begin(); it != mapAddresses.end(); it++) {
791         vAddresses.push_back({ it->second, it->first });
792     }
793     mapAddresses.clear();
794     sort(vAddresses.begin(), vAddresses.end());
795
796     // open outputfile as a stream
797     ofstream file;
798     file.open(strDest.c_str());
799     if (!file.is_open())
800        return false;
801
802     // produce output
803     file << strprintf("# Wallet dump created by NovaCoin %s (%s)\n", CLIENT_BUILD.c_str(), CLIENT_DATE.c_str());
804     file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime()).c_str());
805     file << strprintf("# * Best block at time of backup was %i (%s),\n", nBestHeight, hashBestChain.ToString().c_str());
806     file << strprintf("#   mined on %s\n", EncodeDumpTime(pindexBest->nTime).c_str());
807     file << "\n";
808
809     for (const auto& addrItem : vAddresses) {
810         const auto &addr = addrItem.second;
811         const auto strTime = EncodeDumpTime(addrItem.first);
812         const auto strAddr = addr.ToString();
813
814         if (addr.IsPair()) {
815             // Pubkey pair address
816             CMalleableKeyView keyView;
817             CMalleablePubKey mPubKey(addr.GetData());
818             if (!pwallet->GetMalleableView(mPubKey, keyView))
819                 continue;
820             CMalleableKey mKey;
821             pwallet->GetMalleableKey(keyView, mKey);
822             file << mKey.ToString();
823             if (pwallet->mapAddressBook.count(addr))
824                 file << strprintf(" %s label=%s # view=%s addr=%s\n", strTime.c_str(), EncodeDumpString(pwallet->mapAddressBook[addr]).c_str(), keyView.ToString().c_str(), strAddr.c_str());
825             else
826                 file << strprintf(" %s # view=%s addr=%s\n", strTime.c_str(), keyView.ToString().c_str(), strAddr.c_str());
827         }
828         else {
829             // Pubkey hash address
830             CKeyID keyid;
831             addr.GetKeyID(keyid);
832             bool IsCompressed;
833             CKey key;
834             if (!pwallet->GetKey(keyid, key))
835                 continue;
836             auto secret = key.GetSecret(IsCompressed);
837             file << CBitcoinSecret(secret, IsCompressed).ToString();
838             if (pwallet->mapAddressBook.count(addr))
839                 file << strprintf(" %s label=%s # addr=%s\n", strTime.c_str(), EncodeDumpString(pwallet->mapAddressBook[addr]).c_str(), strAddr.c_str());
840             else if (setKeyPool.count(keyid))
841                 file << strprintf(" %s reserve=1 # addr=%s\n", strTime.c_str(), strAddr.c_str());
842             else
843                 file << strprintf(" %s change=1 # addr=%s\n", strTime.c_str(), strAddr.c_str());
844         }
845     }
846
847     file << "\n";
848     file << "# End of dump\n";
849     file.close();
850
851     return true;
852 }
853
854 bool ImportWallet(CWallet *pwallet, const string& strLocation)
855 {
856
857    if (!pwallet->fFileBacked)
858        return false;
859
860    // open inputfile as stream
861    ifstream file;
862    file.open(strLocation.c_str());
863    if (!file.is_open())
864        return false;
865
866    bool fGood = true;
867    int64_t nTimeBegin = pindexBest->nTime;
868
869    // read through input file checking and importing keys into wallet.
870    while (file.good()) {
871        string line;
872        getline(file, line);
873        if (line.empty() || line[0] == '#')
874            continue; // Skip comments and empty lines
875
876        vector<string> vstr;
877        istringstream iss(line);
878        copy(istream_iterator<string>(iss), istream_iterator<string>(), back_inserter(vstr));
879        if (vstr.size() < 2)
880            continue;
881
882        int64_t nTime = DecodeDumpTime(vstr[1]);
883        string strLabel;
884        bool fLabel = true;
885        for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
886            if (vstr[nStr].compare(0,1, "#") == 0)
887                break;
888            if (vstr[nStr] == "change=1")
889                fLabel = false;
890            if (vstr[nStr] == "reserve=1")
891                fLabel = false;
892            if (vstr[nStr].compare(0,6, "label=") == 0) {
893                strLabel = DecodeDumpString(vstr[nStr].substr(6));
894                fLabel = true;
895            }
896        }
897
898        CBitcoinAddress addr;
899        CBitcoinSecret vchSecret;
900        if (vchSecret.SetString(vstr[0])) {
901            // Simple private key
902
903            bool fCompressed;
904            CKey key;
905            auto secret = vchSecret.GetSecret(fCompressed);
906            key.SetSecret(secret, fCompressed);
907            auto keyid = key.GetPubKey().GetID();
908            addr = CBitcoinAddress(keyid);
909
910            if (pwallet->HaveKey(keyid)) {
911                printf("Skipping import of %s (key already present)\n", addr.ToString().c_str());
912                continue;
913            }
914
915            printf("Importing %s...\n", addr.ToString().c_str());
916            if (!pwallet->AddKey(key)) {
917                fGood = false;
918                continue;
919            }
920        } else {
921            // A pair of private keys
922
923            CMalleableKey mKey;
924            if (!mKey.SetString(vstr[0]))
925                continue;
926            auto mPubKey = mKey.GetMalleablePubKey();
927            addr = CBitcoinAddress(mPubKey);
928
929            if (pwallet->CheckOwnership(mPubKey)) {
930                printf("Skipping import of %s (key already present)\n", addr.ToString().c_str());
931                continue;
932            }
933
934            printf("Importing %s...\n", addr.ToString().c_str());
935            if (!pwallet->AddKey(mKey)) {
936                fGood = false;
937                continue;
938            }
939        }
940
941        pwallet->mapKeyMetadata[addr].nCreateTime = nTime;
942        if (fLabel)
943            pwallet->SetAddressBookName(addr, strLabel);
944
945        nTimeBegin = min(nTimeBegin, nTime);
946    }
947    file.close();
948
949    // rescan block chain looking for coins from new keys
950    CBlockIndex *pindex = pindexBest;
951    while (pindex && pindex->pprev && pindex->nTime > nTimeBegin - 7200)
952        pindex = pindex->pprev;
953
954    printf("Rescanning last %i blocks\n", pindexBest->nHeight - pindex->nHeight + 1);
955    pwallet->ScanForWalletTransactions(pindex);
956    pwallet->ReacceptWalletTransactions();
957    pwallet->MarkDirty();
958
959    return fGood;
960 }
961
962 //
963 // Try to (very carefully!) recover wallet.dat if there is a problem.
964 //
965 bool CWalletDB::Recover(CDBEnv& dbenv, string filename, bool fOnlyKeys)
966 {
967     // Recovery procedure:
968     // move wallet.dat to wallet.timestamp.bak
969     // Call Salvage with fAggressive=true to
970     // get as much data as possible.
971     // Rewrite salvaged data to wallet.dat
972     // Set -rescan so any missing transactions will be
973     // found.
974     int64_t now = GetTime();
975     string newFilename = strprintf("wallet.%" PRId64 ".bak", now);
976
977     int result = dbenv.dbenv.dbrename(NULL, filename.c_str(), NULL,
978                                       newFilename.c_str(), DB_AUTO_COMMIT);
979     if (result == 0)
980         printf("Renamed %s to %s\n", filename.c_str(), newFilename.c_str());
981     else
982     {
983         printf("Failed to rename %s to %s\n", filename.c_str(), newFilename.c_str());
984         return false;
985     }
986
987     vector<CDBEnv::KeyValPair> salvagedData;
988     bool allOK = dbenv.Salvage(newFilename, true, salvagedData);
989     if (salvagedData.empty())
990     {
991         printf("Salvage(aggressive) found no records in %s.\n", newFilename.c_str());
992         return false;
993     }
994     printf("Salvage(aggressive) found %" PRIszu " records\n", salvagedData.size());
995
996     bool fSuccess = allOK;
997     unique_ptr<Db> pdbCopy(new Db(&dbenv.dbenv, 0));
998     int ret = pdbCopy->open(NULL,                 // Txn pointer
999                             filename.c_str(),   // Filename
1000                             "main",    // Logical db name
1001                             DB_BTREE,  // Database type
1002                             DB_CREATE,    // Flags
1003                             0);
1004     if (ret > 0)
1005     {
1006         printf("Cannot create database file %s\n", filename.c_str());
1007         return false;
1008     }
1009     CWallet dummyWallet;
1010     CWalletScanState wss;
1011
1012     DbTxn* ptxn = dbenv.TxnBegin();
1013     for(CDBEnv::KeyValPair& row :  salvagedData)
1014     {
1015         if (fOnlyKeys)
1016         {
1017             CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
1018             CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
1019             string strType, strErr;
1020             bool fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue,
1021                                         wss, strType, strErr);
1022             if (!IsKeyType(strType))
1023                 continue;
1024             if (!fReadOK)
1025             {
1026                 printf("WARNING: CWalletDB::Recover skipping %s: %s\n", strType.c_str(), strErr.c_str());
1027                 continue;
1028             }
1029         }
1030         Dbt datKey(&row.first[0], row.first.size());
1031         Dbt datValue(&row.second[0], row.second.size());
1032         int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
1033         if (ret2 > 0)
1034             fSuccess = false;
1035     }
1036     ptxn->commit(0);
1037     pdbCopy->close(0);
1038
1039     return fSuccess;
1040 }
1041
1042 bool CWalletDB::Recover(CDBEnv& dbenv, string filename)
1043 {
1044     return CWalletDB::Recover(dbenv, filename, false);
1045 }