flush wallet.dat, multi-proc, reorg options, revert to startup folder shortcut
[novacoin.git] / db.cpp
1 // Copyright (c) 2009 Satoshi Nakamoto\r
2 // Distributed under the MIT/X11 software license, see the accompanying\r
3 // file license.txt or http://www.opensource.org/licenses/mit-license.php.\r
4 \r
5 #include "headers.h"\r
6 \r
7 \r
8 \r
9 \r
10 \r
11 \r
12 //\r
13 // CDB\r
14 //\r
15 \r
16 static CCriticalSection cs_db;\r
17 static bool fDbEnvInit = false;\r
18 DbEnv dbenv(0);\r
19 static map<string, int> mapFileUseCount;\r
20 \r
21 class CDBInit\r
22 {\r
23 public:\r
24     CDBInit()\r
25     {\r
26     }\r
27     ~CDBInit()\r
28     {\r
29         if (fDbEnvInit)\r
30         {\r
31             dbenv.close(0);\r
32             fDbEnvInit = false;\r
33         }\r
34     }\r
35 }\r
36 instance_of_cdbinit;\r
37 \r
38 \r
39 CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL)\r
40 {\r
41     int ret;\r
42     if (pszFile == NULL)\r
43         return;\r
44 \r
45     bool fCreate = strchr(pszMode, 'c');\r
46     bool fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));\r
47     unsigned int nFlags = DB_THREAD;\r
48     if (fCreate)\r
49         nFlags |= DB_CREATE;\r
50     else if (fReadOnly)\r
51         nFlags |= DB_RDONLY;\r
52     if (!fReadOnly || fTxn)\r
53         nFlags |= DB_AUTO_COMMIT;\r
54 \r
55     CRITICAL_BLOCK(cs_db)\r
56     {\r
57         if (!fDbEnvInit)\r
58         {\r
59             string strAppDir = GetAppDir();\r
60             string strLogDir = strAppDir + "\\database";\r
61             _mkdir(strLogDir.c_str());\r
62             printf("dbenv.open strAppDir=%s\n", strAppDir.c_str());\r
63 \r
64             dbenv.set_lg_dir(strLogDir.c_str());\r
65             dbenv.set_lg_max(10000000);\r
66             dbenv.set_lk_max_locks(10000);\r
67             dbenv.set_lk_max_objects(10000);\r
68             dbenv.set_errfile(fopen("db.log", "a")); /// debug\r
69             ///dbenv.log_set_config(DB_LOG_AUTO_REMOVE, 1); /// causes corruption\r
70             ret = dbenv.open(strAppDir.c_str(),\r
71                              DB_CREATE     |\r
72                              DB_INIT_LOCK  |\r
73                              DB_INIT_LOG   |\r
74                              DB_INIT_MPOOL |\r
75                              DB_INIT_TXN   |\r
76                              DB_THREAD     |\r
77                              DB_PRIVATE    |\r
78                              DB_RECOVER,\r
79                              0);\r
80             if (ret > 0)\r
81                 throw runtime_error(strprintf("CDB() : error %d opening database environment\n", ret));\r
82             fDbEnvInit = true;\r
83         }\r
84 \r
85         strFile = pszFile;\r
86         ++mapFileUseCount[strFile];\r
87     }\r
88 \r
89     pdb = new Db(&dbenv, 0);\r
90 \r
91     ret = pdb->open(NULL,      // Txn pointer\r
92                     pszFile,   // Filename\r
93                     "main",    // Logical db name\r
94                     DB_BTREE,  // Database type\r
95                     nFlags,    // Flags\r
96                     0);\r
97 \r
98     if (ret > 0)\r
99     {\r
100         delete pdb;\r
101         pdb = NULL;\r
102         CRITICAL_BLOCK(cs_db)\r
103             --mapFileUseCount[strFile];\r
104         strFile = "";\r
105         throw runtime_error(strprintf("CDB() : can't open database file %s, error %d\n", pszFile, ret));\r
106     }\r
107 \r
108     if (fCreate && !Exists(string("version")))\r
109         WriteVersion(VERSION);\r
110 \r
111     RandAddSeed();\r
112 }\r
113 \r
114 void CDB::Close()\r
115 {\r
116     if (!pdb)\r
117         return;\r
118     if (!vTxn.empty())\r
119         vTxn.front()->abort();\r
120     vTxn.clear();\r
121     pdb->close(0);\r
122     delete pdb;\r
123     pdb = NULL;\r
124     dbenv.txn_checkpoint(0, 0, 0);\r
125 \r
126     CRITICAL_BLOCK(cs_db)\r
127         --mapFileUseCount[strFile];\r
128 \r
129     RandAddSeed();\r
130 }\r
131 \r
132 void DBFlush(bool fShutdown)\r
133 {\r
134     // Flush log data to the actual data file\r
135     //  on all files that are not in use\r
136     printf("DBFlush(%s)\n", fShutdown ? "true" : "false");\r
137     CRITICAL_BLOCK(cs_db)\r
138     {\r
139         dbenv.txn_checkpoint(0, 0, 0);\r
140         map<string, int>::iterator mi = mapFileUseCount.begin();\r
141         while (mi != mapFileUseCount.end())\r
142         {\r
143             string strFile = (*mi).first;\r
144             int nRefCount = (*mi).second;\r
145             if (nRefCount == 0)\r
146             {\r
147                 dbenv.lsn_reset(strFile.c_str(), 0);\r
148                 mapFileUseCount.erase(mi++);\r
149             }\r
150             else\r
151                 mi++;\r
152         }\r
153         if (fShutdown)\r
154         {\r
155             char** listp;\r
156             if (mapFileUseCount.empty())\r
157                 dbenv.log_archive(&listp, DB_ARCH_REMOVE);\r
158             dbenv.close(0);\r
159             fDbEnvInit = false;\r
160         }\r
161     }\r
162 }\r
163 \r
164 \r
165 \r
166 \r
167 \r
168 \r
169 //\r
170 // CTxDB\r
171 //\r
172 \r
173 bool CTxDB::ReadTxIndex(uint256 hash, CTxIndex& txindex)\r
174 {\r
175     assert(!fClient);\r
176     txindex.SetNull();\r
177     return Read(make_pair(string("tx"), hash), txindex);\r
178 }\r
179 \r
180 bool CTxDB::UpdateTxIndex(uint256 hash, const CTxIndex& txindex)\r
181 {\r
182     assert(!fClient);\r
183     return Write(make_pair(string("tx"), hash), txindex);\r
184 }\r
185 \r
186 bool CTxDB::AddTxIndex(const CTransaction& tx, const CDiskTxPos& pos, int nHeight)\r
187 {\r
188     assert(!fClient);\r
189 \r
190     // Add to tx index\r
191     uint256 hash = tx.GetHash();\r
192     CTxIndex txindex(pos, tx.vout.size());\r
193     return Write(make_pair(string("tx"), hash), txindex);\r
194 }\r
195 \r
196 bool CTxDB::EraseTxIndex(const CTransaction& tx)\r
197 {\r
198     assert(!fClient);\r
199     uint256 hash = tx.GetHash();\r
200 \r
201     return Erase(make_pair(string("tx"), hash));\r
202 }\r
203 \r
204 bool CTxDB::ContainsTx(uint256 hash)\r
205 {\r
206     assert(!fClient);\r
207     return Exists(make_pair(string("tx"), hash));\r
208 }\r
209 \r
210 bool CTxDB::ReadOwnerTxes(uint160 hash160, int nMinHeight, vector<CTransaction>& vtx)\r
211 {\r
212     assert(!fClient);\r
213     vtx.clear();\r
214 \r
215     // Get cursor\r
216     Dbc* pcursor = GetCursor();\r
217     if (!pcursor)\r
218         return false;\r
219 \r
220     unsigned int fFlags = DB_SET_RANGE;\r
221     loop\r
222     {\r
223         // Read next record\r
224         CDataStream ssKey;\r
225         if (fFlags == DB_SET_RANGE)\r
226             ssKey << string("owner") << hash160 << CDiskTxPos(0, 0, 0);\r
227         CDataStream ssValue;\r
228         int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);\r
229         fFlags = DB_NEXT;\r
230         if (ret == DB_NOTFOUND)\r
231             break;\r
232         else if (ret != 0)\r
233             return false;\r
234 \r
235         // Unserialize\r
236         string strType;\r
237         uint160 hashItem;\r
238         CDiskTxPos pos;\r
239         ssKey >> strType >> hashItem >> pos;\r
240         int nItemHeight;\r
241         ssValue >> nItemHeight;\r
242 \r
243         // Read transaction\r
244         if (strType != "owner" || hashItem != hash160)\r
245             break;\r
246         if (nItemHeight >= nMinHeight)\r
247         {\r
248             vtx.resize(vtx.size()+1);\r
249             if (!vtx.back().ReadFromDisk(pos))\r
250                 return false;\r
251         }\r
252     }\r
253     return true;\r
254 }\r
255 \r
256 bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx, CTxIndex& txindex)\r
257 {\r
258     assert(!fClient);\r
259     tx.SetNull();\r
260     if (!ReadTxIndex(hash, txindex))\r
261         return false;\r
262     return (tx.ReadFromDisk(txindex.pos));\r
263 }\r
264 \r
265 bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx)\r
266 {\r
267     CTxIndex txindex;\r
268     return ReadDiskTx(hash, tx, txindex);\r
269 }\r
270 \r
271 bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx, CTxIndex& txindex)\r
272 {\r
273     return ReadDiskTx(outpoint.hash, tx, txindex);\r
274 }\r
275 \r
276 bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx)\r
277 {\r
278     CTxIndex txindex;\r
279     return ReadDiskTx(outpoint.hash, tx, txindex);\r
280 }\r
281 \r
282 bool CTxDB::WriteBlockIndex(const CDiskBlockIndex& blockindex)\r
283 {\r
284     return Write(make_pair(string("blockindex"), blockindex.GetBlockHash()), blockindex);\r
285 }\r
286 \r
287 bool CTxDB::EraseBlockIndex(uint256 hash)\r
288 {\r
289     return Erase(make_pair(string("blockindex"), hash));\r
290 }\r
291 \r
292 bool CTxDB::ReadHashBestChain(uint256& hashBestChain)\r
293 {\r
294     return Read(string("hashBestChain"), hashBestChain);\r
295 }\r
296 \r
297 bool CTxDB::WriteHashBestChain(uint256 hashBestChain)\r
298 {\r
299     return Write(string("hashBestChain"), hashBestChain);\r
300 }\r
301 \r
302 CBlockIndex* InsertBlockIndex(uint256 hash)\r
303 {\r
304     if (hash == 0)\r
305         return NULL;\r
306 \r
307     // Return existing\r
308     map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash);\r
309     if (mi != mapBlockIndex.end())\r
310         return (*mi).second;\r
311 \r
312     // Create new\r
313     CBlockIndex* pindexNew = new CBlockIndex();\r
314     if (!pindexNew)\r
315         throw runtime_error("LoadBlockIndex() : new CBlockIndex failed");\r
316     mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;\r
317     pindexNew->phashBlock = &((*mi).first);\r
318 \r
319     return pindexNew;\r
320 }\r
321 \r
322 bool CTxDB::LoadBlockIndex()\r
323 {\r
324     // Get cursor\r
325     Dbc* pcursor = GetCursor();\r
326     if (!pcursor)\r
327         return false;\r
328 \r
329     unsigned int fFlags = DB_SET_RANGE;\r
330     loop\r
331     {\r
332         // Read next record\r
333         CDataStream ssKey;\r
334         if (fFlags == DB_SET_RANGE)\r
335             ssKey << make_pair(string("blockindex"), uint256(0));\r
336         CDataStream ssValue;\r
337         int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);\r
338         fFlags = DB_NEXT;\r
339         if (ret == DB_NOTFOUND)\r
340             break;\r
341         else if (ret != 0)\r
342             return false;\r
343 \r
344         // Unserialize\r
345         string strType;\r
346         ssKey >> strType;\r
347         if (strType == "blockindex")\r
348         {\r
349             CDiskBlockIndex diskindex;\r
350             ssValue >> diskindex;\r
351 \r
352             // Construct block index object\r
353             CBlockIndex* pindexNew = InsertBlockIndex(diskindex.GetBlockHash());\r
354             pindexNew->pprev          = InsertBlockIndex(diskindex.hashPrev);\r
355             pindexNew->pnext          = InsertBlockIndex(diskindex.hashNext);\r
356             pindexNew->nFile          = diskindex.nFile;\r
357             pindexNew->nBlockPos      = diskindex.nBlockPos;\r
358             pindexNew->nHeight        = diskindex.nHeight;\r
359             pindexNew->nVersion       = diskindex.nVersion;\r
360             pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot;\r
361             pindexNew->nTime          = diskindex.nTime;\r
362             pindexNew->nBits          = diskindex.nBits;\r
363             pindexNew->nNonce         = diskindex.nNonce;\r
364 \r
365             // Watch for genesis block and best block\r
366             if (pindexGenesisBlock == NULL && diskindex.GetBlockHash() == hashGenesisBlock)\r
367                 pindexGenesisBlock = pindexNew;\r
368         }\r
369         else\r
370         {\r
371             break;\r
372         }\r
373     }\r
374 \r
375     if (!ReadHashBestChain(hashBestChain))\r
376     {\r
377         if (pindexGenesisBlock == NULL)\r
378             return true;\r
379         return error("CTxDB::LoadBlockIndex() : hashBestChain not found\n");\r
380     }\r
381 \r
382     if (!mapBlockIndex.count(hashBestChain))\r
383         return error("CTxDB::LoadBlockIndex() : blockindex for hashBestChain not found\n");\r
384     pindexBest = mapBlockIndex[hashBestChain];\r
385     nBestHeight = pindexBest->nHeight;\r
386     printf("LoadBlockIndex(): hashBestChain=%s  height=%d\n", hashBestChain.ToString().substr(0,14).c_str(), nBestHeight);\r
387 \r
388     return true;\r
389 }\r
390 \r
391 \r
392 \r
393 \r
394 \r
395 //\r
396 // CAddrDB\r
397 //\r
398 \r
399 bool CAddrDB::WriteAddress(const CAddress& addr)\r
400 {\r
401     return Write(make_pair(string("addr"), addr.GetKey()), addr);\r
402 }\r
403 \r
404 bool CAddrDB::LoadAddresses()\r
405 {\r
406     CRITICAL_BLOCK(cs_mapIRCAddresses)\r
407     CRITICAL_BLOCK(cs_mapAddresses)\r
408     {\r
409         // Load user provided addresses\r
410         CAutoFile filein = fopen("addr.txt", "rt");\r
411         if (filein)\r
412         {\r
413             try\r
414             {\r
415                 char psz[1000];\r
416                 while (fgets(psz, sizeof(psz), filein))\r
417                 {\r
418                     CAddress addr(psz, NODE_NETWORK);\r
419                     if (addr.ip != 0)\r
420                     {\r
421                         AddAddress(*this, addr);\r
422                         mapIRCAddresses.insert(make_pair(addr.GetKey(), addr));\r
423                     }\r
424                 }\r
425             }\r
426             catch (...) { }\r
427         }\r
428 \r
429         // Get cursor\r
430         Dbc* pcursor = GetCursor();\r
431         if (!pcursor)\r
432             return false;\r
433 \r
434         loop\r
435         {\r
436             // Read next record\r
437             CDataStream ssKey;\r
438             CDataStream ssValue;\r
439             int ret = ReadAtCursor(pcursor, ssKey, ssValue);\r
440             if (ret == DB_NOTFOUND)\r
441                 break;\r
442             else if (ret != 0)\r
443                 return false;\r
444 \r
445             // Unserialize\r
446             string strType;\r
447             ssKey >> strType;\r
448             if (strType == "addr")\r
449             {\r
450                 CAddress addr;\r
451                 ssValue >> addr;\r
452                 mapAddresses.insert(make_pair(addr.GetKey(), addr));\r
453             }\r
454         }\r
455 \r
456         printf("Loaded %d addresses\n", mapAddresses.size());\r
457 \r
458         // Fix for possible bug that manifests in mapAddresses.count in irc.cpp,\r
459         // just need to call count here and it doesn't happen there.  The bug was the\r
460         // pack pragma in irc.cpp and has been fixed, but I'm not in a hurry to delete this.\r
461         mapAddresses.count(vector<unsigned char>(18));\r
462     }\r
463 \r
464     return true;\r
465 }\r
466 \r
467 bool LoadAddresses()\r
468 {\r
469     return CAddrDB("cr+").LoadAddresses();\r
470 }\r
471 \r
472 \r
473 \r
474 \r
475 //\r
476 // CReviewDB\r
477 //\r
478 \r
479 bool CReviewDB::ReadReviews(uint256 hash, vector<CReview>& vReviews)\r
480 {\r
481     vReviews.size(); // msvc workaround, just need to do anything with vReviews\r
482     return Read(make_pair(string("reviews"), hash), vReviews);\r
483 }\r
484 \r
485 bool CReviewDB::WriteReviews(uint256 hash, const vector<CReview>& vReviews)\r
486 {\r
487     return Write(make_pair(string("reviews"), hash), vReviews);\r
488 }\r
489 \r
490 \r
491 \r
492 \r
493 \r
494 \r
495 \r
496 //\r
497 // CWalletDB\r
498 //\r
499 \r
500 CWalletDB::~CWalletDB()\r
501 {\r
502     // Flush whenever all handles to wallet.dat are closed\r
503     Close();\r
504     CRITICAL_BLOCK(cs_db)\r
505     {\r
506         map<string, int>::iterator mi = mapFileUseCount.find(strFile);\r
507         if (mi != mapFileUseCount.end())\r
508         {\r
509             int nRefCount = (*mi).second;\r
510             if (nRefCount == 0)\r
511             {\r
512                 dbenv.txn_checkpoint(0, 0, 0);\r
513                 dbenv.lsn_reset(strFile.c_str(), 0);\r
514                 mapFileUseCount.erase(mi++);\r
515             }\r
516         }\r
517     }\r
518 }\r
519 \r
520 bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet)\r
521 {\r
522     vchDefaultKeyRet.clear();\r
523 \r
524     //// todo: shouldn't we catch exceptions and try to recover and continue?\r
525     CRITICAL_BLOCK(cs_mapKeys)\r
526     CRITICAL_BLOCK(cs_mapWallet)\r
527     {\r
528         // Get cursor\r
529         Dbc* pcursor = GetCursor();\r
530         if (!pcursor)\r
531             return false;\r
532 \r
533         loop\r
534         {\r
535             // Read next record\r
536             CDataStream ssKey;\r
537             CDataStream ssValue;\r
538             int ret = ReadAtCursor(pcursor, ssKey, ssValue);\r
539             if (ret == DB_NOTFOUND)\r
540                 break;\r
541             else if (ret != 0)\r
542                 return false;\r
543 \r
544             // Unserialize\r
545             // Taking advantage of the fact that pair serialization\r
546             // is just the two items serialized one after the other\r
547             string strType;\r
548             ssKey >> strType;\r
549             if (strType == "name")\r
550             {\r
551                 string strAddress;\r
552                 ssKey >> strAddress;\r
553                 ssValue >> mapAddressBook[strAddress];\r
554             }\r
555             else if (strType == "tx")\r
556             {\r
557                 uint256 hash;\r
558                 ssKey >> hash;\r
559                 CWalletTx& wtx = mapWallet[hash];\r
560                 ssValue >> wtx;\r
561 \r
562                 if (wtx.GetHash() != hash)\r
563                     printf("Error in wallet.dat, hash mismatch\n");\r
564 \r
565                 //// debug print\r
566                 //printf("LoadWallet  %s\n", wtx.GetHash().ToString().c_str());\r
567                 //printf(" %12I64d  %s  %s  %s\n",\r
568                 //    wtx.vout[0].nValue,\r
569                 //    DateTimeStr(wtx.nTime).c_str(),\r
570                 //    wtx.hashBlock.ToString().substr(0,14).c_str(),\r
571                 //    wtx.mapValue["message"].c_str());\r
572             }\r
573             else if (strType == "key")\r
574             {\r
575                 vector<unsigned char> vchPubKey;\r
576                 ssKey >> vchPubKey;\r
577                 CPrivKey vchPrivKey;\r
578                 ssValue >> vchPrivKey;\r
579 \r
580                 mapKeys[vchPubKey] = vchPrivKey;\r
581                 mapPubKeys[Hash160(vchPubKey)] = vchPubKey;\r
582             }\r
583             else if (strType == "defaultkey")\r
584             {\r
585                 ssValue >> vchDefaultKeyRet;\r
586             }\r
587             else if (strType == "setting")\r
588             {\r
589                 string strKey;\r
590                 ssKey >> strKey;\r
591 \r
592                 // Menu state\r
593                 if (strKey == "fShowGenerated")     ssValue >> fShowGenerated;\r
594                 if (strKey == "fGenerateBitcoins")  ssValue >> fGenerateBitcoins;\r
595 \r
596                 // Options\r
597                 if (strKey == "nTransactionFee")    ssValue >> nTransactionFee;\r
598                 if (strKey == "addrIncoming")       ssValue >> addrIncoming;\r
599                 if (strKey == "fLimitProcessors")   ssValue >> fLimitProcessors;\r
600                 if (strKey == "nLimitProcessors")   ssValue >> nLimitProcessors;\r
601                 if (strKey == "fMinimizeToTray")    ssValue >> fMinimizeToTray;\r
602                 if (strKey == "fMinimizeOnClose")   ssValue >> fMinimizeOnClose;\r
603             }\r
604         }\r
605     }\r
606 \r
607     printf("fShowGenerated = %d\n", fShowGenerated);\r
608     printf("fGenerateBitcoins = %d\n", fGenerateBitcoins);\r
609     printf("nTransactionFee = %I64d\n", nTransactionFee);\r
610     printf("addrIncoming = %s\n", addrIncoming.ToString().c_str());\r
611     printf("fMinimizeToTray = %d\n", fMinimizeToTray);\r
612     printf("fMinimizeOnClose = %d\n", fMinimizeOnClose);\r
613 \r
614     // The transaction fee setting won't be needed for many years to come.\r
615     // Setting it to zero here in case they set it to something in an earlier version.\r
616     if (nTransactionFee != 0)\r
617     {\r
618         nTransactionFee = 0;\r
619         WriteSetting("nTransactionFee", nTransactionFee);\r
620     }\r
621 \r
622     return true;\r
623 }\r
624 \r
625 bool LoadWallet(bool& fFirstRunRet)\r
626 {\r
627     fFirstRunRet = false;\r
628     vector<unsigned char> vchDefaultKey;\r
629     if (!CWalletDB("cr").LoadWallet(vchDefaultKey))\r
630         return false;\r
631     fFirstRunRet = vchDefaultKey.empty();\r
632 \r
633     if (mapKeys.count(vchDefaultKey))\r
634     {\r
635         // Set keyUser\r
636         keyUser.SetPubKey(vchDefaultKey);\r
637         keyUser.SetPrivKey(mapKeys[vchDefaultKey]);\r
638     }\r
639     else\r
640     {\r
641         // Create new keyUser and set as default key\r
642         RandAddSeed(true);\r
643         keyUser.MakeNewKey();\r
644         if (!AddKey(keyUser))\r
645             return false;\r
646         if (!SetAddressBookName(PubKeyToAddress(keyUser.GetPubKey()), "Your Address"))\r
647             return false;\r
648         CWalletDB().WriteDefaultKey(keyUser.GetPubKey());\r
649     }\r
650 \r
651     return true;\r
652 }\r