1 // Copyright (c) 2009 Satoshi Nakamoto
\r
2 // Distributed under the MIT/X11 software license, see the accompanying
\r
3 // file license.txt or http://www.opensource.org/licenses/mit-license.php.
\r
16 static CCriticalSection cs_db;
\r
17 static bool fDbEnvInit = false;
\r
19 static map<string, int> mapFileUseCount;
\r
36 instance_of_cdbinit;
\r
39 CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL)
\r
42 if (pszFile == NULL)
\r
45 bool fCreate = strchr(pszMode, 'c');
\r
46 bool fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
\r
47 unsigned int nFlags = DB_THREAD;
\r
49 nFlags |= DB_CREATE;
\r
51 nFlags |= DB_RDONLY;
\r
52 if (!fReadOnly || fTxn)
\r
53 nFlags |= DB_AUTO_COMMIT;
\r
55 CRITICAL_BLOCK(cs_db)
\r
59 string strAppDir = GetAppDir();
\r
60 string strLogDir = strAppDir + "\\database";
\r
61 _mkdir(strLogDir.c_str());
\r
62 printf("dbenv.open strAppDir=%s\n", strAppDir.c_str());
\r
64 dbenv.set_lg_dir(strLogDir.c_str());
\r
65 dbenv.set_lg_max(10000000);
\r
66 dbenv.set_lk_max_locks(10000);
\r
67 dbenv.set_lk_max_objects(10000);
\r
68 dbenv.set_errfile(fopen("db.log", "a")); /// debug
\r
69 ///dbenv.log_set_config(DB_LOG_AUTO_REMOVE, 1); /// causes corruption
\r
70 ret = dbenv.open(strAppDir.c_str(),
\r
81 throw runtime_error(strprintf("CDB() : error %d opening database environment\n", ret));
\r
86 ++mapFileUseCount[strFile];
\r
89 pdb = new Db(&dbenv, 0);
\r
91 ret = pdb->open(NULL, // Txn pointer
\r
92 pszFile, // Filename
\r
93 "main", // Logical db name
\r
94 DB_BTREE, // Database type
\r
102 CRITICAL_BLOCK(cs_db)
\r
103 --mapFileUseCount[strFile];
\r
105 throw runtime_error(strprintf("CDB() : can't open database file %s, error %d\n", pszFile, ret));
\r
108 if (fCreate && !Exists(string("version")))
\r
109 WriteVersion(VERSION);
\r
119 vTxn.front()->abort();
\r
124 dbenv.txn_checkpoint(0, 0, 0);
\r
126 CRITICAL_BLOCK(cs_db)
\r
127 --mapFileUseCount[strFile];
\r
132 void DBFlush(bool fShutdown)
\r
134 // Flush log data to the actual data file
\r
135 // on all files that are not in use
\r
136 printf("DBFlush(%s)\n", fShutdown ? "true" : "false");
\r
137 CRITICAL_BLOCK(cs_db)
\r
139 dbenv.txn_checkpoint(0, 0, 0);
\r
140 map<string, int>::iterator mi = mapFileUseCount.begin();
\r
141 while (mi != mapFileUseCount.end())
\r
143 string strFile = (*mi).first;
\r
144 int nRefCount = (*mi).second;
\r
145 if (nRefCount == 0)
\r
147 dbenv.lsn_reset(strFile.c_str(), 0);
\r
148 mapFileUseCount.erase(mi++);
\r
156 if (mapFileUseCount.empty())
\r
157 dbenv.log_archive(&listp, DB_ARCH_REMOVE);
\r
159 fDbEnvInit = false;
\r
173 bool CTxDB::ReadTxIndex(uint256 hash, CTxIndex& txindex)
\r
177 return Read(make_pair(string("tx"), hash), txindex);
\r
180 bool CTxDB::UpdateTxIndex(uint256 hash, const CTxIndex& txindex)
\r
183 return Write(make_pair(string("tx"), hash), txindex);
\r
186 bool CTxDB::AddTxIndex(const CTransaction& tx, const CDiskTxPos& pos, int nHeight)
\r
191 uint256 hash = tx.GetHash();
\r
192 CTxIndex txindex(pos, tx.vout.size());
\r
193 return Write(make_pair(string("tx"), hash), txindex);
\r
196 bool CTxDB::EraseTxIndex(const CTransaction& tx)
\r
199 uint256 hash = tx.GetHash();
\r
201 return Erase(make_pair(string("tx"), hash));
\r
204 bool CTxDB::ContainsTx(uint256 hash)
\r
207 return Exists(make_pair(string("tx"), hash));
\r
210 bool CTxDB::ReadOwnerTxes(uint160 hash160, int nMinHeight, vector<CTransaction>& vtx)
\r
216 Dbc* pcursor = GetCursor();
\r
220 unsigned int fFlags = DB_SET_RANGE;
\r
223 // Read next record
\r
225 if (fFlags == DB_SET_RANGE)
\r
226 ssKey << string("owner") << hash160 << CDiskTxPos(0, 0, 0);
\r
227 CDataStream ssValue;
\r
228 int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
\r
230 if (ret == DB_NOTFOUND)
\r
239 ssKey >> strType >> hashItem >> pos;
\r
241 ssValue >> nItemHeight;
\r
243 // Read transaction
\r
244 if (strType != "owner" || hashItem != hash160)
\r
246 if (nItemHeight >= nMinHeight)
\r
248 vtx.resize(vtx.size()+1);
\r
249 if (!vtx.back().ReadFromDisk(pos))
\r
256 bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx, CTxIndex& txindex)
\r
260 if (!ReadTxIndex(hash, txindex))
\r
262 return (tx.ReadFromDisk(txindex.pos));
\r
265 bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx)
\r
268 return ReadDiskTx(hash, tx, txindex);
\r
271 bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx, CTxIndex& txindex)
\r
273 return ReadDiskTx(outpoint.hash, tx, txindex);
\r
276 bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx)
\r
279 return ReadDiskTx(outpoint.hash, tx, txindex);
\r
282 bool CTxDB::WriteBlockIndex(const CDiskBlockIndex& blockindex)
\r
284 return Write(make_pair(string("blockindex"), blockindex.GetBlockHash()), blockindex);
\r
287 bool CTxDB::EraseBlockIndex(uint256 hash)
\r
289 return Erase(make_pair(string("blockindex"), hash));
\r
292 bool CTxDB::ReadHashBestChain(uint256& hashBestChain)
\r
294 return Read(string("hashBestChain"), hashBestChain);
\r
297 bool CTxDB::WriteHashBestChain(uint256 hashBestChain)
\r
299 return Write(string("hashBestChain"), hashBestChain);
\r
302 CBlockIndex* InsertBlockIndex(uint256 hash)
\r
308 map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash);
\r
309 if (mi != mapBlockIndex.end())
\r
310 return (*mi).second;
\r
313 CBlockIndex* pindexNew = new CBlockIndex();
\r
315 throw runtime_error("LoadBlockIndex() : new CBlockIndex failed");
\r
316 mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
\r
317 pindexNew->phashBlock = &((*mi).first);
\r
322 bool CTxDB::LoadBlockIndex()
\r
325 Dbc* pcursor = GetCursor();
\r
329 unsigned int fFlags = DB_SET_RANGE;
\r
332 // Read next record
\r
334 if (fFlags == DB_SET_RANGE)
\r
335 ssKey << make_pair(string("blockindex"), uint256(0));
\r
336 CDataStream ssValue;
\r
337 int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
\r
339 if (ret == DB_NOTFOUND)
\r
347 if (strType == "blockindex")
\r
349 CDiskBlockIndex diskindex;
\r
350 ssValue >> diskindex;
\r
352 // Construct block index object
\r
353 CBlockIndex* pindexNew = InsertBlockIndex(diskindex.GetBlockHash());
\r
354 pindexNew->pprev = InsertBlockIndex(diskindex.hashPrev);
\r
355 pindexNew->pnext = InsertBlockIndex(diskindex.hashNext);
\r
356 pindexNew->nFile = diskindex.nFile;
\r
357 pindexNew->nBlockPos = diskindex.nBlockPos;
\r
358 pindexNew->nHeight = diskindex.nHeight;
\r
359 pindexNew->nVersion = diskindex.nVersion;
\r
360 pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot;
\r
361 pindexNew->nTime = diskindex.nTime;
\r
362 pindexNew->nBits = diskindex.nBits;
\r
363 pindexNew->nNonce = diskindex.nNonce;
\r
365 // Watch for genesis block and best block
\r
366 if (pindexGenesisBlock == NULL && diskindex.GetBlockHash() == hashGenesisBlock)
\r
367 pindexGenesisBlock = pindexNew;
\r
375 if (!ReadHashBestChain(hashBestChain))
\r
377 if (pindexGenesisBlock == NULL)
\r
379 return error("CTxDB::LoadBlockIndex() : hashBestChain not found\n");
\r
382 if (!mapBlockIndex.count(hashBestChain))
\r
383 return error("CTxDB::LoadBlockIndex() : blockindex for hashBestChain not found\n");
\r
384 pindexBest = mapBlockIndex[hashBestChain];
\r
385 nBestHeight = pindexBest->nHeight;
\r
386 printf("LoadBlockIndex(): hashBestChain=%s height=%d\n", hashBestChain.ToString().substr(0,14).c_str(), nBestHeight);
\r
399 bool CAddrDB::WriteAddress(const CAddress& addr)
\r
401 return Write(make_pair(string("addr"), addr.GetKey()), addr);
\r
404 bool CAddrDB::LoadAddresses()
\r
406 CRITICAL_BLOCK(cs_mapIRCAddresses)
\r
407 CRITICAL_BLOCK(cs_mapAddresses)
\r
409 // Load user provided addresses
\r
410 CAutoFile filein = fopen("addr.txt", "rt");
\r
416 while (fgets(psz, sizeof(psz), filein))
\r
418 CAddress addr(psz, NODE_NETWORK);
\r
421 AddAddress(*this, addr);
\r
422 mapIRCAddresses.insert(make_pair(addr.GetKey(), addr));
\r
430 Dbc* pcursor = GetCursor();
\r
436 // Read next record
\r
438 CDataStream ssValue;
\r
439 int ret = ReadAtCursor(pcursor, ssKey, ssValue);
\r
440 if (ret == DB_NOTFOUND)
\r
448 if (strType == "addr")
\r
452 mapAddresses.insert(make_pair(addr.GetKey(), addr));
\r
456 printf("Loaded %d addresses\n", mapAddresses.size());
\r
458 // Fix for possible bug that manifests in mapAddresses.count in irc.cpp,
\r
459 // just need to call count here and it doesn't happen there. The bug was the
\r
460 // pack pragma in irc.cpp and has been fixed, but I'm not in a hurry to delete this.
\r
461 mapAddresses.count(vector<unsigned char>(18));
\r
467 bool LoadAddresses()
\r
469 return CAddrDB("cr+").LoadAddresses();
\r
479 bool CReviewDB::ReadReviews(uint256 hash, vector<CReview>& vReviews)
\r
481 vReviews.size(); // msvc workaround, just need to do anything with vReviews
\r
482 return Read(make_pair(string("reviews"), hash), vReviews);
\r
485 bool CReviewDB::WriteReviews(uint256 hash, const vector<CReview>& vReviews)
\r
487 return Write(make_pair(string("reviews"), hash), vReviews);
\r
500 CWalletDB::~CWalletDB()
\r
502 // Flush whenever all handles to wallet.dat are closed
\r
504 CRITICAL_BLOCK(cs_db)
\r
506 map<string, int>::iterator mi = mapFileUseCount.find(strFile);
\r
507 if (mi != mapFileUseCount.end())
\r
509 int nRefCount = (*mi).second;
\r
510 if (nRefCount == 0)
\r
512 dbenv.txn_checkpoint(0, 0, 0);
\r
513 dbenv.lsn_reset(strFile.c_str(), 0);
\r
514 mapFileUseCount.erase(mi++);
\r
520 bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet)
\r
522 vchDefaultKeyRet.clear();
\r
524 //// todo: shouldn't we catch exceptions and try to recover and continue?
\r
525 CRITICAL_BLOCK(cs_mapKeys)
\r
526 CRITICAL_BLOCK(cs_mapWallet)
\r
529 Dbc* pcursor = GetCursor();
\r
535 // Read next record
\r
537 CDataStream ssValue;
\r
538 int ret = ReadAtCursor(pcursor, ssKey, ssValue);
\r
539 if (ret == DB_NOTFOUND)
\r
545 // Taking advantage of the fact that pair serialization
\r
546 // is just the two items serialized one after the other
\r
549 if (strType == "name")
\r
552 ssKey >> strAddress;
\r
553 ssValue >> mapAddressBook[strAddress];
\r
555 else if (strType == "tx")
\r
559 CWalletTx& wtx = mapWallet[hash];
\r
562 if (wtx.GetHash() != hash)
\r
563 printf("Error in wallet.dat, hash mismatch\n");
\r
566 //printf("LoadWallet %s\n", wtx.GetHash().ToString().c_str());
\r
567 //printf(" %12I64d %s %s %s\n",
\r
568 // wtx.vout[0].nValue,
\r
569 // DateTimeStr(wtx.nTime).c_str(),
\r
570 // wtx.hashBlock.ToString().substr(0,14).c_str(),
\r
571 // wtx.mapValue["message"].c_str());
\r
573 else if (strType == "key")
\r
575 vector<unsigned char> vchPubKey;
\r
576 ssKey >> vchPubKey;
\r
577 CPrivKey vchPrivKey;
\r
578 ssValue >> vchPrivKey;
\r
580 mapKeys[vchPubKey] = vchPrivKey;
\r
581 mapPubKeys[Hash160(vchPubKey)] = vchPubKey;
\r
583 else if (strType == "defaultkey")
\r
585 ssValue >> vchDefaultKeyRet;
\r
587 else if (strType == "setting")
\r
593 if (strKey == "fShowGenerated") ssValue >> fShowGenerated;
\r
594 if (strKey == "fGenerateBitcoins") ssValue >> fGenerateBitcoins;
\r
597 if (strKey == "nTransactionFee") ssValue >> nTransactionFee;
\r
598 if (strKey == "addrIncoming") ssValue >> addrIncoming;
\r
599 if (strKey == "fLimitProcessors") ssValue >> fLimitProcessors;
\r
600 if (strKey == "nLimitProcessors") ssValue >> nLimitProcessors;
\r
601 if (strKey == "fMinimizeToTray") ssValue >> fMinimizeToTray;
\r
602 if (strKey == "fMinimizeOnClose") ssValue >> fMinimizeOnClose;
\r
607 printf("fShowGenerated = %d\n", fShowGenerated);
\r
608 printf("fGenerateBitcoins = %d\n", fGenerateBitcoins);
\r
609 printf("nTransactionFee = %I64d\n", nTransactionFee);
\r
610 printf("addrIncoming = %s\n", addrIncoming.ToString().c_str());
\r
611 printf("fMinimizeToTray = %d\n", fMinimizeToTray);
\r
612 printf("fMinimizeOnClose = %d\n", fMinimizeOnClose);
\r
614 // The transaction fee setting won't be needed for many years to come.
\r
615 // Setting it to zero here in case they set it to something in an earlier version.
\r
616 if (nTransactionFee != 0)
\r
618 nTransactionFee = 0;
\r
619 WriteSetting("nTransactionFee", nTransactionFee);
\r
625 bool LoadWallet(bool& fFirstRunRet)
\r
627 fFirstRunRet = false;
\r
628 vector<unsigned char> vchDefaultKey;
\r
629 if (!CWalletDB("cr").LoadWallet(vchDefaultKey))
\r
631 fFirstRunRet = vchDefaultKey.empty();
\r
633 if (mapKeys.count(vchDefaultKey))
\r
636 keyUser.SetPubKey(vchDefaultKey);
\r
637 keyUser.SetPrivKey(mapKeys[vchDefaultKey]);
\r
641 // Create new keyUser and set as default key
\r
643 keyUser.MakeNewKey();
\r
644 if (!AddKey(keyUser))
\r
646 if (!SetAddressBookName(PubKeyToAddress(keyUser.GetPubKey()), "Your Address"))
\r
648 CWalletDB().WriteDefaultKey(keyUser.GetPubKey());
\r