123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710 |
- // ECOin - Copyright (c) - 2014/2024 - GPLv3 - epsylon@riseup.net (https://03c8.net)
- #include "walletdb.h"
- #include "wallet.h"
- #include <boost/version.hpp>
- #include <boost/filesystem.hpp>
- using namespace std;
- using namespace boost;
- static uint64 nAccountingEntryNumber = 0;
- extern bool fWalletUnlockStakingOnly;
- //
- // CWalletDB
- //
- bool CWalletDB::WriteName(const string& strAddress, const string& strName)
- {
- nWalletDBUpdated++;
- return Write(make_pair(string("name"), strAddress), strName);
- }
- bool CWalletDB::EraseName(const string& strAddress)
- {
- // This should only be used for sending addresses, never for receiving addresses,
- // receiving addresses must always have an address book entry if they're not change return.
- nWalletDBUpdated++;
- return Erase(make_pair(string("name"), strAddress));
- }
- bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account)
- {
- account.SetNull();
- return Read(make_pair(string("acc"), strAccount), account);
- }
- bool CWalletDB::WriteAccount(const string& strAccount, const CAccount& account)
- {
- return Write(make_pair(string("acc"), strAccount), account);
- }
- bool CWalletDB::WriteAccountingEntry(const uint64 nAccEntryNum, const CAccountingEntry& acentry)
- {
- return Write(boost::make_tuple(string("acentry"), acentry.strAccount, nAccEntryNum), acentry);
- }
- bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry)
- {
- return WriteAccountingEntry(++nAccountingEntryNumber, acentry);
- }
- int64 CWalletDB::GetAccountCreditDebit(const string& strAccount)
- {
- list<CAccountingEntry> entries;
- ListAccountCreditDebit(strAccount, entries);
- int64 nCreditDebit = 0;
- BOOST_FOREACH (const CAccountingEntry& entry, entries)
- nCreditDebit += entry.nCreditDebit;
- return nCreditDebit;
- }
- void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountingEntry>& entries)
- {
- bool fAllAccounts = (strAccount == "*");
- Dbc* pcursor = GetCursor();
- if (!pcursor)
- throw runtime_error("CWalletDB::ListAccountCreditDebit() : cannot create DB cursor");
- unsigned int fFlags = DB_SET_RANGE;
- while (true)
- {
- // Read next record
- CDataStream ssKey(SER_DISK, CLIENT_VERSION);
- if (fFlags == DB_SET_RANGE)
- ssKey << boost::make_tuple(string("acentry"), (fAllAccounts? string("") : strAccount), uint64(0));
- CDataStream ssValue(SER_DISK, CLIENT_VERSION);
- int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
- fFlags = DB_NEXT;
- if (ret == DB_NOTFOUND)
- break;
- else if (ret != 0)
- {
- pcursor->close();
- throw runtime_error("CWalletDB::ListAccountCreditDebit() : error scanning DB");
- }
- // Unserialize
- string strType;
- ssKey >> strType;
- if (strType != "acentry")
- break;
- CAccountingEntry acentry;
- ssKey >> acentry.strAccount;
- if (!fAllAccounts && acentry.strAccount != strAccount)
- break;
- ssValue >> acentry;
- ssKey >> acentry.nEntryNo;
- entries.push_back(acentry);
- }
- pcursor->close();
- }
- DBErrors
- CWalletDB::ReorderTransactions(CWallet* pwallet)
- {
- LOCK(pwallet->cs_wallet);
- // Old wallets didn't have any defined order for transactions
- // Probably a bad idea to change the output of this
- // First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap.
- typedef pair<CWalletTx*, CAccountingEntry*> TxPair;
- typedef multimap<int64, TxPair > TxItems;
- TxItems txByTime;
- for (map<uint256, CWalletTx>::iterator it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it)
- {
- CWalletTx* wtx = &((*it).second);
- txByTime.insert(make_pair(wtx->nTimeReceived, TxPair(wtx, (CAccountingEntry*)0)));
- }
- list<CAccountingEntry> acentries;
- ListAccountCreditDebit("", acentries);
- BOOST_FOREACH(CAccountingEntry& entry, acentries)
- {
- txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry)));
- }
- int64& nOrderPosNext = pwallet->nOrderPosNext;
- nOrderPosNext = 0;
- std::vector<int64> nOrderPosOffsets;
- for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it)
- {
- CWalletTx *const pwtx = (*it).second.first;
- CAccountingEntry *const pacentry = (*it).second.second;
- int64& nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos;
- if (nOrderPos == -1)
- {
- nOrderPos = nOrderPosNext++;
- nOrderPosOffsets.push_back(nOrderPos);
- if (pacentry)
- // Have to write accounting regardless, since we don't keep it in memory
- if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
- return DB_LOAD_FAIL;
- }
- else
- {
- int64 nOrderPosOff = 0;
- BOOST_FOREACH(const int64& nOffsetStart, nOrderPosOffsets)
- {
- if (nOrderPos >= nOffsetStart)
- ++nOrderPosOff;
- }
- nOrderPos += nOrderPosOff;
- nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1);
- if (!nOrderPosOff)
- continue;
- // Since we're changing the order, write it back
- if (pwtx)
- {
- if (!WriteTx(pwtx->GetHash(), *pwtx))
- return DB_LOAD_FAIL;
- }
- else
- if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
- return DB_LOAD_FAIL;
- }
- }
- return DB_LOAD_OK;
- }
- class CWalletScanState {
- public:
- unsigned int nKeys;
- unsigned int nCKeys;
- unsigned int nKeyMeta;
- bool fIsEncrypted;
- bool fAnyUnordered;
- int nFileVersion;
- vector<uint256> vWalletUpgrade;
- CWalletScanState() {
- nKeys = nCKeys = nKeyMeta = 0;
- fIsEncrypted = false;
- fAnyUnordered = false;
- nFileVersion = 0;
- }
- };
- bool
- ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
- CWalletScanState &wss, 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[CEcoinAddress(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;
- }
- wss.vWalletUpgrade.push_back(hash);
- }
- if (wtx.nOrderPos == -1)
- wss.fAnyUnordered = true;
- }
- else if (strType == "acentry")
- {
- string strAccount;
- ssKey >> strAccount;
- uint64 nNumber;
- ssKey >> nNumber;
- if (nNumber > nAccountingEntryNumber)
- nAccountingEntryNumber = nNumber;
- if (!wss.fAnyUnordered)
- {
- CAccountingEntry acentry;
- ssValue >> acentry;
- if (acentry.nOrderPos == -1)
- wss.fAnyUnordered = true;
- }
- }
- else if (strType == "key" || strType == "wkey")
- {
- vector<unsigned char> vchPubKey;
- ssKey >> vchPubKey;
- CKey key;
- if (strType == "key")
- {
- wss.nKeys++;
- 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")
- {
- wss.nCKeys++;
- 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;
- }
- wss.fIsEncrypted = true;
- }
- else if (strType == "keymeta")
- {
- CPubKey vchPubKey;
- ssKey >> vchPubKey;
- CKeyMetadata keyMeta;
- ssValue >> keyMeta;
- wss.nKeyMeta++;
- pwallet->LoadKeyMetadata(vchPubKey, keyMeta);
- // find earliest key creation time, as wallet birthday
- if (!pwallet->nTimeFirstKey ||
- (keyMeta.nCreateTime < pwallet->nTimeFirstKey))
- pwallet->nTimeFirstKey = keyMeta.nCreateTime;
- }
- else if (strType == "defaultkey")
- {
- ssValue >> pwallet->vchDefaultKey;
- }
- else if (strType == "pool")
- {
- int64 nIndex;
- ssKey >> nIndex;
- CKeyPool keypool;
- ssValue >> keypool;
- pwallet->setKeyPool.insert(nIndex);
- // If no metadata exists yet, create a default with the pool key's
- // creation time. Note that this may be overwritten by actually
- // stored metadata for that key later, which is fine.
- CKeyID keyid = keypool.vchPubKey.GetID();
- if (pwallet->mapKeyMetadata.count(keyid) == 0)
- pwallet->mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
- }
- else if (strType == "version")
- {
- ssValue >> wss.nFileVersion;
- if (wss.nFileVersion == 10300)
- wss.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();
- CWalletScanState wss;
- bool fNoncriticalErrors = false;
- DBErrors result = DB_LOAD_OK;
- try {
- LOCK(pwallet->cs_wallet);
- int nMinVersion = 0;
- if (Read((string)"minversion", nMinVersion))
- {
- if (nMinVersion > CLIENT_VERSION)
- return DB_TOO_NEW;
- pwallet->LoadMinVersion(nMinVersion);
- }
- // Get cursor
- Dbc* pcursor = GetCursor();
- if (!pcursor)
- {
- printf("Error getting wallet database cursor\n");
- return DB_CORRUPT;
- }
- while (true)
- {
- // Read next record
- CDataStream ssKey(SER_DISK, CLIENT_VERSION);
- CDataStream ssValue(SER_DISK, CLIENT_VERSION);
- int ret = ReadAtCursor(pcursor, ssKey, ssValue);
- if (ret == DB_NOTFOUND)
- break;
- else if (ret != 0)
- {
- printf("Error reading next record from wallet database\n");
- return DB_CORRUPT;
- }
- // Try to be tolerant of single corrupt records:
- string strType, strErr;
- if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr))
- {
- // losing keys is considered a catastrophic error, anything else
- // we assume the user can live with:
- if (IsKeyType(strType))
- result = DB_CORRUPT;
- else
- {
- // 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);
- }
- }
- if (!strErr.empty())
- printf("%s\n", strErr.c_str());
- }
- pcursor->close();
- }
- catch (...)
- {
- result = DB_CORRUPT;
- }
- 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", wss.nFileVersion);
- printf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total\n",
- wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys);
- // nTimeFirstKey is only reliable if all keys have metadata
- if ((wss.nKeys + wss.nCKeys) != wss.nKeyMeta)
- pwallet->nTimeFirstKey = 1; // 0 would be considered 'no value'
- BOOST_FOREACH(uint256 hash, wss.vWalletUpgrade)
- WriteTx(hash, pwallet->mapWallet[hash]);
- // Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc:
- if (wss.fIsEncrypted && (wss.nFileVersion == 40000 || wss.nFileVersion == 50000))
- return DB_NEED_REWRITE;
- if (wss.nFileVersion < CLIENT_VERSION) // Update
- WriteVersion(CLIENT_VERSION);
- if (wss.fAnyUnordered)
- result = ReorderTransactions(pwallet);
- return result;
- }
- void ThreadFlushWalletDB(void* parg)
- {
- // Make this thread recognisable as the wallet flushing thread
- RenameThread("ecoin-wallet");
- const string& strFile = ((const string*)parg)[0];
- static bool fOneThread;
- if (fOneThread)
- return;
- fOneThread = true;
- if (!GetBoolArg("-flushwallet", true))
- return;
- unsigned int nLastSeen = nWalletDBUpdated;
- unsigned int nLastFlushed = nWalletDBUpdated;
- int64 nLastWalletUpdate = GetTime();
- while (!fShutdown)
- {
- Sleep(500);
- if (nLastSeen != nWalletDBUpdated)
- {
- nLastSeen = nWalletDBUpdated;
- nLastWalletUpdate = GetTime();
- }
- if (nLastFlushed != nWalletDBUpdated && GetTime() - nLastWalletUpdate >= 2)
- {
- TRY_LOCK(bitdb.cs_db,lockDb);
- if (lockDb)
- {
- // Don't do this if any databases are in use
- int nRefCount = 0;
- map<string, int>::iterator mi = bitdb.mapFileUseCount.begin();
- while (mi != bitdb.mapFileUseCount.end())
- {
- nRefCount += (*mi).second;
- mi++;
- }
- if (nRefCount == 0 && !fShutdown)
- {
- map<string, int>::iterator mi = bitdb.mapFileUseCount.find(strFile);
- if (mi != bitdb.mapFileUseCount.end())
- {
- printf("Flushing wallet.dat\n");
- nLastFlushed = nWalletDBUpdated;
- int64 nStart = GetTimeMillis();
- // Flush wallet.dat so it's self contained
- bitdb.CloseDb(strFile);
- bitdb.CheckpointLSN(strFile);
- bitdb.mapFileUseCount.erase(mi++);
- printf("Flushed wallet.dat %" PRI64d"ms\n", GetTimeMillis() - nStart);
- }
- }
- }
- }
- }
- }
- bool BackupWallet(const CWallet& wallet, const string& strDest)
- {
- if (!wallet.fFileBacked)
- return false;
- while (!fShutdown)
- {
- {
- LOCK(bitdb.cs_db);
- if (!bitdb.mapFileUseCount.count(wallet.strWalletFile) || bitdb.mapFileUseCount[wallet.strWalletFile] == 0)
- {
- // Flush log data to the dat file
- bitdb.CloseDb(wallet.strWalletFile);
- bitdb.CheckpointLSN(wallet.strWalletFile);
- bitdb.mapFileUseCount.erase(wallet.strWalletFile);
- // Copy wallet.dat
- boost::filesystem::path pathSrc = GetDataDir() / wallet.strWalletFile;
- boost::filesystem::path pathDest(strDest);
- if (boost::filesystem::is_directory(pathDest))
- pathDest /= wallet.strWalletFile;
- try {
- #if BOOST_VERSION >= 104000
- boost::filesystem::copy_file(pathSrc, pathDest, boost::filesystem::copy_option::overwrite_if_exists);
- #else
- boost::filesystem::copy_file(pathSrc, pathDest);
- #endif
- printf("copied wallet.dat to %s\n", pathDest.string().c_str());
- return true;
- } catch(const boost::filesystem::filesystem_error &e) {
- printf("error copying wallet.dat to %s - %s\n", pathDest.string().c_str(), e.what());
- return false;
- }
- }
- }
- Sleep(100);
- }
- 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;
- CWalletScanState wss;
- 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,
- wss, 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);
- }
|