optionsmodel.cpp raw

   1  // Copyright (c) 2011-2022 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <limenka-build-config.h> // IWYU pragma: keep
   6  
   7  #include <qt/optionsmodel.h>
   8  
   9  #include <qt/limenkaunits.h>
  10  #include <qt/guiconstants.h>
  11  #include <qt/guiutil.h>
  12  #include <qt/tonalutils.h>
  13  
  14  #include <chainparams.h>
  15  #include <consensus/params.h>
  16  #include <common/args.h>
  17  #include <consensus/consensus.h>
  18  #include <index/blockfilterindex.h>
  19  #include <interfaces/node.h>
  20  #include <kernel/mempool_options.h> // for DEFAULT_MAX_MEMPOOL_SIZE_MB, DEFAULT_MEMPOOL_EXPIRY_HOURS
  21  #include <mapport.h>
  22  #include <policy/settings.h>
  23  #include <net.h>
  24  #include <net_processing.h>
  25  #include <netbase.h>
  26  #include <node/caches.h>
  27  #include <node/dbcache.h>
  28  #include <node/chainstatemanager_args.h>
  29  #include <node/context.h>
  30  #include <node/mempool_args.h> // for ParseDustDynamicOpt
  31  #include <outputtype.h>
  32  #include <policy/settings.h>
  33  #include <util/moneystr.h> // for FormatMoney
  34  #include <util/string.h>
  35  #include <validation.h>    // For DEFAULT_SCRIPTCHECK_THREADS
  36  #include <wallet/wallet.h> // For DEFAULT_SPEND_ZEROCONF_CHANGE
  37  
  38  #ifdef ENABLE_WALLET
  39  #include <interfaces/wallet.h>
  40  #endif
  41  
  42  #include <chrono>
  43  #include <string>
  44  #include <unordered_set>
  45  #include <utility>
  46  
  47  #include <QDebug>
  48  #include <QLatin1Char>
  49  #include <QSettings>
  50  #include <QStringList>
  51  #include <QVariant>
  52  
  53  #include <univalue.h>
  54  
  55  const char *DEFAULT_GUI_PROXY_HOST = "127.0.0.1";
  56  
  57  static QString GetDefaultProxyAddress();
  58  
  59  /** Map GUI option ID to node setting name. */
  60  static const char* SettingName(OptionsModel::OptionID option)
  61  {
  62      switch (option) {
  63      case OptionsModel::DatabaseCache: return "dbcache";
  64      case OptionsModel::ThreadsScriptVerif: return "par";
  65      case OptionsModel::SpendZeroConfChange: return "spendzeroconfchange";
  66      case OptionsModel::ExternalSignerPath: return "signer";
  67      case OptionsModel::MapPortUPnP: return "upnp";
  68      case OptionsModel::MapPortNatpmp: return "natpmp";
  69      case OptionsModel::Listen: return "listen";
  70      case OptionsModel::Server: return "server";
  71      case OptionsModel::walletrbf: return "walletrbf";
  72      case OptionsModel::addresstype: return "addresstype";
  73      case OptionsModel::PruneSizeMiB: return "prune";
  74      case OptionsModel::PruneTristate: return "prune";
  75      case OptionsModel::ProxyIP: return "proxy";
  76      case OptionsModel::ProxyPort: return "proxy";
  77      case OptionsModel::ProxyUse: return "proxy";
  78      case OptionsModel::ProxyIPTor: return "onion";
  79      case OptionsModel::ProxyPortTor: return "onion";
  80      case OptionsModel::ProxyUseTor: return "onion";
  81      case OptionsModel::Language: return "lang";
  82      case OptionsModel::maxuploadtarget: return "maxuploadtarget";
  83      case OptionsModel::peerbloomfilters: return "peerbloomfilters";
  84      case OptionsModel::peerblockfilters: return "peerblockfilters";
  85      case OptionsModel::mempoolreplacement: return "mempoolreplacement";
  86      case OptionsModel::mempooltruc: return "mempooltruc";
  87      case OptionsModel::maxorphantx: return "maxorphantx";
  88      case OptionsModel::maxmempool: return "maxmempool";
  89      case OptionsModel::incrementalrelayfee: return "incrementalrelayfee";
  90      case OptionsModel::mempoolexpiry: return "mempoolexpiry";
  91      case OptionsModel::rejectunknownscripts: return "rejectunknownscripts";
  92      case OptionsModel::rejectunknownwitness: return "rejectunknownwitness";
  93      case OptionsModel::rejectparasites: return "rejectparasites";
  94      case OptionsModel::rejecttokens: return "rejecttokens";
  95      case OptionsModel::subdustfeepenalty: return "subdustfeepenalty";
  96      case OptionsModel::rejectspkreuse: return "rejectspkreuse";
  97      case OptionsModel::minrelaytxfee: return "minrelaytxfee";
  98      case OptionsModel::minrelaycoinblocks: return "minrelaycoinblocks";
  99      case OptionsModel::minrelaymaturity: return "minrelaymaturity";
 100      case OptionsModel::bytespersigop: return "bytespersigop";
 101      case OptionsModel::bytespersigopstrict: return "bytespersigopstrict";
 102      case OptionsModel::limitancestorcount: return "limitancestorcount";
 103      case OptionsModel::limitancestorsize: return "limitancestorsize";
 104      case OptionsModel::limitdescendantcount: return "limitdescendantcount";
 105      case OptionsModel::limitdescendantsize: return "limitdescendantsize";
 106      case OptionsModel::rejectbarepubkey: return "rejectbarepubkey";
 107      case OptionsModel::rejectbaremultisig: return "rejectbaremultisig";
 108      case OptionsModel::permitephemeral: return "permitephemeral";
 109      case OptionsModel::rejectbareanchor: return "rejectbareanchor";
 110      case OptionsModel::rejectbaredatacarrier: return "rejectbaredatacarrier";
 111      case OptionsModel::maxscriptsize: return "maxscriptsize";
 112      case OptionsModel::maxtxlegacysigops: return "maxtxlegacysigops";
 113      case OptionsModel::datacarriercost: return "datacarriercost";
 114      case OptionsModel::datacarriersize: return "datacarriersize";
 115      case OptionsModel::rejectnonstddatacarrier: return "rejectnonstddatacarrier";
 116      case OptionsModel::dustrelayfee: return "dustrelayfee";
 117      case OptionsModel::dustdynamic: return "dustdynamic";
 118      case OptionsModel::blockmintxfee: return "blockmintxfee";
 119      case OptionsModel::blockmaxsize: return "blockmaxsize";
 120      case OptionsModel::blockprioritysize: return "blockprioritysize";
 121      case OptionsModel::blockmaxweight: return "blockmaxweight";
 122      case OptionsModel::blockreconstructionextratxn: return "blockreconstructionextratxn";
 123      case OptionsModel::blockreconstructionextratxnsize: return "blockreconstructionextratxnsize";
 124      default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option));
 125      }
 126  }
 127  
 128  /** Call node.updateRwSetting() with Limenka 22.x workaround. */
 129  static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID option, const std::string& suffix, const common::SettingsValue& value)
 130  {
 131      if (value.isNum() &&
 132          (option == OptionsModel::DatabaseCache ||
 133           option == OptionsModel::ThreadsScriptVerif ||
 134           option == OptionsModel::PruneTristate ||
 135           option == OptionsModel::PruneSizeMiB)) {
 136          // Write certain old settings as strings, even though they are numbers,
 137          // because Limenka 22.x releases try to read these specific settings as
 138          // strings in addOverriddenOption() calls at startup, triggering
 139          // uncaught exceptions in UniValue::get_str(). These errors were fixed
 140          // in later releases by https://github.com/limenka/limenka/pull/24498.
 141          // If new numeric settings are added, they can be written as numbers
 142          // instead of strings, because limenka 22.x will not try to read these.
 143          node.updateRwSetting(SettingName(option) + suffix, value.getValStr());
 144      } else {
 145          node.updateRwSetting(SettingName(option) + suffix, value);
 146      }
 147  }
 148  
 149  //! Convert enabled/size values to limenka -prune setting.
 150  static common::SettingsValue PruneSettingFromMiB(Qt::CheckState prune_enabled, int prune_size_mib)
 151  {
 152      assert(prune_enabled != Qt::Checked || prune_size_mib >= 1); // PruneSizeMiB and ParsePruneSizeMiB never return less
 153      switch (prune_enabled) {
 154      case Qt::Unchecked:
 155          return 0;
 156      case Qt::PartiallyChecked:
 157          return 1;
 158      default:
 159          return prune_size_mib;
 160      }
 161  }
 162  
 163  //! Get pruning enabled value to show in GUI from limenka -prune setting.
 164  static bool PruneEnabled(const common::SettingsValue& prune_setting)
 165  {
 166      // -prune=1 setting is manual pruning mode, so disabled for purposes of the gui
 167      return SettingToInt(prune_setting, 0) > 1;
 168  }
 169  
 170  //! Get pruning enabled value to show in GUI from limenka -prune setting.
 171  static Qt::CheckState PruneSettingAsTristate(const common::SettingsValue& prune_setting)
 172  {
 173      switch (SettingToInt(prune_setting, 0)) {
 174      case 0:
 175          return Qt::Unchecked;
 176      case 1:
 177          return Qt::PartiallyChecked;
 178      default:
 179          return Qt::Checked;
 180      }
 181  }
 182  
 183  //! Get pruning size value to show in GUI from limenka -prune setting. If
 184  //! pruning is not enabled, just show default recommended pruning size (2GB).
 185  static int PruneSizeAsMiB(const common::SettingsValue& prune_setting)
 186  {
 187      int value = SettingToInt(prune_setting, 0);
 188      return value > 1 ? value : DEFAULT_PRUNE_TARGET_MiB;
 189  }
 190  
 191  struct ProxySetting {
 192      bool is_set;
 193      QString ip;
 194      QString port;
 195  };
 196  static ProxySetting ParseProxyString(const std::string& proxy);
 197  static std::string ProxyString(bool is_set, QString ip, QString port);
 198  
 199  static const QLatin1String fontchoice_str_embedded{"embedded"};
 200  static const QLatin1String fontchoice_str_best_system{"best_system"};
 201  static const QString fontchoice_str_custom_prefix{QStringLiteral("custom, ")};
 202  
 203  static const std::map<OutputType, std::pair<const char*, const char*>> UntranslatedOutputTypeDescriptions{
 204      {OutputType::LEGACY, {
 205          QT_TRANSLATE_NOOP("Output type name", "Base58 (Legacy)"),
 206          QT_TRANSLATE_NOOP("Output type description", "Widest compatibility and best for health of the Limenka network, but may result in higher fees later. Recommended."),
 207      }},
 208      {OutputType::P2SH_SEGWIT, {
 209          QT_TRANSLATE_NOOP("Output type name", "Base58 (P2SH Segwit)"),
 210          QT_TRANSLATE_NOOP("Output type description", "Compatible with most older wallets, and may result in lower fees than Legacy."),
 211      }},
 212      {OutputType::BECH32, {
 213          QT_TRANSLATE_NOOP("Output type name", "Native Segwit (Bech32)"),
 214          QT_TRANSLATE_NOOP("Output type description", "Lower fees than Base58, but some old wallets don't support it."),
 215      }},
 216      {OutputType::BECH32M, {
 217          QT_TRANSLATE_NOOP("Output type name", "Taproot (Bech32m)"),
 218          QT_TRANSLATE_NOOP("Output type description", "Lowest fees, but wallet support is still limited."),
 219      }},
 220  };
 221  
 222  std::pair<QString, QString> GetOutputTypeDescription(const OutputType type)
 223  {
 224      auto& untr = UntranslatedOutputTypeDescriptions.at(type);
 225      QString text = QCoreApplication::translate("Output type name", untr.first);
 226      QString tooltip = QCoreApplication::translate("Output type description", untr.second);
 227      return std::make_pair(text, tooltip);
 228  }
 229  
 230  QString OptionsModel::FontChoiceToString(const OptionsModel::FontChoice& f)
 231  {
 232      if (std::holds_alternative<FontChoiceAbstract>(f)) {
 233          if (f == UseBestSystemFont) {
 234              return fontchoice_str_best_system;
 235          } else {
 236              return fontchoice_str_embedded;
 237          }
 238      }
 239      return fontchoice_str_custom_prefix + std::get<QFont>(f).toString();
 240  }
 241  
 242  OptionsModel::FontChoice OptionsModel::FontChoiceFromString(const QString& s)
 243  {
 244      if (s == fontchoice_str_best_system) {
 245          return FontChoiceAbstract::BestSystemFont;
 246      } else if (s == fontchoice_str_embedded) {
 247          return FontChoiceAbstract::EmbeddedFont;
 248      } else if (s.startsWith(fontchoice_str_custom_prefix)) {
 249          QFont f;
 250          f.fromString(s.mid(fontchoice_str_custom_prefix.size()));
 251          return f;
 252      } else {
 253          return FontChoiceAbstract::EmbeddedFont;  // default
 254      }
 255  }
 256  
 257  static QString CanonicalMempoolReplacement(const OptionsModel& model)
 258  {
 259      switch (model.node().mempool().m_opts.rbf_policy) {
 260      case RBFPolicy::Never:  return "never";
 261      case RBFPolicy::OptIn:  return "fee,optin";
 262      case RBFPolicy::Always: return "fee,-optin";
 263      }
 264      assert(0);
 265  }
 266  
 267  static QString CanonicalMempoolTRUC(const OptionsModel& model)
 268  {
 269      switch (model.node().mempool().m_opts.truc_policy) {
 270      case TRUCPolicy::Reject:  return "reject";
 271      case TRUCPolicy::Accept:  return "accept";
 272      case TRUCPolicy::Enforce: return "enforce";
 273      }
 274      assert(0);
 275  }
 276  
 277  static QString CanonicalPermitEphemeral(const OptionsModel& model)
 278  {
 279      const auto& opts = model.node().mempool().m_opts;
 280      if (!(opts.permitephemeral_anchor || opts.permitephemeral_send)) {
 281          return "reject";
 282      }
 283      return
 284          QString(opts.permitephemeral_anchor ? "" : "-") + "anchor," +
 285          (opts.permitephemeral_send ? "" : "-") + "send," +
 286          (opts.permitephemeral_dust ? "" : "-") + "dust";
 287  }
 288  
 289  OptionsModel::OptionsModel(interfaces::Node& node, QObject *parent) :
 290      QAbstractListModel(parent), m_node{node}
 291  {
 292  }
 293  
 294  void OptionsModel::addOverriddenOption(const std::string &option)
 295  {
 296      strOverriddenByCommandLine += QString::fromStdString(option) + "=" + QString::fromStdString(gArgs.GetArg(option, "")) + " ";
 297  }
 298  
 299  // Writes all missing QSettings with their default values
 300  bool OptionsModel::Init(bilingual_str& error)
 301  {
 302      // Initialize display settings from stored settings.
 303      language = QString::fromStdString(SettingToString(node().getPersistentSetting("lang"), ""));
 304  
 305      checkAndMigrate();
 306  
 307      QSettings settings;
 308  
 309      // Ensure restart flag is unset on client startup
 310      setRestartRequired(false);
 311  
 312      // These are Qt-only settings:
 313  
 314      // Window
 315      if (!settings.contains("fHideTrayIcon")) {
 316          settings.setValue("fHideTrayIcon", false);
 317      }
 318      m_show_tray_icon = !settings.value("fHideTrayIcon").toBool();
 319      Q_EMIT showTrayIconChanged(m_show_tray_icon);
 320  
 321      if (!settings.contains("fMinimizeToTray"))
 322          settings.setValue("fMinimizeToTray", false);
 323      fMinimizeToTray = settings.value("fMinimizeToTray").toBool() && m_show_tray_icon;
 324  
 325      if (!settings.contains("fMinimizeOnClose"))
 326          settings.setValue("fMinimizeOnClose", false);
 327      fMinimizeOnClose = settings.value("fMinimizeOnClose").toBool();
 328  
 329      // Display
 330      if (!settings.contains("DisplayLimenkaUnit")) {
 331          auto init_unit = LimenkaUnit::BTC;
 332          if (settings.contains("nDisplayUnit")) {
 333              // Migrate to new setting
 334              init_unit = LimenkaUnits::FromSetting(settings.value("nDisplayUnit").toString(), init_unit);
 335          }
 336          settings.setValue("DisplayLimenkaUnit", QVariant::fromValue(init_unit));
 337      }
 338  
 339      constexpr auto unit_set_to_variant = [](LimenkaUnit& out, const QVariant& unit_variant){
 340          if (unit_variant.isNull()) return false;
 341          if (!unit_variant.canConvert<LimenkaUnit>()) return false;
 342          const auto unit = unit_variant.value<LimenkaUnit>();
 343          if (!LimenkaUnits::availableUnits().contains(unit)) return false;
 344          out = unit;
 345          return true;
 346      };
 347      if (!unit_set_to_variant(m_display_limenka_unit, settings.value("DisplayLimenkaUnitKnots"))) {
 348          if (!unit_set_to_variant(m_display_limenka_unit, settings.value("DisplayLimenkaUnit"))) {
 349              m_display_limenka_unit = LimenkaUnit::BTC;
 350          }
 351      }
 352  
 353      if (!settings.contains("bDisplayAddresses"))
 354          settings.setValue("bDisplayAddresses", false);
 355      bDisplayAddresses = settings.value("bDisplayAddresses", false).toBool();
 356  
 357      if (!settings.contains("strThirdPartyTxUrls"))
 358          settings.setValue("strThirdPartyTxUrls", "");
 359      strThirdPartyTxUrls = settings.value("strThirdPartyTxUrls", "").toString();
 360  
 361      if (!settings.contains("fCoinControlFeatures"))
 362          settings.setValue("fCoinControlFeatures", false);
 363      fCoinControlFeatures = settings.value("fCoinControlFeatures", false).toBool();
 364  
 365      if (!settings.contains("enable_psbt_controls")) {
 366          settings.setValue("enable_psbt_controls", false);
 367      }
 368      m_enable_psbt_controls = settings.value("enable_psbt_controls", false).toBool();
 369  
 370      // These are shared with the core or have a command-line parameter
 371      // and we want command-line parameters to overwrite the GUI settings.
 372      std::unordered_set<std::string> checked_settings;
 373      for (OptionID option : {DatabaseCache, ThreadsScriptVerif, SpendZeroConfChange, ExternalSignerPath, MapPortUPnP,
 374                              MapPortNatpmp, Listen, Server, PruneTristate, ProxyUse, ProxyUseTor, Language}) {
 375          // isSettingIgnored will have a false positive here during first-run prune changes
 376          if (option == PruneTristate && m_prune_forced_by_gui) continue;
 377  
 378          std::string setting = SettingName(option);
 379          checked_settings.insert(setting);
 380          if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting);
 381          try {
 382              getOption(option);
 383          } catch (const std::exception& e) {
 384              // This handles exceptions thrown by univalue that can happen if
 385              // settings in settings.json don't have the expected types.
 386              error.original = strprintf("Could not read setting \"%s\", %s.", setting, e.what());
 387              error.translated = tr("Could not read setting \"%1\", %2.").arg(QString::fromStdString(setting), e.what()).toStdString();
 388              return false;
 389          }
 390      }
 391  
 392      if (m_prune_forced_by_gui) checked_settings.insert("prune");
 393      for (OptionID option = OptionID(0); option < OptionIDRowCount; option = OptionID(option + 1)) {
 394          std::string setting;
 395          try {
 396              setting = SettingName(option);
 397          } catch (const std::logic_error&) {
 398              continue;  // Ignore GUI-only settings
 399          }
 400          if (!checked_settings.insert(setting).second) continue;
 401          if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting);
 402      }
 403  
 404      // If setting doesn't exist create it with defaults.
 405  
 406      // Main
 407      if (!settings.contains("strDataDir"))
 408          settings.setValue("strDataDir", GUIUtil::getDefaultDataDirectory());
 409  
 410      // Wallet
 411  #ifdef ENABLE_WALLET
 412      if (!settings.contains("SubFeeFromAmount")) {
 413          settings.setValue("SubFeeFromAmount", false);
 414      }
 415      m_sub_fee_from_amount = settings.value("SubFeeFromAmount", false).toBool();
 416  #endif
 417  
 418      // Network
 419      if (!settings.contains("nNetworkPort"))
 420          settings.setValue("nNetworkPort", (quint16)Params().GetDefaultPort());
 421      if (!gArgs.SoftSetArg("-port", settings.value("nNetworkPort").toString().toStdString()))
 422          addOverriddenOption("-port");
 423  
 424      // rwconf settings that require a restart
 425      // Caution: This is before general initialisation occurs!
 426      f_peerbloomfilters = gArgs.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS);
 427      f_rejectspkreuse = !(gArgs.GetArg("-spkreuse", DEFAULT_SPKREUSE) == "allow" || gArgs.GetBoolArg("-spkreuse", false));
 428  
 429      // Display
 430      if (settings.contains("FontForMoney")) {
 431          m_font_money = FontChoiceFromString(settings.value("FontForMoney").toString());
 432      } else if (settings.contains("UseEmbeddedMonospacedFont")) {
 433          if (settings.value("UseEmbeddedMonospacedFont").toBool()) {
 434              m_font_money = FontChoiceAbstract::EmbeddedFont;
 435          } else {
 436              m_font_money = FontChoiceAbstract::BestSystemFont;
 437          }
 438      }
 439      m_font_money_supports_tonal = TonalUtils::font_supports_tonal(getFontForMoney(LimenkaUnit::BTC));
 440      Q_EMIT fontForMoneyChanged(getFontForMoney(LimenkaUnit::BTC));
 441  
 442      if (settings.contains("FontForQRCodes")) {
 443          m_font_qrcodes = FontChoiceFromString(settings.value("FontForQRCodes").toString());
 444      }
 445      Q_EMIT fontForQRCodesChanged(getFontChoiceForQRCodes());
 446  
 447      if (!settings.contains("PeersTabAlternatingRowColors")) {
 448          settings.setValue("PeersTabAlternatingRowColors", "false");
 449      }
 450      m_peers_tab_alternating_row_colors = settings.value("PeersTabAlternatingRowColors").toBool();
 451      Q_EMIT peersTabAlternatingRowColorsChanged(m_peers_tab_alternating_row_colors);
 452  
 453      m_mask_values = settings.value("mask_values", false).toBool();
 454  
 455      return true;
 456  }
 457  
 458  /** Helper function to copy contents from one QSettings to another.
 459   * By using allKeys this also covers nested settings in a hierarchy.
 460   */
 461  static void CopySettings(QSettings& dst, const QSettings& src)
 462  {
 463      for (const QString& key : src.allKeys()) {
 464          dst.setValue(key, src.value(key));
 465      }
 466  }
 467  
 468  /** Back up a QSettings to an ini-formatted file. */
 469  static void BackupSettings(const fs::path& filename, const QSettings& src)
 470  {
 471      qInfo() << "Backing up GUI settings to" << GUIUtil::PathToQString(filename);
 472      QSettings dst(GUIUtil::PathToQString(filename), QSettings::IniFormat);
 473      dst.clear();
 474      CopySettings(dst, src);
 475  }
 476  
 477  void OptionsModel::Reset()
 478  {
 479      // Backup and reset settings.json
 480      node().resetSettings();
 481  
 482      QSettings settings;
 483  
 484      // Backup old settings to chain-specific datadir for troubleshooting
 485      BackupSettings(gArgs.GetDataDirNet() / "guisettings.ini.bak", settings);
 486  
 487      // Save the strDataDir setting
 488      QString dataDir = GUIUtil::getDefaultDataDirectory();
 489      dataDir = settings.value("strDataDir", dataDir).toString();
 490  
 491      // Remove rw config file
 492      gArgs.EraseRWConfigFile();
 493  
 494      // Remove all entries from our QSettings object
 495      settings.clear();
 496  
 497      // Set strDataDir
 498      settings.setValue("strDataDir", dataDir);
 499  
 500      // Set prune option iff it was configured in rwconf
 501      if (gArgs.RWConfigHasPruneOption()) {
 502          SetPruneTargetMiB(gArgs.GetIntArg("-prune", 0));
 503      }
 504  
 505      // Set that this was reset
 506      settings.setValue("fReset", true);
 507  
 508      // default setting for OptionsModel::StartAtStartup - disabled
 509      if (GUIUtil::GetStartOnSystemStartup())
 510          GUIUtil::SetStartOnSystemStartup(false);
 511  }
 512  
 513  int OptionsModel::rowCount(const QModelIndex & parent) const
 514  {
 515      return OptionIDRowCount;
 516  }
 517  
 518  static ProxySetting ParseProxyString(const QString& proxy)
 519  {
 520      static const ProxySetting default_val = {false, DEFAULT_GUI_PROXY_HOST, QString("%1").arg(DEFAULT_GUI_PROXY_PORT)};
 521      // Handle the case that the setting is not set at all
 522      if (proxy.isEmpty()) {
 523          return default_val;
 524      }
 525      uint16_t port{0};
 526      std::string hostname;
 527      if (SplitHostPort(proxy.toStdString(), port, hostname) && port != 0) {
 528          // Valid and port within the valid range
 529          // Check if the hostname contains a colon, indicating an IPv6 address
 530          if (hostname.find(':') != std::string::npos) {
 531              hostname = "[" + hostname + "]"; // Wrap IPv6 address in brackets
 532          }
 533          return {true, QString::fromStdString(hostname), QString::number(port)};
 534      } else { // Invalid: return default
 535          return default_val;
 536      }
 537  }
 538  
 539  static ProxySetting ParseProxyString(const std::string& proxy)
 540  {
 541      return ParseProxyString(QString::fromStdString(proxy));
 542  }
 543  
 544  static std::string ProxyString(bool is_set, QString ip, QString port)
 545  {
 546      return is_set ? QString(ip + ":" + port).toStdString() : "";
 547  }
 548  
 549  static QString GetDefaultProxyAddress()
 550  {
 551      return QString("%1:%2").arg(DEFAULT_GUI_PROXY_HOST).arg(DEFAULT_GUI_PROXY_PORT);
 552  }
 553  
 554  void OptionsModel::SetPruneTargetMiB(int prune_target_mib)
 555  {
 556      const common::SettingsValue cur_value = node().getPersistentSetting("prune");
 557      const common::SettingsValue new_value = prune_target_mib;
 558  
 559      // Force setting to take effect. It is still safe to change the value at
 560      // this point because this function is only called after the intro screen is
 561      // shown, before the node starts.
 562      node().forceSetting("prune", new_value);
 563      m_prune_forced_by_gui = true;
 564  
 565      // Update settings.json if value configured in intro screen is different
 566      // from saved value. Avoid writing settings.json if limenka.conf value
 567      // doesn't need to be overridden.
 568      if (cur_value.write() != new_value.write()) {
 569          // Call UpdateRwSetting() instead of setOption() to avoid setting
 570          // RestartRequired flag
 571          UpdateRwSetting(node(), PruneTristate, "", new_value);
 572          gArgs.ModifyRWConfigFile("prune", new_value.getValStr());
 573      }
 574  
 575      // Keep previous pruning size, if pruning was disabled.
 576      if (PruneEnabled(cur_value)) {
 577          UpdateRwSetting(node(), PruneTristate, "-prev", PruneEnabled(new_value) ? common::SettingsValue{} : cur_value);
 578      }
 579  }
 580  
 581  // read QSettings values and return them
 582  QVariant OptionsModel::data(const QModelIndex & index, int role) const
 583  {
 584      if(role == Qt::EditRole)
 585      {
 586          return getOption(OptionID(index.row()));
 587      }
 588      return QVariant();
 589  }
 590  
 591  // write QSettings values
 592  bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role)
 593  {
 594      bool successful = true; /* set to false on parse error */
 595      if(role == Qt::EditRole)
 596      {
 597          successful = setOption(OptionID(index.row()), value);
 598      }
 599  
 600      Q_EMIT dataChanged(index, index);
 601  
 602      return successful;
 603  }
 604  
 605  // NOLINTNEXTLINE(misc-no-recursion)
 606  QVariant OptionsModel::getOption(OptionID option, const std::string& suffix) const
 607  {
 608      auto setting = [&]{ return node().getPersistentSetting(SettingName(option) + suffix); };
 609  
 610      QSettings settings;
 611      switch (option) {
 612      case StartAtStartup:
 613          return GUIUtil::GetStartOnSystemStartup();
 614      case ShowTrayIcon:
 615          return m_show_tray_icon;
 616      case MinimizeToTray:
 617          return fMinimizeToTray;
 618      case NetworkPort:
 619          return settings.value("nNetworkPort");
 620      case MapPortUPnP:
 621  #ifdef USE_UPNP
 622          return SettingToBool(setting(), DEFAULT_UPNP);
 623  #else
 624          return false;
 625  #endif // USE_UPNP
 626      case MapPortNatpmp:
 627          return SettingToBool(setting(), DEFAULT_NATPMP);
 628      case MinimizeOnClose:
 629          return fMinimizeOnClose;
 630  
 631      // default proxy
 632      case ProxyUse:
 633      case ProxyUseTor:
 634          return ParseProxyString(SettingToString(setting(), "")).is_set;
 635      case ProxyIP:
 636      case ProxyIPTor: {
 637          ProxySetting proxy = ParseProxyString(SettingToString(setting(), ""));
 638          if (proxy.is_set) {
 639              return proxy.ip;
 640          } else if (suffix.empty()) {
 641              return getOption(option, "-prev");
 642          } else {
 643              return ParseProxyString(GetDefaultProxyAddress().toStdString()).ip;
 644          }
 645      }
 646      case ProxyPort:
 647      case ProxyPortTor: {
 648          ProxySetting proxy = ParseProxyString(SettingToString(setting(), ""));
 649          if (proxy.is_set) {
 650              return proxy.port;
 651          } else if (suffix.empty()) {
 652              return getOption(option, "-prev");
 653          } else {
 654              return ParseProxyString(GetDefaultProxyAddress().toStdString()).port;
 655          }
 656      }
 657  
 658  #ifdef ENABLE_WALLET
 659      case SpendZeroConfChange:
 660          return SettingToBool(setting(), wallet::DEFAULT_SPEND_ZEROCONF_CHANGE);
 661      case ExternalSignerPath:
 662          return QString::fromStdString(SettingToString(setting(), ""));
 663      case SubFeeFromAmount:
 664          return m_sub_fee_from_amount;
 665      case addresstype:
 666      {
 667          const OutputType default_address_type = ParseOutputType(gArgs.GetArg("-addresstype", "")).value_or(wallet::DEFAULT_ADDRESS_TYPE);
 668          return QString::fromStdString(FormatOutputType(default_address_type));
 669      }
 670  #endif
 671      case DisplayUnit:
 672          return QVariant::fromValue(m_display_limenka_unit);
 673      case DisplayAddresses:
 674          return bDisplayAddresses;
 675      case ThirdPartyTxUrls:
 676          return strThirdPartyTxUrls;
 677      case Language:
 678          return QString::fromStdString(SettingToString(setting(), ""));
 679      case FontForMoney:
 680          return QVariant::fromValue(m_font_money);
 681      case FontForQRCodes:
 682          return QVariant::fromValue(m_font_qrcodes);
 683      case PeersTabAlternatingRowColors:
 684          return m_peers_tab_alternating_row_colors;
 685  #ifdef ENABLE_WALLET
 686      case walletrbf:
 687          return gArgs.GetBoolArg("-walletrbf", wallet::DEFAULT_WALLET_RBF);
 688  #endif
 689      case CoinControlFeatures:
 690          return fCoinControlFeatures;
 691      case EnablePSBTControls:
 692          return settings.value("enable_psbt_controls");
 693      case PruneTristate:
 694          return PruneSettingAsTristate(setting());
 695      case PruneSizeMiB:
 696          return PruneEnabled(setting()) ? PruneSizeAsMiB(setting()) :
 697                 suffix.empty()          ? getOption(option, "-prev") :
 698                                           DEFAULT_PRUNE_TARGET_MiB;
 699      case DatabaseCache:
 700          return qlonglong(SettingToInt(setting(), node::GetDefaultDBCache() / 1_MiB));
 701      case ThreadsScriptVerif:
 702          return qlonglong(SettingToInt(setting(), DEFAULT_SCRIPTCHECK_THREADS));
 703      case Listen:
 704          return SettingToBool(setting(), DEFAULT_LISTEN);
 705      case Server:
 706          return SettingToBool(setting(), false);
 707      case MaskValues:
 708          return m_mask_values;
 709      case maxuploadtarget:
 710          return qlonglong(node().context()->connman->GetMaxOutboundTarget() / 1024 / 1024);
 711      case peerbloomfilters:
 712          return f_peerbloomfilters;
 713      case peerblockfilters:
 714          return gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS);
 715      case mempoolreplacement:
 716          return CanonicalMempoolReplacement(*this);
 717      case mempooltruc:
 718          return CanonicalMempoolTRUC(*this);
 719      case maxorphantx:
 720          return qlonglong(gArgs.GetIntArg("-maxorphantx", DEFAULT_MAX_ORPHAN_TRANSACTIONS));
 721      case maxmempool:
 722          return qlonglong(node().mempool().m_opts.max_size_bytes / 1'000'000);
 723      case incrementalrelayfee:
 724          return qlonglong(node().mempool().m_opts.incremental_relay_feerate.GetFeePerK());
 725      case mempoolexpiry:
 726          return qlonglong(std::chrono::duration_cast<std::chrono::hours>(node().mempool().m_opts.expiry).count());
 727      case rejectunknownscripts:
 728          return node().mempool().m_opts.require_standard;
 729      case rejectunknownwitness:
 730          return !node().mempool().m_opts.acceptunknownwitness;
 731      case rejectparasites:
 732          return node().mempool().m_opts.reject_parasites;
 733      case rejecttokens:
 734          return node().mempool().m_opts.reject_tokens;
 735      case subdustfeepenalty:
 736          return node().mempool().m_opts.subdustfeepenalty;
 737      case rejectspkreuse:
 738          return f_rejectspkreuse;
 739      case minrelaytxfee:
 740          return qlonglong(node().mempool().m_opts.min_relay_feerate.GetFeePerK());
 741      case minrelaycoinblocks:
 742          return qlonglong(node().mempool().m_opts.minrelaycoinblocks);
 743      case minrelaymaturity:
 744          return node().mempool().m_opts.minrelaymaturity;
 745      case bytespersigop:
 746          return nBytesPerSigOp;
 747      case bytespersigopstrict:
 748          return nBytesPerSigOpStrict;
 749      case limitancestorcount:
 750          return qlonglong(node().mempool().m_opts.limits.ancestor_count);
 751      case limitancestorsize:
 752          return qlonglong(node().mempool().m_opts.limits.ancestor_size_vbytes / 1'000);
 753      case limitdescendantcount:
 754          return qlonglong(node().mempool().m_opts.limits.descendant_count);
 755      case limitdescendantsize:
 756          return qlonglong(node().mempool().m_opts.limits.descendant_size_vbytes / 1'000);
 757      case rejectbarepubkey:
 758          return !node().mempool().m_opts.permit_bare_pubkey;
 759      case rejectbaremultisig:
 760          return !node().mempool().m_opts.permit_bare_multisig;
 761      case permitephemeral:
 762          return CanonicalPermitEphemeral(*this);
 763      case rejectbareanchor:
 764          return !node().mempool().m_opts.permitbareanchor;
 765      case rejectbaredatacarrier:
 766          return !node().mempool().m_opts.permitbaredatacarrier;
 767      case maxscriptsize:
 768          return ::g_script_size_policy_limit;
 769      case maxtxlegacysigops:
 770          return node().mempool().m_opts.maxtxlegacysigops;
 771      case datacarriercost:
 772          return double(::g_weight_per_data_byte) / WITNESS_SCALE_FACTOR;
 773      case datacarriersize:
 774          return qlonglong(node().mempool().m_opts.max_datacarrier_bytes.value_or(0));
 775      case rejectnonstddatacarrier:
 776          return !node().mempool().m_opts.accept_non_std_datacarrier;
 777      case dustrelayfee:
 778          return qlonglong(node().mempool().m_opts.dust_relay_feerate_floor.GetFeePerK());
 779      case dustdynamic:
 780          return QString::fromStdString(SettingToString(setting(), DEFAULT_DUST_DYNAMIC));
 781      case blockmintxfee:
 782          if (gArgs.IsArgSet("-blockmintxfee")) {
 783              return qlonglong(ParseMoney(gArgs.GetArg("-blockmintxfee", "")).value_or(0));
 784          } else {
 785              return qlonglong(DEFAULT_BLOCK_MIN_TX_FEE);
 786          }
 787      case blockmaxsize:
 788          return qlonglong(gArgs.GetIntArg("-blockmaxsize", DEFAULT_BLOCK_MAX_SIZE) / 1000);
 789      case blockprioritysize:
 790          return qlonglong(gArgs.GetIntArg("-blockprioritysize", DEFAULT_BLOCK_PRIORITY_SIZE) / 1000);
 791      case blockmaxweight:
 792          return qlonglong(gArgs.GetIntArg("-blockmaxweight", DEFAULT_BLOCK_MAX_WEIGHT) / 1000);
 793      case blockreconstructionextratxn:
 794          return qlonglong(gArgs.GetIntArg("-blockreconstructionextratxn", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN));
 795      case blockreconstructionextratxnsize:
 796          return qlonglong(gArgs.GetIntArg("-blockreconstructionextratxnsize", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN_SIZE / 1'000'000));
 797      default:
 798          return QVariant();
 799      }
 800  }
 801  
 802  QFont OptionsModel::getFontForChoice(const FontChoice& fc)
 803  {
 804      QFont f;
 805      if (std::holds_alternative<FontChoiceAbstract>(fc)) {
 806          f = GUIUtil::fixedPitchFont(fc != UseBestSystemFont);
 807          if (fc == UseBestSystemFont) f.setWeight(QFont::Bold);
 808      } else {
 809          f = std::get<QFont>(fc);
 810      }
 811      return f;
 812  }
 813  
 814  QFont OptionsModel::getFontForMoney(const LimenkaUnit unit) const
 815  {
 816      if (LimenkaUnits::numsys(unit) == LimenkaUnits::Unit::TBC && !m_font_money_supports_tonal) {
 817          return getFontForChoice(FontChoiceAbstract::EmbeddedFont);
 818      }
 819      return getFontForChoice(m_font_money);
 820  }
 821  
 822  // NOLINTNEXTLINE(misc-no-recursion)
 823  bool OptionsModel::setOption(OptionID option, const QVariant& value, const std::string& suffix)
 824  {
 825      auto changed = [&] { return value.isValid() && value != getOption(option, suffix); };
 826      auto update = [&](const common::SettingsValue& value) { return UpdateRwSetting(node(), option, suffix, value); };
 827  
 828      bool successful = true; /* set to false on parse error */
 829      QSettings settings;
 830  
 831      switch (option) {
 832      case StartAtStartup:
 833          successful = GUIUtil::SetStartOnSystemStartup(value.toBool());
 834          break;
 835      case ShowTrayIcon:
 836          m_show_tray_icon = value.toBool();
 837          settings.setValue("fHideTrayIcon", !m_show_tray_icon);
 838          Q_EMIT showTrayIconChanged(m_show_tray_icon);
 839          break;
 840      case MinimizeToTray:
 841          fMinimizeToTray = value.toBool();
 842          settings.setValue("fMinimizeToTray", fMinimizeToTray);
 843          break;
 844      case NetworkPort:
 845          if (settings.value("nNetworkPort") != value) {
 846              // If the port input box is empty, set to default port
 847              if (value.toString().isEmpty()) {
 848                  settings.setValue("nNetworkPort", (quint16)Params().GetDefaultPort());
 849              }
 850              else {
 851                  settings.setValue("nNetworkPort", (quint16)value.toInt());
 852              }
 853              setRestartRequired(true);
 854          }
 855          break;
 856      case MapPortUPnP: // core option - can be changed on-the-fly
 857          if (changed()) {
 858              update(value.toBool());
 859              if (gArgs.GetBoolArg("-listen", DEFAULT_LISTEN)) {
 860                  node().mapPort(value.toBool(), getOption(MapPortNatpmp).toBool());
 861              }
 862          }
 863          break;
 864      case MapPortNatpmp: // core option - can be changed on-the-fly
 865          if (changed()) {
 866              update(value.toBool());
 867              if (gArgs.GetBoolArg("-listen", DEFAULT_LISTEN)) {
 868                  node().mapPort(getOption(MapPortUPnP).toBool(), value.toBool());
 869              }
 870          }
 871          break;
 872      case MinimizeOnClose:
 873          fMinimizeOnClose = value.toBool();
 874          settings.setValue("fMinimizeOnClose", fMinimizeOnClose);
 875          break;
 876  
 877      // default proxy
 878      case ProxyUse:
 879          if (changed()) {
 880              if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev");
 881              update(ProxyString(value.toBool(), getOption(ProxyIP).toString(), getOption(ProxyPort).toString()));
 882              if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {});
 883              if (suffix.empty()) setRestartRequired(true);
 884          }
 885          break;
 886      case ProxyIP:
 887          if (changed()) {
 888              if (suffix.empty() && !getOption(ProxyUse).toBool()) {
 889                  setOption(option, value, "-prev");
 890              } else {
 891                  update(ProxyString(true, value.toString(), getOption(ProxyPort).toString()));
 892              }
 893              if (suffix.empty() && getOption(ProxyUse).toBool()) setRestartRequired(true);
 894          }
 895          break;
 896      case ProxyPort:
 897          if (changed()) {
 898              if (suffix.empty() && !getOption(ProxyUse).toBool()) {
 899                  setOption(option, value, "-prev");
 900              } else {
 901                  update(ProxyString(true, getOption(ProxyIP).toString(), value.toString()));
 902              }
 903              if (suffix.empty() && getOption(ProxyUse).toBool()) setRestartRequired(true);
 904          }
 905          break;
 906  
 907      // separate Tor proxy
 908      case ProxyUseTor:
 909          if (changed()) {
 910              if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev");
 911              update(ProxyString(value.toBool(), getOption(ProxyIPTor).toString(), getOption(ProxyPortTor).toString()));
 912              if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {});
 913              if (suffix.empty()) setRestartRequired(true);
 914          }
 915          break;
 916      case ProxyIPTor:
 917          if (changed()) {
 918              if (suffix.empty() && !getOption(ProxyUseTor).toBool()) {
 919                  setOption(option, value, "-prev");
 920              } else {
 921                  update(ProxyString(true, value.toString(), getOption(ProxyPortTor).toString()));
 922              }
 923              if (suffix.empty() && getOption(ProxyUseTor).toBool()) setRestartRequired(true);
 924          }
 925          break;
 926      case ProxyPortTor:
 927          if (changed()) {
 928              if (suffix.empty() && !getOption(ProxyUseTor).toBool()) {
 929                  setOption(option, value, "-prev");
 930              } else {
 931                  update(ProxyString(true, getOption(ProxyIPTor).toString(), value.toString()));
 932              }
 933              if (suffix.empty() && getOption(ProxyUseTor).toBool()) setRestartRequired(true);
 934          }
 935          break;
 936  
 937  #ifdef ENABLE_WALLET
 938      case SpendZeroConfChange:
 939          if (changed()) {
 940              update(value.toBool());
 941              setRestartRequired(true);
 942          }
 943          break;
 944      case ExternalSignerPath:
 945          if (changed()) {
 946              update(value.toString().toStdString());
 947              setRestartRequired(true);
 948          }
 949          break;
 950      case SubFeeFromAmount:
 951          m_sub_fee_from_amount = value.toBool();
 952          settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount);
 953          break;
 954      case addresstype:
 955      {
 956          const std::string newvalue_str = value.toString().toStdString();
 957          const OutputType oldvalue = ParseOutputType(gArgs.GetArg("-addresstype", "")).value_or(wallet::DEFAULT_ADDRESS_TYPE);
 958          const OutputType newvalue = ParseOutputType(newvalue_str).value_or(oldvalue);
 959          if (newvalue != oldvalue) {
 960              gArgs.ModifyRWConfigFile("addresstype", newvalue_str);
 961              gArgs.ForceSetArg("-addresstype", newvalue_str);
 962              for (auto& wallet_interface : m_node.walletLoader().getWallets()) {
 963                  wallet::CWallet *wallet;
 964                  if (wallet_interface && (wallet = wallet_interface->wallet())) {
 965                      wallet->m_default_address_type = newvalue;
 966                  } else {
 967                      setRestartRequired(true);
 968                      continue;
 969                  }
 970              }
 971              Q_EMIT addresstypeChanged(newvalue);
 972          }
 973          break;
 974      }
 975  #endif
 976      case DisplayUnit:
 977          setDisplayUnit(value);
 978          break;
 979      case DisplayAddresses:
 980          bDisplayAddresses = value.toBool();
 981          settings.setValue("bDisplayAddresses", bDisplayAddresses);
 982          break;
 983      case ThirdPartyTxUrls:
 984          if (strThirdPartyTxUrls != value.toString()) {
 985              strThirdPartyTxUrls = value.toString();
 986              settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls);
 987              setRestartRequired(true);
 988          }
 989          break;
 990      case Language:
 991          if (changed()) {
 992              update(value.toString().toStdString());
 993              setRestartRequired(true);
 994          }
 995          break;
 996      case FontForMoney:
 997      {
 998          const auto& new_font = value.value<FontChoice>();
 999          if (m_font_money == new_font) break;
1000          settings.setValue("FontForMoney", FontChoiceToString(new_font));
1001          m_font_money = new_font;
1002          m_font_money_supports_tonal = TonalUtils::font_supports_tonal(getFontForMoney(LimenkaUnit::BTC));
1003          Q_EMIT fontForMoneyChanged(getFontForMoney(LimenkaUnit::BTC));
1004          break;
1005      }
1006      case FontForQRCodes:
1007      {
1008          const auto& new_font = value.value<FontChoice>();
1009          if (m_font_qrcodes == new_font) break;
1010          settings.setValue("FontForQRCodes", FontChoiceToString(new_font));
1011          m_font_qrcodes = new_font;
1012          Q_EMIT fontForQRCodesChanged(new_font);
1013          break;
1014      }
1015      case PeersTabAlternatingRowColors:
1016          m_peers_tab_alternating_row_colors = value.toBool();
1017          settings.setValue("PeersTabAlternatingRowColors", m_peers_tab_alternating_row_colors);
1018          Q_EMIT peersTabAlternatingRowColorsChanged(m_peers_tab_alternating_row_colors);
1019          break;
1020  #ifdef ENABLE_WALLET
1021      case walletrbf:
1022          if (changed()) {
1023              const bool fNewValue = value.toBool();
1024              const std::string newvalue_str = strprintf("%d", fNewValue);
1025              gArgs.ModifyRWConfigFile("walletrbf", newvalue_str);
1026              gArgs.ForceSetArg("-walletrbf", newvalue_str);
1027              for (auto& wallet_interface : m_node.walletLoader().getWallets()) {
1028                  wallet::CWallet *wallet;
1029                  if (wallet_interface && (wallet = wallet_interface->wallet())) {
1030                      wallet->m_signal_rbf = fNewValue;
1031                  } else {
1032                      setRestartRequired(true);
1033                  }
1034              }
1035          }
1036          break;
1037  #endif
1038      case CoinControlFeatures:
1039          fCoinControlFeatures = value.toBool();
1040          settings.setValue("fCoinControlFeatures", fCoinControlFeatures);
1041          Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures);
1042          break;
1043      case EnablePSBTControls:
1044          m_enable_psbt_controls = value.toBool();
1045          settings.setValue("enable_psbt_controls", m_enable_psbt_controls);
1046          break;
1047      case PruneTristate:
1048          if (changed()) {
1049              const bool is_autoprune = (value.value<Qt::CheckState>() == Qt::Checked);
1050              const auto prune_setting = PruneSettingFromMiB(value.value<Qt::CheckState>(), getOption(PruneSizeMiB).toInt());
1051              if (suffix.empty() && !is_autoprune) setOption(option, Qt::Checked, "-prev");
1052              update(prune_setting);
1053              if (suffix.empty()) gArgs.ModifyRWConfigFile("prune", prune_setting.getValStr());
1054              if (suffix.empty() && is_autoprune) UpdateRwSetting(node(), option, "-prev", {});
1055              if (suffix.empty()) setRestartRequired(true);
1056          }
1057          break;
1058      case PruneSizeMiB:
1059          if (changed()) {
1060              const bool is_autoprune = (Qt::Checked == getOption(PruneTristate).value<Qt::CheckState>());
1061              if (suffix.empty() && !is_autoprune) {
1062                  setOption(option, value, "-prev");
1063              } else {
1064                  const auto prune_setting = PruneSettingFromMiB(Qt::Checked, value.toInt());
1065                  update(prune_setting);
1066                  if (suffix.empty()) gArgs.ModifyRWConfigFile("prune", prune_setting.getValStr());
1067              }
1068              if (suffix.empty() && is_autoprune) setRestartRequired(true);
1069          }
1070          break;
1071      case DatabaseCache:
1072          if (changed()) {
1073              update(static_cast<int64_t>(value.toLongLong()));
1074              setRestartRequired(true);
1075          }
1076          break;
1077      case ThreadsScriptVerif:
1078          if (changed()) {
1079              update(static_cast<int64_t>(value.toLongLong()));
1080              setRestartRequired(true);
1081          }
1082          break;
1083      case Listen:
1084      case Server:
1085          if (changed()) {
1086              update(value.toBool());
1087              setRestartRequired(true);
1088          }
1089          break;
1090      case MaskValues:
1091          m_mask_values = value.toBool();
1092          settings.setValue("mask_values", m_mask_values);
1093          break;
1094      case maxuploadtarget:
1095      {
1096          if (changed()) {
1097              gArgs.ModifyRWConfigFile("maxuploadtarget", value.toString().toStdString());
1098              node().context()->connman->SetMaxOutboundTarget(value.toLongLong() * 1024 * 1024);
1099          }
1100          break;
1101      }
1102      case peerbloomfilters:
1103          if (changed()) {
1104              gArgs.ModifyRWConfigFile("peerbloomfilters", strprintf("%d", value.toBool()));
1105              f_peerbloomfilters = value.toBool();
1106              setRestartRequired(true);
1107          }
1108          break;
1109      case peerblockfilters:
1110      {
1111          bool nv = value.toBool();
1112          if (gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS) != nv) {
1113              gArgs.ModifyRWConfigFile("peerblockfilters", strprintf("%d", nv));
1114              gArgs.ModifyRWConfigFile("peercfilters", strprintf("%d", nv), /*also_settings_json=*/ false);  // for downgrade compatibility with Knots 0.19
1115              gArgs.ForceSetArg("peerblockfilters", nv);
1116              if (nv && !GetBlockFilterIndex(BlockFilterType::BASIC)) {
1117                  // TODO: When other options are possible, we need to append a list!
1118                  // TODO: Some way to unset/delete this...
1119                  gArgs.ModifyRWConfigFile("blockfilterindex", "basic");
1120                  gArgs.ForceSetArg("blockfilterindex", "basic");
1121              }
1122              setRestartRequired(true);
1123          }
1124          break;
1125      }
1126      case mempoolreplacement:
1127      {
1128          if (changed()) {
1129              QString nv = value.toString();
1130              if (nv == "never") {
1131                  node().mempool().m_opts.rbf_policy = RBFPolicy::Never;
1132                  node().updateRwSetting("mempoolfullrbf", "0");
1133              } else if (nv == "fee,optin") {
1134                  node().mempool().m_opts.rbf_policy = RBFPolicy::OptIn;
1135                  node().updateRwSetting("mempoolfullrbf", "0");
1136              } else {  // "fee,-optin"
1137                  node().mempool().m_opts.rbf_policy = RBFPolicy::Always;
1138                  node().updateRwSetting("mempoolfullrbf", "1");
1139              }
1140              gArgs.ModifyRWConfigFile("mempoolreplacement", nv.toStdString());
1141          }
1142          break;
1143      }
1144      case mempooltruc:
1145      {
1146          if (changed()) {
1147              QString nv = value.toString();
1148              if (nv == "reject") {
1149                  node().mempool().m_opts.truc_policy = TRUCPolicy::Reject;
1150              } else if (nv == "accept") {
1151                  node().mempool().m_opts.truc_policy = TRUCPolicy::Accept;
1152              } else if (nv == "enforce") {
1153                  node().mempool().m_opts.truc_policy = TRUCPolicy::Enforce;
1154              }
1155              node().updateRwSetting("mempooltruc", nv.toStdString());
1156          }
1157          break;
1158      }
1159      case maxorphantx:
1160      {
1161          if (changed()) {
1162              unsigned int nMaxOrphanTx = gArgs.GetIntArg("-maxorphantx", DEFAULT_MAX_ORPHAN_TRANSACTIONS);
1163              unsigned int nNv = value.toLongLong();
1164              std::string strNv = value.toString().toStdString();
1165              gArgs.ForceSetArg("-maxorphantx", strNv);
1166              gArgs.ModifyRWConfigFile("maxorphantx", strNv);
1167              if (nNv < nMaxOrphanTx) {
1168                  assert(node().context() && node().context()->peerman);
1169                  node().context()->peerman->LimitOrphanTxSize(nNv);
1170              }
1171          }
1172          break;
1173      }
1174      case maxmempool:
1175      {
1176          if (changed()) {
1177              long long nOldValue = node().mempool().m_opts.max_size_bytes;
1178              long long nNv = value.toLongLong();
1179              std::string strNv = value.toString().toStdString();
1180              node().mempool().m_opts.max_size_bytes = nNv * 1'000'000;
1181              gArgs.ForceSetArg("-maxmempool", strNv);
1182              gArgs.ModifyRWConfigFile("maxmempool", strNv);
1183              if (nNv < nOldValue) {
1184                  LOCK(cs_main);
1185                  auto node_ctx = node().context();
1186                  assert(node_ctx && node_ctx->mempool && node_ctx->chainman);
1187                  auto& active_chainstate = node_ctx->chainman->ActiveChainstate();
1188                  LOCK(node_ctx->mempool->cs);
1189                  LimitMempoolSize(*node_ctx->mempool, active_chainstate.CoinsTip());
1190              }
1191          }
1192          break;
1193      }
1194      case incrementalrelayfee:
1195          if (changed()) {
1196              CAmount nNv = value.toLongLong();
1197              gArgs.ModifyRWConfigFile("incrementalrelayfee", FormatMoney(nNv));
1198              node().mempool().m_opts.incremental_relay_feerate = CFeeRate(nNv);
1199          }
1200          break;
1201      case mempoolexpiry:
1202      {
1203          if (changed()) {
1204              const auto old_value = node().mempool().m_opts.expiry;
1205              const std::chrono::hours new_value{value.toLongLong()};
1206              std::string strNv = value.toString().toStdString();
1207              node().mempool().m_opts.expiry = new_value;
1208              gArgs.ForceSetArg("-mempoolexpiry", strNv);
1209              gArgs.ModifyRWConfigFile("mempoolexpiry", strNv);
1210              if (new_value < old_value) {
1211                  LOCK(cs_main);
1212                  auto node_ctx = node().context();
1213                  assert(node_ctx && node_ctx->mempool && node_ctx->chainman);
1214                  auto& active_chainstate = node_ctx->chainman->ActiveChainstate();
1215                  LOCK(node_ctx->mempool->cs);
1216                  LimitMempoolSize(*node_ctx->mempool, active_chainstate.CoinsTip());
1217              }
1218          }
1219          break;
1220      }
1221      case rejectunknownscripts:
1222      {
1223          if (changed()) {
1224              const bool fNewValue = value.toBool();
1225              node().mempool().m_opts.require_standard = fNewValue;
1226              // This option is inverted in the config:
1227              gArgs.ModifyRWConfigFile("acceptnonstdtxn", strprintf("%d", ! fNewValue));
1228          }
1229          break;
1230      }
1231      case rejectunknownwitness:
1232          if (changed()) {
1233              // This option is inverted
1234              const bool new_value = ! value.toBool();
1235              node().updateRwSetting("acceptunknownwitness" + suffix, new_value);
1236              node().mempool().m_opts.acceptunknownwitness = new_value;
1237          }
1238          break;
1239      case rejectparasites:
1240      {
1241          if (changed()) {
1242              const bool nv = value.toBool();
1243              node().mempool().m_opts.reject_parasites = nv;
1244              node().updateRwSetting("rejectparasites", nv);
1245          }
1246          break;
1247      }
1248      case rejecttokens:
1249      {
1250          if (changed()) {
1251              const bool nv = value.toBool();
1252              node().mempool().m_opts.reject_tokens = nv;
1253              node().updateRwSetting("rejecttokens", nv);
1254          }
1255          break;
1256      }
1257      case subdustfeepenalty:
1258      {
1259          if (changed()) {
1260              const bool nv = value.toBool();
1261              node().mempool().m_opts.subdustfeepenalty = nv;
1262              node().updateRwSetting("subdustfeepenalty", nv);
1263          }
1264          break;
1265      }
1266      case rejectspkreuse:
1267          if (changed()) {
1268              const bool fNewValue = value.toBool();
1269              gArgs.ModifyRWConfigFile("spkreuse", fNewValue ? "conflict" : "allow");
1270              f_rejectspkreuse = fNewValue;
1271              setRestartRequired(true);
1272          }
1273          break;
1274      case minrelaytxfee:
1275          if (changed()) {
1276              CAmount nNv = value.toLongLong();
1277              gArgs.ModifyRWConfigFile("minrelaytxfee", FormatMoney(nNv));
1278              node().mempool().m_opts.min_relay_feerate = CFeeRate(nNv);
1279          }
1280          break;
1281      case minrelaycoinblocks:
1282          if (changed()) {
1283              uint64_t nNv = value.toLongLong();
1284              update(nNv);
1285              node().mempool().m_opts.minrelaycoinblocks = nNv;
1286          }
1287          break;
1288      case minrelaymaturity:
1289          if (changed()) {
1290              int nNv = value.toInt();
1291              update(nNv);
1292              node().mempool().m_opts.minrelaymaturity = nNv;
1293          }
1294          break;
1295      case bytespersigop:
1296          if (changed()) {
1297              gArgs.ModifyRWConfigFile("bytespersigop", value.toString().toStdString());
1298              nBytesPerSigOp = value.toLongLong();
1299          }
1300          break;
1301      case bytespersigopstrict:
1302          if (changed()) {
1303              gArgs.ModifyRWConfigFile("bytespersigopstrict", value.toString().toStdString());
1304              nBytesPerSigOpStrict = value.toLongLong();
1305          }
1306          break;
1307      case limitancestorcount:
1308          if (changed()) {
1309              long long nNv = value.toLongLong();
1310              std::string strNv = value.toString().toStdString();
1311              node().mempool().m_opts.limits.ancestor_count = nNv;
1312              gArgs.ForceSetArg("-limitancestorcount", strNv);
1313              gArgs.ModifyRWConfigFile("limitancestorcount", strNv);
1314          }
1315          break;
1316      case limitancestorsize:
1317          if (changed()) {
1318              long long nNv = value.toLongLong();
1319              std::string strNv = value.toString().toStdString();
1320              node().mempool().m_opts.limits.ancestor_size_vbytes = nNv * 1'000;
1321              gArgs.ForceSetArg("-limitancestorsize", strNv);
1322              gArgs.ModifyRWConfigFile("limitancestorsize", strNv);
1323          }
1324          break;
1325      case limitdescendantcount:
1326          if (changed()) {
1327              long long nNv = value.toLongLong();
1328              std::string strNv = value.toString().toStdString();
1329              node().mempool().m_opts.limits.descendant_count = nNv;
1330              gArgs.ForceSetArg("-limitdescendantcount", strNv);
1331              gArgs.ModifyRWConfigFile("limitdescendantcount", strNv);
1332          }
1333          break;
1334      case limitdescendantsize:
1335          if (changed()) {
1336              long long nNv = value.toLongLong();
1337              std::string strNv = value.toString().toStdString();
1338              node().mempool().m_opts.limits.descendant_size_vbytes = nNv * 1'000;
1339              gArgs.ForceSetArg("-limitdescendantsize", strNv);
1340              gArgs.ModifyRWConfigFile("limitdescendantsize", strNv);
1341          }
1342          break;
1343      case rejectbarepubkey:
1344          if (changed()) {
1345              // The config and internal option is inverted
1346              const bool nv = ! value.toBool();
1347              node().mempool().m_opts.permit_bare_pubkey = nv;
1348              node().updateRwSetting("permitbarepubkey", nv);
1349          }
1350          break;
1351      case rejectbaremultisig:
1352          if (changed()) {
1353              // The config and internal option is inverted
1354              const bool fNewValue = ! value.toBool();
1355              node().mempool().m_opts.permit_bare_multisig = fNewValue;
1356              gArgs.ModifyRWConfigFile("permitbaremultisig", strprintf("%d", fNewValue));
1357          }
1358          break;
1359      case permitephemeral:
1360      {
1361          if (changed()) {
1362              std::string nv = value.toString().toStdString();
1363              ApplyPermitEphemeralOption(nv, node().mempool().m_opts);
1364              update(nv);
1365          }
1366          break;
1367      }
1368      case rejectbareanchor:
1369          if (changed()) {
1370              // The config and internal option is inverted
1371              const bool nv = ! value.toBool();
1372              node().mempool().m_opts.permitbareanchor = nv;
1373              node().updateRwSetting("permitbareanchor", nv);
1374          }
1375          break;
1376      case rejectbaredatacarrier:
1377          if (changed()) {
1378              // The config and internal option is inverted
1379              const bool nv = ! value.toBool();
1380              node().mempool().m_opts.permitbaredatacarrier = nv;
1381              node().updateRwSetting("permitbaredatacarrier", nv);
1382          }
1383          break;
1384      case maxscriptsize:
1385          if (changed()) {
1386              const auto nv = value.toLongLong();
1387              update(nv);
1388              ::g_script_size_policy_limit = nv;
1389          }
1390          break;
1391      case maxtxlegacysigops:
1392          if (changed()) {
1393              const auto nv = value.toLongLong();
1394              update(nv);
1395              node().mempool().m_opts.maxtxlegacysigops = nv;
1396          }
1397          break;
1398      case datacarriercost:
1399          if (changed()) {
1400              const double nNewSize = value.toDouble();
1401              update(nNewSize);
1402              ::g_weight_per_data_byte = nNewSize * WITNESS_SCALE_FACTOR;
1403          }
1404          break;
1405      case datacarriersize:
1406          if (changed()) {
1407              const int nNewSize = value.toInt();
1408              const bool fNewEn = (nNewSize > 0);
1409              if (fNewEn) {
1410                  if (!node().mempool().m_opts.max_datacarrier_bytes.has_value()) {
1411                      gArgs.ModifyRWConfigFile("datacarrier", strprintf("%d", fNewEn));
1412                  }
1413                  gArgs.ModifyRWConfigFile("datacarriersize", value.toString().toStdString());
1414                  node().mempool().m_opts.max_datacarrier_bytes = nNewSize;
1415              } else {
1416                  gArgs.ModifyRWConfigFile("datacarrier", "0");
1417                  node().mempool().m_opts.max_datacarrier_bytes = std::nullopt;
1418              }
1419          }
1420          break;
1421      case rejectnonstddatacarrier:
1422          if (changed()) {
1423              // This option is inverted
1424              const bool new_value = ! value.toBool();
1425              node().updateRwSetting("acceptnonstddatacarrier" + suffix, new_value);
1426              node().mempool().m_opts.accept_non_std_datacarrier = new_value;
1427          }
1428          break;
1429      case dustrelayfee:
1430          if (changed()) {
1431              CAmount nNv = value.toLongLong();
1432              gArgs.ModifyRWConfigFile("dustrelayfee", FormatMoney(nNv));
1433              CFeeRate feerate{nNv};
1434              node().mempool().m_opts.dust_relay_feerate_floor = feerate;
1435              if (node().mempool().m_opts.dust_relay_feerate < feerate || !node().mempool().m_opts.dust_relay_target) {
1436                  node().mempool().m_opts.dust_relay_feerate = feerate;
1437              } else {
1438                  node().mempool().UpdateDynamicDustFeerate();
1439              }
1440          }
1441          break;
1442      case dustdynamic:
1443          if (changed()) {
1444              const std::string newvalue_str = value.toString().toStdString();
1445              const util::Result<std::pair<int32_t, int>> parsed = ParseDustDynamicOpt(newvalue_str, 1008 /* FIXME: get from estimator */);
1446              assert(parsed);  // FIXME: what to do if it fails to parse?
1447              // FIXME: save -prev-<type> for each type
1448              update(newvalue_str);
1449              node().mempool().m_opts.dust_relay_target = parsed->first;
1450              node().mempool().m_opts.dust_relay_multiplier = parsed->second;
1451          }
1452          break;
1453      case blockmintxfee:
1454          if (changed()) {
1455              std::string strNv = FormatMoney(value.toLongLong());
1456              gArgs.ForceSetArg("-blockmintxfee", strNv);
1457              gArgs.ModifyRWConfigFile("blockmintxfee", strNv);
1458          }
1459          break;
1460      case blockmaxsize:
1461      case blockprioritysize:
1462      case blockmaxweight:
1463          if (changed()) {
1464              const int nNewValue_kB = value.toInt();
1465              std::string strNv = strprintf("%d000", nNewValue_kB);
1466              std::string strKey;
1467              switch (option) {
1468                  case blockmaxsize:
1469                      strKey = "blockmaxsize";
1470                      break;
1471                  case blockprioritysize:
1472                      strKey = "blockprioritysize";
1473                      break;
1474                  case blockmaxweight:
1475                      strKey = "blockmaxweight";
1476                      break;
1477                  default: assert(0);
1478              }
1479              gArgs.ForceSetArg("-" + strKey, strNv);
1480              gArgs.ModifyRWConfigFile(strKey, strNv);
1481          }
1482          break;
1483      case blockreconstructionextratxn:
1484          if (changed()) {
1485              std::string strNv = value.toString().toStdString();
1486              gArgs.ForceSetArg("-blockreconstructionextratxn", strNv);
1487              gArgs.ModifyRWConfigFile("blockreconstructionextratxn", strNv);
1488              setRestartRequired(true);
1489          }
1490          break;
1491      case blockreconstructionextratxnsize:
1492          if (changed()) {
1493              update(value.toLongLong());
1494              gArgs.ForceSetArg("-blockreconstructionextratxnsize", value.toString().toStdString());
1495              setRestartRequired(true);
1496          }
1497          break;
1498      case corepolicy:
1499          gArgs.ModifyRWConfigFile("corepolicy", value.toString().toStdString());
1500          break;
1501      default:
1502          break;
1503      }
1504  
1505      return successful;
1506  }
1507  
1508  void OptionsModel::setDisplayUnit(const QVariant& new_unit)
1509  {
1510      if (new_unit.isNull() || new_unit.value<LimenkaUnit>() == m_display_limenka_unit) return;
1511      
1512      LimenkaUnit unit = new_unit.value<LimenkaUnit>();
1513      
1514      // ATTO unit only available after fork activation
1515      if (unit == LimenkaUnit::ATTO) {
1516          // Reject ATTO selection before fork activation (MTP gate).
1517          const Consensus::Params& consensus = Params().GetConsensus();
1518          if (GetTime() < consensus.nForkActivationMTP) {
1519              return;
1520          }
1521      }
1522      
1523      m_display_limenka_unit = unit;
1524      QSettings settings;
1525      if (LimenkaUnits::numsys(m_display_limenka_unit) == LimenkaUnit::BTC) {
1526          settings.setValue("DisplayLimenkaUnit", QVariant::fromValue(m_display_limenka_unit));
1527          settings.remove("DisplayLimenkaUnitKnots");
1528      } else {
1529          settings.setValue("DisplayLimenkaUnitKnots", QVariant::fromValue(m_display_limenka_unit));
1530      }
1531      {
1532          // For older versions:
1533          auto setting_val = LimenkaUnits::ToSetting(m_display_limenka_unit);
1534          if (const QString* setting_str = std::get_if<QString>(&setting_val)) {
1535              settings.setValue("nDisplayUnit", *setting_str);
1536          } else {
1537              settings.setValue("nDisplayUnit", std::get<qint8>(setting_val));
1538          }
1539      }
1540      Q_EMIT displayUnitChanged(m_display_limenka_unit);
1541  }
1542  
1543  void OptionsModel::setRestartRequired(bool fRequired)
1544  {
1545      QSettings settings;
1546      return settings.setValue("fRestartRequired", fRequired);
1547  }
1548  
1549  bool OptionsModel::isRestartRequired() const
1550  {
1551      QSettings settings;
1552      return settings.value("fRestartRequired", false).toBool();
1553  }
1554  
1555  bool OptionsModel::hasSigner()
1556  {
1557      return gArgs.GetArg("-signer", "") != "";
1558  }
1559  
1560  void OptionsModel::checkAndMigrate()
1561  {
1562      // Migration of default values
1563      // Check if the QSettings container was already loaded with this client version
1564      QSettings settings;
1565      static const char strSettingsVersionKey[] = "nSettingsVersion";
1566      int settingsVersion = settings.contains(strSettingsVersionKey) ? settings.value(strSettingsVersionKey).toInt() : 0;
1567      if (settingsVersion < CLIENT_VERSION)
1568      {
1569          // -dbcache was bumped from 100 to 300 in 0.13
1570          // see https://github.com/limenka/limenka/pull/8273
1571          // force people to upgrade to the new value if they are using 100MB
1572          if (settingsVersion < 130000 && settings.contains("nDatabaseCache") && settings.value("nDatabaseCache").toLongLong() == 100)
1573              settings.setValue("nDatabaseCache", qint64(node::GetDefaultDBCache() / 1_MiB));
1574  
1575          settings.setValue(strSettingsVersionKey, CLIENT_VERSION);
1576      }
1577  
1578      // Overwrite the 'addrProxy' setting in case it has been set to an illegal
1579      // default value (see issue #12623; PR #12650).
1580      if (settings.contains("addrProxy") && settings.value("addrProxy").toString().endsWith("%2")) {
1581          settings.setValue("addrProxy", GetDefaultProxyAddress());
1582      }
1583  
1584      // Overwrite the 'addrSeparateProxyTor' setting in case it has been set to an illegal
1585      // default value (see issue #12623; PR #12650).
1586      if (settings.contains("addrSeparateProxyTor") && settings.value("addrSeparateProxyTor").toString().endsWith("%2")) {
1587          settings.setValue("addrSeparateProxyTor", GetDefaultProxyAddress());
1588      }
1589  
1590      // Migrate and delete legacy GUI settings that have now moved to <datadir>/settings.json.
1591      auto migrate_setting = [&](OptionID option, const QString& qt_name) {
1592          if (!settings.contains(qt_name)) return;
1593          QVariant value = settings.value(qt_name);
1594          if (node().getPersistentSetting(SettingName(option)).isNull()) {
1595              if (option == ProxyIP) {
1596                  ProxySetting parsed = ParseProxyString(value.toString());
1597                  setOption(ProxyIP, parsed.ip);
1598                  setOption(ProxyPort, parsed.port);
1599              } else if (option == ProxyIPTor) {
1600                  ProxySetting parsed = ParseProxyString(value.toString());
1601                  setOption(ProxyIPTor, parsed.ip);
1602                  setOption(ProxyPortTor, parsed.port);
1603              } else if (option == PruneSizeMiB) {
1604                  // Stored as GB
1605                  const int64_t prune_size_gb = value.toInt();
1606                  const int prune_size_mib = std::max(prune_size_gb * GB_BYTES / MiB_BYTES, MIN_DISK_SPACE_FOR_BLOCK_FILES / MiB_BYTES);
1607                  setOption(option, prune_size_mib);
1608              } else if (option == PruneTristate) {
1609                  // Stored as bool
1610                  setOption(option, value.toBool() ? Qt::Checked : Qt::Unchecked);
1611              } else {
1612                  setOption(option, value);
1613              }
1614          }
1615          settings.remove(qt_name);
1616      };
1617  
1618      migrate_setting(DatabaseCache, "nDatabaseCache");
1619      migrate_setting(ThreadsScriptVerif, "nThreadsScriptVerif");
1620  #ifdef ENABLE_WALLET
1621      migrate_setting(SpendZeroConfChange, "bSpendZeroConfChange");
1622      migrate_setting(ExternalSignerPath, "external_signer_path");
1623  #endif
1624      migrate_setting(MapPortUPnP, "fUseUPnP");
1625      migrate_setting(MapPortNatpmp, "fUseNatpmp");
1626      migrate_setting(Listen, "fListen");
1627      migrate_setting(Server, "server");
1628      migrate_setting(PruneSizeMiB, "nPruneSize");
1629      migrate_setting(PruneTristate, "bPrune");
1630      migrate_setting(ProxyIP, "addrProxy");
1631      migrate_setting(ProxyUse, "fUseProxy");
1632      migrate_setting(ProxyIPTor, "addrSeparateProxyTor");
1633      migrate_setting(ProxyUseTor, "fUseSeparateProxyTor");
1634      migrate_setting(Language, "language");
1635  
1636      // In case migrating QSettings caused any settings value to change, rerun
1637      // parameter interaction code to update other settings. This is particularly
1638      // important for the -listen setting, which should cause -listenonion, -upnp,
1639      // and other settings to default to false if it was set to false.
1640      // (https://github.com/limenka/gui/issues/567).
1641      node().initParameterInteraction();
1642  }
1643