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