mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-20 10:38:42 +01:00
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:
parent
8d5f461cb6
commit
eed1785f70
72
src/db.cpp
72
src/db.cpp
@ -136,6 +136,69 @@ void CDBEnv::MakeMock()
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
while (!fShutdown)
|
||||
|
20
src/db.h
20
src/db.h
@ -50,6 +50,25 @@ public:
|
||||
~CDBEnv();
|
||||
void MakeMock();
|
||||
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_);
|
||||
void Close();
|
||||
void Flush(bool fShutdown);
|
||||
@ -58,6 +77,7 @@ public:
|
||||
bool GetDetach() { return fDetachDB; }
|
||||
|
||||
void CloseDb(const std::string& strFile);
|
||||
bool RemoveDb(const std::string& strFile);
|
||||
|
||||
DbTxn *TxnBegin(int flags=DB_TXN_WRITE_NOSYNC)
|
||||
{
|
||||
|
71
src/init.cpp
71
src/init.cpp
@ -279,6 +279,7 @@ std::string HelpMessage()
|
||||
" -upgradewallet " + _("Upgrade wallet to latest format") + "\n" +
|
||||
" -keypool=<n> " + _("Set key pool size to <n> (default: 100)") + "\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" +
|
||||
" -checklevel=<n> " + _("How thorough the block verification is (0-6, default: 1)") + "\n" +
|
||||
" -loadblock=<file> " + _("Imports blocks from external blk000?.dat file") + "\n" +
|
||||
@ -379,6 +380,11 @@ bool AppInit2()
|
||||
SoftSetBoolArg("-discover", false);
|
||||
}
|
||||
|
||||
if (GetBoolArg("-salvagewallet")) {
|
||||
// Rewrite just private keys: rescan to find transactions
|
||||
SoftSetBoolArg("-rescan", true);
|
||||
}
|
||||
|
||||
// ********************************************************* Step 3: parameter-to-internal-flags
|
||||
|
||||
fDebug = GetBoolArg("-debug");
|
||||
@ -434,12 +440,13 @@ bool AppInit2()
|
||||
|
||||
// ********************************************************* 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.
|
||||
boost::filesystem::path pathLockFile = GetDataDir() / ".lock";
|
||||
FILE* file = fopen(pathLockFile.string().c_str(), "a"); // empty lock file; created if it doesn't exist.
|
||||
if (file) fclose(file);
|
||||
static boost::interprocess::file_lock lock(pathLockFile.string().c_str());
|
||||
const char* pszDataDir = GetDataDir().string().c_str();
|
||||
if (!lock.try_lock())
|
||||
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;
|
||||
|
||||
// ********************************************************* 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);
|
||||
|
||||
@ -587,15 +625,7 @@ bool AppInit2()
|
||||
BOOST_FOREACH(string strDest, mapMultiArgs["-seednode"])
|
||||
AddOneShot(strDest);
|
||||
|
||||
// ********************************************************* Step 6: 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);
|
||||
}
|
||||
// ********************************************************* Step 7: load blockchain
|
||||
|
||||
if (GetBoolArg("-loadblockindextest"))
|
||||
{
|
||||
@ -650,18 +680,24 @@ bool AppInit2()
|
||||
return false;
|
||||
}
|
||||
|
||||
// ********************************************************* Step 7: load wallet
|
||||
// ********************************************************* Step 8: load wallet
|
||||
|
||||
uiInterface.InitMessage(_("Loading wallet..."));
|
||||
printf("Loading wallet...\n");
|
||||
nStart = GetTimeMillis();
|
||||
bool fFirstRun = true;
|
||||
pwalletMain = new CWallet("wallet.dat");
|
||||
int nLoadWalletRet = pwalletMain->LoadWallet(fFirstRun);
|
||||
DBErrors nLoadWalletRet = pwalletMain->LoadWallet(fFirstRun);
|
||||
if (nLoadWalletRet != DB_LOAD_OK)
|
||||
{
|
||||
if (nLoadWalletRet == DB_CORRUPT)
|
||||
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)
|
||||
strErrors << _("Error loading wallet.dat: Wallet requires newer version of Bitcoin") << "\n";
|
||||
else if (nLoadWalletRet == DB_NEED_REWRITE)
|
||||
@ -727,7 +763,7 @@ bool AppInit2()
|
||||
printf(" rescan %15"PRI64d"ms\n", GetTimeMillis() - nStart);
|
||||
}
|
||||
|
||||
// ********************************************************* Step 8: import blocks
|
||||
// ********************************************************* Step 9: import blocks
|
||||
|
||||
if (mapArgs.count("-loadblock"))
|
||||
{
|
||||
@ -753,7 +789,7 @@ bool AppInit2()
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************************* Step 9: load peers
|
||||
// ********************************************************* Step 10: load peers
|
||||
|
||||
uiInterface.InitMessage(_("Loading addresses..."));
|
||||
printf("Loading addresses...\n");
|
||||
@ -768,7 +804,7 @@ bool AppInit2()
|
||||
printf("Loaded %i addresses from peers.dat %"PRI64d"ms\n",
|
||||
addrman.size(), GetTimeMillis() - nStart);
|
||||
|
||||
// ********************************************************* Step 10: start node
|
||||
// ********************************************************* Step 11: start node
|
||||
|
||||
if (!CheckDiskSpace())
|
||||
return false;
|
||||
@ -788,7 +824,7 @@ bool AppInit2()
|
||||
if (fServer)
|
||||
NewThread(ThreadRPCServer, NULL);
|
||||
|
||||
// ********************************************************* Step 11: finished
|
||||
// ********************************************************* Step 12: finished
|
||||
|
||||
uiInterface.InitMessage(_("Done loading"));
|
||||
printf("Done loading\n");
|
||||
@ -808,4 +844,3 @@ bool AppInit2()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
29
src/key.cpp
29
src/key.cpp
@ -186,11 +186,25 @@ void CKey::MakeNewKey(bool fCompressed)
|
||||
bool CKey::SetPrivKey(const CPrivKey& vchPrivKey)
|
||||
{
|
||||
const unsigned char* pbegin = &vchPrivKey[0];
|
||||
if (!d2i_ECPrivateKey(&pkey, &pbegin, vchPrivKey.size()))
|
||||
return false;
|
||||
if (d2i_ECPrivateKey(&pkey, &pbegin, vchPrivKey.size()))
|
||||
{
|
||||
// In testing, d2i_ECPrivateKey can 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)
|
||||
{
|
||||
@ -245,13 +259,17 @@ CPrivKey CKey::GetPrivKey() const
|
||||
bool CKey::SetPubKey(const CPubKey& vchPubKey)
|
||||
{
|
||||
const unsigned char* pbegin = &vchPubKey.vchPubKey[0];
|
||||
if (!o2i_ECPublicKey(&pkey, &pbegin, vchPubKey.vchPubKey.size()))
|
||||
return false;
|
||||
if (o2i_ECPublicKey(&pkey, &pbegin, vchPubKey.vchPubKey.size()))
|
||||
{
|
||||
fSet = true;
|
||||
if (vchPubKey.vchPubKey.size() == 33)
|
||||
SetCompressedPubKey();
|
||||
return true;
|
||||
}
|
||||
pkey = NULL;
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
CPubKey CKey::GetPubKey() const
|
||||
{
|
||||
@ -377,6 +395,9 @@ bool CKey::IsValid()
|
||||
if (!fSet)
|
||||
return false;
|
||||
|
||||
if (!EC_KEY_check_key(pkey))
|
||||
return false;
|
||||
|
||||
bool fCompr;
|
||||
CSecret secret = GetSecret(fCompr);
|
||||
CKey key2;
|
||||
|
@ -335,7 +335,9 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx)
|
||||
if (mi != mapWallet.end())
|
||||
{
|
||||
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());
|
||||
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)
|
||||
return DB_LOAD_OK;
|
||||
fFirstRunRet = false;
|
||||
int nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this);
|
||||
DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this);
|
||||
if (nLoadWalletRet == DB_NEED_REWRITE)
|
||||
{
|
||||
if (CDB::Rewrite(strWalletFile, "\x04pool"))
|
||||
|
@ -16,11 +16,11 @@
|
||||
#include "script.h"
|
||||
#include "ui_interface.h"
|
||||
#include "util.h"
|
||||
#include "walletdb.h"
|
||||
|
||||
class CAccountingEntry;
|
||||
class CWalletTx;
|
||||
class CReserveKey;
|
||||
class CWalletDB;
|
||||
class COutput;
|
||||
|
||||
/** (client) version numbers for particular wallet features */
|
||||
@ -256,7 +256,7 @@ public:
|
||||
}
|
||||
void SetBestChain(const CBlockLocator& loc);
|
||||
|
||||
int LoadWallet(bool& fFirstRunRet);
|
||||
DBErrors LoadWallet(bool& fFirstRunRet);
|
||||
|
||||
bool SetAddressBookName(const CTxDestination& address, const std::string& strName);
|
||||
|
||||
|
292
src/walletdb.cpp
292
src/walletdb.cpp
@ -108,7 +108,7 @@ void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountin
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
DBErrors
|
||||
CWalletDB::ReorderTransactions(CWallet* pwallet)
|
||||
{
|
||||
LOCK(pwallet->cs_wallet);
|
||||
@ -181,51 +181,15 @@ 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)
|
||||
{
|
||||
pwallet->vchDefaultKey = CPubKey();
|
||||
int nFileVersion = 0;
|
||||
vector<uint256> vWalletUpgrade;
|
||||
bool fIsEncrypted = false;
|
||||
bool fAnyUnordered = false;
|
||||
|
||||
//// todo: shouldn't we catch exceptions and try to recover and continue?
|
||||
{
|
||||
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 {
|
||||
// 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")
|
||||
{
|
||||
@ -239,10 +203,13 @@ int CWalletDB::LoadWallet(CWallet* pwallet)
|
||||
ssKey >> hash;
|
||||
CWalletTx& wtx = pwallet->mapWallet[hash];
|
||||
ssValue >> wtx;
|
||||
if (wtx.CheckTransaction() && (wtx.GetHash() == hash))
|
||||
wtx.BindWallet(pwallet);
|
||||
|
||||
if (wtx.GetHash() != hash)
|
||||
printf("Error in wallet.dat, hash mismatch\n");
|
||||
else
|
||||
{
|
||||
pwallet->mapWallet.erase(hash);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Undo serialize changes in 31600
|
||||
if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703)
|
||||
@ -252,12 +219,13 @@ int CWalletDB::LoadWallet(CWallet* pwallet)
|
||||
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());
|
||||
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
|
||||
{
|
||||
printf("LoadWallet() repairing tx ver=%d %s\n", wtx.fTimeReceivedIsTxTime, hash.ToString().c_str());
|
||||
strErr = strprintf("LoadWallet() repairing tx ver=%d %s", wtx.fTimeReceivedIsTxTime, hash.ToString().c_str());
|
||||
wtx.fTimeReceivedIsTxTime = 0;
|
||||
}
|
||||
vWalletUpgrade.push_back(hash);
|
||||
@ -301,16 +269,20 @@ int CWalletDB::LoadWallet(CWallet* pwallet)
|
||||
CPrivKey pkey;
|
||||
ssValue >> pkey;
|
||||
key.SetPubKey(vchPubKey);
|
||||
key.SetPrivKey(pkey);
|
||||
if (!key.SetPrivKey(pkey))
|
||||
{
|
||||
strErr = "Error reading wallet database: CPrivKey corrupt";
|
||||
return false;
|
||||
}
|
||||
if (key.GetPubKey() != vchPubKey)
|
||||
{
|
||||
printf("Error reading wallet database: CPrivKey pubkey inconsistency\n");
|
||||
return DB_CORRUPT;
|
||||
strErr = "Error reading wallet database: CPrivKey pubkey inconsistency";
|
||||
return false;
|
||||
}
|
||||
if (!key.IsValid())
|
||||
{
|
||||
printf("Error reading wallet database: invalid CPrivKey\n");
|
||||
return DB_CORRUPT;
|
||||
strErr = "Error reading wallet database: invalid CPrivKey";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -318,22 +290,26 @@ int CWalletDB::LoadWallet(CWallet* pwallet)
|
||||
CWalletKey wkey;
|
||||
ssValue >> wkey;
|
||||
key.SetPubKey(vchPubKey);
|
||||
key.SetPrivKey(wkey.vchPrivKey);
|
||||
if (!key.SetPrivKey(wkey.vchPrivKey))
|
||||
{
|
||||
strErr = "Error reading wallet database: CPrivKey corrupt";
|
||||
return false;
|
||||
}
|
||||
if (key.GetPubKey() != vchPubKey)
|
||||
{
|
||||
printf("Error reading wallet database: CWalletKey pubkey inconsistency\n");
|
||||
return DB_CORRUPT;
|
||||
strErr = "Error reading wallet database: CWalletKey pubkey inconsistency";
|
||||
return false;
|
||||
}
|
||||
if (!key.IsValid())
|
||||
{
|
||||
printf("Error reading wallet database: invalid CWalletKey\n");
|
||||
return DB_CORRUPT;
|
||||
strErr = "Error reading wallet database: invalid CWalletKey";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!pwallet->LoadKey(key))
|
||||
{
|
||||
printf("Error reading wallet database: LoadKey failed\n");
|
||||
return DB_CORRUPT;
|
||||
strErr = "Error reading wallet database: LoadKey failed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (strType == "mkey")
|
||||
@ -344,8 +320,8 @@ int CWalletDB::LoadWallet(CWallet* pwallet)
|
||||
ssValue >> kMasterKey;
|
||||
if(pwallet->mapMasterKeys.count(nID) != 0)
|
||||
{
|
||||
printf("Error reading wallet database: duplicate CMasterKey id %u\n", nID);
|
||||
return DB_CORRUPT;
|
||||
strErr = strprintf("Error reading wallet database: duplicate CMasterKey id %u", nID);
|
||||
return false;
|
||||
}
|
||||
pwallet->mapMasterKeys[nID] = kMasterKey;
|
||||
if (pwallet->nMasterKeyMaxID < nID)
|
||||
@ -359,8 +335,8 @@ int CWalletDB::LoadWallet(CWallet* pwallet)
|
||||
ssValue >> vchPrivKey;
|
||||
if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey))
|
||||
{
|
||||
printf("Error reading wallet database: LoadCryptedKey failed\n");
|
||||
return DB_CORRUPT;
|
||||
strErr = "Error reading wallet database: LoadCryptedKey failed";
|
||||
return false;
|
||||
}
|
||||
fIsEncrypted = true;
|
||||
}
|
||||
@ -388,23 +364,109 @@ int CWalletDB::LoadWallet(CWallet* pwallet)
|
||||
ssValue >> script;
|
||||
if (!pwallet->LoadCScript(script))
|
||||
{
|
||||
printf("Error reading wallet database: LoadCScript failed\n");
|
||||
return DB_CORRUPT;
|
||||
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();
|
||||
int nFileVersion = 0;
|
||||
vector<uint256> vWalletUpgrade;
|
||||
bool fIsEncrypted = false;
|
||||
bool fAnyUnordered = false;
|
||||
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, nFileVersion,
|
||||
vWalletUpgrade, fIsEncrypted, fAnyUnordered, 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 (...)
|
||||
{
|
||||
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);
|
||||
|
||||
BOOST_FOREACH(uint256 hash, vWalletUpgrade)
|
||||
WriteTx(hash, pwallet->mapWallet[hash]);
|
||||
|
||||
// Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc:
|
||||
if (fIsEncrypted && (nFileVersion == 40000 || nFileVersion == 50000))
|
||||
@ -414,10 +476,9 @@ int CWalletDB::LoadWallet(CWallet* pwallet)
|
||||
WriteVersion(CLIENT_VERSION);
|
||||
|
||||
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 DB_LOAD_OK;
|
||||
return result;
|
||||
}
|
||||
|
||||
void ThreadFlushWalletDB(void* parg)
|
||||
@ -521,3 +582,94 @@ bool BackupWallet(const CWallet& wallet, const string& strDest)
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ enum DBErrors
|
||||
{
|
||||
DB_LOAD_OK,
|
||||
DB_CORRUPT,
|
||||
DB_NONCRITICAL_ERROR,
|
||||
DB_TOO_NEW,
|
||||
DB_LOAD_FAIL,
|
||||
DB_NEED_REWRITE
|
||||
@ -153,8 +154,10 @@ public:
|
||||
int64 GetAccountCreditDebit(const std::string& strAccount);
|
||||
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& acentries);
|
||||
|
||||
int ReorderTransactions(CWallet*);
|
||||
int LoadWallet(CWallet* pwallet);
|
||||
DBErrors ReorderTransactions(CWallet*);
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user