From 1c860ced5757f077a02158c813fb8c97923d075c Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sun, 12 Jun 2016 13:33:37 +0300 Subject: [PATCH 1/4] Autobackup refactoring and improvements: - make nWalletBackups globally accessable - move autobackup code from init.cpp to walletdb.cpp, see AutoBackupWallet function - refactor autobackup code to warn user if autobackup failed instead of silently ignoring this fact - refactor autobackup code to be able to backup fresh new wallet right after it was created, add this functionality to init sequence - add new cmd-line option "-walletbackupsdir" to specify full path to directory for automatic wallet backups, see GetBackupsDir function --- src/init.cpp | 95 ++++++++++-------------------------- src/qt/guiutil.cpp | 6 +-- src/util.cpp | 29 +++++++++++ src/util.h | 2 + src/wallet/walletdb.cpp | 104 ++++++++++++++++++++++++++++++++++++++++ src/wallet/walletdb.h | 2 + 6 files changed, 164 insertions(+), 74 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 95b6a8afdd586..448835e8dc341 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -78,7 +78,6 @@ using namespace std; #ifdef ENABLE_WALLET CWallet* pwalletMain = NULL; -int nWalletBackups = 10; #endif bool fFeeEstimatesInitialized = false; bool fRestartRequested = false; // true: restart false: shutdown @@ -467,7 +466,8 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-walletnotify=", _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)")); strUsage += HelpMessageOpt("-zapwallettxes=", _("Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup") + " " + _("(1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)")); - strUsage += HelpMessageOpt("-createwalletbackups=", _("Number of automatic wallet backups (default: 10)")); + strUsage += HelpMessageOpt("-createwalletbackups=", strprintf(_("Number of automatic wallet backups (default: %u)"), nWalletBackups)); + strUsage += HelpMessageOpt("-walletbackupsdir=", _("Specify full path to directory for automatic wallet backups (must exist)")); strUsage += HelpMessageOpt("-keepass", strprintf(_("Use KeePass 2 integration using KeePassHttp plugin (default: %u)"), 0)); strUsage += HelpMessageOpt("-keepassport=", strprintf(_("Connect to KeePassHttp on port (default: %u)"), 19455)); strUsage += HelpMessageOpt("-keepasskey=", _("KeePassHttp key for AES encrypted communication with KeePass")); @@ -1230,82 +1230,24 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // ********************************************************* Step 5: Backup wallet and verify wallet database integrity #ifdef ENABLE_WALLET if (!fDisableWallet) { - - filesystem::path backupDir = GetDataDir() / "backups"; - if (!filesystem::exists(backupDir)) - { - // Always create backup folder to not confuse the operating system's file browser - filesystem::create_directories(backupDir); - } + std::string warningString; + std::string errorString; + nWalletBackups = GetArg("-createwalletbackups", 10); nWalletBackups = std::max(0, std::min(10, nWalletBackups)); - if(nWalletBackups > 0) - { - if (filesystem::exists(backupDir)) - { - // Create backup of the wallet - std::string dateTimeStr = DateTimeStrFormat(".%Y-%m-%d-%H-%M", GetTime()); - std::string backupPathStr = backupDir.string(); - backupPathStr += "/" + strWalletFile; - std::string sourcePathStr = GetDataDir().string(); - sourcePathStr += "/" + strWalletFile; - boost::filesystem::path sourceFile = sourcePathStr; - boost::filesystem::path backupFile = backupPathStr + dateTimeStr; - sourceFile.make_preferred(); - backupFile.make_preferred(); - if(boost::filesystem::exists(sourceFile)) { - try { - boost::filesystem::copy_file(sourceFile, backupFile); - LogPrintf("Creating backup of %s -> %s\n", sourceFile, backupFile); - } catch(boost::filesystem::filesystem_error &error) { - LogPrintf("Failed to create backup %s\n", error.what()); - } - } - // Keep only the last 10 backups, including the new one of course - typedef std::multimap folder_set_t; - folder_set_t folder_set; - boost::filesystem::directory_iterator end_iter; - boost::filesystem::path backupFolder = backupDir.string(); - backupFolder.make_preferred(); - // Build map of backup files for current(!) wallet sorted by last write time - boost::filesystem::path currentFile; - for (boost::filesystem::directory_iterator dir_iter(backupFolder); dir_iter != end_iter; ++dir_iter) - { - // Only check regular files - if ( boost::filesystem::is_regular_file(dir_iter->status())) - { - currentFile = dir_iter->path().filename(); - // Only add the backups for the current wallet, e.g. wallet.dat.* - if(dir_iter->path().stem().string() == strWalletFile) - { - folder_set.insert(folder_set_t::value_type(boost::filesystem::last_write_time(dir_iter->path()), *dir_iter)); - } - } - } - // Loop backward through backup files and keep the N newest ones (1 <= N <= 10) - int counter = 0; - BOOST_REVERSE_FOREACH(PAIRTYPE(const std::time_t, boost::filesystem::path) file, folder_set) - { - counter++; - if (counter > nWalletBackups) - { - // More than nWalletBackups backups: delete oldest one(s) - try { - boost::filesystem::remove(file.second); - LogPrintf("Old backup deleted: %s\n", file.second); - } catch(boost::filesystem::filesystem_error &error) { - LogPrintf("Failed to delete backup %s\n", error.what()); - } - } - } - } + + if(!AutoBackupWallet(NULL, strWalletFile, warningString, errorString)) { + if (!warningString.empty()) + InitWarning(warningString); + if (!errorString.empty()) + return InitError(errorString); } LogPrintf("Using wallet %s\n", strWalletFile); uiInterface.InitMessage(_("Verifying wallet...")); - std::string warningString; - std::string errorString; + // reset warning string + warningString = ""; if (!CWallet::Verify(strWalletFile, warningString, errorString)) return false; @@ -1706,6 +1648,17 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } pwalletMain->SetBestChain(chainActive.GetLocator()); + + // Try to create wallet backup right after new wallet was created + std::string warningString; + std::string errorString; + if(!AutoBackupWallet(pwalletMain, "", warningString, errorString)) { + if (!warningString.empty()) + InitWarning(warningString); + if (!errorString.empty()) + return InitError(errorString); + } + } LogPrintf("%s", strErrors.str()); diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index c13175e86c246..58b0ef0520eb9 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -430,11 +430,11 @@ void openMNConfigfile() void showBackups() { - boost::filesystem::path pathBackups = GetDataDir() / "backups"; + boost::filesystem::path backupsDir = GetBackupsDir(); /* Open folder with default browser */ - if (boost::filesystem::exists(pathBackups)) - QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathBackups))); + if (boost::filesystem::exists(backupsDir)) + QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(backupsDir))); } void SubstituteFonts(const QString& language) diff --git a/src/util.cpp b/src/util.cpp index fa179ce3b3d83..7bbbe89ac6192 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -112,6 +112,7 @@ int nInstantSendDepth = 5; int nPrivateSendRounds = 2; int nAnonymizeDashAmount = 1000; int nLiquidityProvider = 0; +int nWalletBackups = 10; /** Spork enforcement enabled time */ int64_t enforceMasternodePaymentsTime = 4085657524; bool fSucessfullyLoaded = false; @@ -580,6 +581,34 @@ const boost::filesystem::path &GetDataDir(bool fNetSpecific) return path; } +static boost::filesystem::path backupsDirCached; +static CCriticalSection csBackupsDirCached; + +const boost::filesystem::path &GetBackupsDir() +{ + namespace fs = boost::filesystem; + + LOCK(csBackupsDirCached); + + fs::path &backupsDir = backupsDirCached; + + if (!backupsDir.empty()) + return backupsDir; + + if (mapArgs.count("-walletbackupsdir")) { + backupsDir = fs::absolute(mapArgs["-walletbackupsdir"]); + // Path must exist + if (fs::is_directory(backupsDir)) return backupsDir; + // Fallback to default path if it doesn't + LogPrintf("%s: Warning: incorrect parameter -walletbackupsdir, path must exist! Using default path.\n", __func__); + strMiscWarning = _("Warning: incorrect parameter -walletbackupsdir, path must exist! Using default path."); + } + // Default path + backupsDir = GetDataDir() / "backups"; + + return backupsDir; +} + void ClearDatadirCache() { pathCached = boost::filesystem::path(); diff --git a/src/util.h b/src/util.h index 3139ef947dd7a..1dfe7435db3ea 100644 --- a/src/util.h +++ b/src/util.h @@ -39,6 +39,7 @@ extern int nInstantSendDepth; extern int nPrivateSendRounds; extern int nAnonymizeDashAmount; extern int nLiquidityProvider; +extern int nWalletBackups; extern bool fEnablePrivateSend; extern bool fPrivateSendMultiSession; extern int64_t enforceMasternodePaymentsTime; @@ -145,6 +146,7 @@ bool RenameOver(boost::filesystem::path src, boost::filesystem::path dest); bool TryCreateDirectory(const boost::filesystem::path& p); boost::filesystem::path GetDefaultDataDir(); const boost::filesystem::path &GetDataDir(bool fNetSpecific = true); +const boost::filesystem::path &GetBackupsDir(); void ClearDatadirCache(); boost::filesystem::path GetConfigFile(); boost::filesystem::path GetMasternodeConfigFile(); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 2ee614e04b47d..dfd18ff1cfefc 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -904,6 +904,110 @@ bool BackupWallet(const CWallet& wallet, const string& strDest) return false; } +// This should be called carefully: +// either supply "wallet" (if already loaded) or "strWalletFile" (if wallet wasn't loaded yet) +bool AutoBackupWallet (CWallet* wallet, std::string strWalletFile, std::string& strBackupWarning, std::string& strBackupError) +{ + namespace fs = boost::filesystem; + + strBackupWarning = strBackupError = ""; + + if(nWalletBackups > 0) + { + fs::path backupsDir = GetBackupsDir(); + + if (!fs::exists(backupsDir)) + { + // Always create backup folder to not confuse the operating system's file browser + LogPrintf("Creating backup folder %s\n", backupsDir.string()); + if(!fs::create_directories(backupsDir)) { + // smth is wrong, we shouldn't continue until it's resolved + strBackupError = strprintf(_("Wasn't able to create wallet backup folder %s!"), backupsDir.string()); + LogPrintf("%s\n", strBackupError); + nWalletBackups = -1; + return false; + } + } + + // Create backup of the ... + std::string dateTimeStr = DateTimeStrFormat(".%Y-%m-%d-%H-%M", GetTime()); + if (wallet) + { + // ... opened wallet + LOCK2(cs_main, wallet->cs_wallet); + strWalletFile = wallet->strWalletFile; + fs::path backupFile = backupsDir / (strWalletFile + dateTimeStr); + if(!BackupWallet(*wallet, backupFile.string())) { + strBackupWarning = strprintf(_("Failed to create backup %s!"), backupFile.string()); + LogPrintf("%s\n", strBackupWarning); + nWalletBackups = -1; + return false; + } + } else { + // ... strWalletFile file + fs::path sourceFile = GetDataDir() / strWalletFile; + fs::path backupFile = backupsDir / (strWalletFile + dateTimeStr); + sourceFile.make_preferred(); + backupFile.make_preferred(); + if(fs::exists(sourceFile)) { + try { + fs::copy_file(sourceFile, backupFile); + LogPrintf("Creating backup of %s -> %s\n", sourceFile.string(), backupFile.string()); + } catch(fs::filesystem_error &error) { + strBackupWarning = strprintf(_("Failed to create backup, error: %s"), error.what()); + LogPrintf("%s\n", strBackupWarning); + nWalletBackups = -1; + return false; + } + } + } + + // Keep only the last 10 backups, including the new one of course + typedef std::multimap folder_set_t; + folder_set_t folder_set; + fs::directory_iterator end_iter; + backupsDir.make_preferred(); + // Build map of backup files for current(!) wallet sorted by last write time + fs::path currentFile; + for (fs::directory_iterator dir_iter(backupsDir); dir_iter != end_iter; ++dir_iter) + { + // Only check regular files + if ( fs::is_regular_file(dir_iter->status())) + { + currentFile = dir_iter->path().filename(); + // Only add the backups for the current wallet, e.g. wallet.dat.* + if(dir_iter->path().stem().string() == strWalletFile) + { + folder_set.insert(folder_set_t::value_type(fs::last_write_time(dir_iter->path()), *dir_iter)); + } + } + } + + // Loop backward through backup files and keep the N newest ones (1 <= N <= 10) + int counter = 0; + BOOST_REVERSE_FOREACH(PAIRTYPE(const std::time_t, fs::path) file, folder_set) + { + counter++; + if (counter > nWalletBackups) + { + // More than nWalletBackups backups: delete oldest one(s) + try { + fs::remove(file.second); + LogPrintf("Old backup deleted: %s\n", file.second); + } catch(fs::filesystem_error &error) { + strBackupWarning = strprintf(_("Failed to delete backup, error: %s"), error.what()); + LogPrintf("%s\n", strBackupWarning); + return false; + } + } + } + return true; + } + + LogPrintf("Wallet backups are disabled!\n"); + return false; +} + // // Try to (very carefully!) recover wallet.dat if there is a problem. // diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 8da33dead20c2..64431ef0b6abe 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -143,4 +143,6 @@ class CWalletDB : public CDB bool BackupWallet(const CWallet& wallet, const std::string& strDest); void ThreadFlushWalletDB(const std::string& strFile); +bool AutoBackupWallet (CWallet* wallet, std::string strWalletFile, std::string& strBackupWarning, std::string& strBackupError); + #endif // BITCOIN_WALLET_WALLETDB_H From 0ba15486bb83d82ad39ce41bfd308e975d049103 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sun, 12 Jun 2016 14:03:11 +0300 Subject: [PATCH 2/4] autobackup in PS: - add nKeysLeftSinceAutoBackup to have some idea how many keys in keypool are more or less safe, show it in advanced PS UI mode and in rpc output for privatesend and getwalletinfo commands - add autobackups support in PrivateSend mixing both in daemon and QT mode, warn user if number of keys left since last autobackup is very low or even stop mixing completely if it's too low --- src/darksend.cpp | 55 ++++++++++++++++++++- src/darksend.h | 9 +++- src/qt/overviewpage.cpp | 101 ++++++++++++++++++++++++++++++++------- src/qt/overviewpage.h | 2 + src/rpcmasternode.cpp | 8 +++- src/wallet/rpcwallet.cpp | 2 + src/wallet/wallet.cpp | 4 ++ src/wallet/wallet.h | 1 + src/wallet/walletdb.cpp | 7 +++ 9 files changed, 166 insertions(+), 23 deletions(-) diff --git a/src/darksend.cpp b/src/darksend.cpp index 55888db802073..f00da3d7d11e5 100644 --- a/src/darksend.cpp +++ b/src/darksend.cpp @@ -1351,8 +1351,6 @@ void CDarksendPool::ClearLastMessage() // // Passively run Darksend in the background to anonymize funds based on the given configuration. // -// This does NOT run by default for daemons, only for QT. -// bool CDarksendPool::DoAutomaticDenominating(bool fDryRun) { if(!fEnablePrivateSend) return false; @@ -1361,6 +1359,59 @@ bool CDarksendPool::DoAutomaticDenominating(bool fDryRun) if(!pCurrentBlockIndex) return false; if(state == POOL_STATUS_ERROR || state == POOL_STATUS_SUCCESS) return false; + + if (nWalletBackups == 0) { + LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - Automatic backups disabled, no mixing available\n"); + strAutoDenomResult = _("Automatic backups disabled") + ", " + _("no mixing available."); + fEnablePrivateSend = false; // stop mixing + pwalletMain->nKeysLeftSinceAutoBackup = 0; // no backup, no "keys since last backup" + return false; + } else if (nWalletBackups == -1) { + // Automatic backup failed, nothing else we can do until user fixes the issue manually. + // There is no way to bring user attention in daemon mode so we just update status and + // keep spaming if debug is on. + LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - ERROR! Failed to create automatic backup\n"); + strAutoDenomResult = _("ERROR! Failed to create automatic backup") + ", " + _("see debug.log for details"); + return false; + } + + if (pwalletMain->nKeysLeftSinceAutoBackup < PS_KEYS_THRESHOLD_STOP) { + // We should never get here via mixing itself but probably smth else is still actively using keypool + LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - Very low number of keys left: %d, no mixing available\n", pwalletMain->nKeysLeftSinceAutoBackup); + strAutoDenomResult = strprintf(_("Very low number of keys left: %d") + ", " + _("no mixing available."), pwalletMain->nKeysLeftSinceAutoBackup); + // It's getting really dangerous, stop mixing + fEnablePrivateSend = false; + return false; + } else if (pwalletMain->nKeysLeftSinceAutoBackup < PS_KEYS_THRESHOLD_WARNING) { + // Low number of keys left but it's still more or less safe to continue + LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - Very low number of keys left: %d\n", pwalletMain->nKeysLeftSinceAutoBackup); + strAutoDenomResult = strprintf(_("Very low number of keys left: %d"), pwalletMain->nKeysLeftSinceAutoBackup); + + if (fCreateAutoBackups) { + LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - Trying to create new backup\n"); + std::string warningString; + std::string errorString; + + if(!AutoBackupWallet(pwalletMain, "", warningString, errorString)) { + if (!warningString.empty()) { + // There were some issues saving backup but yet more or less safe to continue + LogPrintf("CDarksendPool::DoAutomaticDenominating - WARNING! Something went wrong on automatic backup: %s\n", warningString); + } + if (!errorString.empty()) { + // Things are really broken + LogPrintf("CDarksendPool::DoAutomaticDenominating - ERROR! Failed to create automatic backup: %s\n", errorString); + strAutoDenomResult = strprintf(_("ERROR! Failed to create automatic backup") + ": %s", errorString); + return false; + } + } + } else { + // Wait for someone else (e.g. GUI action) to create automatic backup for us + return false; + } + } + + LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - Keys left since latest backup: %d\n", pwalletMain->nKeysLeftSinceAutoBackup); + if(GetEntriesCount() > 0) { strAutoDenomResult = _("Mixing in progress..."); return false; diff --git a/src/darksend.h b/src/darksend.h index 250a9115a0da8..7113d0d1f4e1f 100644 --- a/src/darksend.h +++ b/src/darksend.h @@ -47,6 +47,11 @@ class CActiveMasternode; static const CAmount DARKSEND_COLLATERAL = (0.01*COIN); static const CAmount DARKSEND_POOL_MAX = (999.99*COIN); static const CAmount DENOMS_COUNT_MAX = 100; +// Warn user if mixing in gui or try to create backup if mixing in daemon mode +// when we have only this many keys left +static const int PS_KEYS_THRESHOLD_WARNING = 100; +// Stop mixing completely, it's too dangerous to continue when we have only this many keys left +static const int PS_KEYS_THRESHOLD_STOP = 50; extern CDarksendPool darkSendPool; extern CDarkSendSigner darkSendSigner; @@ -326,6 +331,7 @@ class CDarksendPool CMasternode* pSubmittedToMasternode; int sessionDenom; //Users must submit an denom matching this int cachedNumBlocks; //used for the overview screen + bool fCreateAutoBackups; //builtin support for automatic backups CDarksendPool() { @@ -338,6 +344,7 @@ class CDarksendPool txCollateral = CMutableTransaction(); minBlockSpacing = 0; lastNewBlock = 0; + fCreateAutoBackups = true; SetNull(); } @@ -466,7 +473,7 @@ class CDarksendPool /// Is this amount compatible with other client in the pool? bool IsCompatibleWithSession(CAmount nAmount, CTransaction txCollateral, int &errorID); - /// Passively run Darksend in the background according to the configuration in settings (only for QT) + /// Passively run Darksend in the background according to the configuration in settings bool DoAutomaticDenominating(bool fDryRun=false); bool PrepareDarksendDenominate(); diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index db28d309ce1b4..4c3f5d6c93ac0 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -158,18 +158,22 @@ OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent) // that's it for litemode if(fLiteMode) return; - // disable any PS UI for masternode - if(fMasterNode){ - ui->togglePrivateSend->setText("(" + tr("Disabled") + ")"); - ui->privateSendAuto->setText("(" + tr("Disabled") + ")"); - ui->privateSendReset->setText("(" + tr("Disabled") + ")"); - ui->framePrivateSend->setEnabled(false); + // Disable any PS UI for masternode or when autobackup is disabled or failed for whatever reason + if(fMasterNode || nWalletBackups <= 0){ + DisablePrivateSendCompletely(); + if (nWalletBackups <= 0) { + ui->labelPrivateSendEnabled->setToolTip(tr("Automatic backups are disabled, no mixing available!")); + } } else { if(!fEnablePrivateSend){ ui->togglePrivateSend->setText(tr("Start Mixing")); } else { ui->togglePrivateSend->setText(tr("Stop Mixing")); } + // Disable darkSendPool builtin support for automatic backups while we are in GUI, + // we'll handle automatic backups and user warnings in privateSendStatus() + darkSendPool.fCreateAutoBackups = false; + timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(privateSendStatus())); timer->start(1000); @@ -424,6 +428,7 @@ void OverviewPage::updatePrivateSendProgress() } void OverviewPage::updateAdvancedPSUI(bool fShowAdvancedPSUI) { + this->fShowAdvancedPSUI = fShowAdvancedPSUI; int nNumItems = (fLiteMode || !fShowAdvancedPSUI) ? NUM_ITEMS : NUM_ITEMS_ADV; SetupTransactionList(nNumItems); @@ -450,28 +455,77 @@ void OverviewPage::privateSendStatus() if(((nBestHeight - darkSendPool.cachedNumBlocks) / (GetTimeMillis() - nLastDSProgressBlockTime + 1) > 1)) return; nLastDSProgressBlockTime = GetTimeMillis(); + QString strEnabled = fEnablePrivateSend ? tr("Enabled") : tr("Disabled"); + QString strKeysLeftText(tr("keys left: %1").arg(pwalletMain->nKeysLeftSinceAutoBackup)); + + if(nWalletBackups == -1) { + // Automatic backup failed, nothing else we can do until user fixes the issue manually + DisablePrivateSendCompletely(); + + QString strError = tr("ERROR! Failed to create automatic backup") + ", " + + tr("see debug.log for details") + "
" + + tr("Mixing is disabled! Please close your wallet and fix the issue!"); + ui->labelPrivateSendEnabled->setToolTip(strError); + + return; + } + + + // Warn user that wallet is running out of keys + if (nWalletBackups > 0 && pwalletMain->nKeysLeftSinceAutoBackup < PS_KEYS_THRESHOLD_WARNING) { + strKeysLeftText = "" + strKeysLeftText + ""; + QString strWarn = tr("Very low number of keys left since last automatic backup! " + "We are about to create a new automatic backup for you, however " + " you should always make sure you have backups saved in some safe place!"); + ui->labelPrivateSendEnabled->setToolTip(strWarn); + LogPrintf("OverviewPage::privateSendStatus - Very low number of keys left since last automatic backup, warning user and trying to create new backup...\n"); + QMessageBox::warning(this, tr("PrivateSend"), strWarn, QMessageBox::Ok, QMessageBox::Ok); + + std::string warningString; + std::string errorString; + if(!AutoBackupWallet(pwalletMain, "", warningString, errorString)) { + if (!warningString.empty()) { + // It's still more or less safe to continue but warn user anyway + LogPrintf("OverviewPage::privateSendStatus - WARNING! Something went wrong on automatic backup: %s\n", warningString); + + QMessageBox::warning(this, tr("PrivateSend"), + tr("WARNING! Something went wrong on automatic backup: %1").arg(warningString.c_str()), + QMessageBox::Ok, QMessageBox::Ok); + } + if (!errorString.empty()) { + // Things are really broken, warn user and stop mixing immediately + LogPrintf("OverviewPage::privateSendStatus - ERROR! Failed to create automatic backup: %s\n", errorString); + DisablePrivateSendCompletely(); + + QMessageBox::warning(this, tr("PrivateSend"), + tr("ERROR! Failed to create automatic backup") + ": " + errorString.c_str() + "
" + + tr("Mixing is disabled! Please close your wallet and fix the issue!"), + QMessageBox::Ok, QMessageBox::Ok); + } + } + } + + // Show how many keys left in advanced PS UI mode only + if(fShowAdvancedPSUI) strEnabled += ", " + strKeysLeftText; + ui->labelPrivateSendEnabled->setText(strEnabled); + if(!fEnablePrivateSend) { - if(nBestHeight != darkSendPool.cachedNumBlocks) - { + if(nBestHeight != darkSendPool.cachedNumBlocks) { darkSendPool.cachedNumBlocks = nBestHeight; updatePrivateSendProgress(); - - ui->labelPrivateSendEnabled->setText(tr("Disabled")); - ui->labelPrivateSendLastMessage->setText(""); - ui->togglePrivateSend->setText(tr("Start Mixing")); } + ui->labelPrivateSendLastMessage->setText(""); + ui->togglePrivateSend->setText(tr("Start Mixing")); + return; } // check darksend status and unlock if needed - if(nBestHeight != darkSendPool.cachedNumBlocks) - { + if(nBestHeight != darkSendPool.cachedNumBlocks) { // Balance and number of transactions might have changed darkSendPool.cachedNumBlocks = nBestHeight; updatePrivateSendProgress(); - - ui->labelPrivateSendEnabled->setText(tr("Enabled")); } QString strStatus = QString(darkSendPool.GetStatus().c_str()); @@ -479,7 +533,7 @@ void OverviewPage::privateSendStatus() QString s = tr("Last PrivateSend message:\n") + strStatus; if(s != ui->labelPrivateSendLastMessage->text()) - LogPrintf("Last PrivateSend message: %s\n", strStatus.toStdString()); + LogPrintf("OverviewPage::privateSendStatus - Last PrivateSend message: %s\n", strStatus.toStdString()); ui->labelPrivateSendLastMessage->setText(s); @@ -538,7 +592,7 @@ void OverviewPage::togglePrivateSend(){ QMessageBox::warning(this, tr("PrivateSend"), tr("Wallet is locked and user declined to unlock. Disabling PrivateSend."), QMessageBox::Ok, QMessageBox::Ok); - if (fDebug) LogPrintf("Wallet is locked and user declined to unlock. Disabling PrivateSend.\n"); + LogPrint("privatesend", "OverviewPage::togglePrivateSend - Wallet is locked and user declined to unlock. Disabling PrivateSend.\n"); return; } } @@ -582,3 +636,14 @@ void OverviewPage::SetupTransactionList(int nNumItems) { ui->listTransactions->setModelColumn(TransactionTableModel::ToAddress); } } + +void OverviewPage::DisablePrivateSendCompletely() { + ui->togglePrivateSend->setText("(" + tr("Disabled") + ")"); + ui->privateSendAuto->setText("(" + tr("Disabled") + ")"); + ui->privateSendReset->setText("(" + tr("Disabled") + ")"); + ui->framePrivateSend->setEnabled(false); + if (nWalletBackups <= 0) { + ui->labelPrivateSendEnabled->setText("(" + tr("Disabled") + ")"); + } + fEnablePrivateSend = false; +} \ No newline at end of file diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index a5e5e14f02424..1a43da34e5432 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -57,11 +57,13 @@ public Q_SLOTS: CAmount currentWatchUnconfBalance; CAmount currentWatchImmatureBalance; int nDisplayUnit; + bool fShowAdvancedPSUI; TxViewDelegate *txdelegate; TransactionFilterProxy *filter; void SetupTransactionList(int nNumItems); + void DisablePrivateSendCompletely(); private Q_SLOTS: void togglePrivateSend(); diff --git a/src/rpcmasternode.cpp b/src/rpcmasternode.cpp index eef5f0802fd9c..8c2192b657c38 100644 --- a/src/rpcmasternode.cpp +++ b/src/rpcmasternode.cpp @@ -42,7 +42,6 @@ UniValue privatesend(const UniValue& params, bool fHelp) fEnablePrivateSend = true; bool result = darkSendPool.DoAutomaticDenominating(); -// fEnablePrivateSend = result; return "Mixing " + (result ? "started successfully" : ("start failed: " + darkSendPool.GetStatus() + ", will retry")); } @@ -57,7 +56,12 @@ UniValue privatesend(const UniValue& params, bool fHelp) } if(params[0].get_str() == "status"){ - return "Mixing status: " + darkSendPool.GetStatus(); + UniValue obj(UniValue::VOBJ); + obj.push_back(Pair("status", darkSendPool.GetStatus())); + obj.push_back(Pair("keys_left", pwalletMain->nKeysLeftSinceAutoBackup)); + obj.push_back(Pair("warnings", (pwalletMain->nKeysLeftSinceAutoBackup < PS_KEYS_THRESHOLD_WARNING + ? "WARNING: keypool is almost depleted!" : ""))); + return obj; } return "Unknown command, please see \"help privatesend\""; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index d9a6d18aecf47..31cdf3bb4eaeb 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2371,6 +2371,7 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n" " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since GMT epoch) of the oldest pre-generated key in the key pool\n" " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n" + " \"keys_left\": xxxx, (numeric) how many new keys are left since last automatic backup\n" " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" "}\n" @@ -2389,6 +2390,7 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) obj.push_back(Pair("txcount", (int)pwalletMain->mapWallet.size())); obj.push_back(Pair("keypoololdest", pwalletMain->GetOldestKeyPoolTime())); obj.push_back(Pair("keypoolsize", (int)pwalletMain->GetKeyPoolSize())); + obj.push_back(Pair("keys_left", pwalletMain->nKeysLeftSinceAutoBackup)); if (pwalletMain->IsCrypted()) obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 045a62048b645..0dc75a1df0701 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3381,6 +3381,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { LOCK(cs_wallet); setKeyPool.clear(); + pwalletMain->nKeysLeftSinceAutoBackup = 0; // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. @@ -3408,6 +3409,7 @@ DBErrors CWallet::ZapWalletTx(std::vector& vWtx) { LOCK(cs_wallet); setKeyPool.clear(); + pwalletMain->nKeysLeftSinceAutoBackup = 0; // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. @@ -3489,6 +3491,7 @@ bool CWallet::NewKeyPool() BOOST_FOREACH(int64_t nIndex, setKeyPool) walletdb.ErasePool(nIndex); setKeyPool.clear(); + pwalletMain->nKeysLeftSinceAutoBackup = 0; if (IsLocked()) return false; @@ -3573,6 +3576,7 @@ void CWallet::KeepKey(int64_t nIndex) { CWalletDB walletdb(strWalletFile); walletdb.ErasePool(nIndex); + nKeysLeftSinceAutoBackup = nWalletBackups ? nKeysLeftSinceAutoBackup - 1 : 0; } LogPrintf("keypool keep %d\n", nIndex); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a27a32c3d4fc6..5d7971e8f1f86 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -612,6 +612,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::set setLockedCoins; int64_t nTimeFirstKey; + int64_t nKeysLeftSinceAutoBackup; const CWalletTx* GetWalletTx(const uint256& hash) const; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index dfd18ff1cfefc..484243a1cc832 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -672,6 +672,10 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) LogPrintf("%s\n", strErr); } pcursor->close(); + + // Store initial pool size + pwallet->nKeysLeftSinceAutoBackup = pwallet->GetKeyPoolSize(); + LogPrintf("nKeysLeftSinceAutoBackup: %d\n", pwallet->nKeysLeftSinceAutoBackup); } catch (const boost::thread_interrupted&) { throw; @@ -943,6 +947,9 @@ bool AutoBackupWallet (CWallet* wallet, std::string strWalletFile, std::string& nWalletBackups = -1; return false; } + // Update nKeysLeftSinceAutoBackup using current pool size + wallet->nKeysLeftSinceAutoBackup = wallet->GetKeyPoolSize(); + LogPrintf("nKeysLeftSinceAutoBackup: %d\n", wallet->nKeysLeftSinceAutoBackup); } else { // ... strWalletFile file fs::path sourceFile = GetDataDir() / strWalletFile; From f3a24944f0c4043b4db8b01f6e158362b934c963 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Tue, 14 Jun 2016 18:45:24 +0300 Subject: [PATCH 3/4] Warn about a special case - less than 60 seconds between restarts i.e. backup file name is the same as previos one. Continue and do not disable automatic backups in this case . --- src/wallet/walletdb.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 484243a1cc832..a6418694bc59c 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -956,6 +956,12 @@ bool AutoBackupWallet (CWallet* wallet, std::string strWalletFile, std::string& fs::path backupFile = backupsDir / (strWalletFile + dateTimeStr); sourceFile.make_preferred(); backupFile.make_preferred(); + if (fs::exists(backupFile)) + { + strBackupWarning = _("Failed to create backup, file already exists! This could happen if you restarted wallet in less than 60 seconds. You can continue if you are ok with this."); + LogPrintf("%s\n", strBackupWarning); + return false; + } if(fs::exists(sourceFile)) { try { fs::copy_file(sourceFile, backupFile); From e7b56bd264acf7a2ad82549636e5f8ad6d5f7890 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Wed, 15 Jun 2016 03:46:10 +0300 Subject: [PATCH 4/4] Refactor to address locked wallets issue, replenish keypool and re-initialize autobackup on unlock (only if was disabled due to keypool issue) Adjust few message strings. --- src/darksend.cpp | 18 +++++++++++----- src/qt/overviewpage.cpp | 48 +++++++++++++++++++++++------------------ src/util.cpp | 7 ++++++ src/wallet/wallet.cpp | 5 +++++ src/wallet/walletdb.cpp | 8 ++++++- 5 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/darksend.cpp b/src/darksend.cpp index f00da3d7d11e5..06ba2b3281f29 100644 --- a/src/darksend.cpp +++ b/src/darksend.cpp @@ -1357,11 +1357,12 @@ bool CDarksendPool::DoAutomaticDenominating(bool fDryRun) if(fMasterNode) return false; if(!pCurrentBlockIndex) return false; + if(!pwalletMain || pwalletMain->IsLocked()) return false; if(state == POOL_STATUS_ERROR || state == POOL_STATUS_SUCCESS) return false; if (nWalletBackups == 0) { - LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - Automatic backups disabled, no mixing available\n"); + LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - Automatic backups disabled, no mixing available.\n"); strAutoDenomResult = _("Automatic backups disabled") + ", " + _("no mixing available."); fEnablePrivateSend = false; // stop mixing pwalletMain->nKeysLeftSinceAutoBackup = 0; // no backup, no "keys since last backup" @@ -1370,14 +1371,21 @@ bool CDarksendPool::DoAutomaticDenominating(bool fDryRun) // Automatic backup failed, nothing else we can do until user fixes the issue manually. // There is no way to bring user attention in daemon mode so we just update status and // keep spaming if debug is on. - LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - ERROR! Failed to create automatic backup\n"); - strAutoDenomResult = _("ERROR! Failed to create automatic backup") + ", " + _("see debug.log for details"); + LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - ERROR! Failed to create automatic backup.\n"); + strAutoDenomResult = _("ERROR! Failed to create automatic backup") + ", " + _("see debug.log for details."); + return false; + } else if (nWalletBackups == -2) { + // We were able to create automatic backup but keypool was not replenished because wallet is locked. + // There is no way to bring user attention in daemon mode so we just update status and + // keep spaming if debug is on. + LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - WARNING! Failed to create replenish keypool, please unlock your wallet to do so.\n"); + strAutoDenomResult = _("WARNING! Failed to replenish keypool, please unlock your wallet to do so.") + ", " + _("see debug.log for details."); return false; } if (pwalletMain->nKeysLeftSinceAutoBackup < PS_KEYS_THRESHOLD_STOP) { // We should never get here via mixing itself but probably smth else is still actively using keypool - LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - Very low number of keys left: %d, no mixing available\n", pwalletMain->nKeysLeftSinceAutoBackup); + LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - Very low number of keys left: %d, no mixing available.\n", pwalletMain->nKeysLeftSinceAutoBackup); strAutoDenomResult = strprintf(_("Very low number of keys left: %d") + ", " + _("no mixing available."), pwalletMain->nKeysLeftSinceAutoBackup); // It's getting really dangerous, stop mixing fEnablePrivateSend = false; @@ -1388,7 +1396,7 @@ bool CDarksendPool::DoAutomaticDenominating(bool fDryRun) strAutoDenomResult = strprintf(_("Very low number of keys left: %d"), pwalletMain->nKeysLeftSinceAutoBackup); if (fCreateAutoBackups) { - LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - Trying to create new backup\n"); + LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating - Trying to create new backup.\n"); std::string warningString; std::string errorString; diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 4c3f5d6c93ac0..c3a76ccbbfcaf 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -455,28 +455,18 @@ void OverviewPage::privateSendStatus() if(((nBestHeight - darkSendPool.cachedNumBlocks) / (GetTimeMillis() - nLastDSProgressBlockTime + 1) > 1)) return; nLastDSProgressBlockTime = GetTimeMillis(); - QString strEnabled = fEnablePrivateSend ? tr("Enabled") : tr("Disabled"); QString strKeysLeftText(tr("keys left: %1").arg(pwalletMain->nKeysLeftSinceAutoBackup)); - - if(nWalletBackups == -1) { - // Automatic backup failed, nothing else we can do until user fixes the issue manually - DisablePrivateSendCompletely(); - - QString strError = tr("ERROR! Failed to create automatic backup") + ", " + - tr("see debug.log for details") + "
" + - tr("Mixing is disabled! Please close your wallet and fix the issue!"); - ui->labelPrivateSendEnabled->setToolTip(strError); - - return; + if(pwalletMain->nKeysLeftSinceAutoBackup < PS_KEYS_THRESHOLD_WARNING) { + strKeysLeftText = "" + strKeysLeftText + ""; } - + ui->labelPrivateSendEnabled->setToolTip(strKeysLeftText); // Warn user that wallet is running out of keys if (nWalletBackups > 0 && pwalletMain->nKeysLeftSinceAutoBackup < PS_KEYS_THRESHOLD_WARNING) { - strKeysLeftText = "" + strKeysLeftText + ""; - QString strWarn = tr("Very low number of keys left since last automatic backup! " - "We are about to create a new automatic backup for you, however " - " you should always make sure you have backups saved in some safe place!"); + QString strWarn = tr("Very low number of keys left since last automatic backup!") + "

" + + tr("We are about to create a new automatic backup for you, however " + " you should always make sure you have backups " + "saved in some safe place!"); ui->labelPrivateSendEnabled->setToolTip(strWarn); LogPrintf("OverviewPage::privateSendStatus - Very low number of keys left since last automatic backup, warning user and trying to create new backup...\n"); QMessageBox::warning(this, tr("PrivateSend"), strWarn, QMessageBox::Ok, QMessageBox::Ok); @@ -489,26 +479,42 @@ void OverviewPage::privateSendStatus() LogPrintf("OverviewPage::privateSendStatus - WARNING! Something went wrong on automatic backup: %s\n", warningString); QMessageBox::warning(this, tr("PrivateSend"), - tr("WARNING! Something went wrong on automatic backup: %1").arg(warningString.c_str()), + tr("WARNING! Something went wrong on automatic backup") + ":

" + warningString.c_str(), QMessageBox::Ok, QMessageBox::Ok); } if (!errorString.empty()) { // Things are really broken, warn user and stop mixing immediately LogPrintf("OverviewPage::privateSendStatus - ERROR! Failed to create automatic backup: %s\n", errorString); - DisablePrivateSendCompletely(); QMessageBox::warning(this, tr("PrivateSend"), - tr("ERROR! Failed to create automatic backup") + ": " + errorString.c_str() + "
" + - tr("Mixing is disabled! Please close your wallet and fix the issue!"), + tr("ERROR! Failed to create automatic backup") + ":

" + errorString.c_str() + "
" + + tr("Mixing is disabled, please close your wallet and fix the issue!"), QMessageBox::Ok, QMessageBox::Ok); } } } + QString strEnabled = fEnablePrivateSend ? tr("Enabled") : tr("Disabled"); // Show how many keys left in advanced PS UI mode only if(fShowAdvancedPSUI) strEnabled += ", " + strKeysLeftText; ui->labelPrivateSendEnabled->setText(strEnabled); + if(nWalletBackups == -1) { + // Automatic backup failed, nothing else we can do until user fixes the issue manually + DisablePrivateSendCompletely(); + + QString strError = tr("ERROR! Failed to create automatic backup") + ", " + + tr("see debug.log for details.") + "

" + + tr("Mixing is disabled, please close your wallet and fix the issue!"); + ui->labelPrivateSendEnabled->setToolTip(strError); + + return; + } else if(nWalletBackups == -2) { + // We were able to create automatic backup but keypool was not replenished because wallet is locked. + QString strWarning = tr("WARNING! Failed to replenish keypool, please unlock your wallet to do so."); + ui->labelPrivateSendEnabled->setToolTip(strWarning); + } + if(!fEnablePrivateSend) { if(nBestHeight != darkSendPool.cachedNumBlocks) { darkSendPool.cachedNumBlocks = nBestHeight; diff --git a/src/util.cpp b/src/util.cpp index 7bbbe89ac6192..155045529545e 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -112,6 +112,13 @@ int nInstantSendDepth = 5; int nPrivateSendRounds = 2; int nAnonymizeDashAmount = 1000; int nLiquidityProvider = 0; +/** + nWalletBackups: + 1..10 - number of automatic backups to keep + 0 - disabled by command-line + -1 - disabled because of some error during run-time + -2 - disabled because wallet was locked and we were not able to replenish keypool +*/ int nWalletBackups = 10; /** Spork enforcement enabled time */ int64_t enforceMasternodePaymentsTime = 4085657524; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0dc75a1df0701..53264746cdd4a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -279,6 +279,11 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool anonymizeOnly if (CCryptoKeyStore::Unlock(vMasterKey)) { fWalletUnlockAnonymizeOnly = anonymizeOnly; + if(nWalletBackups == -2) { + TopUpKeyPool(); + LogPrintf("Keypool replenished, re-initializing automatic backups.\n"); + nWalletBackups = GetArg("-createwalletbackups", 10); + } return true; } } diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index a6418694bc59c..4e7392b54ac52 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -950,6 +950,12 @@ bool AutoBackupWallet (CWallet* wallet, std::string strWalletFile, std::string& // Update nKeysLeftSinceAutoBackup using current pool size wallet->nKeysLeftSinceAutoBackup = wallet->GetKeyPoolSize(); LogPrintf("nKeysLeftSinceAutoBackup: %d\n", wallet->nKeysLeftSinceAutoBackup); + if(wallet->IsLocked()) { + strBackupWarning = _("Wallet is locked, can't replenish keypool! Automatic backups and mixing are disabled, please unlock your wallet to replenish keypool."); + LogPrintf("%s\n", strBackupWarning); + nWalletBackups = -2; + return false; + } } else { // ... strWalletFile file fs::path sourceFile = GetDataDir() / strWalletFile; @@ -1017,7 +1023,7 @@ bool AutoBackupWallet (CWallet* wallet, std::string strWalletFile, std::string& return true; } - LogPrintf("Wallet backups are disabled!\n"); + LogPrintf("Automatic wallet backups are disabled!\n"); return false; }