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
8 changed files with 518 additions and 213 deletions
72
src/db.cpp
72
src/db.cpp
|
@ -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)
|
||||||
|
|
20
src/db.h
20
src/db.h
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
71
src/init.cpp
71
src/init.cpp
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
41
src/key.cpp
41
src/key.cpp
|
@ -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;
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
508
src/walletdb.cpp
508
src/walletdb.cpp
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue