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