// Copyright (c) 2009-2010 Satoshi Nakamoto // Distributed under the MIT/X11 software license, see the accompanying // file license.txt or http://www.opensource.org/licenses/mit-license.php. #include "headers.h" void ThreadFlushWalletDB(void* parg); unsigned int nWalletDBUpdated; // // CDB // static CCriticalSection cs_db; static bool fDbEnvInit = false; DbEnv dbenv(0); static map mapFileUseCount; static map mapDb; class CDBInit { public: CDBInit() { } ~CDBInit() { if (fDbEnvInit) { dbenv.close(0); fDbEnvInit = false; } } } instance_of_cdbinit; CDB::CDB(const char* pszFile, const char* pszMode) : pdb(NULL) { int ret; if (pszFile == NULL) return; fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w')); bool fCreate = strchr(pszMode, 'c'); unsigned int nFlags = DB_THREAD; if (fCreate) nFlags |= DB_CREATE; CRITICAL_BLOCK(cs_db) { if (!fDbEnvInit) { if (fShutdown) return; string strDataDir = GetDataDir(); string strLogDir = strDataDir + "/database"; _mkdir(strLogDir.c_str()); string strErrorFile = strDataDir + "/db.log"; printf("dbenv.open strLogDir=%s strErrorFile=%s\n", strLogDir.c_str(), strErrorFile.c_str()); dbenv.set_lg_dir(strLogDir.c_str()); dbenv.set_lg_max(10000000); dbenv.set_lk_max_locks(10000); dbenv.set_lk_max_objects(10000); dbenv.set_errfile(fopen(strErrorFile.c_str(), "a")); /// debug dbenv.set_flags(DB_AUTO_COMMIT, 1); ret = dbenv.open(strDataDir.c_str(), DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_THREAD | DB_PRIVATE | DB_RECOVER, S_IRUSR | S_IWUSR); if (ret > 0) throw runtime_error(strprintf("CDB() : error %d opening database environment\n", ret)); fDbEnvInit = true; } strFile = pszFile; ++mapFileUseCount[strFile]; pdb = mapDb[strFile]; if (pdb == NULL) { pdb = new Db(&dbenv, 0); ret = pdb->open(NULL, // Txn pointer pszFile, // Filename "main", // Logical db name DB_BTREE, // Database type nFlags, // Flags 0); if (ret > 0) { delete pdb; pdb = NULL; CRITICAL_BLOCK(cs_db) --mapFileUseCount[strFile]; strFile = ""; throw runtime_error(strprintf("CDB() : can't open database file %s, error %d\n", pszFile, ret)); } if (fCreate && !Exists(string("version"))) { bool fTmp = fReadOnly; fReadOnly = false; WriteVersion(VERSION); fReadOnly = fTmp; } mapDb[strFile] = pdb; } } } void CDB::Close() { if (!pdb) return; if (!vTxn.empty()) vTxn.front()->abort(); vTxn.clear(); pdb = NULL; dbenv.txn_checkpoint(0, 0, 0); CRITICAL_BLOCK(cs_db) --mapFileUseCount[strFile]; } void CloseDb(const string& strFile) { CRITICAL_BLOCK(cs_db) { if (mapDb[strFile] != NULL) { // Close the database handle Db* pdb = mapDb[strFile]; pdb->close(0); delete pdb; mapDb[strFile] = NULL; } } } void DBFlush(bool fShutdown) { // Flush log data to the actual data file // on all files that are not in use printf("DBFlush(%s)%s\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " db not started"); if (!fDbEnvInit) return; CRITICAL_BLOCK(cs_db) { map::iterator mi = mapFileUseCount.begin(); while (mi != mapFileUseCount.end()) { string strFile = (*mi).first; int nRefCount = (*mi).second; printf("%s refcount=%d\n", strFile.c_str(), nRefCount); if (nRefCount == 0) { // Move log data to the dat file CloseDb(strFile); dbenv.txn_checkpoint(0, 0, 0); printf("%s flush\n", strFile.c_str()); dbenv.lsn_reset(strFile.c_str(), 0); mapFileUseCount.erase(mi++); } else mi++; } if (fShutdown) { char** listp; if (mapFileUseCount.empty()) dbenv.log_archive(&listp, DB_ARCH_REMOVE); dbenv.close(0); fDbEnvInit = false; } } } // // CTxDB // bool CTxDB::ReadTxIndex(uint256 hash, CTxIndex& txindex) { assert(!fClient); txindex.SetNull(); return Read(make_pair(string("tx"), hash), txindex); } bool CTxDB::UpdateTxIndex(uint256 hash, const CTxIndex& txindex) { assert(!fClient); return Write(make_pair(string("tx"), hash), txindex); } bool CTxDB::AddTxIndex(const CTransaction& tx, const CDiskTxPos& pos, int nHeight) { assert(!fClient); // Add to tx index uint256 hash = tx.GetHash(); CTxIndex txindex(pos, tx.vout.size()); return Write(make_pair(string("tx"), hash), txindex); } bool CTxDB::EraseTxIndex(const CTransaction& tx) { assert(!fClient); uint256 hash = tx.GetHash(); return Erase(make_pair(string("tx"), hash)); } bool CTxDB::ContainsTx(uint256 hash) { assert(!fClient); return Exists(make_pair(string("tx"), hash)); } bool CTxDB::ReadOwnerTxes(uint160 hash160, int nMinHeight, vector& vtx) { assert(!fClient); vtx.clear(); // Get cursor Dbc* pcursor = GetCursor(); if (!pcursor) return false; unsigned int fFlags = DB_SET_RANGE; loop { // Read next record CDataStream ssKey; if (fFlags == DB_SET_RANGE) ssKey << string("owner") << hash160 << CDiskTxPos(0, 0, 0); CDataStream ssValue; int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags); fFlags = DB_NEXT; if (ret == DB_NOTFOUND) break; else if (ret != 0) { pcursor->close(); return false; } // Unserialize string strType; uint160 hashItem; CDiskTxPos pos; ssKey >> strType >> hashItem >> pos; int nItemHeight; ssValue >> nItemHeight; // Read transaction if (strType != "owner" || hashItem != hash160) break; if (nItemHeight >= nMinHeight) { vtx.resize(vtx.size()+1); if (!vtx.back().ReadFromDisk(pos)) { pcursor->close(); return false; } } } pcursor->close(); return true; } bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx, CTxIndex& txindex) { assert(!fClient); tx.SetNull(); if (!ReadTxIndex(hash, txindex)) return false; return (tx.ReadFromDisk(txindex.pos)); } bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx) { CTxIndex txindex; return ReadDiskTx(hash, tx, txindex); } bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx, CTxIndex& txindex) { return ReadDiskTx(outpoint.hash, tx, txindex); } bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx) { CTxIndex txindex; return ReadDiskTx(outpoint.hash, tx, txindex); } bool CTxDB::WriteBlockIndex(const CDiskBlockIndex& blockindex) { return Write(make_pair(string("blockindex"), blockindex.GetBlockHash()), blockindex); } bool CTxDB::EraseBlockIndex(uint256 hash) { return Erase(make_pair(string("blockindex"), hash)); } bool CTxDB::ReadHashBestChain(uint256& hashBestChain) { return Read(string("hashBestChain"), hashBestChain); } bool CTxDB::WriteHashBestChain(uint256 hashBestChain) { return Write(string("hashBestChain"), hashBestChain); } CBlockIndex* InsertBlockIndex(uint256 hash) { if (hash == 0) return NULL; // Return existing map::iterator mi = mapBlockIndex.find(hash); if (mi != mapBlockIndex.end()) return (*mi).second; // Create new CBlockIndex* pindexNew = new CBlockIndex(); if (!pindexNew) throw runtime_error("LoadBlockIndex() : new CBlockIndex failed"); mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first; pindexNew->phashBlock = &((*mi).first); return pindexNew; } bool CTxDB::LoadBlockIndex() { // Get cursor Dbc* pcursor = GetCursor(); if (!pcursor) return false; unsigned int fFlags = DB_SET_RANGE; loop { // Read next record CDataStream ssKey; if (fFlags == DB_SET_RANGE) ssKey << make_pair(string("blockindex"), uint256(0)); CDataStream ssValue; int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags); fFlags = DB_NEXT; if (ret == DB_NOTFOUND) break; else if (ret != 0) return false; // Unserialize string strType; ssKey >> strType; if (strType == "blockindex") { CDiskBlockIndex diskindex; ssValue >> diskindex; // Construct block index object CBlockIndex* pindexNew = InsertBlockIndex(diskindex.GetBlockHash()); pindexNew->pprev = InsertBlockIndex(diskindex.hashPrev); pindexNew->pnext = InsertBlockIndex(diskindex.hashNext); pindexNew->nFile = diskindex.nFile; pindexNew->nBlockPos = diskindex.nBlockPos; pindexNew->nHeight = diskindex.nHeight; pindexNew->nVersion = diskindex.nVersion; pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot; pindexNew->nTime = diskindex.nTime; pindexNew->nBits = diskindex.nBits; pindexNew->nNonce = diskindex.nNonce; // Watch for genesis block and best block if (pindexGenesisBlock == NULL && diskindex.GetBlockHash() == hashGenesisBlock) pindexGenesisBlock = pindexNew; } else { break; } } pcursor->close(); if (!ReadHashBestChain(hashBestChain)) { if (pindexGenesisBlock == NULL) return true; return error("CTxDB::LoadBlockIndex() : hashBestChain not found"); } if (!mapBlockIndex.count(hashBestChain)) return error("CTxDB::LoadBlockIndex() : blockindex for hashBestChain not found"); pindexBest = mapBlockIndex[hashBestChain]; nBestHeight = pindexBest->nHeight; printf("LoadBlockIndex(): hashBestChain=%s height=%d\n", hashBestChain.ToString().substr(0,16).c_str(), nBestHeight); return true; } // // CAddrDB // bool CAddrDB::WriteAddress(const CAddress& addr) { return Write(make_pair(string("addr"), addr.GetKey()), addr); } bool CAddrDB::LoadAddresses() { CRITICAL_BLOCK(cs_mapAddresses) { // Load user provided addresses CAutoFile filein = fopen((GetDataDir() + "/addr.txt").c_str(), "rt"); if (filein) { try { char psz[1000]; while (fgets(psz, sizeof(psz), filein)) { CAddress addr(psz, NODE_NETWORK); addr.nTime = 0; // so it won't relay unless successfully connected if (addr.IsValid()) AddAddress(addr); } } catch (...) { } } // Get cursor Dbc* pcursor = GetCursor(); if (!pcursor) return false; loop { // Read next record CDataStream ssKey; CDataStream ssValue; int ret = ReadAtCursor(pcursor, ssKey, ssValue); if (ret == DB_NOTFOUND) break; else if (ret != 0) return false; // Unserialize string strType; ssKey >> strType; if (strType == "addr") { CAddress addr; ssValue >> addr; mapAddresses.insert(make_pair(addr.GetKey(), addr)); } } pcursor->close(); printf("Loaded %d addresses\n", mapAddresses.size()); // Fix for possible bug that manifests in mapAddresses.count in irc.cpp, // just need to call count here and it doesn't happen there. The bug was the // pack pragma in irc.cpp and has been fixed, but I'm not in a hurry to delete this. mapAddresses.count(vector(18)); } return true; } bool LoadAddresses() { return CAddrDB("cr+").LoadAddresses(); } // // CWalletDB // bool CWalletDB::LoadWallet() { vchDefaultKey.clear(); int nFileVersion = 0; // Modify defaults #ifndef __WXMSW__ // Tray icon sometimes disappears on 9.10 karmic koala 64-bit, leaving no way to access the program fMinimizeToTray = false; fMinimizeOnClose = false; #endif //// todo: shouldn't we catch exceptions and try to recover and continue? CRITICAL_BLOCK(cs_mapKeys) CRITICAL_BLOCK(cs_mapWallet) { // Get cursor Dbc* pcursor = GetCursor(); if (!pcursor) return false; loop { // Read next record CDataStream ssKey; CDataStream ssValue; int ret = ReadAtCursor(pcursor, ssKey, ssValue); if (ret == DB_NOTFOUND) break; else if (ret != 0) return false; // Unserialize // Taking advantage of the fact that pair serialization // is just the two items serialized one after the other string strType; ssKey >> strType; if (strType == "name") { string strAddress; ssKey >> strAddress; ssValue >> mapAddressBook[strAddress]; } else if (strType == "tx") { uint256 hash; ssKey >> hash; CWalletTx& wtx = mapWallet[hash]; ssValue >> wtx; if (wtx.GetHash() != hash) printf("Error in wallet.dat, hash mismatch\n"); //// debug print //printf("LoadWallet %s\n", wtx.GetHash().ToString().c_str()); //printf(" %12I64d %s %s %s\n", // wtx.vout[0].nValue, // DateTimeStrFormat("%x %H:%M:%S", wtx.nTime).c_str(), // wtx.hashBlock.ToString().substr(0,16).c_str(), // wtx.mapValue["message"].c_str()); } else if (strType == "key" || strType == "wkey") { vector vchPubKey; ssKey >> vchPubKey; CWalletKey wkey; if (strType == "key") ssValue >> wkey.vchPrivKey; else ssValue >> wkey; mapKeys[vchPubKey] = wkey.vchPrivKey; mapPubKeys[Hash160(vchPubKey)] = vchPubKey; } else if (strType == "defaultkey") { ssValue >> vchDefaultKey; } else if (strType == "version") { ssValue >> nFileVersion; if (nFileVersion == 10300) nFileVersion = 300; } else if (strType == "setting") { string strKey; ssKey >> strKey; // Menu state if (strKey == "fGenerateBitcoins") ssValue >> fGenerateBitcoins; // Options if (strKey == "nTransactionFee") ssValue >> nTransactionFee; if (strKey == "addrIncoming") ssValue >> addrIncoming; if (strKey == "fLimitProcessors") ssValue >> fLimitProcessors; if (strKey == "nLimitProcessors") ssValue >> nLimitProcessors; if (strKey == "fMinimizeToTray") ssValue >> fMinimizeToTray; if (strKey == "fMinimizeOnClose") ssValue >> fMinimizeOnClose; if (strKey == "fUseProxy") ssValue >> fUseProxy; if (strKey == "addrProxy") ssValue >> addrProxy; } } pcursor->close(); } printf("nFileVersion = %d\n", nFileVersion); printf("fGenerateBitcoins = %d\n", fGenerateBitcoins); printf("nTransactionFee = %"PRI64d"\n", nTransactionFee); printf("addrIncoming = %s\n", addrIncoming.ToString().c_str()); printf("fMinimizeToTray = %d\n", fMinimizeToTray); printf("fMinimizeOnClose = %d\n", fMinimizeOnClose); printf("fUseProxy = %d\n", fUseProxy); printf("addrProxy = %s\n", addrProxy.ToString().c_str()); // The transaction fee setting won't be needed for many years to come. // Setting it to zero here in case they set it to something in an earlier version. if (nTransactionFee != 0) { nTransactionFee = 0; WriteSetting("nTransactionFee", nTransactionFee); } // Upgrade if (nFileVersion < VERSION) { // Get rid of old debug.log file in current directory if (nFileVersion <= 105 && !pszSetDataDir[0]) unlink("debug.log"); WriteVersion(VERSION); } return true; } bool LoadWallet(bool& fFirstRunRet) { fFirstRunRet = false; if (!CWalletDB("cr+").LoadWallet()) return false; fFirstRunRet = vchDefaultKey.empty(); if (mapKeys.count(vchDefaultKey)) { // Set keyUser keyUser.SetPubKey(vchDefaultKey); keyUser.SetPrivKey(mapKeys[vchDefaultKey]); } else { // Create new keyUser and set as default key RandAddSeedPerfmon(); keyUser.MakeNewKey(); if (!AddKey(keyUser)) return false; if (!SetAddressBookName(PubKeyToAddress(keyUser.GetPubKey()), "Your Address")) return false; CWalletDB().WriteDefaultKey(keyUser.GetPubKey()); } CreateThread(ThreadFlushWalletDB, NULL); return true; } void ThreadFlushWalletDB(void* parg) { static bool fOneThread; if (fOneThread) return; fOneThread = true; if (mapArgs.count("-noflushwallet")) return; unsigned int nLastSeen = nWalletDBUpdated; unsigned int nLastFlushed = nWalletDBUpdated; int64 nLastWalletUpdate = GetTime(); while (!fShutdown) { Sleep(500); if (nLastSeen != nWalletDBUpdated) { nLastSeen = nWalletDBUpdated; nLastWalletUpdate = GetTime(); } if (nLastFlushed != nWalletDBUpdated && GetTime() - nLastWalletUpdate >= 2) { TRY_CRITICAL_BLOCK(cs_db) { // Don't do this if any databases are in use int nRefCount = 0; map::iterator mi = mapFileUseCount.begin(); while (mi != mapFileUseCount.end()) { nRefCount += (*mi).second; mi++; } if (nRefCount == 0 && !fShutdown) { string strFile = "wallet.dat"; map::iterator mi = mapFileUseCount.find(strFile); if (mi != mapFileUseCount.end()) { printf("%s ", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str()); printf("Flushing wallet.dat\n"); nLastFlushed = nWalletDBUpdated; int64 nStart = GetTimeMillis(); // Flush wallet.dat so it's self contained CloseDb(strFile); dbenv.txn_checkpoint(0, 0, 0); dbenv.lsn_reset(strFile.c_str(), 0); mapFileUseCount.erase(mi++); printf("Flushed wallet.dat %"PRI64d"ms\n", GetTimeMillis() - nStart); } } } } } }