X-Git-Url: https://git.novaco.in/?p=novacoin.git;a=blobdiff_plain;f=src%2Fwalletdb.cpp;h=3880a93b7844e2ce64bdd858fa2618bc62bdaac3;hp=709ecac1848539589a702f0f30baf1f9da138ada;hb=27ce22da0815f4c1fa0e949cc4740e3f4dd1ea9c;hpb=6b6aaa1698838278a547f16a15e635bd58ec867d diff --git a/src/walletdb.cpp b/src/walletdb.cpp index 709ecac..3880a93 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -1,21 +1,30 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying -// file license.txt or http://www.opensource.org/licenses/mit-license.php. +// file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "walletdb.h" #include "wallet.h" +#include "base58.h" + +#include +#include + +#include #include +#include + +#include +#include +#include +#include using namespace std; using namespace boost; -static uint64 nAccountingEntryNumber = 0; - -extern CCriticalSection cs_db; -extern map mapFileUseCount; -extern void CloseDb(const string& strFile); +static uint64_t nAccountingEntryNumber = 0; +extern bool fWalletUnlockMintOnly; // // CWalletDB @@ -46,17 +55,22 @@ bool CWalletDB::WriteAccount(const string& strAccount, const CAccount& account) return Write(make_pair(string("acc"), strAccount), account); } +bool CWalletDB::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry) +{ + return Write(boost::make_tuple(string("acentry"), acentry.strAccount, nAccEntryNum), acentry); +} + bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry) { - return Write(boost::make_tuple(string("acentry"), acentry.strAccount, ++nAccountingEntryNumber), acentry); + return WriteAccountingEntry(++nAccountingEntryNumber, acentry); } -int64 CWalletDB::GetAccountCreditDebit(const string& strAccount) +int64_t CWalletDB::GetAccountCreditDebit(const string& strAccount) { list entries; ListAccountCreditDebit(strAccount, entries); - int64 nCreditDebit = 0; + int64_t nCreditDebit = 0; BOOST_FOREACH (const CAccountingEntry& entry, entries) nCreditDebit += entry.nCreditDebit; @@ -71,12 +85,12 @@ void CWalletDB::ListAccountCreditDebit(const string& strAccount, list> acentry; + ssKey >> acentry.nEntryNo; entries.push_back(acentry); } @@ -106,227 +121,574 @@ void CWalletDB::ListAccountCreditDebit(const string& strAccount, listvchDefaultKey.clear(); - int nFileVersion = 0; - vector vWalletUpgrade; - bool fIsEncrypted = false; + LOCK(pwallet->cs_wallet); + // Old wallets didn't have any defined order for transactions + // Probably a bad idea to change the output of this - //// todo: shouldn't we catch exceptions and try to recover and continue? + // First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap. + typedef pair TxPair; + typedef multimap TxItems; + TxItems txByTime; + + for (map::iterator it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it) { - LOCK(pwallet->cs_wallet); - int nMinVersion = 0; - if (Read((string)"minversion", nMinVersion)) + CWalletTx* wtx = &((*it).second); + txByTime.insert(make_pair(wtx->nTimeReceived, TxPair(wtx, (CAccountingEntry*)0))); + } + list acentries; + ListAccountCreditDebit("", acentries); + BOOST_FOREACH(CAccountingEntry& entry, acentries) + { + txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry))); + } + + int64_t& nOrderPosNext = pwallet->nOrderPosNext; + nOrderPosNext = 0; + std::vector nOrderPosOffsets; + for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it) + { + CWalletTx *const pwtx = (*it).second.first; + CAccountingEntry *const pacentry = (*it).second.second; + int64_t& nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos; + + if (nOrderPos == -1) { - if (nMinVersion > CLIENT_VERSION) - return DB_TOO_NEW; - pwallet->LoadMinVersion(nMinVersion); - } + nOrderPos = nOrderPosNext++; + nOrderPosOffsets.push_back(nOrderPos); - // Get cursor - Dbc* pcursor = GetCursor(); - if (!pcursor) + if (pacentry) + // Have to write accounting regardless, since we don't keep it in memory + if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry)) + return DB_LOAD_FAIL; + } + else { - printf("Error getting wallet database cursor\n"); - return DB_CORRUPT; + int64_t nOrderPosOff = 0; + BOOST_FOREACH(const int64_t& nOffsetStart, nOrderPosOffsets) + { + if (nOrderPos >= nOffsetStart) + ++nOrderPosOff; + } + nOrderPos += nOrderPosOff; + nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1); + + if (!nOrderPosOff) + continue; + + // Since we're changing the order, write it back + if (pwtx) + { + if (!WriteTx(pwtx->GetHash(), *pwtx)) + return DB_LOAD_FAIL; + } + else + if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry)) + return DB_LOAD_FAIL; } + } + + return DB_LOAD_OK; +} + +class CWalletScanState { +public: + unsigned int nKeys; + unsigned int nCKeys; + unsigned int nKeyMeta; + bool fIsEncrypted; + bool fAnyUnordered; + int nFileVersion; + vector vWalletUpgrade; - loop + CWalletScanState() { + nKeys = nCKeys = nKeyMeta = 0; + fIsEncrypted = false; + fAnyUnordered = false; + nFileVersion = 0; + } +}; + +bool +ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, + CWalletScanState &wss, string& strType, string& strErr) +{ + try { + // Unserialize + // Taking advantage of the fact that pair serialization + // is just the two items serialized one after the other + ssKey >> strType; + + if (strType == "name") { - // Read next record - CDataStream ssKey(SER_DISK, CLIENT_VERSION); - CDataStream ssValue(SER_DISK, CLIENT_VERSION); - int ret = ReadAtCursor(pcursor, ssKey, ssValue); - if (ret == DB_NOTFOUND) - break; - else if (ret != 0) + string strAddress; + ssKey >> strAddress; + ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress)]; + } + else if (strType == "tx") + { + uint256 hash; + ssKey >> hash; + CWalletTx& wtx = pwallet->mapWallet[hash]; + ssValue >> wtx; + if (wtx.CheckTransaction() && (wtx.GetHash() == hash)) + wtx.BindWallet(pwallet); + else { - printf("Error reading next record from wallet database\n"); - return DB_CORRUPT; + pwallet->mapWallet.erase(hash); + 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") + // Undo serialize changes in 31600 + if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703) { - string strAddress; - ssKey >> strAddress; - ssValue >> pwallet->mapAddressBook[strAddress]; + if (!ssValue.empty()) + { + char fTmp; + char fUnused; + ssValue >> fTmp >> fUnused >> wtx.strFromAccount; + strErr = strprintf("LoadWallet() upgrading tx ver=%d %d '%s' %s", + wtx.fTimeReceivedIsTxTime, fTmp, wtx.strFromAccount.c_str(), hash.ToString().c_str()); + wtx.fTimeReceivedIsTxTime = fTmp; + } + else + { + strErr = strprintf("LoadWallet() repairing tx ver=%d %s", wtx.fTimeReceivedIsTxTime, hash.ToString().c_str()); + wtx.fTimeReceivedIsTxTime = 0; + } + wss.vWalletUpgrade.push_back(hash); } - else if (strType == "tx") + + if (wtx.nOrderPos == -1) + wss.fAnyUnordered = true; + + //// debug print + //printf("LoadWallet %s\n", wtx.GetHash().ToString().c_str()); + //printf(" %12"PRId64" %s %s %s\n", + // wtx.vout[0].nValue, + // DateTimeStrFormat("%x %H:%M:%S", wtx.GetBlockTime()).c_str(), + // wtx.hashBlock.ToString().substr(0,20).c_str(), + // wtx.mapValue["message"].c_str()); + } + else if (strType == "acentry") + { + string strAccount; + ssKey >> strAccount; + uint64_t nNumber; + ssKey >> nNumber; + if (nNumber > nAccountingEntryNumber) + nAccountingEntryNumber = nNumber; + + if (!wss.fAnyUnordered) { - uint256 hash; - ssKey >> hash; - CWalletTx& wtx = pwallet->mapWallet[hash]; - ssValue >> wtx; - wtx.BindWallet(pwallet); + CAccountingEntry acentry; + ssValue >> acentry; + if (acentry.nOrderPos == -1) + wss.fAnyUnordered = true; + } + } + else if (strType == "watchs") + { + CScript script; + ssKey >> script; + char fYes; + ssValue >> fYes; + if (fYes == '1') + pwallet->LoadWatchOnly(script); - if (wtx.GetHash() != hash) - printf("Error in wallet.dat, hash mismatch\n"); + // Watch-only addresses have no birthday information for now, + // so set the wallet birthday to the beginning of time. + pwallet->nTimeFirstKey = 1; + } + else if (strType == "malpair") + { + string strKeyView; - // Undo serialize changes in 31600 - if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703) - { - if (!ssValue.empty()) - { - char fTmp; - char fUnused; - ssValue >> fTmp >> fUnused >> wtx.strFromAccount; - printf("LoadWallet() upgrading tx ver=%d %d '%s' %s\n", wtx.fTimeReceivedIsTxTime, fTmp, wtx.strFromAccount.c_str(), hash.ToString().c_str()); - wtx.fTimeReceivedIsTxTime = fTmp; - } - else - { - printf("LoadWallet() repairing tx ver=%d %s\n", wtx.fTimeReceivedIsTxTime, hash.ToString().c_str()); - wtx.fTimeReceivedIsTxTime = 0; - } - vWalletUpgrade.push_back(hash); - } + CSecret vchSecret; + ssKey >> strKeyView; + ssValue >> vchSecret; - //// 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.GetBlockTime()).c_str(), - // wtx.hashBlock.ToString().substr(0,20).c_str(), - // wtx.mapValue["message"].c_str()); + CMalleableKeyView keyView(strKeyView); + if (!pwallet->LoadKey(keyView, vchSecret)) + { + strErr = "Error reading wallet database: LoadKey failed"; + return false; } - else if (strType == "acentry") + } + else if (strType == "malcpair") + { + string strKeyView; + + std::vector vchCryptedSecret; + ssKey >> strKeyView; + ssValue >> vchCryptedSecret; + + CMalleableKeyView keyView(strKeyView); + if (!pwallet->LoadCryptedKey(keyView, vchCryptedSecret)) { - string strAccount; - ssKey >> strAccount; - uint64 nNumber; - ssKey >> nNumber; - if (nNumber > nAccountingEntryNumber) - nAccountingEntryNumber = nNumber; + strErr = "Error reading wallet database: LoadCryptedKey failed"; + return false; } - else if (strType == "key" || strType == "wkey") + } + else if (strType == "key" || strType == "wkey") + { + CKey key; + CPubKey vchPubKey; + ssKey >> vchPubKey; + if (strType == "key") { - vector vchPubKey; - ssKey >> vchPubKey; - CKey key; - if (strType == "key") + wss.nKeys++; + CPrivKey pkey; + ssValue >> pkey; + if (!key.SetPrivKey(pkey)) { - CPrivKey pkey; - ssValue >> pkey; - key.SetPubKey(vchPubKey); - key.SetPrivKey(pkey); - if (key.GetPubKey() != vchPubKey) - { - printf("Error reading wallet database: CPrivKey pubkey inconsistency\n"); - return DB_CORRUPT; - } - if (!key.IsValid()) - { - printf("Error reading wallet database: invalid CPrivKey\n"); - return DB_CORRUPT; - } + strErr = "Error reading wallet database: CPrivKey corrupt"; + return false; } - else + if (key.GetPubKey() != vchPubKey) { - CWalletKey wkey; - ssValue >> wkey; - key.SetPubKey(vchPubKey); - key.SetPrivKey(wkey.vchPrivKey); - if (key.GetPubKey() != vchPubKey) - { - printf("Error reading wallet database: CWalletKey pubkey inconsistency\n"); - return DB_CORRUPT; - } - if (!key.IsValid()) - { - printf("Error reading wallet database: invalid CWalletKey\n"); - return DB_CORRUPT; - } + strErr = "Error reading wallet database: CPrivKey pubkey inconsistency"; + return false; } - if (!pwallet->LoadKey(key)) + if (vchPubKey.size() == 33) { + key.SetCompressedPubKey(); + } + if (!key.IsValid()) { - printf("Error reading wallet database: LoadKey failed\n"); - return DB_CORRUPT; + strErr = "Error reading wallet database: invalid CPrivKey"; + return false; } } - else if (strType == "mkey") + else { - unsigned int nID; - ssKey >> nID; - CMasterKey kMasterKey; - ssValue >> kMasterKey; - if(pwallet->mapMasterKeys.count(nID) != 0) + CWalletKey wkey; + ssValue >> wkey; + if (!key.SetPrivKey(wkey.vchPrivKey)) { - printf("Error reading wallet database: duplicate CMasterKey id %u\n", nID); - return DB_CORRUPT; + strErr = "Error reading wallet database: CPrivKey corrupt"; + return false; } - pwallet->mapMasterKeys[nID] = kMasterKey; - if (pwallet->nMasterKeyMaxID < nID) - pwallet->nMasterKeyMaxID = nID; - } - else if (strType == "ckey") - { - vector vchPubKey; - ssKey >> vchPubKey; - vector vchPrivKey; - ssValue >> vchPrivKey; - if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey)) + if (key.GetPubKey() != vchPubKey) { - printf("Error reading wallet database: LoadCryptedKey failed\n"); - return DB_CORRUPT; + strErr = "Error reading wallet database: CWalletKey pubkey inconsistency"; + return false; + } + if (vchPubKey.size() == 33) { + key.SetCompressedPubKey(); + } + if (!key.IsValid()) + { + strErr = "Error reading wallet database: invalid CWalletKey"; + return false; } - fIsEncrypted = true; } - else if (strType == "defaultkey") + if (!pwallet->LoadKey(key)) { - ssValue >> pwallet->vchDefaultKey; + strErr = "Error reading wallet database: LoadKey failed"; + return false; } - else if (strType == "pool") + } + else if (strType == "mkey") + { + unsigned int nID; + ssKey >> nID; + CMasterKey kMasterKey; + ssValue >> kMasterKey; + + if(pwallet->mapMasterKeys.count(nID) != 0) { - int64 nIndex; - ssKey >> nIndex; - pwallet->setKeyPool.insert(nIndex); + strErr = strprintf("Error reading wallet database: duplicate CMasterKey id %u", nID); + return false; } - else if (strType == "version") + pwallet->mapMasterKeys[nID] = kMasterKey; + if (pwallet->nMasterKeyMaxID < nID) + pwallet->nMasterKeyMaxID = nID; + } + else if (strType == "ckey") + { + wss.nCKeys++; + CPubKey vchPubKey; + ssKey >> vchPubKey; + vector vchPrivKey; + ssValue >> vchPrivKey; + if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey)) { - ssValue >> nFileVersion; - if (nFileVersion == 10300) - nFileVersion = 300; + strErr = "Error reading wallet database: LoadCryptedKey failed"; + return false; } - else if (strType == "cscript") + wss.fIsEncrypted = true; + } + else if (strType == "malmeta") + { + string strKeyView; + ssKey >> strKeyView; + + CMalleableKeyView keyView; + keyView.SetString(strKeyView); + + CKeyMetadata keyMeta; + ssValue >> keyMeta; + wss.nKeyMeta++; + + pwallet->LoadKeyMetadata(keyView, keyMeta); + } + else if (strType == "keymeta") + { + CPubKey vchPubKey; + ssKey >> vchPubKey; + CKeyMetadata keyMeta; + ssValue >> keyMeta; + wss.nKeyMeta++; + + pwallet->LoadKeyMetadata(vchPubKey, keyMeta); + + // find earliest key creation time, as wallet birthday + if (!pwallet->nTimeFirstKey || + (keyMeta.nCreateTime < pwallet->nTimeFirstKey)) + pwallet->nTimeFirstKey = keyMeta.nCreateTime; + } + else if (strType == "defaultkey") + { + ssValue >> pwallet->vchDefaultKey; + } + else if (strType == "pool") + { + int64_t nIndex; + ssKey >> nIndex; + CKeyPool keypool; + ssValue >> keypool; + pwallet->setKeyPool.insert(nIndex); + + // If no metadata exists yet, create a default with the pool key's + // creation time. Note that this may be overwritten by actually + // stored metadata for that key later, which is fine. + CBitcoinAddress addr = CBitcoinAddress(keypool.vchPubKey.GetID()); + if (pwallet->mapKeyMetadata.count(addr) == 0) + pwallet->mapKeyMetadata[addr] = CKeyMetadata(keypool.nTime); + + } + else if (strType == "version") + { + ssValue >> wss.nFileVersion; + if (wss.nFileVersion == 10300) + wss.nFileVersion = 300; + } + else if (strType == "cscript") + { + uint160 hash; + ssKey >> hash; + CScript script; + ssValue >> script; + if (!pwallet->LoadCScript(script)) { - uint160 hash; - ssKey >> hash; - CScript script; - ssValue >> script; - if (!pwallet->LoadCScript(script)) + strErr = "Error reading wallet database: LoadCScript failed"; + return false; + } + } + else if (strType == "orderposnext") + { + ssValue >> pwallet->nOrderPosNext; + } + } catch (...) + { + return false; + } + return true; +} + +static bool IsKeyType(string strType) +{ + return (strType== "key" || strType == "wkey" || + strType == "mkey" || strType == "ckey" || strType == "malpair" || strType == "malcpair"); +} + +DBErrors CWalletDB::LoadWallet(CWallet* pwallet) +{ + pwallet->vchDefaultKey = CPubKey(); + CWalletScanState wss; + bool fNoncriticalErrors = false; + DBErrors result = DB_LOAD_OK; + + try { + LOCK(pwallet->cs_wallet); + int nMinVersion = 0; + if (Read((string)"minversion", nMinVersion)) + { + if (nMinVersion > CLIENT_VERSION) + return DB_TOO_NEW; + pwallet->LoadMinVersion(nMinVersion); + } + + // Get cursor + Dbc* pcursor = GetCursor(); + if (!pcursor) + { + printf("Error getting wallet database cursor\n"); + return DB_CORRUPT; + } + + for ( ; ; ) + { + // Read next record + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + int ret = ReadAtCursor(pcursor, ssKey, ssValue); + if (ret == DB_NOTFOUND) + break; + else if (ret != 0) + { + printf("Error reading next record from wallet database\n"); + return DB_CORRUPT; + } + + // Try to be tolerant of single corrupt records: + string strType, strErr; + if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr)) + { + // losing keys is considered a catastrophic error, anything else + // we assume the user can live with: + if (IsKeyType(strType)) + result = DB_CORRUPT; + else { - printf("Error reading wallet database: LoadCScript failed\n"); - return DB_CORRUPT; + // Leave other errors alone, if we try to fix them we might make things worse. + fNoncriticalErrors = true; // ... but do warn the user there is something wrong. + if (strType == "tx") + // Rescan if there is a bad transaction record: + SoftSetBoolArg("-rescan", true); } } + if (!strErr.empty()) + printf("%s\n", strErr.c_str()); } pcursor->close(); } + catch (...) + { + result = DB_CORRUPT; + } - BOOST_FOREACH(uint256 hash, vWalletUpgrade) - WriteTx(hash, pwallet->mapWallet[hash]); + if (fNoncriticalErrors && result == DB_LOAD_OK) + result = DB_NONCRITICAL_ERROR; + + // Any wallet corruption at all: skip any rewriting or + // upgrading, we don't want to make it worse. + if (result != DB_LOAD_OK) + return result; - printf("nFileVersion = %d\n", nFileVersion); + printf("nFileVersion = %d\n", wss.nFileVersion); + printf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total\n", + wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys); + + // nTimeFirstKey is only reliable if all keys have metadata + if ((wss.nKeys + wss.nCKeys) != wss.nKeyMeta) + pwallet->nTimeFirstKey = 1; // 0 would be considered 'no value' + + + BOOST_FOREACH(uint256 hash, wss.vWalletUpgrade) + WriteTx(hash, pwallet->mapWallet[hash]); // Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc: - if (fIsEncrypted && (nFileVersion == 40000 || nFileVersion == 50000)) + if (wss.fIsEncrypted && (wss.nFileVersion == 40000 || wss.nFileVersion == 50000)) return DB_NEED_REWRITE; - if (nFileVersion < CLIENT_VERSION) // Update + if (wss.nFileVersion < CLIENT_VERSION) // Update WriteVersion(CLIENT_VERSION); + if (wss.fAnyUnordered) + result = ReorderTransactions(pwallet); + + return result; +} + +DBErrors CWalletDB::FindWalletTx(CWallet* pwallet, vector& vTxHash) +{ + pwallet->vchDefaultKey = CPubKey(); + CWalletScanState wss; + bool fNoncriticalErrors = false; + DBErrors result = DB_LOAD_OK; + + try { + LOCK(pwallet->cs_wallet); + int nMinVersion = 0; + if (Read((string)"minversion", nMinVersion)) + { + if (nMinVersion > CLIENT_VERSION) + return DB_TOO_NEW; + pwallet->LoadMinVersion(nMinVersion); + } + + // Get cursor + Dbc* pcursor = GetCursor(); + if (!pcursor) + { + printf("Error getting wallet database cursor\n"); + return DB_CORRUPT; + } + + for ( ; ; ) + { + // Read next record + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + int ret = ReadAtCursor(pcursor, ssKey, ssValue); + if (ret == DB_NOTFOUND) + break; + else if (ret != 0) + { + printf("Error reading next record from wallet database\n"); + return DB_CORRUPT; + } + + string strType; + ssKey >> strType; + if (strType == "tx") { + uint256 hash; + ssKey >> hash; + + vTxHash.push_back(hash); + } + } + pcursor->close(); + } + catch (const boost::thread_interrupted&) { + throw; + } + catch (...) { + result = DB_CORRUPT; + } + + if (fNoncriticalErrors && result == DB_LOAD_OK) + result = DB_NONCRITICAL_ERROR; + + return result; +} + +DBErrors CWalletDB::ZapWalletTx(CWallet* pwallet) +{ + // build list of wallet TXs + vector vTxHash; + DBErrors err = FindWalletTx(pwallet, vTxHash); + if (err != DB_LOAD_OK) + return err; + + // erase each wallet TX + BOOST_FOREACH (uint256& hash, vTxHash) { + if (!EraseTx(hash)) + return DB_CORRUPT; + } + return DB_LOAD_OK; } void ThreadFlushWalletDB(void* parg) { + // Make this thread recognisable as the wallet flushing thread + RenameThread("novacoin-wallet"); + const string& strFile = ((const string*)parg)[0]; static bool fOneThread; if (fOneThread) @@ -337,7 +699,7 @@ void ThreadFlushWalletDB(void* parg) unsigned int nLastSeen = nWalletDBUpdated; unsigned int nLastFlushed = nWalletDBUpdated; - int64 nLastWalletUpdate = GetTime(); + int64_t nLastWalletUpdate = GetTime(); while (!fShutdown) { Sleep(500); @@ -350,13 +712,13 @@ void ThreadFlushWalletDB(void* parg) if (nLastFlushed != nWalletDBUpdated && GetTime() - nLastWalletUpdate >= 2) { - TRY_LOCK(cs_db,lockDb); + TRY_LOCK(bitdb.cs_db,lockDb); if (lockDb) { // Don't do this if any databases are in use int nRefCount = 0; - map::iterator mi = mapFileUseCount.begin(); - while (mi != mapFileUseCount.end()) + map::iterator mi = bitdb.mapFileUseCount.begin(); + while (mi != bitdb.mapFileUseCount.end()) { nRefCount += (*mi).second; mi++; @@ -364,21 +726,19 @@ void ThreadFlushWalletDB(void* parg) if (nRefCount == 0 && !fShutdown) { - map::iterator mi = mapFileUseCount.find(strFile); - if (mi != mapFileUseCount.end()) + map::iterator mi = bitdb.mapFileUseCount.find(strFile); + if (mi != bitdb.mapFileUseCount.end()) { - printf("%s ", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str()); printf("Flushing wallet.dat\n"); nLastFlushed = nWalletDBUpdated; - int64 nStart = GetTimeMillis(); + int64_t 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); + bitdb.CloseDb(strFile); + bitdb.CheckpointLSN(strFile); - mapFileUseCount.erase(mi++); - printf("Flushed wallet.dat %"PRI64d"ms\n", GetTimeMillis() - nStart); + bitdb.mapFileUseCount.erase(mi++); + printf("Flushed wallet.dat %" PRId64 "ms\n", GetTimeMillis() - nStart); } } } @@ -393,14 +753,13 @@ bool BackupWallet(const CWallet& wallet, const string& strDest) while (!fShutdown) { { - LOCK(cs_db); - if (!mapFileUseCount.count(wallet.strWalletFile) || mapFileUseCount[wallet.strWalletFile] == 0) + LOCK(bitdb.cs_db); + if (!bitdb.mapFileUseCount.count(wallet.strWalletFile) || bitdb.mapFileUseCount[wallet.strWalletFile] == 0) { // Flush log data to the dat file - CloseDb(wallet.strWalletFile); - dbenv.txn_checkpoint(0, 0, 0); - dbenv.lsn_reset(wallet.strWalletFile.c_str(), 0); - mapFileUseCount.erase(wallet.strWalletFile); + bitdb.CloseDb(wallet.strWalletFile); + bitdb.CheckpointLSN(wallet.strWalletFile); + bitdb.mapFileUseCount.erase(wallet.strWalletFile); // Copy wallet.dat filesystem::path pathSrc = GetDataDir() / wallet.strWalletFile; @@ -426,3 +785,274 @@ bool BackupWallet(const CWallet& wallet, const string& strDest) } return false; } + +bool DumpWallet(CWallet* pwallet, const string& strDest) +{ + if (!pwallet->fFileBacked) + return false; + + std::map mapAddresses; + std::set setKeyPool; + + pwallet->GetAddresses(mapAddresses); + pwallet->GetAllReserveKeys(setKeyPool); + + // sort time/key pairs + std::vector > vAddresses; + for (std::map::const_iterator it = mapAddresses.begin(); it != mapAddresses.end(); it++) { + vAddresses.push_back(std::make_pair(it->second, it->first)); + } + mapAddresses.clear(); + std::sort(vAddresses.begin(), vAddresses.end()); + + // open outputfile as a stream + ofstream file; + file.open(strDest.c_str()); + if (!file.is_open()) + return false; + + // produce output + file << strprintf("# Wallet dump created by NovaCoin %s (%s)\n", CLIENT_BUILD.c_str(), CLIENT_DATE.c_str()); + file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime()).c_str()); + file << strprintf("# * Best block at time of backup was %i (%s),\n", nBestHeight, hashBestChain.ToString().c_str()); + file << strprintf("# mined on %s\n", EncodeDumpTime(pindexBest->nTime).c_str()); + file << "\n"; + + for (std::vector >::const_iterator it = vAddresses.begin(); it != vAddresses.end(); it++) { + const CBitcoinAddress &addr = it->second; + std::string strTime = EncodeDumpTime(it->first); + std::string strAddr = addr.ToString(); + + if (addr.IsPair()) { + // Pubkey pair address + CMalleableKeyView keyView; + CMalleablePubKey mPubKey(addr.GetData()); + if (!pwallet->GetMalleableView(mPubKey, keyView)) + continue; + CMalleableKey mKey; + pwallet->GetMalleableKey(keyView, mKey); + file << mKey.ToString(); + if (pwallet->mapAddressBook.count(addr)) + file << strprintf(" %s label=%s # view=%s addr=%s\n", strTime.c_str(), EncodeDumpString(pwallet->mapAddressBook[addr]).c_str(), keyView.ToString().c_str(), strAddr.c_str()); + else + file << strprintf(" %s # view=%s addr=%s\n", strTime.c_str(), keyView.ToString().c_str(), strAddr.c_str()); + } + else { + // Pubkey hash address + CKeyID keyid; + addr.GetKeyID(keyid); + bool IsCompressed; + CKey key; + if (!pwallet->GetKey(keyid, key)) + continue; + CSecret secret = key.GetSecret(IsCompressed); + file << CBitcoinSecret(secret, IsCompressed).ToString(); + if (pwallet->mapAddressBook.count(addr)) + file << strprintf(" %s label=%s # addr=%s\n", strTime.c_str(), EncodeDumpString(pwallet->mapAddressBook[addr]).c_str(), strAddr.c_str()); + else if (setKeyPool.count(keyid)) + file << strprintf(" %s reserve=1 # addr=%s\n", strTime.c_str(), strAddr.c_str()); + else + file << strprintf(" %s change=1 # addr=%s\n", strTime.c_str(), strAddr.c_str()); + } + } + + file << "\n"; + file << "# End of dump\n"; + file.close(); + + return true; +} + +bool ImportWallet(CWallet *pwallet, const string& strLocation) +{ + + if (!pwallet->fFileBacked) + return false; + + // open inputfile as stream + ifstream file; + file.open(strLocation.c_str()); + if (!file.is_open()) + return false; + + bool fGood = true; + int64_t nTimeBegin = pindexBest->nTime; + + // read through input file checking and importing keys into wallet. + while (file.good()) { + std::string line; + std::getline(file, line); + if (line.empty() || line[0] == '#') + continue; // Skip comments and empty lines + + std::vector vstr; + istringstream iss(line); + copy(istream_iterator(iss), istream_iterator(), back_inserter(vstr)); + if (vstr.size() < 2) + continue; + + int64_t nTime = DecodeDumpTime(vstr[1]); + std::string strLabel; + bool fLabel = true; + for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) { + if (boost::algorithm::starts_with(vstr[nStr], "#")) + break; + if (vstr[nStr] == "change=1") + fLabel = false; + if (vstr[nStr] == "reserve=1") + fLabel = false; + if (boost::algorithm::starts_with(vstr[nStr], "label=")) { + strLabel = DecodeDumpString(vstr[nStr].substr(6)); + fLabel = true; + } + } + + CBitcoinAddress addr; + CBitcoinSecret vchSecret; + if (vchSecret.SetString(vstr[0])) { + // Simple private key + + bool fCompressed; + CKey key; + CSecret secret = vchSecret.GetSecret(fCompressed); + key.SetSecret(secret, fCompressed); + CKeyID keyid = key.GetPubKey().GetID(); + addr = CBitcoinAddress(keyid); + + if (pwallet->HaveKey(keyid)) { + printf("Skipping import of %s (key already present)\n", addr.ToString().c_str()); + continue; + } + + printf("Importing %s...\n", addr.ToString().c_str()); + if (!pwallet->AddKey(key)) { + fGood = false; + continue; + } + } else { + // A pair of private keys + + CMalleableKey mKey; + if (!mKey.SetString(vstr[0])) + continue; + CMalleablePubKey mPubKey = mKey.GetMalleablePubKey(); + addr = CBitcoinAddress(mPubKey); + + if (pwallet->CheckOwnership(mPubKey)) { + printf("Skipping import of %s (key already present)\n", addr.ToString().c_str()); + continue; + } + + printf("Importing %s...\n", addr.ToString().c_str()); + if (!pwallet->AddKey(mKey)) { + fGood = false; + continue; + } + } + + pwallet->mapKeyMetadata[addr].nCreateTime = nTime; + if (fLabel) + pwallet->SetAddressBookName(addr, strLabel); + + nTimeBegin = std::min(nTimeBegin, nTime); + } + file.close(); + + // rescan block chain looking for coins from new keys + CBlockIndex *pindex = pindexBest; + while (pindex && pindex->pprev && pindex->nTime > nTimeBegin - 7200) + pindex = pindex->pprev; + + printf("Rescanning last %i blocks\n", pindexBest->nHeight - pindex->nHeight + 1); + pwallet->ScanForWalletTransactions(pindex); + pwallet->ReacceptWalletTransactions(); + pwallet->MarkDirty(); + + return fGood; +} + +// +// Try to (very carefully!) recover wallet.dat if there is a problem. +// +bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys) +{ + // Recovery procedure: + // move wallet.dat to wallet.timestamp.bak + // Call Salvage with fAggressive=true to + // get as much data as possible. + // Rewrite salvaged data to wallet.dat + // Set -rescan so any missing transactions will be + // found. + int64_t now = GetTime(); + std::string newFilename = strprintf("wallet.%" PRId64 ".bak", now); + + int result = dbenv.dbenv.dbrename(NULL, filename.c_str(), NULL, + newFilename.c_str(), DB_AUTO_COMMIT); + if (result == 0) + printf("Renamed %s to %s\n", filename.c_str(), newFilename.c_str()); + else + { + printf("Failed to rename %s to %s\n", filename.c_str(), newFilename.c_str()); + return false; + } + + std::vector salvagedData; + bool allOK = dbenv.Salvage(newFilename, true, salvagedData); + if (salvagedData.empty()) + { + printf("Salvage(aggressive) found no records in %s.\n", newFilename.c_str()); + return false; + } + printf("Salvage(aggressive) found %" PRIszu " records\n", salvagedData.size()); + + bool fSuccess = allOK; + Db* pdbCopy = new Db(&dbenv.dbenv, 0); + int ret = pdbCopy->open(NULL, // Txn pointer + filename.c_str(), // Filename + "main", // Logical db name + DB_BTREE, // Database type + DB_CREATE, // Flags + 0); + if (ret > 0) + { + printf("Cannot create database file %s\n", filename.c_str()); + return false; + } + CWallet dummyWallet; + CWalletScanState wss; + + DbTxn* ptxn = dbenv.TxnBegin(); + BOOST_FOREACH(CDBEnv::KeyValPair& row, salvagedData) + { + if (fOnlyKeys) + { + CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION); + CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); + string strType, strErr; + bool fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, + wss, strType, strErr); + if (!IsKeyType(strType)) + continue; + if (!fReadOK) + { + printf("WARNING: CWalletDB::Recover skipping %s: %s\n", strType.c_str(), strErr.c_str()); + continue; + } + } + Dbt datKey(&row.first[0], row.first.size()); + Dbt datValue(&row.second[0], row.second.size()); + int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE); + if (ret2 > 0) + fSuccess = false; + } + ptxn->commit(0); + pdbCopy->close(0); + delete pdbCopy; + + return fSuccess; +} + +bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename) +{ + return CWalletDB::Recover(dbenv, filename, false); +}