// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2012 The Bitcoin developers
-// Copyright (c) 2012 The PPCoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "walletdb.h"
#include "wallet.h"
+
+#include <iostream>
+#include <fstream>
+
+#include <boost/version.hpp>
#include <boost/filesystem.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/variant/get.hpp>
+#include <boost/algorithm/string.hpp>
using namespace std;
using namespace boost;
-static uint64 nAccountingEntryNumber = 0;
-
-extern CCriticalSection cs_db;
-extern map<string, int> mapFileUseCount;
-extern void CloseDb(const string& strFile);
+static uint64_t nAccountingEntryNumber = 0;
+extern bool fWalletUnlockMintOnly;
//
// CWalletDB
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<CAccountingEntry> entries;
ListAccountCreditDebit(strAccount, entries);
- int64 nCreditDebit = 0;
+ int64_t nCreditDebit = 0;
BOOST_FOREACH (const CAccountingEntry& entry, entries)
nCreditDebit += entry.nCreditDebit;
if (!pcursor)
throw runtime_error("CWalletDB::ListAccountCreditDebit() : cannot create DB cursor");
unsigned int fFlags = DB_SET_RANGE;
- loop
+ while (true)
{
// Read next record
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
if (fFlags == DB_SET_RANGE)
- ssKey << boost::make_tuple(string("acentry"), (fAllAccounts? string("") : strAccount), uint64(0));
+ ssKey << boost::make_tuple(string("acentry"), (fAllAccounts? string("") : strAccount), uint64_t(0));
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
fFlags = DB_NEXT;
break;
ssValue >> acentry;
+ ssKey >> acentry.nEntryNo;
entries.push_back(acentry);
}
}
-int CWalletDB::LoadWallet(CWallet* pwallet)
+DBErrors
+CWalletDB::ReorderTransactions(CWallet* pwallet)
{
- pwallet->vchDefaultKey.clear();
- int nFileVersion = 0;
- vector<uint256> 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
+
+ // First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap.
+ typedef pair<CWalletTx*, CAccountingEntry*> TxPair;
+ typedef multimap<int64_t, TxPair > TxItems;
+ TxItems txByTime;
- //// todo: shouldn't we catch exceptions and try to recover and continue?
+ for (map<uint256, CWalletTx>::iterator it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it)
{
- LOCK(pwallet->cs_wallet);
- int nMinVersion = 0;
- if (Read((string)"minversion", nMinVersion))
- {
- if (nMinVersion > CLIENT_VERSION)
- return DB_TOO_NEW;
- pwallet->LoadMinVersion(nMinVersion);
- }
+ CWalletTx* wtx = &((*it).second);
+ txByTime.insert(make_pair(wtx->nTimeReceived, TxPair(wtx, (CAccountingEntry*)0)));
+ }
+ list<CAccountingEntry> acentries;
+ ListAccountCreditDebit("", acentries);
+ BOOST_FOREACH(CAccountingEntry& entry, acentries)
+ {
+ txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry)));
+ }
- // Get cursor
- Dbc* pcursor = GetCursor();
- if (!pcursor)
+ int64_t& nOrderPosNext = pwallet->nOrderPosNext;
+ nOrderPosNext = 0;
+ std::vector<int64_t> 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)
{
- printf("Error getting wallet database cursor\n");
- return DB_CORRUPT;
- }
+ nOrderPos = nOrderPosNext++;
+ nOrderPosOffsets.push_back(nOrderPos);
- loop
+ 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
{
- // 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)
+ int64_t nOrderPosOff = 0;
+ BOOST_FOREACH(const int64_t& nOffsetStart, nOrderPosOffsets)
{
- printf("Error reading next record from wallet database\n");
- return DB_CORRUPT;
+ if (nOrderPos >= nOffsetStart)
+ ++nOrderPosOff;
}
+ nOrderPos += nOrderPosOff;
+ nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1);
- // 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")
+ if (!nOrderPosOff)
+ continue;
+
+ // Since we're changing the order, write it back
+ if (pwtx)
{
- string strAddress;
- ssKey >> strAddress;
- ssValue >> pwallet->mapAddressBook[strAddress];
+ if (!WriteTx(pwtx->GetHash(), *pwtx))
+ return DB_LOAD_FAIL;
}
- else if (strType == "tx")
- {
- uint256 hash;
- ssKey >> hash;
- CWalletTx& wtx = pwallet->mapWallet[hash];
- ssValue >> wtx;
- wtx.BindWallet(pwallet);
+ else
+ if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
+ return DB_LOAD_FAIL;
+ }
+ }
- if (wtx.GetHash() != hash)
- printf("Error in wallet.dat, hash mismatch\n");
+ return DB_LOAD_OK;
+}
- // Undo serialize changes in 31600
- if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703)
+class CWalletScanState {
+public:
+ unsigned int nKeys;
+ unsigned int nCKeys;
+ unsigned int nKeyMeta;
+ bool fIsEncrypted;
+ bool fAnyUnordered;
+ int nFileVersion;
+ vector<uint256> vWalletUpgrade;
+
+ 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")
+ {
+ string strAddress;
+ ssKey >> strAddress;
+ ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()];
+ }
+ 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
+ {
+ pwallet->mapWallet.erase(hash);
+ return false;
+ }
+
+ // Undo serialize changes in 31600
+ if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703)
+ {
+ if (!ssValue.empty())
{
- 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);
+ 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;
}
-
- //// debug print
- //printf("LoadWallet %s\n", wtx.GetHash().ToString().c_str());
- //printf(" %12"PRI64d" %s %s %s\n",
- // wtx.vout[0].nValue,
- // DateTimeStrFormat(wtx.GetBlockTime()).c_str(),
- // wtx.hashBlock.ToString().substr(0,20).c_str(),
- // wtx.mapValue["message"].c_str());
+ 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 == "acentry")
+
+ 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)
{
- string strAccount;
- ssKey >> strAccount;
- uint64 nNumber;
- ssKey >> nNumber;
- if (nNumber > nAccountingEntryNumber)
- nAccountingEntryNumber = nNumber;
+ CAccountingEntry acentry;
+ ssValue >> acentry;
+ if (acentry.nOrderPos == -1)
+ wss.fAnyUnordered = true;
}
- else if (strType == "key" || strType == "wkey")
+ }
+ else if (strType == "watchs")
+ {
+ CScript script;
+ ssKey >> script;
+ char fYes;
+ ssValue >> fYes;
+ if (fYes == '1')
+ pwallet->LoadWatchOnly(script);
+
+ // 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 == "key" || strType == "wkey")
+ {
+ vector<unsigned char> vchPubKey;
+ ssKey >> vchPubKey;
+ CKey key;
+ if (strType == "key")
{
- vector<unsigned char> vchPubKey;
- ssKey >> vchPubKey;
- CKey key;
- if (strType == "key")
+ wss.nKeys++;
+ CPrivKey pkey;
+ ssValue >> pkey;
+ key.SetPubKey(vchPubKey);
+ 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 (!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;
+ key.SetPubKey(vchPubKey);
+ 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<unsigned char> vchPubKey;
- ssKey >> vchPubKey;
- vector<unsigned char> 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 (!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++;
+ vector<unsigned char> vchPubKey;
+ ssKey >> vchPubKey;
+ vector<unsigned char> 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 == "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.
+ CKeyID keyid = keypool.vchPubKey.GetID();
+ if (pwallet->mapKeyMetadata.count(keyid) == 0)
+ pwallet->mapKeyMetadata[keyid] = 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");
+}
+
+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;
+ }
+
+ while (true)
+ {
+ // 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", wss.nFileVersion);
- printf("nFileVersion = %d\n", 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<uint256>& 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;
+ }
+
+ while (true)
+ {
+ // 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 (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<uint256> 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)
unsigned int nLastSeen = nWalletDBUpdated;
unsigned int nLastFlushed = nWalletDBUpdated;
- int64 nLastWalletUpdate = GetTime();
+ int64_t nLastWalletUpdate = GetTime();
while (!fShutdown)
{
Sleep(500);
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<string, int>::iterator mi = mapFileUseCount.begin();
- while (mi != mapFileUseCount.end())
+ map<string, int>::iterator mi = bitdb.mapFileUseCount.begin();
+ while (mi != bitdb.mapFileUseCount.end())
{
nRefCount += (*mi).second;
mi++;
if (nRefCount == 0 && !fShutdown)
{
- map<string, int>::iterator mi = mapFileUseCount.find(strFile);
- if (mi != mapFileUseCount.end())
+ map<string, int>::iterator mi = bitdb.mapFileUseCount.find(strFile);
+ if (mi != bitdb.mapFileUseCount.end())
{
- printf("%s ", DateTimeStrFormat(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);
}
}
}
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;
}
return false;
}
+
+bool DumpWallet(CWallet* pwallet, const string& strDest)
+{
+
+ if (!pwallet->fFileBacked)
+ return false;
+ while (!fShutdown)
+ {
+ // Populate maps
+ std::map<CKeyID, int64_t> mapKeyBirth;
+ std::set<CKeyID> setKeyPool;
+ pwallet->GetKeyBirthTimes(mapKeyBirth);
+ pwallet->GetAllReserveKeys(setKeyPool);
+
+ // sort time/key pairs
+ std::vector<std::pair<int64_t, CKeyID> > vKeyBirth;
+ for (std::map<CKeyID, int64_t>::const_iterator it = mapKeyBirth.begin(); it != mapKeyBirth.end(); it++) {
+ vKeyBirth.push_back(std::make_pair(it->second, it->first));
+ }
+ mapKeyBirth.clear();
+ std::sort(vKeyBirth.begin(), vKeyBirth.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<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
+ const CKeyID &keyid = it->second;
+ std::string strTime = EncodeDumpTime(it->first);
+ std::string strAddr = CBitcoinAddress(keyid).ToString();
+ bool IsCompressed;
+
+ CKey key;
+ if (pwallet->GetKey(keyid, key)) {
+ if (pwallet->mapAddressBook.count(keyid)) {
+ CSecret secret = key.GetSecret(IsCompressed);
+ file << strprintf("%s %s label=%s # addr=%s\n",
+ CBitcoinSecret(secret, IsCompressed).ToString().c_str(),
+ strTime.c_str(),
+ EncodeDumpString(pwallet->mapAddressBook[keyid]).c_str(),
+ strAddr.c_str());
+ } else if (setKeyPool.count(keyid)) {
+ CSecret secret = key.GetSecret(IsCompressed);
+ file << strprintf("%s %s reserve=1 # addr=%s\n",
+ CBitcoinSecret(secret, IsCompressed).ToString().c_str(),
+ strTime.c_str(),
+ strAddr.c_str());
+ } else {
+ CSecret secret = key.GetSecret(IsCompressed);
+ file << strprintf("%s %s change=1 # addr=%s\n",
+ CBitcoinSecret(secret, IsCompressed).ToString().c_str(),
+ strTime.c_str(),
+ strAddr.c_str());
+ }
+ }
+ }
+ file << "\n";
+ file << "# End of dump\n";
+ file.close();
+ return true;
+ }
+ return false;
+}
+
+
+bool ImportWallet(CWallet *pwallet, const string& strLocation)
+{
+
+ if (!pwallet->fFileBacked)
+ return false;
+ while (!fShutdown)
+ {
+ // open inputfile as stream
+ ifstream file;
+ file.open(strLocation.c_str());
+ if (!file.is_open())
+ return false;
+
+ int64_t nTimeBegin = pindexBest->nTime;
+
+ bool fGood = true;
+
+ // 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;
+
+ std::vector<std::string> vstr;
+ boost::split(vstr, line, boost::is_any_of(" "));
+ if (vstr.size() < 2)
+ continue;
+ CBitcoinSecret vchSecret;
+ if (!vchSecret.SetString(vstr[0]))
+ continue;
+
+ bool fCompressed;
+ CKey key;
+ CSecret secret = vchSecret.GetSecret(fCompressed);
+ key.SetSecret(secret, fCompressed);
+ CKeyID keyid = key.GetPubKey().GetID();
+
+ if (pwallet->HaveKey(keyid)) {
+ printf("Skipping import of %s (key already present)\n", CBitcoinAddress(keyid).ToString().c_str());
+ 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;
+ }
+ }
+ printf("Importing %s...\n", CBitcoinAddress(keyid).ToString().c_str());
+ if (!pwallet->AddKey(key)) {
+ fGood = false;
+ continue;
+ }
+ pwallet->mapKeyMetadata[keyid].nCreateTime = nTime;
+ if (fLabel)
+ pwallet->SetAddressBookName(keyid, 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;
+
+ }
+
+ return false;
+
+}
+
+
+//
+// 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<CDBEnv::KeyValPair> 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);
+}