aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGavin Andresen <gavinandresen@gmail.com>2012-09-18 14:30:47 -0400
committerGavin Andresen <gavinandresen@gmail.com>2012-10-08 17:46:45 -0400
commiteed1785f701be93ac2464e854c2a7de1f748ef84 (patch)
tree012b0bae35dfc7f35c10d341e50dd31fb88c6cf1
parent8d5f461cb6d4bb954fef5c3deebe2b2a7bdbfe27 (diff)
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)
-rw-r--r--src/db.cpp72
-rw-r--r--src/db.h20
-rw-r--r--src/init.cpp71
-rw-r--r--src/key.cpp41
-rw-r--r--src/wallet.cpp8
-rw-r--r--src/wallet.h4
-rw-r--r--src/walletdb.cpp508
-rw-r--r--src/walletdb.h7
8 files changed, 518 insertions, 213 deletions
diff --git a/src/db.cpp b/src/db.cpp
index 7ca9e34953..9ad67892f1 100644
--- a/src/db.cpp
+++ b/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)
diff --git a/src/db.h b/src/db.h
index 4a08bf10a1..798913645f 100644
--- a/src/db.h
+++ b/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)
{
diff --git a/src/init.cpp b/src/init.cpp
index d271893644..480d65422c 100644
--- a/src/init.cpp
+++ b/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;
}
-
diff --git a/src/key.cpp b/src/key.cpp
index 76c45d0635..23f315203e 100644
--- a/src/key.cpp
+++ b/src/key.cpp
@@ -186,10 +186,24 @@ 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;
- fSet = true;
- return true;
+ 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,12 +259,16 @@ 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;
- fSet = true;
- if (vchPubKey.vchPubKey.size() == 33)
- SetCompressedPubKey();
- return true;
+ 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;
diff --git a/src/wallet.cpp b/src/wallet.cpp
index f3e71f59aa..1a6a1082b1 100644
--- a/src/wallet.cpp
+++ b/src/wallet.cpp
@@ -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"))
diff --git a/src/wallet.h b/src/wallet.h
index 22795b75ba..c5f1243907 100644
--- a/src/wallet.h
+++ b/src/wallet.h
@@ -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);
diff --git a/src/walletdb.cpp b/src/walletdb.cpp
index 0fac0109c8..e102df9720 100644
--- a/src/walletdb.cpp
+++ b/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,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();
int nFileVersion = 0;
vector<uint256> vWalletUpgrade;
bool fIsEncrypted = 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);
int nMinVersion = 0;
if (Read((string)"minversion", nMinVersion))
@@ -222,189 +427,46 @@ int CWalletDB::LoadWallet(CWallet* pwallet)
return DB_CORRUPT;
}
- // 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")
- {
- 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;
- 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")
+ // Try to be tolerant of single corrupt records:
+ string strType, strErr;
+ if (!ReadKeyValue(pwallet, ssKey, ssValue, nFileVersion,
+ vWalletUpgrade, fIsEncrypted, fAnyUnordered, strType, strErr))
{
- 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;
- }
- }
+ // losing keys is considered a catastrophic error, anything else
+ // we assume the user can live with:
+ if (IsKeyType(strType))
+ result = DB_CORRUPT;
else
{
- 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;
- }
- }
- if (!pwallet->LoadKey(key))
- {
- printf("Error reading wallet database: LoadKey failed\n");
- return DB_CORRUPT;
- }
- }
- else if (strType == "mkey")
- {
- 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;
+ // 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);
}
- 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;
}
+ 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);
+}
diff --git a/src/walletdb.h b/src/walletdb.h
index f078481811..a3e779ab9d 100644
--- a/src/walletdb.h
+++ b/src/walletdb.h
@@ -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