diff --git a/doc/release-notes-6833.md b/doc/release-notes-6833.md new file mode 100644 index 000000000000..23e50378905b --- /dev/null +++ b/doc/release-notes-6833.md @@ -0,0 +1,15 @@ +GUI changes +----------- + +Configuration changes made in the Dash GUI (such as the pruning setting, +proxy settings, UPNP preferences) are now saved to `/settings.json` +file rather than to the Qt settings backend (windows registry or unix desktop +config files), so these settings will now apply to dashd, instead of being +ignored. + +Also, the interaction between GUI settings and `dash.conf` settings is +simplified. Settings from `dash.conf` are now displayed normally in the GUI +settings dialog, instead of in a separate warning message ("Options set in this +dialog are overridden by the configuration file: -setting=value"). And these +settings can now be edited because `settings.json` values take precedence over +`dash.conf` values. diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 6f468642f74f..aa895e627754 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -265,9 +265,26 @@ void BitcoinApplication::createPaymentServer() } #endif -void BitcoinApplication::createOptionsModel(bool resetSettings) +bool BitcoinApplication::createOptionsModel(bool resetSettings) { - optionsModel = new OptionsModel(this, resetSettings); + optionsModel = new OptionsModel(node(), this); + if (resetSettings) { + optionsModel->Reset(); + } + bilingual_str error; + if (!optionsModel->Init(error)) { + fs::path settings_path; + if (gArgs.GetSettingsPath(&settings_path)) { + error += Untranslated("\n"); + std::string quoted_path = strprintf("%s", fs::quoted(fs::PathToString(settings_path))); + error.original += strprintf("Settings file %s might be corrupt or invalid.", quoted_path); + error.translated += tr("Settings file %1 might be corrupt or invalid.").arg(QString::fromStdString(quoted_path)).toStdString(); + } + InitError(error); + QMessageBox::critical(nullptr, PACKAGE_NAME, QString::fromStdString(error.translated)); + return false; + } + return true; } void BitcoinApplication::createWindow(const NetworkStyle *networkStyle) @@ -293,7 +310,6 @@ void BitcoinApplication::createNode(interfaces::Init& init) { assert(!m_node); m_node = init.makeNode(); - if (optionsModel) optionsModel->setNode(*m_node); if (m_splash) m_splash->setNode(*m_node); } @@ -330,7 +346,7 @@ void BitcoinApplication::parameterSetup() void BitcoinApplication::InitPruneSetting(int64_t prune_MiB) { - optionsModel->SetPruneTargetGB(PruneMiBtoGB(prune_MiB), true); + optionsModel->SetPruneTargetGB(PruneMiBtoGB(prune_MiB)); } void BitcoinApplication::requestInitialize() @@ -668,8 +684,17 @@ int GuiMain(int argc, char* argv[]) QMessageBox::critical(nullptr, PACKAGE_NAME, QObject::tr("Error: Failed to load application fonts.")); return EXIT_FAILURE; } + + if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) + app.createSplashScreen(networkStyle.data()); + + app.createNode(*init); + // Load GUI settings from QSettings - app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false)); + if (!app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false))) { + return EXIT_FAILURE; + } + // Validate/set font family if (gArgs.IsArgSet("-font-family")) { QString family = gArgs.GetArg("-font-family", GUIUtil::FontRegistry::DEFAULT_FONT.toUtf8().toStdString()).c_str(); @@ -758,11 +783,6 @@ int GuiMain(int argc, char* argv[]) app.InitPruneSetting(prune_MiB); } - if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) - app.createSplashScreen(networkStyle.data()); - - app.createNode(*init); - int rv = EXIT_SUCCESS; try { diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h index 7aa12efbc2fb..0cf8ee03bcfc 100644 --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -46,7 +46,7 @@ class BitcoinApplication: public QApplication /// parameter interaction/setup based on rules void parameterSetup(); /// Create options model - void createOptionsModel(bool resetSettings); + [[nodiscard]] bool createOptionsModel(bool resetSettings); /// Initialize prune setting void InitPruneSetting(int64_t prune_MiB); /// Create main window diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 779d1054e0fb..db33a094ea82 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -889,6 +889,8 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndH connect(optionsModel, &OptionsModel::coinJoinEnabledChanged, this, &BitcoinGUI::updateCoinJoinVisibility); } + + m_mask_values_action->setChecked(_clientModel->getOptionsModel()->getOption(OptionsModel::OptionID::MaskValues).toBool()); } else { if(trayIconMenu) { diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index 49e06646e9c3..eda0ce5cdd69 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -1140,7 +1140,7 @@ https://explore.transifex.com/dash/dash/ - Options set in this dialog are overridden by the command line or in the configuration file: + Options set in this dialog are overridden by the command line: Qt::PlainText diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 9518d6f6ff38..034ae4f908ef 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -84,10 +83,6 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : #ifndef USE_NATPMP ui->mapPortNatpmp->setEnabled(false); #endif - connect(this, &QDialog::accepted, [this](){ - QSettings settings; - model->node().mapPort(settings.value("fUseUPnP").toBool(), settings.value("fUseNatpmp").toBool()); - }); ui->proxyIp->setEnabled(false); ui->proxyPort->setEnabled(false); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index ba84e8e75558..ce4579ca5814 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -24,6 +24,7 @@ #ifdef ENABLE_WALLET #include +#include // For DEFAULT_SPEND_ZEROCONF_CHANGE #endif #include @@ -32,10 +33,147 @@ #include #include +#include + const char *DEFAULT_GUI_PROXY_HOST = "127.0.0.1"; static QString GetDefaultProxyAddress(); +/** Map GUI option ID to node setting name. */ +static const char* SettingName(OptionsModel::OptionID option) +{ + switch (option) { + case OptionsModel::DatabaseCache: return "dbcache"; + case OptionsModel::ThreadsScriptVerif: return "par"; + case OptionsModel::SpendZeroConfChange: return "spendzeroconfchange"; + case OptionsModel::ExternalSignerPath: return "signer"; + case OptionsModel::MapPortUPnP: return "upnp"; + case OptionsModel::MapPortNatpmp: return "natpmp"; + case OptionsModel::Listen: return "listen"; + case OptionsModel::Server: return "server"; + case OptionsModel::PruneSize: return "prune"; + case OptionsModel::Prune: return "prune"; + case OptionsModel::ProxyIP: return "proxy"; + case OptionsModel::ProxyPort: return "proxy"; + case OptionsModel::ProxyUse: return "proxy"; + case OptionsModel::ProxyIPTor: return "onion"; + case OptionsModel::ProxyPortTor: return "onion"; + case OptionsModel::ProxyUseTor: return "onion"; + case OptionsModel::Language: return "lang"; + //! Dash + case OptionsModel::CoinJoinAmount: return "coinjoinamount"; + case OptionsModel::CoinJoinDenomsGoal: return "coinjoindenomsgoal"; + case OptionsModel::CoinJoinDenomsHardCap: return "coinjoindenomshardcap"; + case OptionsModel::CoinJoinEnabled: return "enablecoinjoin"; + case OptionsModel::CoinJoinMultiSession: return "coinjoinmultisession"; + case OptionsModel::CoinJoinRounds: return "coinjoinrounds"; + case OptionsModel::CoinJoinSessions: return "coinjoinsessions"; + case OptionsModel::FontFamily: return "font-family"; + case OptionsModel::FontScale: return "font-scale"; + case OptionsModel::FontWeightBold: return "font-weight-bold"; + case OptionsModel::FontWeightNormal: return "font-weight-normal"; + default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option)); + } +} + +static bool RequiresNumWorkaround(OptionsModel::OptionID option) +{ + switch (option) { + case OptionsModel::CoinJoinAmount: + case OptionsModel::CoinJoinDenomsGoal: + case OptionsModel::CoinJoinDenomsHardCap: + case OptionsModel::CoinJoinRounds: + case OptionsModel::CoinJoinSessions: + case OptionsModel::DatabaseCache: + case OptionsModel::FontScale: + case OptionsModel::FontWeightBold: + case OptionsModel::FontWeightNormal: + case OptionsModel::Prune: + case OptionsModel::PruneSize: + case OptionsModel::ThreadsScriptVerif: + return true; + default: + return false; + } +} + +/** Call node.updateRwSetting() with Bitcoin 22.x workaround. */ +static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID option, const std::string& suffix, const util::SettingsValue& value) +{ + if (value.isNum() && RequiresNumWorkaround(option)) { + // Write certain old settings as strings, even though they are numbers, + // because Bitcoin 22.x releases try to read these specific settings as + // strings in addOverriddenOption() calls at startup, triggering + // uncaught exceptions in UniValue::get_str(). These errors were fixed + // in later releases by https://github.com/bitcoin/bitcoin/pull/24498. + // If new numeric settings are added, they can be written as numbers + // instead of strings, because bitcoin 22.x will not try to read these. + node.updateRwSetting(SettingName(option) + suffix, value.getValStr()); + } else { + node.updateRwSetting(SettingName(option) + suffix, value); + } +} + +//! Convert enabled/size values to bitcoin -prune setting. +static util::SettingsValue PruneSetting(bool prune_enabled, int prune_size_gb) +{ + assert(!prune_enabled || prune_size_gb >= 1); // PruneSizeGB and ParsePruneSizeGB never return less + return prune_enabled ? PruneGBtoMiB(prune_size_gb) : 0; +} + +//! Get pruning enabled value to show in GUI from bitcoin -prune setting. +static bool PruneEnabled(const util::SettingsValue& prune_setting) +{ + // -prune=1 setting is manual pruning mode, so disabled for purposes of the gui + return SettingToInt(prune_setting, 0) > 1; +} + +//! Get pruning size value to show in GUI from bitcoin -prune setting. If +//! pruning is not enabled, just show default recommended pruning size (2GB). +static int PruneSizeGB(const util::SettingsValue& prune_setting) +{ + int value = SettingToInt(prune_setting, 0); + return value > 1 ? PruneMiBtoGB(value) : DEFAULT_PRUNE_TARGET_GB; +} + +//! Parse pruning size value provided by user in GUI or loaded from QSettings +//! (windows registry key or qt .conf file). Smallest value that the GUI can +//! display is 1 GB, so round up if anything less is parsed. +static int ParsePruneSizeGB(const QVariant& prune_size) +{ + return std::max(1, prune_size.toInt()); +} + +static int GetFallbackWeightIndex(bool is_bold) +{ + if (is_bold) { + // If the currently selected weight is not supported fallback to the second lightest weight for bold font + // or the lightest if there is only one. + return GUIUtil::g_font_registry.GetSupportedWeights().size() > 1 ? 1 : 0; + } else { + // If the currently selected weight is not supported fallback to the lightest weight for normal font. + return 0; + } +} + +static int WeightArgToIdx(bool is_bold, int weight_arg) +{ + if (QFont::Weight weight; GUIUtil::weightFromArg(weight_arg, weight)) { + if (int index = GUIUtil::g_font_registry.WeightToIdx(weight); index != -1) { + return index; + } + } + return GetFallbackWeightIndex(is_bold); +} + +struct ProxySetting { + bool is_set; + QString ip; + QString port; +}; +static ProxySetting ParseProxyString(const std::string& proxy); +static std::string ProxyString(bool is_set, QString ip, QString port); + static const QLatin1String fontchoice_str_app_font{"application_font"}; static const QLatin1String fontchoice_str_embedded{"embedded"}; static const QLatin1String fontchoice_str_best_system{"best_system"}; @@ -74,10 +212,9 @@ OptionsModel::FontChoice OptionsModel::FontChoiceFromString(const QString& s) } } -OptionsModel::OptionsModel(QObject *parent, bool resetSettings) : - QAbstractListModel(parent) +OptionsModel::OptionsModel(interfaces::Node& node, QObject *parent) : + QAbstractListModel(parent), m_node{node} { - Init(resetSettings); } void OptionsModel::addOverriddenOption(const std::string &option) @@ -86,10 +223,10 @@ void OptionsModel::addOverriddenOption(const std::string &option) } // Writes all missing QSettings with their default values -void OptionsModel::Init(bool resetSettings) +bool OptionsModel::Init(bilingual_str& error) { - if (resetSettings) - Reset(); + // Initialize display settings from stored settings. + language = QString::fromStdString(SettingToString(node().getPersistentSetting("lang"), "")); checkAndMigrate(); @@ -136,35 +273,26 @@ void OptionsModel::Init(bool resetSettings) settings.setValue("theme", GUIUtil::getDefaultTheme()); // Font Family - if (!settings.contains("fontFamily")) { - settings.setValue("fontFamily", GUIUtil::FontRegistry::DEFAULT_FONT.toUtf8()); - } - if (!gArgs.SoftSetArg("-font-family", settings.value("fontFamily").toString().toStdString())) { + if (node().isSettingIgnored("font-family")) { addOverriddenOption("-font-family"); } if (GUIUtil::fontsLoaded()) { - if (auto font_name = QString::fromStdString(gArgs.GetArg("-font-family", settings.value("fontFamily").toString().toStdString())); + if (auto font_name = QString::fromStdString(SettingToString(node().getPersistentSetting("font-family"), GUIUtil::FontRegistry::DEFAULT_FONT.toUtf8().toStdString())); GUIUtil::g_font_registry.RegisterFont(font_name, /*selectable=*/true) && GUIUtil::g_font_registry.SetFont(font_name)) { GUIUtil::setApplicationFont(); } } // Font Scale - if (!settings.contains("fontScale")) { - settings.setValue("fontScale", GUIUtil::FontRegistry::DEFAULT_FONT_SCALE); - } - if (!gArgs.SoftSetArg("-font-scale", settings.value("fontScale").toString().toStdString())) { + if (node().isSettingIgnored("font-scale")) { addOverriddenOption("-font-scale"); } if (GUIUtil::fontsLoaded()) { - GUIUtil::g_font_registry.SetFontScale(gArgs.GetIntArg("-font-scale", settings.value("fontScale").toString().toInt())); + GUIUtil::g_font_registry.SetFontScale(SettingToInt(node().getPersistentSetting("font-scale"), GUIUtil::FontRegistry::DEFAULT_FONT_SCALE)); } // Font Weight (Normal) - if (!settings.contains("fontWeightNormal")) { - settings.setValue("fontWeightNormal", GUIUtil::weightToArg(GUIUtil::FontRegistry::TARGET_WEIGHT_NORMAL)); - } - if (gArgs.IsArgSet("-font-weight-normal") && !gArgs.SoftSetArg("-font-weight-normal", settings.value("fontWeightNormal").toString().toStdString())) { + if (node().isSettingIgnored("font-weight-normal")) { addOverriddenOption("-font-weight-normal"); } @@ -173,32 +301,27 @@ void OptionsModel::Init(bool resetSettings) // If font was overridden by CLI but weight wasn't, use the font's default weight QFont::Weight weight{GUIUtil::g_font_registry.GetWeightNormalDefault()}; if (!override_family || isOptionOverridden("-font-weight-normal")) { - GUIUtil::weightFromArg(gArgs.GetIntArg("-font-weight-normal", settings.value("fontWeightNormal").toInt()), weight); - if (!GUIUtil::g_font_registry.IsValidWeight(weight)) { - // If the currently selected weight is not supported fallback to the lightest weight for normal font. - weight = GUIUtil::g_font_registry.GetSupportedWeights().front(); + const auto raw_weight{SettingToInt(node().getPersistentSetting("font-weight-normal"), GUIUtil::weightToArg(GUIUtil::FontRegistry::TARGET_WEIGHT_NORMAL))}; + if (!GUIUtil::weightFromArg(raw_weight, weight) || !GUIUtil::g_font_registry.IsValidWeight(weight)) { + weight = GUIUtil::g_font_registry.IdxToWeight(GetFallbackWeightIndex(/*is_bold=*/false)); + node().forceSetting("font-weight-normal", GUIUtil::weightToArg(weight)); } } GUIUtil::g_font_registry.SetWeightNormal(weight); } // Font Weight (Bold) - if (!settings.contains("fontWeightBold")) { - settings.setValue("fontWeightBold", GUIUtil::weightToArg(GUIUtil::FontRegistry::TARGET_WEIGHT_BOLD)); - } - if (gArgs.IsArgSet("-font-weight-bold") && !gArgs.SoftSetArg("-font-weight-bold", settings.value("fontWeightBold").toString().toStdString())) { + if (node().isSettingIgnored("font-weight-bold")) { addOverriddenOption("-font-weight-bold"); } if (GUIUtil::fontsLoaded()) { // If font was overridden by CLI but weight wasn't, use the font's default weight QFont::Weight weight{GUIUtil::g_font_registry.GetWeightBoldDefault()}; if (!override_family || isOptionOverridden("-font-weight-bold")) { - GUIUtil::weightFromArg(gArgs.GetIntArg("-font-weight-bold", settings.value("fontWeightBold").toInt()), weight); - if (!GUIUtil::g_font_registry.IsValidWeight(weight)) { - // If the currently selected weight is not supported fallback to the second lightest weight for bold font - // or the lightest if there is only one. - auto vecSupported = GUIUtil::g_font_registry.GetSupportedWeights(); - weight = vecSupported[vecSupported.size() > 1 ? 1 : 0]; + const auto raw_weight{SettingToInt(node().getPersistentSetting("font-weight-bold"), GUIUtil::weightToArg(GUIUtil::FontRegistry::TARGET_WEIGHT_BOLD))}; + if (!GUIUtil::weightFromArg(raw_weight, weight) || !GUIUtil::g_font_registry.IsValidWeight(weight)) { + weight = GUIUtil::g_font_registry.IdxToWeight(GetFallbackWeightIndex(/*is_bold=*/true)); + node().forceSetting("font-weight-bold", GUIUtil::weightToArg(weight)); } } GUIUtil::g_font_registry.SetWeightBold(weight); @@ -225,13 +348,6 @@ void OptionsModel::Init(bool resetSettings) settings.setValue("digits", "2"); // CoinJoin - if (!settings.contains("fCoinJoinEnabled")) { - settings.setValue("fCoinJoinEnabled", true); - } - if (!gArgs.SoftSetBoolArg("-enablecoinjoin", settings.value("fCoinJoinEnabled").toBool())) { - addOverriddenOption("-enablecoinjoin"); - } - if (!settings.contains("fShowAdvancedCJUI")) settings.setValue("fShowAdvancedCJUI", false); fShowAdvancedCJUI = settings.value("fShowAdvancedCJUI", false).toBool(); @@ -245,156 +361,38 @@ void OptionsModel::Init(bool resetSettings) // These are shared with the core or have a command-line parameter // and we want command-line parameters to overwrite the GUI settings. - // + for (OptionID option : {DatabaseCache, ThreadsScriptVerif, SpendZeroConfChange, ExternalSignerPath, MapPortUPnP, + MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language, CoinJoinAmount, + CoinJoinDenomsGoal, CoinJoinDenomsHardCap, CoinJoinEnabled, CoinJoinMultiSession, + CoinJoinRounds, CoinJoinSessions}) { + std::string setting = SettingName(option); + if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting); + try { + getOption(option); + } catch (const std::exception& e) { + // This handles exceptions thrown by univalue that can happen if + // settings in settings.json don't have the expected types. + error.original = strprintf("Could not read setting \"%s\", %s.", setting, e.what()); + error.translated = tr("Could not read setting \"%1\", %2.").arg(QString::fromStdString(setting), e.what()).toStdString(); + return false; + } + } + // If setting doesn't exist create it with defaults. - // - // If gArgs.SoftSetArg() or m_node.softSetBoolArg() return false we were overridden - // by command-line and show this in the UI. // Main - if (!settings.contains("bPrune")) - settings.setValue("bPrune", false); - if (!settings.contains("nPruneSize")) - settings.setValue("nPruneSize", DEFAULT_PRUNE_TARGET_GB); - SetPruneEnabled(settings.value("bPrune").toBool()); - - if (!settings.contains("nDatabaseCache")) - settings.setValue("nDatabaseCache", (qint64)nDefaultDbCache); - if (!gArgs.SoftSetArg("-dbcache", settings.value("nDatabaseCache").toString().toStdString())) - addOverriddenOption("-dbcache"); - - if (!settings.contains("nThreadsScriptVerif")) - settings.setValue("nThreadsScriptVerif", DEFAULT_SCRIPTCHECK_THREADS); - if (!gArgs.SoftSetArg("-par", settings.value("nThreadsScriptVerif").toString().toStdString())) - addOverriddenOption("-par"); - if (!settings.contains("strDataDir")) settings.setValue("strDataDir", GUIUtil::getDefaultDataDirectory()); // Wallet #ifdef ENABLE_WALLET - if (!settings.contains("bSpendZeroConfChange")) - settings.setValue("bSpendZeroConfChange", true); - if (!gArgs.SoftSetBoolArg("-spendzeroconfchange", settings.value("bSpendZeroConfChange").toBool())) - addOverriddenOption("-spendzeroconfchange"); - - if (!settings.contains("external_signer_path")) - settings.setValue("external_signer_path", ""); - - if (!gArgs.SoftSetArg("-signer", settings.value("external_signer_path").toString().toStdString())) { - addOverriddenOption("-signer"); - } - if (!settings.contains("SubFeeFromAmount")) { settings.setValue("SubFeeFromAmount", false); } m_sub_fee_from_amount = settings.value("SubFeeFromAmount", false).toBool(); - - // CoinJoin - if (!settings.contains("nCoinJoinSessions")) - settings.setValue("nCoinJoinSessions", DEFAULT_COINJOIN_SESSIONS); - if (!gArgs.SoftSetArg("-coinjoinsessions", settings.value("nCoinJoinSessions").toString().toStdString())) - addOverriddenOption("-coinjoinsessions"); - - if (!settings.contains("nCoinJoinRounds")) - settings.setValue("nCoinJoinRounds", DEFAULT_COINJOIN_ROUNDS); - if (!gArgs.SoftSetArg("-coinjoinrounds", settings.value("nCoinJoinRounds").toString().toStdString())) - addOverriddenOption("-coinjoinrounds"); - - if (!settings.contains("nCoinJoinAmount")) - settings.setValue("nCoinJoinAmount", DEFAULT_COINJOIN_AMOUNT); - if (!gArgs.SoftSetArg("-coinjoinamount", settings.value("nCoinJoinAmount").toString().toStdString())) - addOverriddenOption("-coinjoinamount"); - - if (!settings.contains("fCoinJoinMultiSession")) - settings.setValue("fCoinJoinMultiSession", DEFAULT_COINJOIN_MULTISESSION); - if (!gArgs.SoftSetBoolArg("-coinjoinmultisession", settings.value("fCoinJoinMultiSession").toBool())) - addOverriddenOption("-coinjoinmultisession"); - - if (!settings.contains("nCoinJoinDenomsGoal")) - settings.setValue("nCoinJoinDenomsGoal", DEFAULT_COINJOIN_DENOMS_GOAL); - if (!gArgs.SoftSetArg("-coinjoindenomsgoal", settings.value("nCoinJoinDenomsGoal").toString().toStdString())) - addOverriddenOption("-coinjoindenomsgoal"); - - if (!settings.contains("nCoinJoinDenomsHardCap")) - settings.setValue("nCoinJoinDenomsHardCap", DEFAULT_COINJOIN_DENOMS_HARDCAP); - if (!gArgs.SoftSetArg("-coinjoindenomshardcap", settings.value("nCoinJoinDenomsHardCap").toString().toStdString())) - addOverriddenOption("-coinjoindenomshardcap"); #endif - // Network - if (!settings.contains("fUseUPnP")) - settings.setValue("fUseUPnP", DEFAULT_UPNP); - if (!gArgs.SoftSetBoolArg("-upnp", settings.value("fUseUPnP").toBool())) - addOverriddenOption("-upnp"); - - if (!settings.contains("fUseNatpmp")) { - settings.setValue("fUseNatpmp", DEFAULT_NATPMP); - } - if (!gArgs.SoftSetBoolArg("-natpmp", settings.value("fUseNatpmp").toBool())) { - addOverriddenOption("-natpmp"); - } - - if (!settings.contains("fListen")) - settings.setValue("fListen", DEFAULT_LISTEN); - const bool listen{settings.value("fListen").toBool()}; - if (!gArgs.SoftSetBoolArg("-listen", listen)) { - addOverriddenOption("-listen"); - } else if (!listen) { - // We successfully set -listen=0, thus mimic the logic from InitParameterInteraction(): - // "parameter interaction: -listen=0 -> setting -listenonion=0". - // - // Both -listen and -listenonion default to true. - // - // The call order is: - // - // InitParameterInteraction() - // would set -listenonion=0 if it sees -listen=0, but for bitcoin-qt with - // fListen=false -listen is 1 at this point - // - // OptionsModel::Init() - // (this method) can flip -listen from 1 to 0 if fListen=false - // - // AppInitParameterInteraction() - // raises an error if -listen=0 and -listenonion=1 - gArgs.SoftSetBoolArg("-listenonion", false); - } - - if (!settings.contains("server")) { - settings.setValue("server", false); - } - if (!gArgs.SoftSetBoolArg("-server", settings.value("server").toBool())) { - addOverriddenOption("-server"); - } - - if (!settings.contains("fUseProxy")) - settings.setValue("fUseProxy", false); - if (!settings.contains("addrProxy")) - settings.setValue("addrProxy", GetDefaultProxyAddress()); - // Only try to set -proxy, if user has enabled fUseProxy - if ((settings.value("fUseProxy").toBool() && !gArgs.SoftSetArg("-proxy", settings.value("addrProxy").toString().toStdString()))) - addOverriddenOption("-proxy"); - else if(!settings.value("fUseProxy").toBool() && !gArgs.GetArg("-proxy", "").empty()) - addOverriddenOption("-proxy"); - - if (!settings.contains("fUseSeparateProxyTor")) - settings.setValue("fUseSeparateProxyTor", false); - if (!settings.contains("addrSeparateProxyTor")) - settings.setValue("addrSeparateProxyTor", GetDefaultProxyAddress()); - // Only try to set -onion, if user has enabled fUseSeparateProxyTor - if ((settings.value("fUseSeparateProxyTor").toBool() && !gArgs.SoftSetArg("-onion", settings.value("addrSeparateProxyTor").toString().toStdString()))) - addOverriddenOption("-onion"); - else if(!settings.value("fUseSeparateProxyTor").toBool() && !gArgs.GetArg("-onion", "").empty()) - addOverriddenOption("-onion"); - // Display - if (!settings.contains("language")) - settings.setValue("language", ""); - if (!gArgs.SoftSetArg("-lang", settings.value("language").toString().toStdString())) - addOverriddenOption("-lang"); - - language = settings.value("language").toString(); - if (settings.contains("FontForMoney")) { m_font_money = FontChoiceFromString(settings.value("FontForMoney").toString()); } else if (settings.contains("UseEmbeddedMonospacedFont")) { @@ -405,6 +403,10 @@ void OptionsModel::Init(bool resetSettings) } } Q_EMIT fontForMoneyChanged(getFontForMoney()); + + m_mask_values = settings.value("mask_values", false).toBool(); + + return true; } /** Helper function to copy contents from one QSettings to another. @@ -428,6 +430,23 @@ static void BackupSettings(const fs::path& filename, const QSettings& src) void OptionsModel::Reset() { + // Backup settings.json + gArgs.WriteSettingsFile(/*errors=*/nullptr, /*backup=*/true); + + // Clear GUI-managed settings from settings.json as node().resetSettings() + // clears ALL settings including non-GUI settings + for (int i{0}; i < OptionIDRowCount; i++) { + const char* setting_name = nullptr; + try { + setting_name = SettingName(static_cast(i)); + } catch (const std::logic_error&) { + // Option doesn't have a corresponding node setting, skip it + continue; + } + node().updateRwSetting(setting_name, {}); + node().updateRwSetting(strprintf("%s-prev", setting_name), {}); + } + QSettings settings; // Backup old settings to chain-specific datadir for troubleshooting @@ -456,21 +475,15 @@ int OptionsModel::rowCount(const QModelIndex & parent) const return OptionIDRowCount; } -struct ProxySetting { - bool is_set; - QString ip; - QString port; -}; - -static ProxySetting GetProxySetting(QSettings &settings, const QString &name) +static ProxySetting ParseProxyString(const QString& proxy) { static const ProxySetting default_val = {false, DEFAULT_GUI_PROXY_HOST, QString("%1").arg(DEFAULT_GUI_PROXY_PORT)}; // Handle the case that the setting is not set at all - if (!settings.contains(name)) { + if (proxy.isEmpty()) { return default_val; } // contains IP at index 0 and port at index 1 - QStringList ip_port = GUIUtil::SplitSkipEmptyParts(settings.value(name).toString(), ":"); + QStringList ip_port = GUIUtil::SplitSkipEmptyParts(proxy, ":"); if (ip_port.size() == 2) { return {true, ip_port.at(0), ip_port.at(1)}; } else { // Invalid: return default @@ -478,9 +491,14 @@ static ProxySetting GetProxySetting(QSettings &settings, const QString &name) } } -static void SetProxySetting(QSettings &settings, const QString &name, const ProxySetting &ip_port) +static ProxySetting ParseProxyString(const std::string& proxy) +{ + return ParseProxyString(QString::fromStdString(proxy)); +} + +static std::string ProxyString(bool is_set, QString ip, QString port) { - settings.setValue(name, QString{ip_port.ip + QLatin1Char(':') + ip_port.port}); + return is_set ? QString(ip + ":" + port).toStdString() : ""; } static QString GetDefaultProxyAddress() @@ -488,37 +506,36 @@ static QString GetDefaultProxyAddress() return QString("%1:%2").arg(DEFAULT_GUI_PROXY_HOST).arg(DEFAULT_GUI_PROXY_PORT); } -void OptionsModel::SetPruneEnabled(bool prune, bool force) +void OptionsModel::SetPruneTargetGB(int prune_target_gb) { - QSettings settings; - settings.setValue("bPrune", prune); - const int64_t prune_target_mib = PruneGBtoMiB(settings.value("nPruneSize").toInt()); - std::string prune_val = prune ? ToString(prune_target_mib) : "0"; - if (force) { - gArgs.ForceSetArg("-prune", prune_val); - if (prune) { - gArgs.ForceSetArg("-disablegovernance", "1"); - gArgs.ForceSetArg("-txindex", "0"); - } - return; + const util::SettingsValue cur_value = node().getPersistentSetting("prune"); + const util::SettingsValue new_value = PruneSetting(prune_target_gb > 0, prune_target_gb); + + // Force setting to take effect. It is still safe to change the value at + // this point because this function is only called after the intro screen is + // shown, before the node starts. + node().forceSetting("prune", new_value); + + // When pruning is enabled, force disable governance and txindex + if (prune_target_gb > 0) { + node().forceSetting("disablegovernance", "1"); + node().forceSetting("txindex", "0"); } - if (!gArgs.SoftSetArg("-prune", prune_val)) { - addOverriddenOption("-prune"); - } - if (gArgs.GetIntArg("-prune", 0) > 0) { - gArgs.SoftSetBoolArg("-disablegovernance", true); - gArgs.SoftSetBoolArg("-txindex", false); + + // Update settings.json if value configured in intro screen is different + // from saved value. Avoid writing settings.json if bitcoin.conf value + // doesn't need to be overridden. + if (PruneEnabled(cur_value) != PruneEnabled(new_value) || + PruneSizeGB(cur_value) != PruneSizeGB(new_value)) { + // Call UpdateRwSetting() instead of setOption() to avoid setting + // RestartRequired flag + UpdateRwSetting(node(), Prune, "", new_value); } -} -void OptionsModel::SetPruneTargetGB(int prune_target_gb, bool force) -{ - const bool prune = prune_target_gb > 0; - if (prune) { - QSettings settings; - settings.setValue("nPruneSize", prune_target_gb); + // Keep previous pruning size, if pruning was disabled. + if (PruneEnabled(cur_value)) { + UpdateRwSetting(node(), Prune, "-prev", PruneEnabled(new_value) ? util::SettingsValue{} : cur_value); } - SetPruneEnabled(prune, force); } QFont OptionsModel::getFontForChoice(const FontChoice& fc) @@ -554,439 +571,468 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const { if(role == Qt::EditRole) { - QSettings settings; - switch(index.row()) - { - case StartAtStartup: - return GUIUtil::GetStartOnSystemStartup(); - case ShowTrayIcon: - return m_show_tray_icon; - case MinimizeToTray: - return fMinimizeToTray; - case MapPortUPnP: + return getOption(OptionID(index.row())); + } + return QVariant(); +} + +// write QSettings values +bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role) +{ + bool successful = true; /* set to false on parse error */ + if(role == Qt::EditRole) + { + successful = setOption(OptionID(index.row()), value); + } + + Q_EMIT dataChanged(index, index); + + return successful; +} + +QVariant OptionsModel::getOption(OptionID option, const std::string& suffix) const +{ + auto setting = [&]{ return node().getPersistentSetting(SettingName(option) + suffix); }; + + QSettings settings; + switch (option) { + case StartAtStartup: + return GUIUtil::GetStartOnSystemStartup(); + case ShowTrayIcon: + return m_show_tray_icon; + case MinimizeToTray: + return fMinimizeToTray; + case MapPortUPnP: #ifdef USE_UPNP - return settings.value("fUseUPnP"); + return SettingToBool(setting(), DEFAULT_UPNP); #else - return false; + return false; #endif // USE_UPNP - case MapPortNatpmp: + case MapPortNatpmp: #ifdef USE_NATPMP - return settings.value("fUseNatpmp"); + return SettingToBool(setting(), DEFAULT_NATPMP); #else - return false; + return false; #endif // USE_NATPMP - case MinimizeOnClose: - return fMinimizeOnClose; - - // default proxy - case ProxyUse: - return settings.value("fUseProxy", false); - case ProxyIP: - return GetProxySetting(settings, "addrProxy").ip; - case ProxyPort: - return GetProxySetting(settings, "addrProxy").port; - - // separate Tor proxy - case ProxyUseTor: - return settings.value("fUseSeparateProxyTor", false); - case ProxyIPTor: - return GetProxySetting(settings, "addrSeparateProxyTor").ip; - case ProxyPortTor: - return GetProxySetting(settings, "addrSeparateProxyTor").port; + case MinimizeOnClose: + return fMinimizeOnClose; + + // default proxy + case ProxyUse: + case ProxyUseTor: + return ParseProxyString(SettingToString(setting(), "")).is_set; + case ProxyIP: + case ProxyIPTor: { + ProxySetting proxy = ParseProxyString(SettingToString(setting(), "")); + if (proxy.is_set) { + return proxy.ip; + } else if (suffix.empty()) { + return getOption(option, "-prev"); + } else { + return ParseProxyString(GetDefaultProxyAddress().toStdString()).ip; + } + } + case ProxyPort: + case ProxyPortTor: { + ProxySetting proxy = ParseProxyString(SettingToString(setting(), "")); + if (proxy.is_set) { + return proxy.port; + } else if (suffix.empty()) { + return getOption(option, "-prev"); + } else { + return ParseProxyString(GetDefaultProxyAddress().toStdString()).port; + } + } #ifdef ENABLE_WALLET - case SpendZeroConfChange: - return settings.value("bSpendZeroConfChange"); - case ExternalSignerPath: - return settings.value("external_signer_path"); - case SubFeeFromAmount: - return m_sub_fee_from_amount; - case ShowMasternodesTab: - return settings.value("fShowMasternodesTab"); - case ShowGovernanceTab: - return settings.value("fShowGovernanceTab"); - case CoinJoinEnabled: - return settings.value("fCoinJoinEnabled"); - case ShowAdvancedCJUI: - return fShowAdvancedCJUI; - case ShowCoinJoinPopups: - return settings.value("fShowCoinJoinPopups"); - case LowKeysWarning: - return settings.value("fLowKeysWarning"); - case CoinJoinSessions: - return settings.value("nCoinJoinSessions"); - case CoinJoinRounds: - return settings.value("nCoinJoinRounds"); - case CoinJoinAmount: - return settings.value("nCoinJoinAmount"); - case CoinJoinDenomsGoal: - return settings.value("nCoinJoinDenomsGoal"); - case CoinJoinDenomsHardCap: - return settings.value("nCoinJoinDenomsHardCap"); - case CoinJoinMultiSession: - return settings.value("fCoinJoinMultiSession"); + case SpendZeroConfChange: + return SettingToBool(setting(), wallet::DEFAULT_SPEND_ZEROCONF_CHANGE); + case ExternalSignerPath: + return QString::fromStdString(SettingToString(setting(), "")); + case SubFeeFromAmount: + return m_sub_fee_from_amount; + case ShowMasternodesTab: + return settings.value("fShowMasternodesTab"); + case ShowGovernanceTab: + return settings.value("fShowGovernanceTab"); + case CoinJoinEnabled: + return SettingToBool(setting(), /*fDefault=*/true); + case ShowAdvancedCJUI: + return fShowAdvancedCJUI; + case ShowCoinJoinPopups: + return settings.value("fShowCoinJoinPopups"); + case LowKeysWarning: + return settings.value("fLowKeysWarning"); + case CoinJoinSessions: + return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_SESSIONS)); + case CoinJoinRounds: + return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_ROUNDS)); + case CoinJoinAmount: + return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_AMOUNT)); + case CoinJoinDenomsGoal: + return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_DENOMS_GOAL)); + case CoinJoinDenomsHardCap: + return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_DENOMS_HARDCAP)); + case CoinJoinMultiSession: + return SettingToBool(setting(), DEFAULT_COINJOIN_MULTISESSION); #endif - case DisplayUnit: - return QVariant::fromValue(m_display_bitcoin_unit); - case ThirdPartyTxUrls: - return strThirdPartyTxUrls; + case DisplayUnit: + return QVariant::fromValue(m_display_bitcoin_unit); + case ThirdPartyTxUrls: + return strThirdPartyTxUrls; #ifdef ENABLE_WALLET - case Digits: - return settings.value("digits"); + case Digits: + return settings.value("digits"); #endif // ENABLE_WALLET - case Theme: - return settings.value("theme"); - case FontFamily: - return settings.value("fontFamily"); - case FontScale: - return settings.value("fontScale"); - case FontWeightNormal: { - int nIndex = [&]() -> int { - if (QFont::Weight weight; GUIUtil::weightFromArg(settings.value("fontWeightNormal").toInt(), weight) && GUIUtil::g_font_registry.IsValidWeight(weight)) { - return GUIUtil::g_font_registry.WeightToIdx(weight); - } - return GUIUtil::g_font_registry.WeightToIdx(GUIUtil::g_font_registry.GetWeightNormalDefault()); - }(); - assert(nIndex != -1); - return nIndex; - } - case FontWeightBold: { - int nIndex = [&]() -> int { - if (QFont::Weight weight; GUIUtil::weightFromArg(settings.value("fontWeightBold").toInt(), weight) && GUIUtil::g_font_registry.IsValidWeight(weight)) { - return GUIUtil::g_font_registry.WeightToIdx(weight); - } - return GUIUtil::g_font_registry.WeightToIdx(GUIUtil::g_font_registry.GetWeightBoldDefault()); - }(); - assert(nIndex != -1); - return nIndex; - } - case Language: - return settings.value("language"); - case FontForMoney: - return QVariant::fromValue(m_font_money); + case Theme: + return settings.value("theme"); + case FontFamily: + return QString::fromStdString(SettingToString(setting(), GUIUtil::FontRegistry::DEFAULT_FONT.toUtf8().toStdString())); + case FontScale: + return qlonglong(SettingToInt(setting(), GUIUtil::FontRegistry::DEFAULT_FONT_SCALE)); + case FontWeightNormal: + return WeightArgToIdx(/*is_bold=*/false, SettingToInt(setting(), GUIUtil::g_font_registry.GetWeightNormalDefault())); + case FontWeightBold: + return WeightArgToIdx(/*is_bold=*/true, SettingToInt(setting(), GUIUtil::g_font_registry.GetWeightBoldDefault())); + case Language: + return QString::fromStdString(SettingToString(setting(), "")); + case FontForMoney: + return QVariant::fromValue(m_font_money); #ifdef ENABLE_WALLET - case CoinControlFeatures: - return fCoinControlFeatures; - case EnablePSBTControls: - return settings.value("enable_psbt_controls"); - case KeepChangeAddress: - return fKeepChangeAddress; + case CoinControlFeatures: + return fCoinControlFeatures; + case EnablePSBTControls: + return settings.value("enable_psbt_controls"); + case KeepChangeAddress: + return fKeepChangeAddress; #endif // ENABLE_WALLET - case Prune: - return settings.value("bPrune"); - case PruneSize: - return settings.value("nPruneSize"); - case DatabaseCache: - return settings.value("nDatabaseCache"); - case ThreadsScriptVerif: - return settings.value("nThreadsScriptVerif"); - case Listen: - return settings.value("fListen"); - case Server: - return settings.value("server"); - default: - return QVariant(); - } + case Prune: + return PruneEnabled(setting()); + case PruneSize: + return PruneEnabled(setting()) ? PruneSizeGB(setting()) : + suffix.empty() ? getOption(option, "-prev") : + DEFAULT_PRUNE_TARGET_GB; + case DatabaseCache: + return qlonglong(SettingToInt(setting(), nDefaultDbCache)); + case ThreadsScriptVerif: + return qlonglong(SettingToInt(setting(), DEFAULT_SCRIPTCHECK_THREADS)); + case Listen: + return SettingToBool(setting(), DEFAULT_LISTEN); + case Server: + return SettingToBool(setting(), false); + case MaskValues: + return m_mask_values; + default: + return QVariant(); } - return QVariant(); } -// write QSettings values -bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role) +bool OptionsModel::setOption(OptionID option, const QVariant& value, const std::string& suffix) { + auto changed = [&] { return value.isValid() && value != getOption(option, suffix); }; + auto update = [&](const util::SettingsValue& value) { return UpdateRwSetting(node(), option, suffix, value); }; + bool successful = true; /* set to false on parse error */ - if(role == Qt::EditRole) - { - QSettings settings; - switch(index.row()) - { - case StartAtStartup: - successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); - break; - case ShowTrayIcon: - m_show_tray_icon = value.toBool(); - settings.setValue("fHideTrayIcon", !m_show_tray_icon); - Q_EMIT showTrayIconChanged(m_show_tray_icon); - break; - case MinimizeToTray: - fMinimizeToTray = value.toBool(); - settings.setValue("fMinimizeToTray", fMinimizeToTray); - break; - case MapPortUPnP: // core option - can be changed on-the-fly - settings.setValue("fUseUPnP", value.toBool()); - break; - case MapPortNatpmp: // core option - can be changed on-the-fly - settings.setValue("fUseNatpmp", value.toBool()); - break; - case MinimizeOnClose: - fMinimizeOnClose = value.toBool(); - settings.setValue("fMinimizeOnClose", fMinimizeOnClose); - break; + QSettings settings; + switch (option) { + case StartAtStartup: + successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); + break; + case ShowTrayIcon: + m_show_tray_icon = value.toBool(); + settings.setValue("fHideTrayIcon", !m_show_tray_icon); + Q_EMIT showTrayIconChanged(m_show_tray_icon); + break; + case MinimizeToTray: + fMinimizeToTray = value.toBool(); + settings.setValue("fMinimizeToTray", fMinimizeToTray); + break; + case MapPortUPnP: // core option - can be changed on-the-fly + if (changed()) { + update(value.toBool()); + node().mapPort(value.toBool(), getOption(MapPortNatpmp).toBool()); + } + break; + case MapPortNatpmp: // core option - can be changed on-the-fly + if (changed()) { + update(value.toBool()); + node().mapPort(getOption(MapPortUPnP).toBool(), value.toBool()); + } + break; + case MinimizeOnClose: + fMinimizeOnClose = value.toBool(); + settings.setValue("fMinimizeOnClose", fMinimizeOnClose); + break; - // default proxy - case ProxyUse: - if (settings.value("fUseProxy") != value) { - settings.setValue("fUseProxy", value.toBool()); - setRestartRequired(true); - } - break; - case ProxyIP: { - auto ip_port = GetProxySetting(settings, "addrProxy"); - if (!ip_port.is_set || ip_port.ip != value.toString()) { - ip_port.ip = value.toString(); - SetProxySetting(settings, "addrProxy", ip_port); - setRestartRequired(true); + // default proxy + case ProxyUse: + if (changed()) { + if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); + update(ProxyString(value.toBool(), getOption(ProxyIP).toString(), getOption(ProxyPort).toString())); + if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); + if (suffix.empty()) setRestartRequired(true); + } + break; + case ProxyIP: + if (changed()) { + if (suffix.empty() && !getOption(ProxyUse).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, value.toString(), getOption(ProxyPort).toString())); } + if (suffix.empty() && getOption(ProxyUse).toBool()) setRestartRequired(true); } break; - case ProxyPort: { - auto ip_port = GetProxySetting(settings, "addrProxy"); - if (!ip_port.is_set || ip_port.port != value.toString()) { - ip_port.port = value.toString(); - SetProxySetting(settings, "addrProxy", ip_port); - setRestartRequired(true); + case ProxyPort: + if (changed()) { + if (suffix.empty() && !getOption(ProxyUse).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, getOption(ProxyIP).toString(), value.toString())); } + if (suffix.empty() && getOption(ProxyUse).toBool()) setRestartRequired(true); } break; - // separate Tor proxy - case ProxyUseTor: - if (settings.value("fUseSeparateProxyTor") != value) { - settings.setValue("fUseSeparateProxyTor", value.toBool()); - setRestartRequired(true); - } - break; - case ProxyIPTor: { - auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); - if (!ip_port.is_set || ip_port.ip != value.toString()) { - ip_port.ip = value.toString(); - SetProxySetting(settings, "addrSeparateProxyTor", ip_port); - setRestartRequired(true); + // separate Tor proxy + case ProxyUseTor: + if (changed()) { + if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); + update(ProxyString(value.toBool(), getOption(ProxyIPTor).toString(), getOption(ProxyPortTor).toString())); + if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); + if (suffix.empty()) setRestartRequired(true); + } + break; + case ProxyIPTor: + if (changed()) { + if (suffix.empty() && !getOption(ProxyUseTor).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, value.toString(), getOption(ProxyPortTor).toString())); } + if (suffix.empty() && getOption(ProxyUseTor).toBool()) setRestartRequired(true); } break; - case ProxyPortTor: { - auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); - if (!ip_port.is_set || ip_port.port != value.toString()) { - ip_port.port = value.toString(); - SetProxySetting(settings, "addrSeparateProxyTor", ip_port); - setRestartRequired(true); + case ProxyPortTor: + if (changed()) { + if (suffix.empty() && !getOption(ProxyUseTor).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, getOption(ProxyIPTor).toString(), value.toString())); } + if (suffix.empty() && getOption(ProxyUseTor).toBool()) setRestartRequired(true); } break; #ifdef ENABLE_WALLET - case SpendZeroConfChange: - if (settings.value("bSpendZeroConfChange") != value) { - settings.setValue("bSpendZeroConfChange", value); - setRestartRequired(true); - } - break; - case ExternalSignerPath: - if (settings.value("external_signer_path") != value.toString()) { - settings.setValue("external_signer_path", value.toString()); - setRestartRequired(true); - } - break; - case ShowMasternodesTab: - if (settings.value("fShowMasternodesTab") != value) { - settings.setValue("fShowMasternodesTab", value); - setRestartRequired(true); - } - break; - case SubFeeFromAmount: - m_sub_fee_from_amount = value.toBool(); - settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount); - break; - case ShowGovernanceTab: - if (settings.value("fShowGovernanceTab") != value) { - settings.setValue("fShowGovernanceTab", value); - setRestartRequired(true); - } - break; - case CoinJoinEnabled: - if (settings.value("fCoinJoinEnabled") != value) { - settings.setValue("fCoinJoinEnabled", value.toBool()); - Q_EMIT coinJoinEnabledChanged(); - } - break; - case ShowAdvancedCJUI: - if (settings.value("fShowAdvancedCJUI") != value) { - fShowAdvancedCJUI = value.toBool(); - settings.setValue("fShowAdvancedCJUI", fShowAdvancedCJUI); - Q_EMIT AdvancedCJUIChanged(fShowAdvancedCJUI); - } - break; - case ShowCoinJoinPopups: - settings.setValue("fShowCoinJoinPopups", value); - break; - case LowKeysWarning: - settings.setValue("fLowKeysWarning", value); - break; - case CoinJoinSessions: - if (settings.value("nCoinJoinSessions") != value) { - node().coinJoinOptions().setSessions(value.toInt()); - settings.setValue("nCoinJoinSessions", node().coinJoinOptions().getSessions()); - Q_EMIT coinJoinRoundsChanged(); - } - break; - case CoinJoinRounds: - if (settings.value("nCoinJoinRounds") != value) - { - node().coinJoinOptions().setRounds(value.toInt()); - settings.setValue("nCoinJoinRounds", node().coinJoinOptions().getRounds()); - Q_EMIT coinJoinRoundsChanged(); - } - break; - case CoinJoinAmount: - if (settings.value("nCoinJoinAmount") != value) - { - node().coinJoinOptions().setAmount(value.toInt()); - settings.setValue("nCoinJoinAmount", node().coinJoinOptions().getAmount()); - Q_EMIT coinJoinAmountChanged(); - } - break; - case CoinJoinDenomsGoal: - if (settings.value("nCoinJoinDenomsGoal") != value) { - node().coinJoinOptions().setDenomsGoal(value.toInt()); - settings.setValue("nCoinJoinDenomsGoal", node().coinJoinOptions().getDenomsGoal()); - } - break; - case CoinJoinDenomsHardCap: - if (settings.value("nCoinJoinDenomsHardCap") != value) { - node().coinJoinOptions().setDenomsHardCap(value.toInt()); - settings.setValue("nCoinJoinDenomsHardCap", node().coinJoinOptions().getDenomsHardCap()); - } - break; - case CoinJoinMultiSession: - if (settings.value("fCoinJoinMultiSession") != value) - { - node().coinJoinOptions().setMultiSessionEnabled(value.toBool()); - settings.setValue("fCoinJoinMultiSession", node().coinJoinOptions().isMultiSessionEnabled()); - } - break; + case SpendZeroConfChange: + if (changed()) { + update(value.toBool()); + setRestartRequired(true); + } + break; + case ExternalSignerPath: + if (changed()) { + update(value.toString().toStdString()); + setRestartRequired(true); + } + break; + case ShowMasternodesTab: + if (settings.value("fShowMasternodesTab") != value) { + settings.setValue("fShowMasternodesTab", value); + setRestartRequired(true); + } + break; + case SubFeeFromAmount: + m_sub_fee_from_amount = value.toBool(); + settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount); + break; + case ShowGovernanceTab: + if (settings.value("fShowGovernanceTab") != value) { + settings.setValue("fShowGovernanceTab", value); + setRestartRequired(true); + } + break; + case CoinJoinEnabled: + if (changed()) { + update(value.toBool()); + Q_EMIT coinJoinEnabledChanged(); + } + break; + case ShowAdvancedCJUI: + if (settings.value("fShowAdvancedCJUI") != value) { + fShowAdvancedCJUI = value.toBool(); + settings.setValue("fShowAdvancedCJUI", fShowAdvancedCJUI); + Q_EMIT AdvancedCJUIChanged(fShowAdvancedCJUI); + } + break; + case ShowCoinJoinPopups: + settings.setValue("fShowCoinJoinPopups", value); + break; + case LowKeysWarning: + settings.setValue("fLowKeysWarning", value); + break; + case CoinJoinSessions: + if (changed()) { + node().coinJoinOptions().setSessions(value.toInt()); + update(value.toInt()); + Q_EMIT coinJoinRoundsChanged(); + } + break; + case CoinJoinRounds: + if (changed()) { + node().coinJoinOptions().setRounds(value.toInt()); + update(value.toInt()); + Q_EMIT coinJoinRoundsChanged(); + } + break; + case CoinJoinAmount: + if (changed()) { + node().coinJoinOptions().setAmount(value.toInt()); + update(value.toInt()); + Q_EMIT coinJoinAmountChanged(); + } + break; + case CoinJoinDenomsGoal: + if (changed()) { + node().coinJoinOptions().setDenomsGoal(value.toInt()); + update(value.toInt()); + } + break; + case CoinJoinDenomsHardCap: + if (changed()) { + node().coinJoinOptions().setDenomsHardCap(value.toInt()); + update(value.toInt()); + } + break; + case CoinJoinMultiSession: + if (changed()) { + node().coinJoinOptions().setMultiSessionEnabled(value.toBool()); + update(value.toBool()); + } + break; #endif - case DisplayUnit: - setDisplayUnit(value); - break; - case ThirdPartyTxUrls: - if (strThirdPartyTxUrls != value.toString()) { - strThirdPartyTxUrls = value.toString(); - settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls); - setRestartRequired(true); - } - break; + case DisplayUnit: + setDisplayUnit(value); + break; + case ThirdPartyTxUrls: + if (strThirdPartyTxUrls != value.toString()) { + strThirdPartyTxUrls = value.toString(); + settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls); + setRestartRequired(true); + } + break; #ifdef ENABLE_WALLET - case Digits: - if (settings.value("digits") != value) { - settings.setValue("digits", value); - setRestartRequired(true); - } - break; + case Digits: + if (settings.value("digits") != value) { + settings.setValue("digits", value); + setRestartRequired(true); + } + break; #endif // ENABLE_WALLET - case Theme: - // Set in AppearanceWidget::updateTheme slot now - // to allow instant theme changes. - break; - case FontFamily: - if (settings.value("fontFamily") != value) { - settings.setValue("fontFamily", value); - } - break; - case FontScale: - if (settings.value("fontScale") != value) { - settings.setValue("fontScale", value); - } - break; - case FontWeightNormal: { - int nWeight = GUIUtil::weightToArg(GUIUtil::g_font_registry.IdxToWeight(value.toInt())); - if (settings.value("fontWeightNormal") != nWeight) { - settings.setValue("fontWeightNormal", nWeight); - } - break; + case Theme: + // Set in AppearanceWidget::updateTheme slot now + // to allow instant theme changes. + break; + case FontFamily: + if (changed()) { + update(value.toString().toStdString()); } - case FontWeightBold: { - int nWeight = GUIUtil::weightToArg(GUIUtil::g_font_registry.IdxToWeight(value.toInt())); - if (settings.value("fontWeightBold") != nWeight) { - settings.setValue("fontWeightBold", nWeight); - } - break; + break; + case FontScale: + if (changed()) { + update(value.toInt()); } - case Language: - if (settings.value("language") != value) { - settings.setValue("language", value); - setRestartRequired(true); - } - break; - case FontForMoney: - { - const auto& new_font = value.value(); - if (m_font_money != new_font) { - settings.setValue("FontForMoney", FontChoiceToString(new_font)); - m_font_money = new_font; - } - Q_EMIT fontForMoneyChanged(getFontForMoney()); - break; + break; + case FontWeightNormal: { + if (changed()) { + update(GUIUtil::weightToArg(GUIUtil::g_font_registry.IdxToWeight(value.toInt()))); } + break; + } + case FontWeightBold: { + if (changed()) { + update(GUIUtil::weightToArg(GUIUtil::g_font_registry.IdxToWeight(value.toInt()))); + } + break; + } + case Language: + if (changed()) { + update(value.toString().toStdString()); + setRestartRequired(true); + } + break; + case FontForMoney: + { + const auto& new_font = value.value(); + if (m_font_money != new_font) { + settings.setValue("FontForMoney", FontChoiceToString(new_font)); + m_font_money = new_font; + } + Q_EMIT fontForMoneyChanged(getFontForMoney()); + break; + } #ifdef ENABLE_WALLET - case CoinControlFeatures: - fCoinControlFeatures = value.toBool(); - settings.setValue("fCoinControlFeatures", fCoinControlFeatures); - Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); - break; - case EnablePSBTControls: - m_enable_psbt_controls = value.toBool(); - settings.setValue("enable_psbt_controls", m_enable_psbt_controls); - break; - case KeepChangeAddress: - fKeepChangeAddress = value.toBool(); - settings.setValue("fKeepChangeAddress", fKeepChangeAddress); - Q_EMIT keepChangeAddressChanged(fKeepChangeAddress); - break; - case Prune: - if (settings.value("bPrune") != value) { - settings.setValue("bPrune", value); - setRestartRequired(true); - } - break; - case PruneSize: - if (settings.value("nPruneSize") != value) { - settings.setValue("nPruneSize", value); - setRestartRequired(true); - } - break; + case CoinControlFeatures: + fCoinControlFeatures = value.toBool(); + settings.setValue("fCoinControlFeatures", fCoinControlFeatures); + Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); + break; + case EnablePSBTControls: + m_enable_psbt_controls = value.toBool(); + settings.setValue("enable_psbt_controls", m_enable_psbt_controls); + break; + case KeepChangeAddress: + fKeepChangeAddress = value.toBool(); + settings.setValue("fKeepChangeAddress", fKeepChangeAddress); + Q_EMIT keepChangeAddressChanged(fKeepChangeAddress); + break; #endif // ENABLE_WALLET - case DatabaseCache: - if (settings.value("nDatabaseCache") != value) { - settings.setValue("nDatabaseCache", value); - setRestartRequired(true); - } - break; - case ThreadsScriptVerif: - if (settings.value("nThreadsScriptVerif") != value) { - settings.setValue("nThreadsScriptVerif", value); - setRestartRequired(true); - } - break; - case Listen: - if (settings.value("fListen") != value) { - settings.setValue("fListen", value); - setRestartRequired(true); - } - break; - case Server: - if (settings.value("server") != value) { - settings.setValue("server", value); - setRestartRequired(true); + case Prune: + if (changed()) { + if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); + update(PruneSetting(value.toBool(), getOption(PruneSize).toInt())); + if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); + if (suffix.empty()) setRestartRequired(true); + } + break; + case PruneSize: + if (changed()) { + if (suffix.empty() && !getOption(Prune).toBool()) { + setOption(option, value, "-prev"); + } else { + update(PruneSetting(true, ParsePruneSizeGB(value))); } - break; - default: - break; + if (suffix.empty() && getOption(Prune).toBool()) setRestartRequired(true); } + break; + case DatabaseCache: + if (changed()) { + update(static_cast(value.toLongLong())); + setRestartRequired(true); + } + break; + case ThreadsScriptVerif: + if (changed()) { + update(static_cast(value.toLongLong())); + setRestartRequired(true); + } + break; + case Listen: + case Server: + if (changed()) { + update(value.toBool()); + setRestartRequired(true); + } + break; + case MaskValues: + m_mask_values = value.toBool(); + settings.setValue("mask_values", m_mask_values); + break; + default: + break; } - Q_EMIT dataChanged(index, index); - return successful; } @@ -1041,6 +1087,15 @@ void OptionsModel::checkAndMigrate() settings.remove("bUsePrivateSend"); } + if (settingsVersion < 230100) { + settings.remove("fPrivateSendEnabled"); + settings.remove("fPrivateSendMultiSession"); + settings.remove("fShowAdvancedPSUI"); + settings.remove("fShowPrivateSendPopups"); + settings.remove("nPrivateSendAmount"); + settings.remove("nPrivateSendRounds"); + } + settings.setValue(strSettingsVersionKey, CLIENT_VERSION); } @@ -1062,30 +1117,67 @@ void OptionsModel::checkAndMigrate() settings.setValue("theme", GUIUtil::getDefaultTheme()); } - // begin PrivateSend -> CoinJoin migration - if (settings.contains("nPrivateSendRounds") && !settings.contains("nCoinJoinRounds")) { - settings.setValue("nCoinJoinRounds", settings.value("nPrivateSendRounds").toInt()); - settings.remove("nPrivateSendRounds"); - } - if (settings.contains("nPrivateSendAmount") && !settings.contains("nCoinJoinAmount")) { - settings.setValue("nCoinJoinAmount", settings.value("nPrivateSendAmount").toInt()); - settings.remove("nPrivateSendAmount"); - } - if (settings.contains("fPrivateSendEnabled") && !settings.contains("fCoinJoinEnabled")) { - settings.setValue("fCoinJoinEnabled", settings.value("fPrivateSendEnabled").toBool()); - settings.remove("fPrivateSendEnabled"); - } - if (settings.contains("fPrivateSendMultiSession") && !settings.contains("fCoinJoinMultiSession")) { - settings.setValue("fCoinJoinMultiSession", settings.value("fPrivateSendMultiSession").toBool()); - settings.remove("fPrivateSendMultiSession"); - } - if (settings.contains("fShowAdvancedPSUI") && !settings.contains("fShowAdvancedCJUI")) { - settings.setValue("fShowAdvancedCJUI", settings.value("fShowAdvancedPSUI").toBool()); - settings.remove("fShowAdvancedPSUI"); - } - if (settings.contains("fShowPrivateSendPopups") && !settings.contains("fShowCoinJoinPopups")) { - settings.setValue("fShowCoinJoinPopups", settings.value("fShowPrivateSendPopups").toBool()); - settings.remove("fShowPrivateSendPopups"); + // Migrate and delete legacy GUI settings that have now moved to /settings.json. + auto migrate_setting = [&](OptionID option, const QString& qt_name) { + if (!settings.contains(qt_name)) return; + QVariant value = settings.value(qt_name); + if (node().getPersistentSetting(SettingName(option)).isNull()) { + if (option == ProxyIP) { + ProxySetting parsed = ParseProxyString(value.toString()); + setOption(ProxyIP, parsed.ip); + setOption(ProxyPort, parsed.port); + } else if (option == ProxyIPTor) { + ProxySetting parsed = ParseProxyString(value.toString()); + setOption(ProxyIPTor, parsed.ip); + setOption(ProxyPortTor, parsed.port); + } else if (option == FontWeightNormal || option == FontWeightBold) { + setOption(option, WeightArgToIdx(/*is_bold=*/option == FontWeightBold, value.toInt())); + } else { + setOption(option, value); + } + } + settings.remove(qt_name); + }; + + migrate_setting(DatabaseCache, "nDatabaseCache"); + migrate_setting(ThreadsScriptVerif, "nThreadsScriptVerif"); +#ifdef ENABLE_WALLET + migrate_setting(SpendZeroConfChange, "bSpendZeroConfChange"); + migrate_setting(ExternalSignerPath, "external_signer_path"); +#endif + migrate_setting(MapPortUPnP, "fUseUPnP"); + migrate_setting(MapPortNatpmp, "fUseNatpmp"); + migrate_setting(Listen, "fListen"); + migrate_setting(Server, "server"); + migrate_setting(PruneSize, "nPruneSize"); + migrate_setting(Prune, "bPrune"); + migrate_setting(ProxyIP, "addrProxy"); + migrate_setting(ProxyUse, "fUseProxy"); + migrate_setting(ProxyIPTor, "addrSeparateProxyTor"); + migrate_setting(ProxyUseTor, "fUseSeparateProxyTor"); + migrate_setting(Language, "language"); + + //! Dash + if (GUIUtil::fontsLoaded()) { + migrate_setting(FontFamily, "fontFamily"); + migrate_setting(FontScale, "fontScale"); + migrate_setting(FontWeightBold, "fontWeightBold"); + migrate_setting(FontWeightNormal, "fontWeightNormal"); } - // end PrivateSend -> CoinJoin migration +#ifdef ENABLE_WALLET + migrate_setting(CoinJoinAmount, "nCoinJoinAmount"); + migrate_setting(CoinJoinDenomsGoal, "nCoinJoinDenomsGoal"); + migrate_setting(CoinJoinDenomsHardCap, "nCoinJoinDenomsHardCap"); + migrate_setting(CoinJoinEnabled, "fCoinJoinEnabled"); + migrate_setting(CoinJoinMultiSession, "fCoinJoinMultiSession"); + migrate_setting(CoinJoinRounds, "nCoinJoinRounds"); + migrate_setting(CoinJoinSessions, "nCoinJoinSessions"); +#endif + + // In case migrating QSettings caused any settings value to change, rerun + // parameter interaction code to update other settings. This is particularly + // important for the -listen setting, which should cause -listenonion, -upnp, + // and other settings to default to false if it was set to false. + // (https://github.com/bitcoin-core/gui/issues/567). + node().initParameterInteraction(); } diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 1220c45ca192..1bf20c7b0efc 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -16,6 +16,7 @@ #include #include +struct bilingual_str; namespace interfaces { class Node; } @@ -44,7 +45,7 @@ class OptionsModel : public QAbstractListModel Q_OBJECT public: - explicit OptionsModel(QObject *parent = nullptr, bool resetSettings = false); + explicit OptionsModel(interfaces::Node& node, QObject *parent = nullptr); enum OptionID { StartAtStartup, // bool @@ -93,6 +94,7 @@ class OptionsModel : public QAbstractListModel Listen, // bool Server, // bool EnablePSBTControls, // bool + MaskValues, // bool OptionIDRowCount, }; @@ -105,12 +107,14 @@ class OptionsModel : public QAbstractListModel static inline const FontChoice UseBestSystemFont{FontChoiceAbstract::BestSystemFont}; static QFont getFontForChoice(const FontChoice& fc); - void Init(bool resetSettings = false); + bool Init(bilingual_str& error); void Reset(); int rowCount(const QModelIndex & parent = QModelIndex()) const override; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override; + QVariant getOption(OptionID option, const std::string& suffix="") const; + bool setOption(OptionID option, const QVariant& value, const std::string& suffix=""); /** Updates current unit in memory, settings and emits displayUnitChanged(new_unit) signal */ void setDisplayUnit(const QVariant& new_unit); @@ -131,19 +135,17 @@ class OptionsModel : public QAbstractListModel void emitCoinJoinEnabledChanged(); /* Explicit setters */ - void SetPruneEnabled(bool prune, bool force = false); - void SetPruneTargetGB(int prune_target_gb, bool force = false); + void SetPruneTargetGB(int prune_target_gb); /* Restart flag helper */ void setRestartRequired(bool fRequired); bool isRestartRequired() const; bool resetSettingsOnShutdown{false}; - interfaces::Node& node() const { assert(m_node); return *m_node; } - void setNode(interfaces::Node& node) { assert(!m_node); m_node = &node; } + interfaces::Node& node() const { return m_node; } private: - interfaces::Node* m_node = nullptr; + interfaces::Node& m_node; /* Qt-only settings */ bool m_show_tray_icon; bool fMinimizeToTray; @@ -155,8 +157,10 @@ class OptionsModel : public QAbstractListModel bool fCoinControlFeatures; bool m_sub_fee_from_amount; bool m_enable_psbt_controls; + bool m_mask_values; bool fKeepChangeAddress; bool fShowAdvancedCJUI; + /* settings that were overridden by command-line */ QString strOverriddenByCommandLine; @@ -168,6 +172,7 @@ class OptionsModel : public QAbstractListModel // Check settings version and upgrade default values if required void checkAndMigrate(); + Q_SIGNALS: void displayUnitChanged(BitcoinUnit unit); void coinJoinEnabledChanged(); diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 6c9f06b956ad..8ed12617a19f 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -204,6 +204,7 @@ void OverviewPage::handleTransactionClicked(const QModelIndex &index) void OverviewPage::setPrivacy(bool privacy) { m_privacy = privacy; + clientModel->getOptionsModel()->setOption(OptionsModel::OptionID::MaskValues, privacy); if (m_balances.balance != -1) { setBalance(m_balances); coinJoinStatus(true); diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 3aa570ce793f..e8216d5531b6 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -122,7 +122,9 @@ void TestAddAddressesToSendBook(interfaces::Node& node) check_addbook_size(2); // Initialize relevant QT models. - OptionsModel optionsModel; + OptionsModel optionsModel(node); + bilingual_str error; + QVERIFY(optionsModel.Init(error)); ClientModel clientModel(node, &optionsModel); WalletContext& context = *node.walletLoader().context(); AddWallet(context, wallet); diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index 1104192ed8bd..9b97b2bad89e 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -72,16 +72,11 @@ void AppTests::appTests() } #endif - fs::create_directories([] { - BasicTestingSetup test{CBaseChainParams::REGTEST}; // Create a temp data directory to backup the gui settings to - return gArgs.GetDataDirNet() / "blocks"; - }()); - qRegisterMetaType("interfaces::BlockAndHeaderTipInfo"); m_app.parameterSetup(); GUIUtil::loadFonts(); GUIUtil::setApplicationFont(); - m_app.createOptionsModel(true /* reset settings */); + QVERIFY(m_app.createOptionsModel(true /* reset settings */)); QScopedPointer style( NetworkStyle::instantiate(Params().NetworkIDString())); m_app.createWindow(style.data()); diff --git a/src/qt/test/optiontests.cpp b/src/qt/test/optiontests.cpp index 343755103cf2..17ffeb220b69 100644 --- a/src/qt/test/optiontests.cpp +++ b/src/qt/test/optiontests.cpp @@ -14,8 +14,64 @@ #include -//! Entry point for BitcoinApplication tests. -void OptionTests::optionTests() +#include + +OptionTests::OptionTests(interfaces::Node& node) : m_node(node) +{ + gArgs.LockSettings([&](util::Settings& s) { m_previous_settings = s; }); +} + +void OptionTests::init() +{ + // reset args + gArgs.LockSettings([&](util::Settings& s) { s = m_previous_settings; }); + gArgs.ClearPathCache(); +} + +void OptionTests::migrateSettings() +{ + // Set legacy QSettings and verify that they get cleared and migrated to + // settings.json + QSettings settings; + settings.setValue("nDatabaseCache", 600); + settings.setValue("nThreadsScriptVerif", 12); + settings.setValue("fUseUPnP", false); + settings.setValue("fListen", false); + settings.setValue("bPrune", true); + settings.setValue("nPruneSize", 3); + settings.setValue("fUseProxy", true); + settings.setValue("addrProxy", "proxy:123"); + settings.setValue("fUseSeparateProxyTor", true); + settings.setValue("addrSeparateProxyTor", "onion:234"); + + settings.sync(); + + OptionsModel options{m_node}; + bilingual_str error; + QVERIFY(options.Init(error)); + QVERIFY(!settings.contains("nDatabaseCache")); + QVERIFY(!settings.contains("nThreadsScriptVerif")); + QVERIFY(!settings.contains("fUseUPnP")); + QVERIFY(!settings.contains("fListen")); + QVERIFY(!settings.contains("bPrune")); + QVERIFY(!settings.contains("nPruneSize")); + QVERIFY(!settings.contains("fUseProxy")); + QVERIFY(!settings.contains("addrProxy")); + QVERIFY(!settings.contains("fUseSeparateProxyTor")); + QVERIFY(!settings.contains("addrSeparateProxyTor")); + + std::ifstream file(gArgs.GetDataDirNet() / "settings.json"); + QCOMPARE(std::string(std::istreambuf_iterator(file), std::istreambuf_iterator()).c_str(), "{\n" + " \"dbcache\": \"600\",\n" + " \"listen\": false,\n" + " \"onion\": \"onion:234\",\n" + " \"par\": \"12\",\n" + " \"proxy\": \"proxy:123\",\n" + " \"prune\": \"2861\"\n" + "}\n"); +} + +void OptionTests::integerGetArgBug() { // Test regression https://github.com/bitcoin/bitcoin/issues/24457. Ensure // that setting integer prune value doesn't cause an exception to be thrown @@ -25,7 +81,8 @@ void OptionTests::optionTests() settings.rw_settings["prune"] = 3814; }); gArgs.WriteSettingsFile(); - OptionsModel{}; + bilingual_str error; + QVERIFY(OptionsModel{m_node}.Init(error)); gArgs.LockSettings([&](util::Settings& settings) { settings.rw_settings.erase("prune"); }); @@ -38,8 +95,6 @@ void OptionTests::parametersInteraction() // It was fixed via https://github.com/bitcoin-core/gui/pull/568. // With fListen=false in ~/.config/Bitcoin/Bitcoin-Qt.conf and all else left as default, // bitcoin-qt should set both -listen and -listenonion to false and start successfully. - gArgs.ClearPathCache(); - gArgs.LockSettings([&](util::Settings& s) { s.forced_settings.erase("listen"); s.forced_settings.erase("listenonion"); @@ -50,7 +105,8 @@ void OptionTests::parametersInteraction() QSettings settings; settings.setValue("fListen", false); - OptionsModel{}; + bilingual_str error; + QVERIFY(OptionsModel{m_node}.Init(error)); const bool expected{false}; diff --git a/src/qt/test/optiontests.h b/src/qt/test/optiontests.h index d5a3bfd6b4a0..57ec8bd0f2ab 100644 --- a/src/qt/test/optiontests.h +++ b/src/qt/test/optiontests.h @@ -6,6 +6,8 @@ #define BITCOIN_QT_TEST_OPTIONTESTS_H #include +#include +#include #include @@ -13,15 +15,18 @@ class OptionTests : public QObject { Q_OBJECT public: - explicit OptionTests(interfaces::Node& node) : m_node(node) {} + explicit OptionTests(interfaces::Node& node); private Q_SLOTS: - void optionTests(); + void init(); // called before each test function execution. + void migrateSettings(); + void integerGetArgBug(); void parametersInteraction(); void extractFilter(); private: interfaces::Node& m_node; + util::Settings m_previous_settings; }; #endif // BITCOIN_QT_TEST_OPTIONTESTS_H diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index a043b24ffe9c..08018e007c4f 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -58,9 +58,10 @@ int main(int argc, char* argv[]) // regtest params. // // All tests must use their own testing setup (if needed). - { + fs::create_directories([] { BasicTestingSetup dummy{CBaseChainParams::REGTEST}; - } + return gArgs.GetDataDirNet() / "blocks"; + }()); std::unique_ptr init = interfaces::MakeGuiInit(argc, argv); gArgs.ForceSetArg("-listen", "0"); diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 9cd8d1ceb885..327d7eacc07b 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -153,7 +153,9 @@ void TestGUI(interfaces::Node& node) // Create widgets for sending coins and listing transactions. SendCoinsDialog sendCoinsDialog; TransactionView transactionView; - OptionsModel optionsModel; + OptionsModel optionsModel(node); + bilingual_str error; + QVERIFY(optionsModel.Init(error)); ClientModel clientModel(node, &optionsModel); WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel); sendCoinsDialog.setModel(&walletModel);