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