From bb6b7f5a36717cacdc07f28ffb496a1bbdcc5d2a Mon Sep 17 00:00:00 2001 From: j-berman Date: Thu, 16 Nov 2023 16:38:52 -0800 Subject: [PATCH] Implement background sync when locked --- components/PasswordDialog.qml | 18 ++++- main.qml | 111 ++++++++++++++++++++++++++---- pages/settings/SettingsLayout.qml | 24 +++++++ src/libwalletqt/Wallet.cpp | 58 ++++++++++++++++ src/libwalletqt/Wallet.h | 21 ++++++ 5 files changed, 217 insertions(+), 15 deletions(-) diff --git a/components/PasswordDialog.qml b/components/PasswordDialog.qml index b2c815f326..4d3b892417 100644 --- a/components/PasswordDialog.qml +++ b/components/PasswordDialog.qml @@ -50,6 +50,7 @@ Item { property bool passwordDialogMode property bool passphraseDialogMode property bool newPasswordDialogMode + property bool backgroundSyncing // same signals as Dialog has signal accepted() @@ -77,10 +78,11 @@ Item { appWindow.updateBalance(); } - function open(walletName, errorText, okButtonText, okButtonIcon) { + function open(walletName, errorText, okButtonText, okButtonIcon, backgroundSyncOn) { passwordDialogMode = true; passphraseDialogMode = false; newPasswordDialogMode = false; + backgroundSyncing = backgroundSyncOn || false; root.okButtonText = okButtonText; root.okButtonIcon = okButtonIcon ? okButtonIcon : ""; _openInit(walletName, errorText); @@ -90,6 +92,7 @@ Item { passwordDialogMode = false; passphraseDialogMode = true; newPasswordDialogMode = false; + backgroundSyncing = false; _openInit("", ""); } @@ -97,6 +100,7 @@ Item { passwordDialogMode = false; passphraseDialogMode = false; newPasswordDialogMode = true; + backgroundSyncing = false; _openInit("", ""); } @@ -300,6 +304,18 @@ Item { onClicked: onOk() } } + + Label { + visible: backgroundSyncing + text: qsTr("Syncing in the background...") + translationManager.emptyString; + Layout.fillWidth: true + + font.pixelSize: 14 + font.family: MoneroComponents.Style.fontLight.name + font.italic: true + + color: MoneroComponents.Style.defaultFontColor + } } } } diff --git a/main.qml b/main.qml index 6b21c1f3f9..45e817925b 100644 --- a/main.qml +++ b/main.qml @@ -99,6 +99,7 @@ ApplicationWindow { property string prevSplashText; property bool splashDisplayedBeforeButtonRequest; property bool themeTransition: false + property int backgroundSyncType: Wallet.BackgroundSync_Off; // fiat price conversion property real fiatPrice: 0 @@ -133,6 +134,12 @@ ApplicationWindow { } function lock() { + if (currentWallet && currentWallet.getBackgroundSyncType() != Wallet.BackgroundSync_Off) { + appWindow.showProcessingSplash(qsTr("Locking...")); + currentWallet.startBackgroundSync() + return; + } + passwordDialog.onRejectedCallback = function() { appWindow.showWizard(); } passwordDialog.onAcceptedCallback = function() { if(walletPassword === passwordDialog.password) @@ -288,6 +295,9 @@ ApplicationWindow { currentWallet.heightRefreshed.disconnect(onHeightRefreshed); currentWallet.refreshed.disconnect(onWalletRefresh) currentWallet.updated.disconnect(onWalletUpdate) + currentWallet.backgroundSyncSetup.disconnect(onBackgroundSyncSetup) + currentWallet.backgroundSyncStarted.disconnect(onBackgroundSyncStarted) + currentWallet.backgroundSyncStopped.disconnect(onBackgroundSyncStopped) currentWallet.newBlock.disconnect(onWalletNewBlock) currentWallet.moneySpent.disconnect(onWalletMoneySent) currentWallet.moneyReceived.disconnect(onWalletMoneyReceived) @@ -324,6 +334,7 @@ ApplicationWindow { walletName = usefulName(wallet.path) viewOnly = currentWallet.viewOnly; + backgroundSyncType = currentWallet.getBackgroundSyncType(); // New wallets saves the testnet flag in keys file. if(persistentSettings.nettype != currentWallet.nettype) { @@ -335,6 +346,9 @@ ApplicationWindow { currentWallet.heightRefreshed.connect(onHeightRefreshed); currentWallet.refreshed.connect(onWalletRefresh) currentWallet.updated.connect(onWalletUpdate) + currentWallet.backgroundSyncSetup.connect(onBackgroundSyncSetup) + currentWallet.backgroundSyncStarted.connect(onBackgroundSyncStarted) + currentWallet.backgroundSyncStopped.connect(onBackgroundSyncStopped) currentWallet.newBlock.connect(onWalletNewBlock) currentWallet.moneySpent.connect(onWalletMoneySent) currentWallet.moneyReceived.connect(onWalletMoneyReceived) @@ -544,6 +558,15 @@ ApplicationWindow { } } + // Don't allow opening background wallets in the GUI + if (wallet.isBackgroundWallet()) { + passwordDialog.onCancel(); + appWindow.showStatusMessage(qsTr("Can't open background wallets in the GUI"),6); + console.log("closing background wallet"); + closeWallet(); + return; + } + // wallet opened successfully, subscribing for wallet updates connectWallet(wallet) @@ -585,16 +608,14 @@ ApplicationWindow { devicePassphraseDialog.open(on_device) } - function onWalletUpdate() { - if (!currentWallet) - return; - + function onWalletUpdate(stoppedBackgroundSync) { console.log(">>> wallet updated") updateBalance(); - // Update history if new block found since last update - if(foundNewBlock) { + // Update history if new block found since last update or background sync was just stopped + if(foundNewBlock || stoppedBackgroundSync) { + if (foundNewBlock) + console.log("New block found - updating history") foundNewBlock = false; - console.log("New block found - updating history") currentWallet.history.refresh(currentWallet.currentSubaddressAccount) if(middlePanel.state == "History") @@ -602,6 +623,61 @@ ApplicationWindow { } } + function onBackgroundSyncSetup() { + console.log(">>> background sync setup"); + hideProcessingSplash(); + if (currentWallet.status !== Wallet.Status_Ok) { + console.error("Error setting up background sync: ", currentWallet.errorString); + appWindow.showStatusMessage(currentWallet.errorString, 5); + return; + } + appWindow.backgroundSyncType = currentWallet.getBackgroundSyncType(); + } + + function onBackgroundSyncStarted() { + console.log(">>> background sync started"); + hideProcessingSplash(); + var started = currentWallet.status === Wallet.Status_Ok; + if (!started) { + console.error("Error starting background sync: ", currentWallet.errorString); + appWindow.showStatusMessage(currentWallet.errorString, 5); + } + + passwordDialog.onRejectedCallback = function() { appWindow.showWizard(); } + passwordDialog.onAcceptedCallback = function() { + if(walletPassword === passwordDialog.password) { + if (currentWallet && started) { + appWindow.showProcessingSplash(qsTr("Unlocking...")); + currentWallet.stopBackgroundSync(walletPassword); + } else { + passwordDialog.close(); + } + } else { + passwordDialog.showError(qsTr("Wrong password") + translationManager.emptyString); + } + } + passwordDialog.open(usefulName(persistentSettings.wallet_path), "", "", "", started); + } + + function onBackgroundSyncStopped() { + console.log(">>> background sync stopped"); + var stopped = currentWallet.status === Wallet.Status_Ok; + if (!stopped) { + hideProcessingSplash(); + console.error("Error stopping background sync: ", currentWallet.errorString); + + // If there is an error stopping background sync, the spend key + // won't be loaded and the wallet will be in a broken state. Don't + // let the user continue normal wallet operations in this state. + passwordDialog.showError(qsTr("Error stopping background sync: ") + currentWallet.errorString); + return; + } + + onWalletUpdate(stopped); + hideProcessingSplash(); + passwordDialog.close(); + } + function connectRemoteNode() { console.log("connecting remote node"); @@ -2258,6 +2334,20 @@ ApplicationWindow { var inactivity = Utils.epoch() - appWindow.userLastActive; if(inactivity < (persistentSettings.lockOnUserInActivityInterval * 60)) return; + if (inputDialogVisible) inputDialog.close() + remoteNodeDialog.close(); + informationPopup.close() + txConfirmationPopup.close() + txConfirmationPopup.clearFields() + txConfirmationPopup.rejected() + successfulTxPopup.close(); + + if (currentWallet && currentWallet.getBackgroundSyncType() != Wallet.BackgroundSync_Off) { + appWindow.showProcessingSplash(qsTr("Locking...")); + currentWallet.startBackgroundSync() + return; + } + passwordDialog.onAcceptedCallback = function() { if(walletPassword === passwordDialog.password){ passwordDialog.close(); @@ -2270,13 +2360,6 @@ ApplicationWindow { } passwordDialog.onRejectedCallback = function() { appWindow.showWizard(); } - if (inputDialogVisible) inputDialog.close() - remoteNodeDialog.close(); - informationPopup.close() - txConfirmationPopup.close() - txConfirmationPopup.clearFields() - txConfirmationPopup.rejected() - successfulTxPopup.close(); passwordDialog.open(); } diff --git a/pages/settings/SettingsLayout.qml b/pages/settings/SettingsLayout.qml index 38ce823ea8..afd44a984e 100644 --- a/pages/settings/SettingsLayout.qml +++ b/pages/settings/SettingsLayout.qml @@ -31,6 +31,8 @@ import QtQuick.Layouts 1.1 import QtQuick.Controls 2.0 import QtQuick.Dialogs 1.2 +import moneroComponents.Wallet 1.0 + import "../../js/Utils.js" as Utils import "../../js/Windows.js" as Windows import "../../components" as MoneroComponents @@ -155,6 +157,28 @@ Rectangle { onMoved: persistentSettings.lockOnUserInActivityInterval = value } + MoneroComponents.CheckBox { + id: backgroundSyncCheckbox + visible: !!currentWallet && !currentWallet.isHwBacked() && !appWindow.viewOnly + checked: appWindow.backgroundSyncType != Wallet.BackgroundSync_Off + text: qsTr("Sync in the background when locked") + translationManager.emptyString + toggleOnClick: false + onClicked: { + if (currentWallet && appWindow) { + appWindow.showProcessingSplash(qsTr("Updating settings...")) + + // TODO: add support for custom background password option + var newBackgroundSyncType = Wallet.BackgroundSync_Off + if (currentWallet.getBackgroundSyncType() === Wallet.BackgroundSync_Off) + newBackgroundSyncType = Wallet.BackgroundSync_ReusePassword + + // TODO: don't keep the wallet password in memory on the appWindow + // https://github.com/monero-project/monero-gui/issues/1537#issuecomment-410055329 + currentWallet.setupBackgroundSync(newBackgroundSyncType, appWindow.walletPassword) + } + } + } + MoneroComponents.CheckBox { checked: persistentSettings.askStopLocalNode onClicked: persistentSettings.askStopLocalNode = !persistentSettings.askStopLocalNode diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index df21bb9b75..930d783681 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -529,6 +529,64 @@ bool Wallet::scanTransactions(const QVector &txids) return m_walletImpl->scanTransactions(c); } +void Wallet::setupBackgroundSync(const Wallet::BackgroundSyncType background_sync_type, const QString &wallet_password) +{ + qDebug() << "Setting up background sync"; + bool refreshEnabled = m_refreshEnabled; + pauseRefresh(); + + // run inside scheduler because of lag when stopping/starting refresh + m_scheduler.run([this, refreshEnabled, background_sync_type, &wallet_password] { + m_walletImpl->setupBackgroundSync( + static_cast(background_sync_type), + wallet_password.toStdString(), + Monero::optional()); + if (refreshEnabled) + startRefresh(); + emit backgroundSyncSetup(); + }); +} + +Wallet::BackgroundSyncType Wallet::getBackgroundSyncType() const +{ + return static_cast(m_walletImpl->getBackgroundSyncType()); +} + +bool Wallet::isBackgroundWallet() const +{ + return m_walletImpl->isBackgroundWallet(); +} + +void Wallet::startBackgroundSync() +{ + qDebug() << "Starting background sync"; + bool refreshEnabled = m_refreshEnabled; + pauseRefresh(); + + // run inside scheduler because of lag when stopping/starting refresh + m_scheduler.run([this, refreshEnabled] { + m_walletImpl->startBackgroundSync(); + if (refreshEnabled) + startRefresh(); + emit backgroundSyncStarted(); + }); +} + +void Wallet::stopBackgroundSync(const QString &password) +{ + qDebug() << "Stopping background sync"; + bool refreshEnabled = m_refreshEnabled; + pauseRefresh(); + + // run inside scheduler because of lag when stopping/starting refresh + m_scheduler.run([this, password, refreshEnabled] { + m_walletImpl->stopBackgroundSync(password.toStdString()); + if (refreshEnabled) + startRefresh(); + emit backgroundSyncStopped(); + }); +} + bool Wallet::refresh(bool historyAndSubaddresses /* = true */) { refreshingSet(true); diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 11293b85dc..a2e6f83ccd 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -112,6 +112,14 @@ class Wallet : public QObject, public PassprasePrompter Q_ENUM(ConnectionStatus) + enum BackgroundSyncType { + BackgroundSync_Off = Monero::Wallet::BackgroundSync_Off, + BackgroundSync_ReusePassword = Monero::Wallet::BackgroundSync_ReusePassword, + BackgroundSync_CustomPassword = Monero::Wallet::BackgroundSync_CustomPassword + }; + + Q_ENUM(BackgroundSyncType) + //! returns mnemonic seed QString getSeed() const; @@ -215,6 +223,16 @@ class Wallet : public QObject, public PassprasePrompter //! scan transactions Q_INVOKABLE bool scanTransactions(const QVector &txids); + Q_INVOKABLE void setupBackgroundSync(const BackgroundSyncType background_sync_type, const QString &wallet_password); + Q_INVOKABLE BackgroundSyncType getBackgroundSyncType() const; + Q_INVOKABLE bool isBackgroundWallet() const; + + //! scan in the background with just the view key (wipe the spend key) + Q_INVOKABLE void startBackgroundSync(); + + //! bring the spend key back and process background synced txs + Q_INVOKABLE void stopBackgroundSync(const QString &password); + //! refreshes the wallet Q_INVOKABLE bool refresh(bool historyAndSubaddresses = true); @@ -369,6 +387,9 @@ class Wallet : public QObject, public PassprasePrompter void moneyReceived(const QString &txId, quint64 amount); void unconfirmedMoneyReceived(const QString &txId, quint64 amount); void newBlock(quint64 height, quint64 targetHeight); + void backgroundSyncSetup() const; + void backgroundSyncStarted() const; + void backgroundSyncStopped() const; void addressBookChanged() const; void historyModelChanged() const; void walletCreationHeightChanged();