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