Changeset View
Changeset View
Standalone View
Standalone View
src/wallet/walletdb.cpp
Show First 20 Lines • Show All 522 Lines • ▼ Show 20 Lines | |||||
bool CWalletDB::IsKeyType(const std::string &strType) { | bool CWalletDB::IsKeyType(const std::string &strType) { | ||||
return (strType == "key" || strType == "wkey" || strType == "mkey" || | return (strType == "key" || strType == "wkey" || strType == "mkey" || | ||||
strType == "ckey"); | strType == "ckey"); | ||||
} | } | ||||
DBErrors CWalletDB::LoadWallet(CWallet *pwallet) { | DBErrors CWalletDB::LoadWallet(CWallet *pwallet) { | ||||
CWalletScanState wss; | CWalletScanState wss; | ||||
bool fNoncriticalErrors = false; | bool fNoncriticalErrors = false; | ||||
DBErrors result = DB_LOAD_OK; | DBErrors result = DBErrors::LOAD_OK; | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
try { | try { | ||||
int nMinVersion = 0; | int nMinVersion = 0; | ||||
if (batch.Read((std::string) "minversion", nMinVersion)) { | if (batch.Read((std::string) "minversion", nMinVersion)) { | ||||
if (nMinVersion > CLIENT_VERSION) { | if (nMinVersion > CLIENT_VERSION) { | ||||
return DB_TOO_NEW; | return DBErrors::TOO_NEW; | ||||
} | } | ||||
pwallet->LoadMinVersion(nMinVersion); | pwallet->LoadMinVersion(nMinVersion); | ||||
} | } | ||||
// Get cursor | // Get cursor | ||||
Dbc *pcursor = batch.GetCursor(); | Dbc *pcursor = batch.GetCursor(); | ||||
if (!pcursor) { | if (!pcursor) { | ||||
LogPrintf("Error getting wallet database cursor\n"); | LogPrintf("Error getting wallet database cursor\n"); | ||||
return DB_CORRUPT; | return DBErrors::CORRUPT; | ||||
} | } | ||||
while (true) { | while (true) { | ||||
// Read next record | // Read next record | ||||
CDataStream ssKey(SER_DISK, CLIENT_VERSION); | CDataStream ssKey(SER_DISK, CLIENT_VERSION); | ||||
CDataStream ssValue(SER_DISK, CLIENT_VERSION); | CDataStream ssValue(SER_DISK, CLIENT_VERSION); | ||||
int ret = batch.ReadAtCursor(pcursor, ssKey, ssValue); | int ret = batch.ReadAtCursor(pcursor, ssKey, ssValue); | ||||
if (ret == DB_NOTFOUND) { | if (ret == DB_NOTFOUND) { | ||||
break; | break; | ||||
} | } | ||||
if (ret != 0) { | if (ret != 0) { | ||||
LogPrintf("Error reading next record from wallet database\n"); | LogPrintf("Error reading next record from wallet database\n"); | ||||
return DB_CORRUPT; | return DBErrors::CORRUPT; | ||||
} | } | ||||
// Try to be tolerant of single corrupt records: | // Try to be tolerant of single corrupt records: | ||||
std::string strType, strErr; | std::string strType, strErr; | ||||
if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr)) { | if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr)) { | ||||
// losing keys is considered a catastrophic error, anything else | // losing keys is considered a catastrophic error, anything else | ||||
// we assume the user can live with: | // we assume the user can live with: | ||||
if (IsKeyType(strType) || strType == "defaultkey") { | if (IsKeyType(strType) || strType == "defaultkey") { | ||||
result = DB_CORRUPT; | result = DBErrors::CORRUPT; | ||||
} else { | } else { | ||||
// Leave other errors alone, if we try to fix them we might | // Leave other errors alone, if we try to fix them we might | ||||
// make things worse. But do warn the user there is | // make things worse. But do warn the user there is | ||||
// something wrong. | // something wrong. | ||||
fNoncriticalErrors = true; | fNoncriticalErrors = true; | ||||
if (strType == "tx") { | if (strType == "tx") { | ||||
// Rescan if there is a bad transaction record: | // Rescan if there is a bad transaction record: | ||||
gArgs.SoftSetBoolArg("-rescan", true); | gArgs.SoftSetBoolArg("-rescan", true); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if (!strErr.empty()) { | if (!strErr.empty()) { | ||||
LogPrintf("%s\n", strErr); | LogPrintf("%s\n", strErr); | ||||
} | } | ||||
} | } | ||||
pcursor->close(); | pcursor->close(); | ||||
} catch (const boost::thread_interrupted &) { | } catch (const boost::thread_interrupted &) { | ||||
throw; | throw; | ||||
} catch (...) { | } catch (...) { | ||||
result = DB_CORRUPT; | result = DBErrors::CORRUPT; | ||||
} | } | ||||
if (fNoncriticalErrors && result == DB_LOAD_OK) { | if (fNoncriticalErrors && result == DBErrors::LOAD_OK) { | ||||
result = DB_NONCRITICAL_ERROR; | result = DBErrors::NONCRITICAL_ERROR; | ||||
} | } | ||||
// Any wallet corruption at all: skip any rewriting or upgrading, we don't | // Any wallet corruption at all: skip any rewriting or upgrading, we don't | ||||
// want to make it worse. | // want to make it worse. | ||||
if (result != DB_LOAD_OK) { | if (result != DBErrors::LOAD_OK) { | ||||
return result; | return result; | ||||
} | } | ||||
LogPrintf("nFileVersion = %d\n", wss.nFileVersion); | LogPrintf("nFileVersion = %d\n", wss.nFileVersion); | ||||
LogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total\n", | LogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total\n", | ||||
wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys); | wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys); | ||||
// nTimeFirstKey is only reliable if all keys have metadata | // nTimeFirstKey is only reliable if all keys have metadata | ||||
if ((wss.nKeys + wss.nCKeys + wss.nWatchKeys) != wss.nKeyMeta) { | if ((wss.nKeys + wss.nCKeys + wss.nWatchKeys) != wss.nKeyMeta) { | ||||
pwallet->UpdateTimeFirstKey(1); | pwallet->UpdateTimeFirstKey(1); | ||||
} | } | ||||
for (const TxId &txid : wss.vWalletUpgrade) { | for (const TxId &txid : wss.vWalletUpgrade) { | ||||
WriteTx(pwallet->mapWallet.at(txid)); | WriteTx(pwallet->mapWallet.at(txid)); | ||||
} | } | ||||
// 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 (wss.fIsEncrypted && | if (wss.fIsEncrypted && | ||||
(wss.nFileVersion == 40000 || wss.nFileVersion == 50000)) { | (wss.nFileVersion == 40000 || wss.nFileVersion == 50000)) { | ||||
return DB_NEED_REWRITE; | return DBErrors::NEED_REWRITE; | ||||
} | } | ||||
if (wss.nFileVersion < CLIENT_VERSION) { | if (wss.nFileVersion < CLIENT_VERSION) { | ||||
// Update | // Update | ||||
WriteVersion(CLIENT_VERSION); | WriteVersion(CLIENT_VERSION); | ||||
} | } | ||||
if (wss.fAnyUnordered) { | if (wss.fAnyUnordered) { | ||||
result = pwallet->ReorderTransactions(); | result = pwallet->ReorderTransactions(); | ||||
} | } | ||||
pwallet->laccentries.clear(); | pwallet->laccentries.clear(); | ||||
ListAccountCreditDebit("*", pwallet->laccentries); | ListAccountCreditDebit("*", pwallet->laccentries); | ||||
for (CAccountingEntry &entry : pwallet->laccentries) { | for (CAccountingEntry &entry : pwallet->laccentries) { | ||||
pwallet->wtxOrdered.insert( | pwallet->wtxOrdered.insert( | ||||
std::make_pair(entry.nOrderPos, CWallet::TxPair(nullptr, &entry))); | std::make_pair(entry.nOrderPos, CWallet::TxPair(nullptr, &entry))); | ||||
} | } | ||||
return result; | return result; | ||||
} | } | ||||
DBErrors CWalletDB::FindWalletTx(std::vector<TxId> &txIds, | DBErrors CWalletDB::FindWalletTx(std::vector<TxId> &txIds, | ||||
std::vector<CWalletTx> &vWtx) { | std::vector<CWalletTx> &vWtx) { | ||||
DBErrors result = DB_LOAD_OK; | DBErrors result = DBErrors::LOAD_OK; | ||||
try { | try { | ||||
int nMinVersion = 0; | int nMinVersion = 0; | ||||
if (batch.Read((std::string) "minversion", nMinVersion)) { | if (batch.Read((std::string) "minversion", nMinVersion)) { | ||||
if (nMinVersion > CLIENT_VERSION) { | if (nMinVersion > CLIENT_VERSION) { | ||||
return DB_TOO_NEW; | return DBErrors::TOO_NEW; | ||||
} | } | ||||
} | } | ||||
// Get cursor | // Get cursor | ||||
Dbc *pcursor = batch.GetCursor(); | Dbc *pcursor = batch.GetCursor(); | ||||
if (!pcursor) { | if (!pcursor) { | ||||
LogPrintf("Error getting wallet database cursor\n"); | LogPrintf("Error getting wallet database cursor\n"); | ||||
return DB_CORRUPT; | return DBErrors::CORRUPT; | ||||
} | } | ||||
while (true) { | while (true) { | ||||
// Read next record | // Read next record | ||||
CDataStream ssKey(SER_DISK, CLIENT_VERSION); | CDataStream ssKey(SER_DISK, CLIENT_VERSION); | ||||
CDataStream ssValue(SER_DISK, CLIENT_VERSION); | CDataStream ssValue(SER_DISK, CLIENT_VERSION); | ||||
int ret = batch.ReadAtCursor(pcursor, ssKey, ssValue); | int ret = batch.ReadAtCursor(pcursor, ssKey, ssValue); | ||||
if (ret == DB_NOTFOUND) { | if (ret == DB_NOTFOUND) { | ||||
break; | break; | ||||
} | } | ||||
if (ret != 0) { | if (ret != 0) { | ||||
LogPrintf("Error reading next record from wallet database\n"); | LogPrintf("Error reading next record from wallet database\n"); | ||||
return DB_CORRUPT; | return DBErrors::CORRUPT; | ||||
} | } | ||||
std::string strType; | std::string strType; | ||||
ssKey >> strType; | ssKey >> strType; | ||||
if (strType == "tx") { | if (strType == "tx") { | ||||
TxId txid; | TxId txid; | ||||
ssKey >> txid; | ssKey >> txid; | ||||
CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef()); | CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef()); | ||||
ssValue >> wtx; | ssValue >> wtx; | ||||
txIds.push_back(txid); | txIds.push_back(txid); | ||||
vWtx.push_back(wtx); | vWtx.push_back(wtx); | ||||
} | } | ||||
} | } | ||||
pcursor->close(); | pcursor->close(); | ||||
} catch (const boost::thread_interrupted &) { | } catch (const boost::thread_interrupted &) { | ||||
throw; | throw; | ||||
} catch (...) { | } catch (...) { | ||||
result = DB_CORRUPT; | result = DBErrors::CORRUPT; | ||||
} | } | ||||
return result; | return result; | ||||
} | } | ||||
DBErrors CWalletDB::ZapSelectTx(std::vector<TxId> &txIdsIn, | DBErrors CWalletDB::ZapSelectTx(std::vector<TxId> &txIdsIn, | ||||
std::vector<TxId> &txIdsOut) { | std::vector<TxId> &txIdsOut) { | ||||
// Build list of wallet TXs and hashes. | // Build list of wallet TXs and hashes. | ||||
std::vector<TxId> txIds; | std::vector<TxId> txIds; | ||||
std::vector<CWalletTx> vWtx; | std::vector<CWalletTx> vWtx; | ||||
DBErrors err = FindWalletTx(txIds, vWtx); | DBErrors err = FindWalletTx(txIds, vWtx); | ||||
if (err != DB_LOAD_OK) { | if (err != DBErrors::LOAD_OK) { | ||||
return err; | return err; | ||||
} | } | ||||
std::sort(txIds.begin(), txIds.end()); | std::sort(txIds.begin(), txIds.end()); | ||||
std::sort(txIdsIn.begin(), txIdsIn.end()); | std::sort(txIdsIn.begin(), txIdsIn.end()); | ||||
// Erase each matching wallet TX. | // Erase each matching wallet TX. | ||||
bool delerror = false; | bool delerror = false; | ||||
Show All 14 Lines | for (const TxId &txid : txIds) { | ||||
txid.GetHex()); | txid.GetHex()); | ||||
delerror = true; | delerror = true; | ||||
} | } | ||||
txIdsOut.push_back(txid); | txIdsOut.push_back(txid); | ||||
} | } | ||||
} | } | ||||
if (delerror) { | if (delerror) { | ||||
return DB_CORRUPT; | return DBErrors::CORRUPT; | ||||
} | } | ||||
return DB_LOAD_OK; | return DBErrors::LOAD_OK; | ||||
} | } | ||||
DBErrors CWalletDB::ZapWalletTx(std::vector<CWalletTx> &vWtx) { | DBErrors CWalletDB::ZapWalletTx(std::vector<CWalletTx> &vWtx) { | ||||
// Build list of wallet TXs. | // Build list of wallet TXs. | ||||
std::vector<TxId> txIds; | std::vector<TxId> txIds; | ||||
DBErrors err = FindWalletTx(txIds, vWtx); | DBErrors err = FindWalletTx(txIds, vWtx); | ||||
if (err != DB_LOAD_OK) { | if (err != DBErrors::LOAD_OK) { | ||||
return err; | return err; | ||||
} | } | ||||
// Erase each wallet TX. | // Erase each wallet TX. | ||||
for (const TxId &txid : txIds) { | for (const TxId &txid : txIds) { | ||||
if (!EraseTx(txid)) { | if (!EraseTx(txid)) { | ||||
return DB_CORRUPT; | return DBErrors::CORRUPT; | ||||
} | } | ||||
} | } | ||||
return DB_LOAD_OK; | return DBErrors::LOAD_OK; | ||||
} | } | ||||
void MaybeCompactWalletDB() { | void MaybeCompactWalletDB() { | ||||
static std::atomic<bool> fOneThread; | static std::atomic<bool> fOneThread; | ||||
if (fOneThread.exchange(true)) { | if (fOneThread.exchange(true)) { | ||||
return; | return; | ||||
} | } | ||||
if (!gArgs.GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) { | if (!gArgs.GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) { | ||||
▲ Show 20 Lines • Show All 127 Lines • Show Last 20 Lines |