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