diff options
author | Gavin Andresen <gavinandresen@gmail.com> | 2012-09-18 14:30:47 -0400 |
---|---|---|
committer | Gavin Andresen <gavinandresen@gmail.com> | 2012-10-08 17:46:45 -0400 |
commit | eed1785f701be93ac2464e854c2a7de1f748ef84 (patch) | |
tree | 012b0bae35dfc7f35c10d341e50dd31fb88c6cf1 /src/init.cpp | |
parent | 8d5f461cb6d4bb954fef5c3deebe2b2a7bdbfe27 (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)
Diffstat (limited to 'src/init.cpp')
-rw-r--r-- | src/init.cpp | 71 |
1 files changed, 53 insertions, 18 deletions
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; } - |