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