Handle corrupt wallets gracefully.

Corrupt wallets used to cause a DB_RUNRECOVERY uncaught exception and a
crash. This commit does three things:

1) Runs a BDB verify early in the startup process, and if there is a
low-level problem with the database:
  + Moves the bad wallet.dat to wallet.timestamp.bak
  + Runs a 'salvage' operation to get key/value pairs, and
    writes them to a new wallet.dat
  + Continues with startup.

2) Much more tolerant of serialization errors. All errors in deserialization
are reported by tolerated EXCEPT for errors related to reading keypairs
or master key records-- those are reported and then shut down, so the user
can get help (or recover from a backup).

3) Adds a new -salvagewallet option, which:
 + Moves the wallet.dat to wallet.timestamp.bak
 + extracts ONLY keypairs and master keys into a new wallet.dat
 + soft-sets -rescan, to recreate transaction history

This was tested by randomly corrupting testnet wallets using a little
python script I wrote (https://gist.github.com/3812689)
This commit is contained in:
Gavin Andresen 2012-09-18 14:30:47 -04:00
parent 8d5f461cb6
commit eed1785f70
8 changed files with 518 additions and 213 deletions

View File

@ -136,6 +136,69 @@ void CDBEnv::MakeMock()
fMockDb = true; fMockDb = true;
} }
CDBEnv::VerifyResult CDBEnv::Verify(std::string strFile, bool (*recoverFunc)(CDBEnv& dbenv, std::string strFile))
{
LOCK(cs_db);
assert(mapFileUseCount.count(strFile) == 0);
Db db(&dbenv, 0);
int result = db.verify(strFile.c_str(), NULL, NULL, 0);
if (result == 0)
return VERIFY_OK;
else if (recoverFunc == NULL)
return RECOVER_FAIL;
// Try to recover:
bool fRecovered = (*recoverFunc)(*this, strFile);
return (fRecovered ? RECOVER_OK : RECOVER_FAIL);
}
bool CDBEnv::Salvage(std::string strFile, bool fAggressive,
std::vector<CDBEnv::KeyValPair >& vResult)
{
LOCK(cs_db);
assert(mapFileUseCount.count(strFile) == 0);
u_int32_t flags = DB_SALVAGE;
if (fAggressive) flags |= DB_AGGRESSIVE;
stringstream strDump;
Db db(&dbenv, 0);
int result = db.verify(strFile.c_str(), NULL, &strDump, flags);
if (result != 0)
{
printf("ERROR: db salvage failed\n");
return false;
}
// Format of bdb dump is ascii lines:
// header lines...
// HEADER=END
// hexadecimal key
// hexadecimal value
// ... repeated
// DATA=END
string strLine;
while (!strDump.eof() && strLine != "HEADER=END")
getline(strDump, strLine); // Skip past header
std::string keyHex, valueHex;
while (!strDump.eof() && keyHex != "DATA=END")
{
getline(strDump, keyHex);
if (keyHex != "DATA_END")
{
getline(strDump, valueHex);
vResult.push_back(make_pair(ParseHex(keyHex),ParseHex(valueHex)));
}
}
return (result == 0);
}
void CDBEnv::CheckpointLSN(std::string strFile) void CDBEnv::CheckpointLSN(std::string strFile)
{ {
dbenv.txn_checkpoint(0, 0, 0); dbenv.txn_checkpoint(0, 0, 0);
@ -257,6 +320,15 @@ void CDBEnv::CloseDb(const string& strFile)
} }
} }
bool CDBEnv::RemoveDb(const string& strFile)
{
this->CloseDb(strFile);
LOCK(cs_db);
int rc = dbenv.dbremove(NULL, strFile.c_str(), NULL, DB_AUTO_COMMIT);
return (rc == 0);
}
bool CDB::Rewrite(const string& strFile, const char* pszSkip) bool CDB::Rewrite(const string& strFile, const char* pszSkip)
{ {
while (!fShutdown) while (!fShutdown)

View File

@ -50,6 +50,25 @@ public:
~CDBEnv(); ~CDBEnv();
void MakeMock(); void MakeMock();
bool IsMock() { return fMockDb; }; bool IsMock() { return fMockDb; };
/*
* Verify that database file strFile is OK. If it is not,
* call the callback to try to recover.
* This must be called BEFORE strFile is opened.
* Returns true if strFile is OK.
*/
enum VerifyResult { VERIFY_OK, RECOVER_OK, RECOVER_FAIL };
VerifyResult Verify(std::string strFile, bool (*recoverFunc)(CDBEnv& dbenv, std::string strFile));
/*
* Salvage data from a file that Verify says is bad.
* fAggressive sets the DB_AGGRESSIVE flag (see berkeley DB->verify() method documentation).
* Appends binary key/value pairs to vResult, returns true if successful.
* NOTE: reads the entire database into memory, so cannot be used
* for huge databases.
*/
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
bool Salvage(std::string strFile, bool fAggressive, std::vector<KeyValPair>& vResult);
bool Open(boost::filesystem::path pathEnv_); bool Open(boost::filesystem::path pathEnv_);
void Close(); void Close();
void Flush(bool fShutdown); void Flush(bool fShutdown);
@ -58,6 +77,7 @@ public:
bool GetDetach() { return fDetachDB; } bool GetDetach() { return fDetachDB; }
void CloseDb(const std::string& strFile); void CloseDb(const std::string& strFile);
bool RemoveDb(const std::string& strFile);
DbTxn *TxnBegin(int flags=DB_TXN_WRITE_NOSYNC) DbTxn *TxnBegin(int flags=DB_TXN_WRITE_NOSYNC)
{ {

View File

@ -279,6 +279,7 @@ std::string HelpMessage()
" -upgradewallet " + _("Upgrade wallet to latest format") + "\n" + " -upgradewallet " + _("Upgrade wallet to latest format") + "\n" +
" -keypool=<n> " + _("Set key pool size to <n> (default: 100)") + "\n" + " -keypool=<n> " + _("Set key pool size to <n> (default: 100)") + "\n" +
" -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" + " -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" +
" -salvagewallet " + _("Attempt to recover private keys from a corrupt wallet.dat") + "\n" +
" -checkblocks=<n> " + _("How many blocks to check at startup (default: 2500, 0 = all)") + "\n" + " -checkblocks=<n> " + _("How many blocks to check at startup (default: 2500, 0 = all)") + "\n" +
" -checklevel=<n> " + _("How thorough the block verification is (0-6, default: 1)") + "\n" + " -checklevel=<n> " + _("How thorough the block verification is (0-6, default: 1)") + "\n" +
" -loadblock=<file> " + _("Imports blocks from external blk000?.dat file") + "\n" + " -loadblock=<file> " + _("Imports blocks from external blk000?.dat file") + "\n" +
@ -379,6 +380,11 @@ bool AppInit2()
SoftSetBoolArg("-discover", false); SoftSetBoolArg("-discover", false);
} }
if (GetBoolArg("-salvagewallet")) {
// Rewrite just private keys: rescan to find transactions
SoftSetBoolArg("-rescan", true);
}
// ********************************************************* Step 3: parameter-to-internal-flags // ********************************************************* Step 3: parameter-to-internal-flags
fDebug = GetBoolArg("-debug"); fDebug = GetBoolArg("-debug");
@ -434,12 +440,13 @@ bool AppInit2()
// ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log // ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log
const char* pszDataDir = GetDataDir().string().c_str();
// Make sure only a single Bitcoin process is using the data directory. // Make sure only a single Bitcoin process is using the data directory.
boost::filesystem::path pathLockFile = GetDataDir() / ".lock"; boost::filesystem::path pathLockFile = GetDataDir() / ".lock";
FILE* file = fopen(pathLockFile.string().c_str(), "a"); // empty lock file; created if it doesn't exist. FILE* file = fopen(pathLockFile.string().c_str(), "a"); // empty lock file; created if it doesn't exist.
if (file) fclose(file); if (file) fclose(file);
static boost::interprocess::file_lock lock(pathLockFile.string().c_str()); static boost::interprocess::file_lock lock(pathLockFile.string().c_str());
const char* pszDataDir = GetDataDir().string().c_str();
if (!lock.try_lock()) if (!lock.try_lock())
return InitError(strprintf(_("Cannot obtain a lock on data directory %s. Bitcoin is probably already running."), pszDataDir)); return InitError(strprintf(_("Cannot obtain a lock on data directory %s. Bitcoin is probably already running."), pszDataDir));
@ -481,7 +488,38 @@ bool AppInit2()
int64 nStart; int64 nStart;
// ********************************************************* Step 5: network initialization // ********************************************************* Step 5: verify database integrity
uiInterface.InitMessage(_("Verifying database integrity..."));
if (!bitdb.Open(GetDataDir()))
{
string msg = strprintf(_("Error initializing database environment %s!"
" To recover, BACKUP THAT DIRECTORY, then remove"
" everything from it except for wallet.dat."), pszDataDir);
return InitError(msg);
}
if (GetBoolArg("-salvagewallet"))
{
// Recover readable keypairs:
if (!CWalletDB::Recover(bitdb, "wallet.dat", true))
return false;
}
CDBEnv::VerifyResult r = bitdb.Verify("wallet.dat", CWalletDB::Recover);
if (r == CDBEnv::RECOVER_OK)
{
string msg = strprintf(_("Warning: wallet.dat corrupt, data salvaged!"
" Original wallet.dat saved as wallet.{timestamp}.bak in %s; if"
" your balance or transactions are incorrect you should"
" restore from a backup."), pszDataDir);
uiInterface.ThreadSafeMessageBox(msg, _("Bitcoin"), CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL);
}
if (r == CDBEnv::RECOVER_FAIL)
return InitError(_("wallet.dat corrupt, salvage failed"));
// ********************************************************* Step 6: network initialization
int nSocksVersion = GetArg("-socks", 5); int nSocksVersion = GetArg("-socks", 5);
@ -587,15 +625,7 @@ bool AppInit2()
BOOST_FOREACH(string strDest, mapMultiArgs["-seednode"]) BOOST_FOREACH(string strDest, mapMultiArgs["-seednode"])
AddOneShot(strDest); AddOneShot(strDest);
// ********************************************************* Step 6: load blockchain // ********************************************************* Step 7: load blockchain
if (!bitdb.Open(GetDataDir()))
{
string msg = strprintf(_("Error initializing database environment %s!"
" To recover, BACKUP THAT DIRECTORY, then remove"
" everything from it except for wallet.dat."), pszDataDir);
return InitError(msg);
}
if (GetBoolArg("-loadblockindextest")) if (GetBoolArg("-loadblockindextest"))
{ {
@ -650,18 +680,24 @@ bool AppInit2()
return false; return false;
} }
// ********************************************************* Step 7: load wallet // ********************************************************* Step 8: load wallet
uiInterface.InitMessage(_("Loading wallet...")); uiInterface.InitMessage(_("Loading wallet..."));
printf("Loading wallet...\n"); printf("Loading wallet...\n");
nStart = GetTimeMillis(); nStart = GetTimeMillis();
bool fFirstRun = true; bool fFirstRun = true;
pwalletMain = new CWallet("wallet.dat"); pwalletMain = new CWallet("wallet.dat");
int nLoadWalletRet = pwalletMain->LoadWallet(fFirstRun); DBErrors nLoadWalletRet = pwalletMain->LoadWallet(fFirstRun);
if (nLoadWalletRet != DB_LOAD_OK) if (nLoadWalletRet != DB_LOAD_OK)
{ {
if (nLoadWalletRet == DB_CORRUPT) if (nLoadWalletRet == DB_CORRUPT)
strErrors << _("Error loading wallet.dat: Wallet corrupted") << "\n"; strErrors << _("Error loading wallet.dat: Wallet corrupted") << "\n";
else if (nLoadWalletRet == DB_NONCRITICAL_ERROR)
{
string msg(_("Warning: error reading wallet.dat! All keys read correctly, but transaction data"
" or address book entries might be missing or incorrect."));
uiInterface.ThreadSafeMessageBox(msg, _("Bitcoin"), CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL);
}
else if (nLoadWalletRet == DB_TOO_NEW) else if (nLoadWalletRet == DB_TOO_NEW)
strErrors << _("Error loading wallet.dat: Wallet requires newer version of Bitcoin") << "\n"; strErrors << _("Error loading wallet.dat: Wallet requires newer version of Bitcoin") << "\n";
else if (nLoadWalletRet == DB_NEED_REWRITE) else if (nLoadWalletRet == DB_NEED_REWRITE)
@ -727,7 +763,7 @@ bool AppInit2()
printf(" rescan %15"PRI64d"ms\n", GetTimeMillis() - nStart); printf(" rescan %15"PRI64d"ms\n", GetTimeMillis() - nStart);
} }
// ********************************************************* Step 8: import blocks // ********************************************************* Step 9: import blocks
if (mapArgs.count("-loadblock")) if (mapArgs.count("-loadblock"))
{ {
@ -753,7 +789,7 @@ bool AppInit2()
} }
} }
// ********************************************************* Step 9: load peers // ********************************************************* Step 10: load peers
uiInterface.InitMessage(_("Loading addresses...")); uiInterface.InitMessage(_("Loading addresses..."));
printf("Loading addresses...\n"); printf("Loading addresses...\n");
@ -768,7 +804,7 @@ bool AppInit2()
printf("Loaded %i addresses from peers.dat %"PRI64d"ms\n", printf("Loaded %i addresses from peers.dat %"PRI64d"ms\n",
addrman.size(), GetTimeMillis() - nStart); addrman.size(), GetTimeMillis() - nStart);
// ********************************************************* Step 10: start node // ********************************************************* Step 11: start node
if (!CheckDiskSpace()) if (!CheckDiskSpace())
return false; return false;
@ -788,7 +824,7 @@ bool AppInit2()
if (fServer) if (fServer)
NewThread(ThreadRPCServer, NULL); NewThread(ThreadRPCServer, NULL);
// ********************************************************* Step 11: finished // ********************************************************* Step 12: finished
uiInterface.InitMessage(_("Done loading")); uiInterface.InitMessage(_("Done loading"));
printf("Done loading\n"); printf("Done loading\n");
@ -808,4 +844,3 @@ bool AppInit2()
return true; return true;
} }

View File

@ -186,10 +186,24 @@ void CKey::MakeNewKey(bool fCompressed)
bool CKey::SetPrivKey(const CPrivKey& vchPrivKey) bool CKey::SetPrivKey(const CPrivKey& vchPrivKey)
{ {
const unsigned char* pbegin = &vchPrivKey[0]; const unsigned char* pbegin = &vchPrivKey[0];
if (!d2i_ECPrivateKey(&pkey, &pbegin, vchPrivKey.size())) if (d2i_ECPrivateKey(&pkey, &pbegin, vchPrivKey.size()))
return false; {
fSet = true; // In testing, d2i_ECPrivateKey can return true
return true; // but fill in pkey with a key that fails
// EC_KEY_check_key, so:
if (EC_KEY_check_key(pkey))
{
fSet = true;
return true;
}
}
// If vchPrivKey data is bad d2i_ECPrivateKey() can
// leave pkey in a state where calling EC_KEY_free()
// crashes. To avoid that, set pkey to NULL and
// leak the memory (a leak is better than a crash)
pkey = NULL;
Reset();
return false;
} }
bool CKey::SetSecret(const CSecret& vchSecret, bool fCompressed) bool CKey::SetSecret(const CSecret& vchSecret, bool fCompressed)
@ -245,12 +259,16 @@ CPrivKey CKey::GetPrivKey() const
bool CKey::SetPubKey(const CPubKey& vchPubKey) bool CKey::SetPubKey(const CPubKey& vchPubKey)
{ {
const unsigned char* pbegin = &vchPubKey.vchPubKey[0]; const unsigned char* pbegin = &vchPubKey.vchPubKey[0];
if (!o2i_ECPublicKey(&pkey, &pbegin, vchPubKey.vchPubKey.size())) if (o2i_ECPublicKey(&pkey, &pbegin, vchPubKey.vchPubKey.size()))
return false; {
fSet = true; fSet = true;
if (vchPubKey.vchPubKey.size() == 33) if (vchPubKey.vchPubKey.size() == 33)
SetCompressedPubKey(); SetCompressedPubKey();
return true; return true;
}
pkey = NULL;
Reset();
return false;
} }
CPubKey CKey::GetPubKey() const CPubKey CKey::GetPubKey() const
@ -377,6 +395,9 @@ bool CKey::IsValid()
if (!fSet) if (!fSet)
return false; return false;
if (!EC_KEY_check_key(pkey))
return false;
bool fCompr; bool fCompr;
CSecret secret = GetSecret(fCompr); CSecret secret = GetSecret(fCompr);
CKey key2; CKey key2;

View File

@ -335,7 +335,9 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx)
if (mi != mapWallet.end()) if (mi != mapWallet.end())
{ {
CWalletTx& wtx = (*mi).second; CWalletTx& wtx = (*mi).second;
if (!wtx.IsSpent(txin.prevout.n) && IsMine(wtx.vout[txin.prevout.n])) if (txin.prevout.n >= wtx.vout.size())
printf("WalletUpdateSpent: bad wtx %s\n", wtx.GetHash().ToString().c_str());
else if (!wtx.IsSpent(txin.prevout.n) && IsMine(wtx.vout[txin.prevout.n]))
{ {
printf("WalletUpdateSpent found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); printf("WalletUpdateSpent found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str());
wtx.MarkSpent(txin.prevout.n); wtx.MarkSpent(txin.prevout.n);
@ -1371,12 +1373,12 @@ string CWallet::SendMoneyToDestination(const CTxDestination& address, int64 nVal
int CWallet::LoadWallet(bool& fFirstRunRet) DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{ {
if (!fFileBacked) if (!fFileBacked)
return DB_LOAD_OK; return DB_LOAD_OK;
fFirstRunRet = false; fFirstRunRet = false;
int nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this); DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this);
if (nLoadWalletRet == DB_NEED_REWRITE) if (nLoadWalletRet == DB_NEED_REWRITE)
{ {
if (CDB::Rewrite(strWalletFile, "\x04pool")) if (CDB::Rewrite(strWalletFile, "\x04pool"))

View File

@ -16,11 +16,11 @@
#include "script.h" #include "script.h"
#include "ui_interface.h" #include "ui_interface.h"
#include "util.h" #include "util.h"
#include "walletdb.h"
class CAccountingEntry; class CAccountingEntry;
class CWalletTx; class CWalletTx;
class CReserveKey; class CReserveKey;
class CWalletDB;
class COutput; class COutput;
/** (client) version numbers for particular wallet features */ /** (client) version numbers for particular wallet features */
@ -256,7 +256,7 @@ public:
} }
void SetBestChain(const CBlockLocator& loc); void SetBestChain(const CBlockLocator& loc);
int LoadWallet(bool& fFirstRunRet); DBErrors LoadWallet(bool& fFirstRunRet);
bool SetAddressBookName(const CTxDestination& address, const std::string& strName); bool SetAddressBookName(const CTxDestination& address, const std::string& strName);

View File

@ -108,7 +108,7 @@ void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountin
} }
int DBErrors
CWalletDB::ReorderTransactions(CWallet* pwallet) CWalletDB::ReorderTransactions(CWallet* pwallet)
{ {
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
@ -181,16 +181,221 @@ CWalletDB::ReorderTransactions(CWallet* pwallet)
} }
int CWalletDB::LoadWallet(CWallet* pwallet) bool
ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
int& nFileVersion, vector<uint256>& vWalletUpgrade,
bool& fIsEncrypted, bool& fAnyUnordered, 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())
{
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;
}
vWalletUpgrade.push_back(hash);
}
if (wtx.nOrderPos == -1)
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("%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 nNumber;
ssKey >> nNumber;
if (nNumber > nAccountingEntryNumber)
nAccountingEntryNumber = nNumber;
if (!fAnyUnordered)
{
CAccountingEntry acentry;
ssValue >> acentry;
if (acentry.nOrderPos == -1)
fAnyUnordered = true;
}
}
else if (strType == "key" || strType == "wkey")
{
vector<unsigned char> vchPubKey;
ssKey >> vchPubKey;
CKey key;
if (strType == "key")
{
CPrivKey pkey;
ssValue >> pkey;
key.SetPubKey(vchPubKey);
if (!key.SetPrivKey(pkey))
{
strErr = "Error reading wallet database: CPrivKey corrupt";
return false;
}
if (key.GetPubKey() != vchPubKey)
{
strErr = "Error reading wallet database: CPrivKey pubkey inconsistency";
return false;
}
if (!key.IsValid())
{
strErr = "Error reading wallet database: invalid CPrivKey";
return false;
}
}
else
{
CWalletKey wkey;
ssValue >> wkey;
key.SetPubKey(vchPubKey);
if (!key.SetPrivKey(wkey.vchPrivKey))
{
strErr = "Error reading wallet database: CPrivKey corrupt";
return false;
}
if (key.GetPubKey() != vchPubKey)
{
strErr = "Error reading wallet database: CWalletKey pubkey inconsistency";
return false;
}
if (!key.IsValid())
{
strErr = "Error reading wallet database: invalid CWalletKey";
return false;
}
}
if (!pwallet->LoadKey(key))
{
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;
if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey))
{
strErr = "Error reading wallet database: LoadCryptedKey failed";
return false;
}
fIsEncrypted = true;
}
else if (strType == "defaultkey")
{
ssValue >> pwallet->vchDefaultKey;
}
else if (strType == "pool")
{
int64 nIndex;
ssKey >> nIndex;
pwallet->setKeyPool.insert(nIndex);
}
else if (strType == "version")
{
ssValue >> nFileVersion;
if (nFileVersion == 10300)
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(); pwallet->vchDefaultKey = CPubKey();
int nFileVersion = 0; int nFileVersion = 0;
vector<uint256> vWalletUpgrade; vector<uint256> vWalletUpgrade;
bool fIsEncrypted = false; bool fIsEncrypted = false;
bool fAnyUnordered = false; bool fAnyUnordered = false;
bool fNoncriticalErrors = false;
DBErrors result = DB_LOAD_OK;
//// todo: shouldn't we catch exceptions and try to recover and continue? try {
{
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
int nMinVersion = 0; int nMinVersion = 0;
if (Read((string)"minversion", nMinVersion)) if (Read((string)"minversion", nMinVersion))
@ -222,189 +427,46 @@ int CWalletDB::LoadWallet(CWallet* pwallet)
return DB_CORRUPT; return DB_CORRUPT;
} }
// Unserialize // Try to be tolerant of single corrupt records:
// Taking advantage of the fact that pair serialization string strType, strErr;
// is just the two items serialized one after the other if (!ReadKeyValue(pwallet, ssKey, ssValue, nFileVersion,
string strType; vWalletUpgrade, fIsEncrypted, fAnyUnordered, strType, strErr))
ssKey >> strType;
if (strType == "name")
{ {
string strAddress; // losing keys is considered a catastrophic error, anything else
ssKey >> strAddress; // we assume the user can live with:
ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()]; if (IsKeyType(strType))
} result = DB_CORRUPT;
else if (strType == "tx")
{
uint256 hash;
ssKey >> hash;
CWalletTx& wtx = pwallet->mapWallet[hash];
ssValue >> wtx;
wtx.BindWallet(pwallet);
if (wtx.GetHash() != hash)
printf("Error in wallet.dat, hash mismatch\n");
// 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);
}
if (wtx.nOrderPos == -1)
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("%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 nNumber;
ssKey >> nNumber;
if (nNumber > nAccountingEntryNumber)
nAccountingEntryNumber = nNumber;
if (!fAnyUnordered)
{
CAccountingEntry acentry;
ssValue >> acentry;
if (acentry.nOrderPos == -1)
fAnyUnordered = true;
}
}
else if (strType == "key" || strType == "wkey")
{
vector<unsigned char> vchPubKey;
ssKey >> vchPubKey;
CKey key;
if (strType == "key")
{
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;
}
}
else else
{ {
CWalletKey wkey; // Leave other errors alone, if we try to fix them we might make things worse.
ssValue >> wkey; fNoncriticalErrors = true; // ... but do warn the user there is something wrong.
key.SetPubKey(vchPubKey); if (strType == "tx")
key.SetPrivKey(wkey.vchPrivKey); // Rescan if there is a bad transaction record:
if (key.GetPubKey() != vchPubKey) SoftSetBoolArg("-rescan", true);
{
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;
}
}
if (!pwallet->LoadKey(key))
{
printf("Error reading wallet database: LoadKey failed\n");
return DB_CORRUPT;
} }
} }
else if (strType == "mkey") if (!strErr.empty())
{ printf("%s\n", strErr.c_str());
unsigned int nID;
ssKey >> nID;
CMasterKey kMasterKey;
ssValue >> kMasterKey;
if(pwallet->mapMasterKeys.count(nID) != 0)
{
printf("Error reading wallet database: duplicate CMasterKey id %u\n", nID);
return DB_CORRUPT;
}
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))
{
printf("Error reading wallet database: LoadCryptedKey failed\n");
return DB_CORRUPT;
}
fIsEncrypted = true;
}
else if (strType == "defaultkey")
{
ssValue >> pwallet->vchDefaultKey;
}
else if (strType == "pool")
{
int64 nIndex;
ssKey >> nIndex;
pwallet->setKeyPool.insert(nIndex);
}
else if (strType == "version")
{
ssValue >> nFileVersion;
if (nFileVersion == 10300)
nFileVersion = 300;
}
else if (strType == "cscript")
{
uint160 hash;
ssKey >> hash;
CScript script;
ssValue >> script;
if (!pwallet->LoadCScript(script))
{
printf("Error reading wallet database: LoadCScript failed\n");
return DB_CORRUPT;
}
}
else if (strType == "orderposnext")
{
ssValue >> pwallet->nOrderPosNext;
}
} }
pcursor->close(); pcursor->close();
} }
catch (...)
{
result = DB_CORRUPT;
}
BOOST_FOREACH(uint256 hash, vWalletUpgrade) if (fNoncriticalErrors && result == DB_LOAD_OK)
WriteTx(hash, pwallet->mapWallet[hash]); 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", nFileVersion);
BOOST_FOREACH(uint256 hash, vWalletUpgrade)
WriteTx(hash, pwallet->mapWallet[hash]);
// Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc: // Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc:
if (fIsEncrypted && (nFileVersion == 40000 || nFileVersion == 50000)) if (fIsEncrypted && (nFileVersion == 40000 || nFileVersion == 50000))
@ -414,10 +476,9 @@ int CWalletDB::LoadWallet(CWallet* pwallet)
WriteVersion(CLIENT_VERSION); WriteVersion(CLIENT_VERSION);
if (fAnyUnordered) if (fAnyUnordered)
return ReorderTransactions(pwallet); result = ReorderTransactions(pwallet);
// If you add anything else here... be sure to do it if ReorderTransactions returns DB_LOAD_OK too! return result;
return DB_LOAD_OK;
} }
void ThreadFlushWalletDB(void* parg) void ThreadFlushWalletDB(void* parg)
@ -521,3 +582,94 @@ bool BackupWallet(const CWallet& wallet, const string& strDest)
} }
return false; 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;
int nFileVersion = 0;
vector<uint256> vWalletUpgrade;
bool fIsEncrypted = false;
bool fAnyUnordered = false;
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,
nFileVersion, vWalletUpgrade,
fIsEncrypted, fAnyUnordered,
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);
}

View File

@ -17,6 +17,7 @@ enum DBErrors
{ {
DB_LOAD_OK, DB_LOAD_OK,
DB_CORRUPT, DB_CORRUPT,
DB_NONCRITICAL_ERROR,
DB_TOO_NEW, DB_TOO_NEW,
DB_LOAD_FAIL, DB_LOAD_FAIL,
DB_NEED_REWRITE DB_NEED_REWRITE
@ -153,8 +154,10 @@ public:
int64 GetAccountCreditDebit(const std::string& strAccount); int64 GetAccountCreditDebit(const std::string& strAccount);
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& acentries); void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& acentries);
int ReorderTransactions(CWallet*); DBErrors ReorderTransactions(CWallet*);
int LoadWallet(CWallet* pwallet); DBErrors LoadWallet(CWallet* pwallet);
static bool Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys);
static bool Recover(CDBEnv& dbenv, std::string filename);
}; };
#endif // BITCOIN_WALLETDB_H #endif // BITCOIN_WALLETDB_H