434e4273b9
Compute safe lower bounds on the birth times of all wallet keys. For pool keys or keys with metadata, the actually stored birth time is used. For all others, the birth times are inferred from the wallet transactions.
702 lines
23 KiB
C++
702 lines
23 KiB
C++
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2012 The Bitcoin 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 <boost/filesystem.hpp>
|
|
|
|
using namespace std;
|
|
using namespace boost;
|
|
|
|
|
|
static uint64 nAccountingEntryNumber = 0;
|
|
|
|
//
|
|
// CWalletDB
|
|
//
|
|
|
|
bool CWalletDB::WriteName(const string& strAddress, const string& strName)
|
|
{
|
|
nWalletDBUpdated++;
|
|
return Write(make_pair(string("name"), strAddress), strName);
|
|
}
|
|
|
|
bool CWalletDB::EraseName(const string& strAddress)
|
|
{
|
|
// This should only be used for sending addresses, never for receiving addresses,
|
|
// receiving addresses must always have an address book entry if they're not change return.
|
|
nWalletDBUpdated++;
|
|
return Erase(make_pair(string("name"), strAddress));
|
|
}
|
|
|
|
bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account)
|
|
{
|
|
account.SetNull();
|
|
return Read(make_pair(string("acc"), strAccount), account);
|
|
}
|
|
|
|
bool CWalletDB::WriteAccount(const string& strAccount, const CAccount& account)
|
|
{
|
|
return Write(make_pair(string("acc"), strAccount), account);
|
|
}
|
|
|
|
bool CWalletDB::WriteAccountingEntry(const uint64 nAccEntryNum, const CAccountingEntry& acentry)
|
|
{
|
|
return Write(boost::make_tuple(string("acentry"), acentry.strAccount, nAccEntryNum), acentry);
|
|
}
|
|
|
|
bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry)
|
|
{
|
|
return WriteAccountingEntry(++nAccountingEntryNumber, acentry);
|
|
}
|
|
|
|
int64 CWalletDB::GetAccountCreditDebit(const string& strAccount)
|
|
{
|
|
list<CAccountingEntry> entries;
|
|
ListAccountCreditDebit(strAccount, entries);
|
|
|
|
int64 nCreditDebit = 0;
|
|
BOOST_FOREACH (const CAccountingEntry& entry, entries)
|
|
nCreditDebit += entry.nCreditDebit;
|
|
|
|
return nCreditDebit;
|
|
}
|
|
|
|
void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountingEntry>& entries)
|
|
{
|
|
bool fAllAccounts = (strAccount == "*");
|
|
|
|
Dbc* pcursor = GetCursor();
|
|
if (!pcursor)
|
|
throw runtime_error("CWalletDB::ListAccountCreditDebit() : cannot create DB cursor");
|
|
unsigned int fFlags = DB_SET_RANGE;
|
|
loop
|
|
{
|
|
// 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));
|
|
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
|
int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
|
|
fFlags = DB_NEXT;
|
|
if (ret == DB_NOTFOUND)
|
|
break;
|
|
else if (ret != 0)
|
|
{
|
|
pcursor->close();
|
|
throw runtime_error("CWalletDB::ListAccountCreditDebit() : error scanning DB");
|
|
}
|
|
|
|
// Unserialize
|
|
string strType;
|
|
ssKey >> strType;
|
|
if (strType != "acentry")
|
|
break;
|
|
CAccountingEntry acentry;
|
|
ssKey >> acentry.strAccount;
|
|
if (!fAllAccounts && acentry.strAccount != strAccount)
|
|
break;
|
|
|
|
ssValue >> acentry;
|
|
ssKey >> acentry.nEntryNo;
|
|
entries.push_back(acentry);
|
|
}
|
|
|
|
pcursor->close();
|
|
}
|
|
|
|
|
|
DBErrors
|
|
CWalletDB::ReorderTransactions(CWallet* pwallet)
|
|
{
|
|
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, TxPair > TxItems;
|
|
TxItems txByTime;
|
|
|
|
for (map<uint256, CWalletTx>::iterator it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it)
|
|
{
|
|
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)));
|
|
}
|
|
|
|
int64& nOrderPosNext = pwallet->nOrderPosNext;
|
|
nOrderPosNext = 0;
|
|
std::vector<int64> nOrderPosOffsets;
|
|
for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it)
|
|
{
|
|
CWalletTx *const pwtx = (*it).second.first;
|
|
CAccountingEntry *const pacentry = (*it).second.second;
|
|
int64& nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos;
|
|
|
|
if (nOrderPos == -1)
|
|
{
|
|
nOrderPos = nOrderPosNext++;
|
|
nOrderPosOffsets.push_back(nOrderPos);
|
|
|
|
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
|
|
{
|
|
int64 nOrderPosOff = 0;
|
|
BOOST_FOREACH(const int64& 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<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;
|
|
CValidationState state;
|
|
if (CheckTransaction(wtx, state) && (wtx.GetHash() == hash) && state.IsValid())
|
|
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())
|
|
{
|
|
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);
|
|
}
|
|
|
|
if (wtx.nOrderPos == -1)
|
|
wss.fAnyUnordered = true;
|
|
|
|
//// debug print
|
|
//printf("LoadWallet %s\n", wtx.GetHash().ToString().c_str());
|
|
//printf(" %12"PRI64d" %s %s %s\n",
|
|
// wtx.vout[0].nValue,
|
|
// DateTimeStrFormat("%Y-%m-%d %H:%M:%S", wtx.GetBlockTime()).c_str(),
|
|
// wtx.hashBlock.ToString().c_str(),
|
|
// wtx.mapValue["message"].c_str());
|
|
}
|
|
else if (strType == "acentry")
|
|
{
|
|
string strAccount;
|
|
ssKey >> strAccount;
|
|
uint64 nNumber;
|
|
ssKey >> nNumber;
|
|
if (nNumber > nAccountingEntryNumber)
|
|
nAccountingEntryNumber = nNumber;
|
|
|
|
if (!wss.fAnyUnordered)
|
|
{
|
|
CAccountingEntry acentry;
|
|
ssValue >> acentry;
|
|
if (acentry.nOrderPos == -1)
|
|
wss.fAnyUnordered = true;
|
|
}
|
|
}
|
|
else if (strType == "key" || strType == "wkey")
|
|
{
|
|
CPubKey vchPubKey;
|
|
ssKey >> vchPubKey;
|
|
if (!vchPubKey.IsValid())
|
|
{
|
|
strErr = "Error reading wallet database: CPubKey corrupt";
|
|
return false;
|
|
}
|
|
CKey key;
|
|
CPrivKey pkey;
|
|
if (strType == "key")
|
|
{
|
|
wss.nKeys++;
|
|
ssValue >> pkey;
|
|
} else {
|
|
CWalletKey wkey;
|
|
ssValue >> wkey;
|
|
pkey = wkey.vchPrivKey;
|
|
}
|
|
if (!key.SetPrivKey(pkey, vchPubKey.IsCompressed()))
|
|
{
|
|
strErr = "Error reading wallet database: CPrivKey corrupt";
|
|
return false;
|
|
}
|
|
if (key.GetPubKey() != vchPubKey)
|
|
{
|
|
strErr = "Error reading wallet database: CPrivKey pubkey inconsistency";
|
|
return false;
|
|
}
|
|
if (!pwallet->LoadKey(key, vchPubKey))
|
|
{
|
|
strErr = "Error reading wallet database: LoadKey failed";
|
|
return false;
|
|
}
|
|
}
|
|
else if (strType == "mkey")
|
|
{
|
|
unsigned int nID;
|
|
ssKey >> nID;
|
|
CMasterKey kMasterKey;
|
|
ssValue >> kMasterKey;
|
|
if(pwallet->mapMasterKeys.count(nID) != 0)
|
|
{
|
|
strErr = strprintf("Error reading wallet database: duplicate CMasterKey id %u", nID);
|
|
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;
|
|
wss.nCKeys++;
|
|
|
|
if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey))
|
|
{
|
|
strErr = "Error reading wallet database: LoadCryptedKey failed";
|
|
return false;
|
|
}
|
|
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 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))
|
|
{
|
|
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;
|
|
}
|
|
|
|
loop
|
|
{
|
|
// 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
|
|
{
|
|
// 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 (boost::thread_interrupted) {
|
|
throw;
|
|
}
|
|
catch (...) {
|
|
result = DB_CORRUPT;
|
|
}
|
|
|
|
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("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 (wss.fIsEncrypted && (wss.nFileVersion == 40000 || wss.nFileVersion == 50000))
|
|
return DB_NEED_REWRITE;
|
|
|
|
if (wss.nFileVersion < CLIENT_VERSION) // Update
|
|
WriteVersion(CLIENT_VERSION);
|
|
|
|
if (wss.fAnyUnordered)
|
|
result = ReorderTransactions(pwallet);
|
|
|
|
return result;
|
|
}
|
|
|
|
void ThreadFlushWalletDB(const string& strFile)
|
|
{
|
|
// Make this thread recognisable as the wallet flushing thread
|
|
RenameThread("bitcoin-wallet");
|
|
|
|
static bool fOneThread;
|
|
if (fOneThread)
|
|
return;
|
|
fOneThread = true;
|
|
if (!GetBoolArg("-flushwallet", true))
|
|
return;
|
|
|
|
unsigned int nLastSeen = nWalletDBUpdated;
|
|
unsigned int nLastFlushed = nWalletDBUpdated;
|
|
int64 nLastWalletUpdate = GetTime();
|
|
while (true)
|
|
{
|
|
MilliSleep(500);
|
|
|
|
if (nLastSeen != nWalletDBUpdated)
|
|
{
|
|
nLastSeen = nWalletDBUpdated;
|
|
nLastWalletUpdate = GetTime();
|
|
}
|
|
|
|
if (nLastFlushed != nWalletDBUpdated && GetTime() - nLastWalletUpdate >= 2)
|
|
{
|
|
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 = bitdb.mapFileUseCount.begin();
|
|
while (mi != bitdb.mapFileUseCount.end())
|
|
{
|
|
nRefCount += (*mi).second;
|
|
mi++;
|
|
}
|
|
|
|
if (nRefCount == 0)
|
|
{
|
|
boost::this_thread::interruption_point();
|
|
map<string, int>::iterator mi = bitdb.mapFileUseCount.find(strFile);
|
|
if (mi != bitdb.mapFileUseCount.end())
|
|
{
|
|
printf("Flushing wallet.dat\n");
|
|
nLastFlushed = nWalletDBUpdated;
|
|
int64 nStart = GetTimeMillis();
|
|
|
|
// Flush wallet.dat so it's self contained
|
|
bitdb.CloseDb(strFile);
|
|
bitdb.CheckpointLSN(strFile);
|
|
|
|
bitdb.mapFileUseCount.erase(mi++);
|
|
printf("Flushed wallet.dat %"PRI64d"ms\n", GetTimeMillis() - nStart);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool BackupWallet(const CWallet& wallet, const string& strDest)
|
|
{
|
|
if (!wallet.fFileBacked)
|
|
return false;
|
|
while (true)
|
|
{
|
|
{
|
|
LOCK(bitdb.cs_db);
|
|
if (!bitdb.mapFileUseCount.count(wallet.strWalletFile) || bitdb.mapFileUseCount[wallet.strWalletFile] == 0)
|
|
{
|
|
// Flush log data to the dat file
|
|
bitdb.CloseDb(wallet.strWalletFile);
|
|
bitdb.CheckpointLSN(wallet.strWalletFile);
|
|
bitdb.mapFileUseCount.erase(wallet.strWalletFile);
|
|
|
|
// Copy wallet.dat
|
|
filesystem::path pathSrc = GetDataDir() / wallet.strWalletFile;
|
|
filesystem::path pathDest(strDest);
|
|
if (filesystem::is_directory(pathDest))
|
|
pathDest /= wallet.strWalletFile;
|
|
|
|
try {
|
|
#if BOOST_VERSION >= 104000
|
|
filesystem::copy_file(pathSrc, pathDest, filesystem::copy_option::overwrite_if_exists);
|
|
#else
|
|
filesystem::copy_file(pathSrc, pathDest);
|
|
#endif
|
|
printf("copied wallet.dat to %s\n", pathDest.string().c_str());
|
|
return true;
|
|
} catch(const filesystem::filesystem_error &e) {
|
|
printf("error copying wallet.dat to %s - %s\n", pathDest.string().c_str(), e.what());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
MilliSleep(100);
|
|
}
|
|
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 now = GetTime();
|
|
std::string newFilename = strprintf("wallet.%"PRI64d".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);
|
|
}
|