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

@@ -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;
}