Fix MSVC C4800: 'type' forcing value to bool 'true' or 'false'
[novacoin.git] / src / db.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 "db.h"
7 #include "net.h"
8 #include "util.h"
9 #include "main.h"
10 #include "ui_interface.h"
11 #include <boost/filesystem.hpp>
12 #include <boost/filesystem/fstream.hpp>
13
14 #ifndef WIN32
15 #include "sys/stat.h"
16 #endif
17
18 using namespace std;
19 using namespace boost;
20
21
22 unsigned int nWalletDBUpdated;
23 extern bool fUseMemoryLog;
24
25
26 //
27 // CDB
28 //
29
30 CDBEnv bitdb;
31
32 void CDBEnv::EnvShutdown()
33 {
34     if (!fDbEnvInit)
35         return;
36
37     fDbEnvInit = false;
38     int ret = dbenv.close(0);
39     if (ret != 0)
40         printf("EnvShutdown exception: %s (%d)\n", DbEnv::strerror(ret), ret);
41     if (!fMockDb)
42         DbEnv(0).remove(strPath.c_str(), 0);
43 }
44
45 CDBEnv::CDBEnv() : dbenv(DB_CXX_NO_EXCEPTIONS)
46 {
47     fDbEnvInit = false;
48     fMockDb = false;
49 }
50
51 CDBEnv::~CDBEnv()
52 {
53     EnvShutdown();
54 }
55
56 void CDBEnv::Close()
57 {
58     EnvShutdown();
59 }
60
61 bool CDBEnv::Open(boost::filesystem::path pathEnv_)
62 {
63     if (fDbEnvInit)
64         return true;
65
66     if (fShutdown)
67         return false;
68
69     pathEnv = pathEnv_;
70     filesystem::path pathDataDir = pathEnv;
71     strPath = pathDataDir.string();
72     filesystem::path pathLogDir = pathDataDir / "database";
73     filesystem::create_directory(pathLogDir);
74     filesystem::path pathErrorFile = pathDataDir / "db.log";
75     printf("dbenv.open LogDir=%s ErrorFile=%s\n", pathLogDir.string().c_str(), pathErrorFile.string().c_str());
76
77     unsigned int nEnvFlags = 0;
78     if (GetBoolArg("-privdb", true))
79         nEnvFlags |= DB_PRIVATE;
80
81     int nDbCache = GetArg("-dbcache", 25);
82     dbenv.set_lg_dir(pathLogDir.string().c_str());
83     dbenv.set_cachesize(nDbCache / 1024, (nDbCache % 1024)*1048576, 1);
84     dbenv.set_lg_bsize(1048576);
85     dbenv.set_lg_max(10485760);
86
87     // Bugfix: Bump lk_max_locks default to 537000, to safely handle reorgs with up to 5 blocks reversed
88     // dbenv.set_lk_max_locks(10000);
89     dbenv.set_lk_max_locks(537000);
90
91     dbenv.set_lk_max_objects(10000);
92     dbenv.set_errfile(fopen(pathErrorFile.string().c_str(), "a")); /// debug
93     dbenv.set_flags(DB_AUTO_COMMIT, 1);
94     dbenv.set_flags(DB_TXN_WRITE_NOSYNC, 1);
95 #ifdef DB_LOG_AUTO_REMOVE
96     dbenv.log_set_config(DB_LOG_AUTO_REMOVE, 1);
97 #endif
98     int ret = dbenv.open(strPath.c_str(),
99                      DB_CREATE     |
100                      DB_INIT_LOCK  |
101                      DB_INIT_LOG   |
102                      DB_INIT_MPOOL |
103                      DB_INIT_TXN   |
104                      DB_THREAD     |
105                      DB_RECOVER    |
106                      nEnvFlags,
107                      S_IRUSR | S_IWUSR);
108     if (ret != 0)
109         return error("CDB() : error %s (%d) opening database environment", DbEnv::strerror(ret), ret);
110
111     fDbEnvInit = true;
112     fMockDb = false;
113
114 #ifndef USE_LEVELDB
115     // Check that the number of locks is sufficient (to prevent chain fork possibility, read http://bitcoin.org/may15 for more info)
116     u_int32_t nMaxLocks;
117     if (!dbenv.get_lk_max_locks(&nMaxLocks))
118     {
119         int nBlocks, nDeepReorg;
120         std::string strMessage;
121
122         nBlocks = nMaxLocks / 48768;
123         nDeepReorg = (nBlocks - 1) / 2;
124
125         printf("Final lk_max_locks is %lu, sufficient for (worst case) %d block%s in a single transaction (up to a %d-deep reorganization)\n", (unsigned long)nMaxLocks, nBlocks, (nBlocks == 1) ? "" : "s", nDeepReorg);
126         if (nDeepReorg < 3)
127         {
128             if (nBlocks < 1)
129                 strMessage = strprintf(_("Warning: DB_CONFIG has set_lk_max_locks %lu, which may be too low for a single block. If this limit is reached, NovaCoin may stop working."), (unsigned long)nMaxLocks);
130             else
131                 strMessage = strprintf(_("Warning: DB_CONFIG has set_lk_max_locks %lu, which may be too low for a common blockchain reorganization. If this limit is reached, NovaCoin may stop working."), (unsigned long)nMaxLocks);
132
133             strMiscWarning = strMessage;
134             printf("*** %s\n", strMessage.c_str());
135         }
136     }
137 #endif
138
139     return true;
140 }
141
142 void CDBEnv::MakeMock()
143 {
144     if (fDbEnvInit)
145         throw runtime_error("CDBEnv::MakeMock(): already initialized");
146
147     if (fShutdown)
148         throw runtime_error("CDBEnv::MakeMock(): during shutdown");
149
150     printf("CDBEnv::MakeMock()\n");
151
152     dbenv.set_cachesize(1, 0, 1);
153     dbenv.set_lg_bsize(10485760*4);
154     dbenv.set_lg_max(10485760);
155     dbenv.set_lk_max_locks(10000);
156     dbenv.set_lk_max_objects(10000);
157     dbenv.set_flags(DB_AUTO_COMMIT, 1);
158 #ifdef DB_LOG_IN_MEMORY
159     dbenv.log_set_config(DB_LOG_IN_MEMORY, fUseMemoryLog ? 1 : 0);
160 #endif
161     int ret = dbenv.open(NULL,
162                      DB_CREATE     |
163                      DB_INIT_LOCK  |
164                      DB_INIT_LOG   |
165                      DB_INIT_MPOOL |
166                      DB_INIT_TXN   |
167                      DB_THREAD     |
168                      DB_PRIVATE,
169                      S_IRUSR | S_IWUSR);
170     if (ret > 0)
171         throw runtime_error(strprintf("CDBEnv::MakeMock(): error %d opening database environment", ret));
172
173     fDbEnvInit = true;
174     fMockDb = true;
175 }
176
177 CDBEnv::VerifyResult CDBEnv::Verify(std::string strFile, bool (*recoverFunc)(CDBEnv& dbenv, std::string strFile))
178 {
179     LOCK(cs_db);
180     assert(mapFileUseCount.count(strFile) == 0);
181
182     Db db(&dbenv, 0);
183     int result = db.verify(strFile.c_str(), NULL, NULL, 0);
184     if (result == 0)
185         return VERIFY_OK;
186     else if (recoverFunc == NULL)
187         return RECOVER_FAIL;
188
189     // Try to recover:
190     bool fRecovered = (*recoverFunc)(*this, strFile);
191     return (fRecovered ? RECOVER_OK : RECOVER_FAIL);
192 }
193
194 bool CDBEnv::Salvage(std::string strFile, bool fAggressive,
195                      std::vector<CDBEnv::KeyValPair >& vResult)
196 {
197     LOCK(cs_db);
198     assert(mapFileUseCount.count(strFile) == 0);
199
200     u_int32_t flags = DB_SALVAGE;
201     if (fAggressive) flags |= DB_AGGRESSIVE;
202
203     stringstream strDump;
204
205     Db db(&dbenv, 0);
206     int result = db.verify(strFile.c_str(), NULL, &strDump, flags);
207     if (result != 0)
208     {
209         printf("ERROR: db salvage failed\n");
210         return false;
211     }
212
213     // Format of bdb dump is ascii lines:
214     // header lines...
215     // HEADER=END
216     // hexadecimal key
217     // hexadecimal value
218     // ... repeated
219     // DATA=END
220
221     string strLine;
222     while (!strDump.eof() && strLine != "HEADER=END")
223         getline(strDump, strLine); // Skip past header
224
225     std::string keyHex, valueHex;
226     while (!strDump.eof() && keyHex != "DATA=END")
227     {
228         getline(strDump, keyHex);
229         if (keyHex != "DATA_END")
230         {
231             getline(strDump, valueHex);
232             vResult.push_back(make_pair(ParseHex(keyHex),ParseHex(valueHex)));
233         }
234     }
235
236     return (result == 0);
237 }
238
239
240 void CDBEnv::CheckpointLSN(std::string strFile)
241 {
242     dbenv.txn_checkpoint(0, 0, 0);
243     if (fMockDb)
244         return;
245     dbenv.lsn_reset(strFile.c_str(), 0);
246 }
247
248
249 CDB::CDB(const char *pszFile, const char* pszMode) :
250     pdb(NULL), activeTxn(NULL)
251 {
252     int ret;
253     if (pszFile == NULL)
254         return;
255
256     fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
257     bool fCreate = strchr(pszMode, 'c') != NULL;
258     unsigned int nFlags = DB_THREAD;
259     if (fCreate)
260         nFlags |= DB_CREATE;
261
262     {
263         LOCK(bitdb.cs_db);
264         if (!bitdb.Open(GetDataDir()))
265             throw runtime_error("env open failed");
266
267         strFile = pszFile;
268         ++bitdb.mapFileUseCount[strFile];
269         pdb = bitdb.mapDb[strFile];
270         if (pdb == NULL)
271         {
272             pdb = new Db(&bitdb.dbenv, 0);
273
274             bool fMockDb = bitdb.IsMock();
275             if (fMockDb)
276             {
277                 DbMpoolFile*mpf = pdb->get_mpf();
278                 ret = mpf->set_flags(DB_MPOOL_NOFILE, 1);
279                 if (ret != 0)
280                     throw runtime_error(strprintf("CDB() : failed to configure for no temp file backing for database %s", pszFile));
281             }
282
283             ret = pdb->open(NULL,      // Txn pointer
284                             fMockDb ? NULL : pszFile,   // Filename
285                             "main",    // Logical db name
286                             DB_BTREE,  // Database type
287                             nFlags,    // Flags
288                             0);
289
290             if (ret != 0)
291             {
292                 delete pdb;
293                 pdb = NULL;
294                 --bitdb.mapFileUseCount[strFile];
295                 strFile = "";
296                 throw runtime_error(strprintf("CDB() : can't open database file %s, error %d", pszFile, ret));
297             }
298
299             if (fCreate && !Exists(string("version")))
300             {
301                 bool fTmp = fReadOnly;
302                 fReadOnly = false;
303                 WriteVersion(CLIENT_VERSION);
304                 fReadOnly = fTmp;
305             }
306
307             bitdb.mapDb[strFile] = pdb;
308         }
309     }
310 }
311
312 static bool IsChainFile(std::string strFile)
313 {
314     if (strFile == "blkindex.dat")
315         return true;
316
317     return false;
318 }
319
320 void CDB::Close()
321 {
322     if (!pdb)
323         return;
324     if (activeTxn)
325         activeTxn->abort();
326     activeTxn = NULL;
327     pdb = NULL;
328
329     // Flush database activity from memory pool to disk log
330     unsigned int nMinutes = 0;
331     if (fReadOnly)
332         nMinutes = 1;
333     if (IsChainFile(strFile))
334         nMinutes = 2;
335     if (IsChainFile(strFile) && IsInitialBlockDownload())
336         nMinutes = 5;
337
338     bitdb.dbenv.txn_checkpoint(nMinutes ? GetArg("-dblogsize", 100)*1024 : 0, nMinutes, 0);
339
340     {
341         LOCK(bitdb.cs_db);
342         --bitdb.mapFileUseCount[strFile];
343     }
344 }
345
346 void CDBEnv::CloseDb(const string& strFile)
347 {
348     {
349         LOCK(cs_db);
350         if (mapDb[strFile] != NULL)
351         {
352             // Close the database handle
353             Db* pdb = mapDb[strFile];
354             pdb->close(0);
355             delete pdb;
356             mapDb[strFile] = NULL;
357         }
358     }
359 }
360
361 bool CDBEnv::RemoveDb(const string& strFile)
362 {
363     this->CloseDb(strFile);
364
365     LOCK(cs_db);
366     int rc = dbenv.dbremove(NULL, strFile.c_str(), NULL, DB_AUTO_COMMIT);
367     return (rc == 0);
368 }
369
370 bool CDB::Rewrite(const string& strFile, const char* pszSkip)
371 {
372     while (!fShutdown)
373     {
374         {
375             LOCK(bitdb.cs_db);
376             if (!bitdb.mapFileUseCount.count(strFile) || bitdb.mapFileUseCount[strFile] == 0)
377             {
378                 // Flush log data to the dat file
379                 bitdb.CloseDb(strFile);
380                 bitdb.CheckpointLSN(strFile);
381                 bitdb.mapFileUseCount.erase(strFile);
382
383                 bool fSuccess = true;
384                 printf("Rewriting %s...\n", strFile.c_str());
385                 string strFileRes = strFile + ".rewrite";
386                 { // surround usage of db with extra {}
387                     CDB db(strFile.c_str(), "r");
388                     Db* pdbCopy = new Db(&bitdb.dbenv, 0);
389
390                     int ret = pdbCopy->open(NULL,                 // Txn pointer
391                                             strFileRes.c_str(),   // Filename
392                                             "main",    // Logical db name
393                                             DB_BTREE,  // Database type
394                                             DB_CREATE,    // Flags
395                                             0);
396                     if (ret > 0)
397                     {
398                         printf("Cannot create database file %s\n", strFileRes.c_str());
399                         fSuccess = false;
400                     }
401
402                     Dbc* pcursor = db.GetCursor();
403                     if (pcursor)
404                         while (fSuccess)
405                         {
406                             CDataStream ssKey(SER_DISK, CLIENT_VERSION);
407                             CDataStream ssValue(SER_DISK, CLIENT_VERSION);
408                             int ret = db.ReadAtCursor(pcursor, ssKey, ssValue, DB_NEXT);
409                             if (ret == DB_NOTFOUND)
410                             {
411                                 pcursor->close();
412                                 break;
413                             }
414                             else if (ret != 0)
415                             {
416                                 pcursor->close();
417                                 fSuccess = false;
418                                 break;
419                             }
420                             if (pszSkip &&
421                                 strncmp(&ssKey[0], pszSkip, std::min(ssKey.size(), strlen(pszSkip))) == 0)
422                                 continue;
423                             if (strncmp(&ssKey[0], "\x07version", 8) == 0)
424                             {
425                                 // Update version:
426                                 ssValue.clear();
427                                 ssValue << CLIENT_VERSION;
428                             }
429                             Dbt datKey(&ssKey[0], ssKey.size());
430                             Dbt datValue(&ssValue[0], ssValue.size());
431                             int ret2 = pdbCopy->put(NULL, &datKey, &datValue, DB_NOOVERWRITE);
432                             if (ret2 > 0)
433                                 fSuccess = false;
434                         }
435                     if (fSuccess)
436                     {
437                         db.Close();
438                         bitdb.CloseDb(strFile);
439                         if (pdbCopy->close(0))
440                             fSuccess = false;
441                         delete pdbCopy;
442                     }
443                 }
444                 if (fSuccess)
445                 {
446                     Db dbA(&bitdb.dbenv, 0);
447                     if (dbA.remove(strFile.c_str(), NULL, 0))
448                         fSuccess = false;
449                     Db dbB(&bitdb.dbenv, 0);
450                     if (dbB.rename(strFileRes.c_str(), NULL, strFile.c_str(), 0))
451                         fSuccess = false;
452                 }
453                 if (!fSuccess)
454                     printf("Rewriting of %s FAILED!\n", strFileRes.c_str());
455                 return fSuccess;
456             }
457         }
458         Sleep(100);
459     }
460     return false;
461 }
462
463
464 void CDBEnv::Flush(bool fShutdown)
465 {
466     int64_t nStart = GetTimeMillis();
467     // Flush log data to the actual data file
468     //  on all files that are not in use
469     printf("Flush(%s)%s\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " db not started");
470     if (!fDbEnvInit)
471         return;
472     {
473         LOCK(cs_db);
474         map<string, int>::iterator mi = mapFileUseCount.begin();
475         while (mi != mapFileUseCount.end())
476         {
477             string strFile = (*mi).first;
478             int nRefCount = (*mi).second;
479             printf("%s refcount=%d\n", strFile.c_str(), nRefCount);
480             if (nRefCount == 0)
481             {
482                 // Move log data to the dat file
483                 CloseDb(strFile);
484                 printf("%s checkpoint\n", strFile.c_str());
485                 dbenv.txn_checkpoint(0, 0, 0);
486                 if (!IsChainFile(strFile) || fDetachDB) {
487                     printf("%s detach\n", strFile.c_str());
488                     if (!fMockDb)
489                         dbenv.lsn_reset(strFile.c_str(), 0);
490                 }
491                 printf("%s closed\n", strFile.c_str());
492                 mapFileUseCount.erase(mi++);
493             }
494             else
495                 mi++;
496         }
497         printf("DBFlush(%s)%s ended %15" PRId64 "ms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " db not started", GetTimeMillis() - nStart);
498         if (fShutdown)
499         {
500             char** listp;
501             if (mapFileUseCount.empty())
502             {
503                 dbenv.log_archive(&listp, DB_ARCH_REMOVE);
504                 Close();
505             }
506         }
507     }
508 }
509
510
511 //
512 // CAddrDB
513 //
514
515
516 CAddrDB::CAddrDB()
517 {
518     pathAddr = GetDataDir() / "peers.dat";
519 }
520
521 bool CAddrDB::Write(const CAddrMan& addr)
522 {
523     // Generate random temporary filename
524     unsigned short randv = 0;
525     RAND_bytes((unsigned char *)&randv, sizeof(randv));
526     std::string tmpfn = strprintf("peers.dat.%04x", randv);
527
528     // serialize addresses, checksum data up to that point, then append csum
529     CDataStream ssPeers(SER_DISK, CLIENT_VERSION);
530     ssPeers << FLATDATA(pchMessageStart);
531     ssPeers << addr;
532     uint256 hash = Hash(ssPeers.begin(), ssPeers.end());
533     ssPeers << hash;
534
535     // open temp output file, and associate with CAutoFile
536     boost::filesystem::path pathTmp = GetDataDir() / tmpfn;
537     FILE *file = fopen(pathTmp.string().c_str(), "wb");
538     CAutoFile fileout = CAutoFile(file, SER_DISK, CLIENT_VERSION);
539     if (!fileout)
540         return error("CAddrman::Write() : open failed");
541
542     // Write and commit header, data
543     try {
544         fileout << ssPeers;
545     }
546     catch (const std::exception&) {
547         return error("CAddrman::Write() : I/O error");
548     }
549     FileCommit(fileout);
550     fileout.fclose();
551
552     // replace existing peers.dat, if any, with new peers.dat.XXXX
553     if (!RenameOver(pathTmp, pathAddr))
554         return error("CAddrman::Write() : Rename-into-place failed");
555
556     return true;
557 }
558
559 bool CAddrDB::Read(CAddrMan& addr)
560 {
561     // open input file, and associate with CAutoFile
562     FILE *file = fopen(pathAddr.string().c_str(), "rb");
563     CAutoFile filein = CAutoFile(file, SER_DISK, CLIENT_VERSION);
564     if (!filein)
565         return error("CAddrman::Read() : open failed");
566
567     // use file size to size memory buffer
568     int fileSize = GetFilesize(filein);
569     int dataSize = fileSize - sizeof(uint256);
570     //Don't try to resize to a negative number if file is small
571     if ( dataSize < 0 ) dataSize = 0;
572     vector<unsigned char> vchData;
573     vchData.resize(dataSize);
574     uint256 hashIn;
575
576     // read data and checksum from file
577     try {
578         filein.read((char *)&vchData[0], dataSize);
579         filein >> hashIn;
580     }
581     catch (const std::exception&) {
582         return error("CAddrman::Read() 2 : I/O error or stream data corrupted");
583     }
584     filein.fclose();
585
586     CDataStream ssPeers(vchData, SER_DISK, CLIENT_VERSION);
587
588     // verify stored checksum matches input data
589     uint256 hashTmp = Hash(ssPeers.begin(), ssPeers.end());
590     if (hashIn != hashTmp)
591         return error("CAddrman::Read() : checksum mismatch; data corrupted");
592
593     unsigned char pchMsgTmp[4];
594     try {
595         // de-serialize file header (pchMessageStart magic number) and
596         ssPeers >> FLATDATA(pchMsgTmp);
597
598         // verify the network matches ours
599         if (memcmp(pchMsgTmp, pchMessageStart, sizeof(pchMsgTmp)))
600             return error("CAddrman::Read() : invalid network magic number");
601
602         // de-serialize address data into one CAddrMan object
603         ssPeers >> addr;
604     }
605     catch (const std::exception&) {
606         return error("CAddrman::Read() : I/O error or stream data corrupted");
607     }
608
609     return true;
610 }
611