diff --git a/src/darksend.cpp b/src/darksend.cpp index 55888db802073..06ba2b3281f29 100644 --- a/src/darksend.cpp +++ b/src/darksend.cpp @@ -1351,16 +1351,75 @@ 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; 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"); + 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; + } 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); + 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/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/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index db28d309ce1b4..c3a76ccbbfcaf 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,83 @@ void OverviewPage::privateSendStatus() if(((nBestHeight - darkSendPool.cachedNumBlocks) / (GetTimeMillis() - nLastDSProgressBlockTime + 1) > 1)) return; nLastDSProgressBlockTime = GetTimeMillis(); + QString strKeysLeftText(tr("keys left: %1").arg(pwalletMain->nKeysLeftSinceAutoBackup)); + 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) { + 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); + + 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") + ":

" + 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); + + 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); + } + } + } + + 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) - { + 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 +539,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 +598,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 +642,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/util.cpp b/src/util.cpp index fa179ce3b3d83..155045529545e 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -112,6 +112,14 @@ 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; bool fSucessfullyLoaded = false; @@ -580,6 +588,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/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..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; } } @@ -3381,6 +3386,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 +3414,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 +3496,7 @@ bool CWallet::NewKeyPool() BOOST_FOREACH(int64_t nIndex, setKeyPool) walletdb.ErasePool(nIndex); setKeyPool.clear(); + pwalletMain->nKeysLeftSinceAutoBackup = 0; if (IsLocked()) return false; @@ -3573,6 +3581,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 2ee614e04b47d..4e7392b54ac52 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; @@ -904,6 +908,125 @@ 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; + } + // 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; + 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); + 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("Automatic 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