diff --git a/src/common/args.cpp b/src/common/args.cpp index 1cd4090b54..86a71fece1 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -1038,7 +1038,7 @@ void ModifyRWConfigStream(std::istream& stream_in, std::ostream& stream_out, con } } -void ArgsManager::ModifyRWConfigFile(const std::map& settings_to_change) +void ArgsManager::ModifyRWConfigFile(const std::map& settings_to_change, const bool also_settings_json) { LOCK(cs_args); fs::path rwconf_path{GetRWConfigFilePath()}; @@ -1065,20 +1065,23 @@ void ArgsManager::ModifyRWConfigFile(const std::map& s for (const auto& setting_change : settings_to_change) { m_settings.rw_config[setting_change.first] = {setting_change.second}; } - if (!IsArgNegated("-settings")) { + if (also_settings_json && !IsArgNegated("-settings")) { // Also save to settings.json for Core (0.21+) compatibility for (const auto& setting_change : settings_to_change) { m_settings.rw_settings[setting_change.first] = setting_change.second; } WriteSettingsFile(); } + if (settings_to_change.count("prune")) { + m_rwconf_had_prune_option = true; + } } -void ArgsManager::ModifyRWConfigFile(const std::string& setting_to_change, const std::string& new_value) +void ArgsManager::ModifyRWConfigFile(const std::string& setting_to_change, const std::string& new_value, const bool also_settings_json) { std::map settings_to_change; settings_to_change[setting_to_change] = new_value; - ModifyRWConfigFile(settings_to_change); + ModifyRWConfigFile(settings_to_change, also_settings_json); } void ArgsManager::EraseRWConfigFile() diff --git a/src/common/args.h b/src/common/args.h index fda7be39ad..1495e20f09 100644 --- a/src/common/args.h +++ b/src/common/args.h @@ -143,6 +143,7 @@ protected: std::list m_config_sections GUARDED_BY(cs_args); std::optional m_config_path GUARDED_BY(cs_args); std::optional m_rwconf_path GUARDED_BY(cs_args); + bool m_rwconf_had_prune_option{false}; mutable fs::path m_cached_blocks_path GUARDED_BY(cs_args); mutable fs::path m_cached_datadir_path GUARDED_BY(cs_args); mutable fs::path m_cached_network_datadir_path GUARDED_BY(cs_args); @@ -189,8 +190,9 @@ protected: fs::path GetRWConfigFilePath() const; [[nodiscard]] bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false); - void ModifyRWConfigFile(const std::map& settings_to_change); - void ModifyRWConfigFile(const std::string& setting_to_change, const std::string& new_value); + bool RWConfigHasPruneOption() const { return m_rwconf_had_prune_option; } + void ModifyRWConfigFile(const std::map& settings_to_change, bool also_settings_json = true); + void ModifyRWConfigFile(const std::string& setting_to_change, const std::string& new_value, bool also_settings_json = true); void EraseRWConfigFile(); /** diff --git a/src/common/config.cpp b/src/common/config.cpp index 42b2eee71f..8fde9ef637 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -129,6 +129,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) LOCK(cs_args); m_settings.ro_config.clear(); m_settings.rw_config.clear(); + m_rwconf_had_prune_option = false; m_config_sections.clear(); m_config_path = AbsPathForConfigVal(*this, GetPathArg("-conf", BITCOIN_CONF_FILENAME), /*net_specific=*/false); } @@ -242,6 +243,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) if (!ReadConfigStream(rwconf_stream, fs::PathToString(rwconf_path), error, ignore_invalid_keys, &m_settings.rw_config)) { return false; } + m_rwconf_had_prune_option = m_settings.rw_config.count("prune"); } return true; diff --git a/src/net.cpp b/src/net.cpp index b6e1442e16..47d98be59c 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -3693,6 +3693,13 @@ void CConnman::RecordBytesSent(uint64_t bytes) nMaxOutboundTotalBytesSentInCycle += bytes; } +void CConnman::SetMaxOutboundTarget(uint64_t limit) +{ + AssertLockNotHeld(m_total_bytes_sent_mutex); + LOCK(m_total_bytes_sent_mutex); + nMaxOutboundLimit = limit; +} + uint64_t CConnman::GetMaxOutboundTarget() const { AssertLockNotHeld(m_total_bytes_sent_mutex); diff --git a/src/net.h b/src/net.h index 2e0b53a268..d85e44d638 100644 --- a/src/net.h +++ b/src/net.h @@ -1258,6 +1258,8 @@ public: void AddLocalServices(ServiceFlags services) { nLocalServices = ServiceFlags(nLocalServices | services); }; void RemoveLocalServices(ServiceFlags services) { nLocalServices = ServiceFlags(nLocalServices & ~services); } + //! set the max outbound target in bytes + void SetMaxOutboundTarget(uint64_t limit) EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); uint64_t GetMaxOutboundTarget() const EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); std::chrono::seconds GetMaxOutboundTimeframe() const; diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 14dbc34a72..d26e18cc71 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -343,7 +343,7 @@ void BitcoinApplication::parameterSetup() void BitcoinApplication::InitPruneSetting(int64_t prune_MiB) { - optionsModel->SetPruneTargetGB(PruneMiBtoGB(prune_MiB)); + optionsModel->SetPruneTargetMiB(prune_MiB); } void BitcoinApplication::requestInitialize() diff --git a/src/qt/forms/intro.ui b/src/qt/forms/intro.ui index b73bb3f3ad..57efb0fa3a 100644 --- a/src/qt/forms/intro.ui +++ b/src/qt/forms/intro.ui @@ -243,16 +243,16 @@ - + - GB + MiB - pruneGB + pruneMiB diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index f88702fefd..36e78b6c33 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -63,12 +63,12 @@ - + - GB + MiB Qt::PlainText @@ -226,6 +226,27 @@ + + + + + + Address type: + + + addressType + + + + + + + Choose the default address type to select when receiving coins. + + + + + @@ -672,6 +693,66 @@ + + + + + + Try to keep upload traffic under + + + + + + + + + + MiB per day + + + Qt::PlainText + + + maxuploadtargetCheckbox + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Support filtering of blocks and transaction with bloom filters + + + Provide search services for light clients + + + + + + + Generate compact block filters and allow peers to download them + + + Provide compact block filters for light clients + + + diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index f1d36ca38f..6f368dabc8 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -58,10 +58,13 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80; #define QAPP_APP_NAME_SIGNET "Bitcoin-Qt-signet" #define QAPP_APP_NAME_REGTEST "Bitcoin-Qt-regtest" +/* One mebibyte (MiB) in bytes */ +static constexpr uint64_t MiB_BYTES{1024 * 1024}; + /* One gigabyte (GB) in bytes */ static constexpr uint64_t GB_BYTES{1000000000}; // Default prune target displayed in GUI. -static constexpr int DEFAULT_PRUNE_TARGET_GB{2}; +static constexpr int DEFAULT_PRUNE_TARGET_MiB{1907}; #endif // BITCOIN_QT_GUICONSTANTS_H diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index 9e55258ac4..4ba510a95d 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -119,11 +119,11 @@ void FreespaceChecker::check() namespace { //! Return pruning size that will be used if automatic pruning is enabled. -int GetPruneTargetGB() +int GetPruneTargetMiB() { int64_t prune_target_mib = gArgs.GetIntArg("-prune", 0); // >1 means automatic pruning is enabled by config, 1 means manual pruning, 0 means no pruning. - return prune_target_mib > 1 ? PruneMiBtoGB(prune_target_mib) : DEFAULT_PRUNE_TARGET_GB; + return prune_target_mib > 1 ? prune_target_mib : DEFAULT_PRUNE_TARGET_MiB; } } // namespace @@ -132,7 +132,7 @@ Intro::Intro(QWidget *parent, int64_t blockchain_size_gb, int64_t chain_state_si ui(new Ui::Intro), m_blockchain_size_gb(blockchain_size_gb), m_chain_state_size_gb(chain_state_size_gb), - m_prune_target_gb{GetPruneTargetGB()} + m_prune_target_mib{GetPruneTargetMiB()} { ui->setupUi(this); ui->welcomeLabel->setText(ui->welcomeLabel->text().arg(PACKAGE_NAME)); @@ -146,26 +146,35 @@ Intro::Intro(QWidget *parent, int64_t blockchain_size_gb, int64_t chain_state_si ); ui->lblExplanation2->setText(ui->lblExplanation2->text().arg(PACKAGE_NAME)); - const int min_prune_target_GB = std::ceil(MIN_DISK_SPACE_FOR_BLOCK_FILES / 1e9); - ui->pruneGB->setRange(min_prune_target_GB, std::numeric_limits::max()); + const int min_prune_target_MiB = (MIN_DISK_SPACE_FOR_BLOCK_FILES + MiB_BYTES - 1) / MiB_BYTES; + ui->pruneMiB->setRange(min_prune_target_MiB, std::numeric_limits::max()); if (gArgs.IsArgSet("-prune")) { m_prune_checkbox_is_default = false; - ui->prune->setChecked(gArgs.GetIntArg("-prune", 0) >= 1); - ui->prune->setEnabled(false); + switch (gArgs.GetIntArg("-prune", 0)) { + case 0: + ui->prune->setChecked(false); + break; + case 1: + ui->prune->setTristate(); + ui->prune->setCheckState(Qt::PartiallyChecked); + break; + default: + ui->prune->setChecked(true); + } } - ui->pruneGB->setValue(m_prune_target_gb); - ui->pruneGB->setToolTip(ui->prune->toolTip()); + ui->pruneMiB->setValue(m_prune_target_mib); + ui->pruneMiB->setToolTip(ui->prune->toolTip()); ui->lblPruneSuffix->setToolTip(ui->prune->toolTip()); - UpdatePruneLabels(ui->prune->isChecked()); + UpdatePruneLabels(ui->prune->checkState() == Qt::Checked); - connect(ui->prune, &QCheckBox::toggled, [this](bool prune_checked) { + connect(ui->prune, &QCheckBox::stateChanged, [this](int prune_state) { m_prune_checkbox_is_default = false; - UpdatePruneLabels(prune_checked); + UpdatePruneLabels(prune_state == Qt::Checked); UpdateFreeSpaceLabel(); }); - connect(ui->pruneGB, qOverload(&QSpinBox::valueChanged), [this](int prune_GB) { - m_prune_target_gb = prune_GB; - UpdatePruneLabels(ui->prune->isChecked()); + connect(ui->pruneMiB, qOverload(&QSpinBox::valueChanged), [this](int prune_MiB) { + m_prune_target_mib = prune_MiB; + UpdatePruneLabels(ui->prune->checkState() == Qt::Checked); UpdateFreeSpaceLabel(); }); @@ -235,7 +244,9 @@ int64_t Intro::getPruneMiB() const { switch (ui->prune->checkState()) { case Qt::Checked: - return PruneGBtoMiB(m_prune_target_gb); + return m_prune_target_mib; + case Qt::PartiallyChecked: + return 1; case Qt::Unchecked: default: return 0; } @@ -360,7 +371,7 @@ void Intro::UpdateFreeSpaceLabel() freeString += " " + tr("(of %n GB needed)", "", m_required_space_gb); ui->freeSpace->setStyleSheet("QLabel { color: #800000 }"); } else if (m_bytes_available / GB_BYTES - m_required_space_gb < 10) { - freeString += " " + tr("(%n GB needed for full chain)", "", m_required_space_gb); + freeString += " " + tr("(%n GB needed)", "", m_required_space_gb); ui->freeSpace->setStyleSheet("QLabel { color: #999900 }"); } else { ui->freeSpace->setStyleSheet(""); @@ -433,14 +444,15 @@ void Intro::UpdatePruneLabels(bool prune_checked) { m_required_space_gb = m_blockchain_size_gb + m_chain_state_size_gb; QString storageRequiresMsg = tr("At least %1 GB of data will be stored in this directory, and it will grow over time."); - if (prune_checked && m_prune_target_gb <= m_blockchain_size_gb) { - m_required_space_gb = m_prune_target_gb + m_chain_state_size_gb; + const int64_t prune_target_gb = (m_prune_target_mib * MiB_BYTES + GB_BYTES - 1) / GB_BYTES; + if (prune_checked && prune_target_gb <= m_blockchain_size_gb) { + m_required_space_gb = prune_target_gb + m_chain_state_size_gb; storageRequiresMsg = tr("Approximately %1 GB of data will be stored in this directory."); } - ui->pruneGB->setEnabled(prune_checked); + ui->pruneMiB->setEnabled(prune_checked); static constexpr uint64_t nPowTargetSpacing = 10 * 60; // from chainparams, which we don't have at this stage static constexpr uint32_t expected_block_data_size = 2250000; // includes undo data - const uint64_t expected_backup_days = m_prune_target_gb * 1e9 / (uint64_t(expected_block_data_size) * 86400 / nPowTargetSpacing); + const uint64_t expected_backup_days = m_prune_target_mib * MiB_BYTES / (uint64_t(expected_block_data_size) * 86400 / nPowTargetSpacing); ui->lblPruneSuffix->setText( //: Explanatory text on the capability of the current prune target. tr("(sufficient to restore backups %n day(s) old)", "", expected_backup_days)); diff --git a/src/qt/intro.h b/src/qt/intro.h index b9cd9353fa..11c3e0150b 100644 --- a/src/qt/intro.h +++ b/src/qt/intro.h @@ -79,7 +79,7 @@ private: //! Total required space (in GB) depending on user choice (prune or not prune). int64_t m_required_space_gb{0}; uint64_t m_bytes_available{0}; - int64_t m_prune_target_gb; + int64_t m_prune_target_mib; void startThread(); void checkPath(const QString &dataDir); diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 4767337d6d..f60c8b3fa3 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -14,9 +14,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -102,8 +104,10 @@ OptionsDialog::OptionsDialog(QWidget* parent, bool enableWallet) ui->pruneWarning->setVisible(false); ui->pruneWarning->setStyleSheet("QLabel { color: red; }"); - ui->pruneSize->setEnabled(false); - connect(ui->prune, &QPushButton::toggled, ui->pruneSize, &QWidget::setEnabled); + ui->pruneSizeMiB->setEnabled(false); + connect(ui->prune, &QCheckBox::stateChanged, [this](int state){ + ui->pruneSizeMiB->setEnabled(state == Qt::Checked); + }); ui->networkPort->setValidator(new QIntValidator(1024, 65535, this)); connect(ui->networkPort, SIGNAL(textChanged(const QString&)), this, SLOT(checkLineEdit())); @@ -132,6 +136,10 @@ OptionsDialog::OptionsDialog(QWidget* parent, bool enableWallet) connect(ui->connectSocksTor, &QPushButton::toggled, ui->proxyPortTor, &QWidget::setEnabled); connect(ui->connectSocksTor, &QPushButton::toggled, this, &OptionsDialog::updateProxyValidationState); + ui->maxuploadtarget->setMinimum(144 /* MiB/day */); + ui->maxuploadtarget->setMaximum(std::numeric_limits::max()); + connect(ui->maxuploadtargetCheckbox, SIGNAL(toggled(bool)), ui->maxuploadtarget, SLOT(setEnabled(bool))); + /* Window elements init */ #ifdef Q_OS_MACOS /* remove Window tab on Mac */ @@ -147,6 +155,15 @@ OptionsDialog::OptionsDialog(QWidget* parent, bool enableWallet) ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWallet)); ui->thirdPartyTxUrlsLabel->setVisible(false); ui->thirdPartyTxUrls->setVisible(false); + } else { + for (OutputType type : OUTPUT_TYPES) { + const QString& val = QString::fromStdString(FormatOutputType(type)); + const auto [text, tooltip] = GetOutputTypeDescription(type); + + const auto index = ui->addressType->count(); + ui->addressType->addItem(text, val); + ui->addressType->setItemData(index, tooltip, Qt::ToolTipRole); + } } #ifdef ENABLE_EXTERNAL_SIGNER @@ -239,9 +256,8 @@ void OptionsDialog::setModel(OptionsModel *_model) if (_model->isRestartRequired()) showRestartWarning(true); - // Prune values are in GB to be consistent with intro.cpp - static constexpr uint64_t nMinDiskSpace = (MIN_DISK_SPACE_FOR_BLOCK_FILES / GB_BYTES) + (MIN_DISK_SPACE_FOR_BLOCK_FILES % GB_BYTES) ? 1 : 0; - ui->pruneSize->setRange(nMinDiskSpace, std::numeric_limits::max()); + static constexpr uint64_t nMinDiskSpace = (MIN_DISK_SPACE_FOR_BLOCK_FILES + MiB_BYTES - 1) / MiB_BYTES; + ui->pruneSizeMiB->setRange(nMinDiskSpace, std::numeric_limits::max()); QString strLabel = _model->getOverriddenByCommandLine(); if (strLabel.isEmpty()) @@ -266,7 +282,7 @@ void OptionsDialog::setModel(OptionsModel *_model) /* Main */ connect(ui->prune, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); connect(ui->prune, &QCheckBox::clicked, this, &OptionsDialog::togglePruneWarning); - connect(ui->pruneSize, qOverload(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); + connect(ui->pruneSizeMiB, qOverload(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); connect(ui->databaseCache, qOverload(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); connect(ui->externalSignerPath, &QLineEdit::textChanged, [this]{ showRestartWarning(); }); connect(ui->threadsScriptVerif, qOverload(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); @@ -278,6 +294,8 @@ void OptionsDialog::setModel(OptionsModel *_model) connect(ui->enableServer, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); connect(ui->connectSocks, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); connect(ui->connectSocksTor, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); + connect(ui->peerbloomfilters, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); + connect(ui->peerblockfilters, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); /* Display */ connect(ui->lang, qOverload<>(&QValueComboBox::valueChanged), [this]{ showRestartWarning(); }); connect(ui->thirdPartyTxUrls, &QLineEdit::textChanged, [this]{ showRestartWarning(); }); @@ -299,10 +317,16 @@ void OptionsDialog::setMapper() mapper->addMapping(ui->bitcoinAtStartup, OptionsModel::StartAtStartup); mapper->addMapping(ui->threadsScriptVerif, OptionsModel::ThreadsScriptVerif); mapper->addMapping(ui->databaseCache, OptionsModel::DatabaseCache); - mapper->addMapping(ui->prune, OptionsModel::Prune); - mapper->addMapping(ui->pruneSize, OptionsModel::PruneSize); + + const auto prune_checkstate = model->data(model->index(OptionsModel::PruneTristate, 0), Qt::EditRole).value(); + if (prune_checkstate == Qt::PartiallyChecked) { + ui->prune->setTristate(); + } + ui->prune->setCheckState(prune_checkstate); + mapper->addMapping(ui->pruneSizeMiB, OptionsModel::PruneSizeMiB); /* Wallet */ + mapper->addMapping(ui->addressType, OptionsModel::addresstype); mapper->addMapping(ui->spendZeroConfChange, OptionsModel::SpendZeroConfChange); mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures); mapper->addMapping(ui->subFeeFromAmount, OptionsModel::SubFeeFromAmount); @@ -324,6 +348,30 @@ void OptionsDialog::setMapper() mapper->addMapping(ui->proxyIpTor, OptionsModel::ProxyIPTor); mapper->addMapping(ui->proxyPortTor, OptionsModel::ProxyPortTor); + int current_maxuploadtarget = model->data(model->index(OptionsModel::maxuploadtarget, 0), Qt::EditRole).toInt(); + if (current_maxuploadtarget == 0) { + ui->maxuploadtargetCheckbox->setChecked(false); + ui->maxuploadtarget->setEnabled(false); + ui->maxuploadtarget->setValue(ui->maxuploadtarget->minimum()); + } else { + if (current_maxuploadtarget < ui->maxuploadtarget->minimum()) { + ui->maxuploadtarget->setMinimum(current_maxuploadtarget); + } + ui->maxuploadtargetCheckbox->setChecked(true); + ui->maxuploadtarget->setEnabled(true); + ui->maxuploadtarget->setValue(current_maxuploadtarget); + } + + mapper->addMapping(ui->peerbloomfilters, OptionsModel::peerbloomfilters); + mapper->addMapping(ui->peerblockfilters, OptionsModel::peerblockfilters); + if (prune_checkstate != Qt::Unchecked && !GetBlockFilterIndex(BlockFilterType::BASIC)) { + // Once pruning begins, it's too late to enable block filters, and doing so will prevent starting the client + // Rather than try to monitor sync state, just disable the option once pruning is enabled + // Advanced users can override this manually anyway + ui->peerblockfilters->setEnabled(false); + ui->peerblockfilters->setToolTip(ui->peerblockfilters->toolTip() + " " + tr("(only available if enabled at least once before turning on pruning)")); + } + /* Window */ #ifndef Q_OS_MACOS if (QSystemTrayIcon::isSystemTrayAvailable()) { @@ -428,9 +476,17 @@ void OptionsDialog::on_okButton_clicked() } } + model->setData(model->index(OptionsModel::PruneTristate, 0), ui->prune->checkState()); + model->setData(model->index(OptionsModel::FontForMoney, 0), ui->moneyFont->itemData(ui->moneyFont->currentIndex())); model->setData(model->index(OptionsModel::FontForQRCodes, 0), ui->qrFont->itemData(ui->qrFont->currentIndex())); + if (ui->maxuploadtargetCheckbox->isChecked()) { + model->setData(model->index(OptionsModel::maxuploadtarget, 0), ui->maxuploadtarget->value()); + } else { + model->setData(model->index(OptionsModel::maxuploadtarget, 0), 0); + } + mapper->submit(); accept(); updateDefaultProxyNets(); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 0d2c7576c3..7d2a82d2d2 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -12,16 +12,27 @@ #include #include +#include #include #include #include +#include #include #include +#include +#include #include // for -dbcache defaults #include #include // For DEFAULT_SCRIPTCHECK_THREADS #include // For DEFAULT_SPEND_ZEROCONF_CHANGE +#ifdef ENABLE_WALLET +#include +#endif + +#include +#include + #include #include #include @@ -46,8 +57,9 @@ static const char* SettingName(OptionsModel::OptionID option) 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::addresstype: return "addresstype"; + case OptionsModel::PruneSizeMiB: return "prune"; + case OptionsModel::PruneTristate: return "prune"; case OptionsModel::ProxyIP: return "proxy"; case OptionsModel::ProxyPort: return "proxy"; case OptionsModel::ProxyUse: return "proxy"; @@ -55,6 +67,9 @@ static const char* SettingName(OptionsModel::OptionID option) case OptionsModel::ProxyPortTor: return "onion"; case OptionsModel::ProxyUseTor: return "onion"; case OptionsModel::Language: return "lang"; + case OptionsModel::maxuploadtarget: return "maxuploadtarget"; + case OptionsModel::peerbloomfilters: return "peerbloomfilters"; + case OptionsModel::peerblockfilters: return "peerblockfilters"; default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option)); } } @@ -65,8 +80,8 @@ static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID optio if (value.isNum() && (option == OptionsModel::DatabaseCache || option == OptionsModel::ThreadsScriptVerif || - option == OptionsModel::Prune || - option == OptionsModel::PruneSize)) { + option == OptionsModel::PruneTristate || + option == OptionsModel::PruneSizeMiB)) { // 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 @@ -81,10 +96,17 @@ static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID optio } //! Convert enabled/size values to bitcoin -prune setting. -static common::SettingsValue PruneSetting(bool prune_enabled, int prune_size_gb) +static common::SettingsValue PruneSettingFromMiB(Qt::CheckState prune_enabled, int prune_size_mib) { - assert(!prune_enabled || prune_size_gb >= 1); // PruneSizeGB and ParsePruneSizeGB never return less - return prune_enabled ? PruneGBtoMiB(prune_size_gb) : 0; + assert(prune_enabled != Qt::Checked || prune_size_mib >= 1); // PruneSizeMiB and ParsePruneSizeMiB never return less + switch (prune_enabled) { + case Qt::Unchecked: + return 0; + case Qt::PartiallyChecked: + return 1; + default: + return prune_size_mib; + } } //! Get pruning enabled value to show in GUI from bitcoin -prune setting. @@ -94,20 +116,25 @@ static bool PruneEnabled(const common::SettingsValue& prune_setting) 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 common::SettingsValue& prune_setting) +//! Get pruning enabled value to show in GUI from bitcoin -prune setting. +static Qt::CheckState PruneSettingAsTristate(const common::SettingsValue& prune_setting) { - int value = SettingToInt(prune_setting, 0); - return value > 1 ? PruneMiBtoGB(value) : DEFAULT_PRUNE_TARGET_GB; + switch (SettingToInt(prune_setting, 0)) { + case 0: + return Qt::Unchecked; + case 1: + return Qt::PartiallyChecked; + default: + return Qt::Checked; + } } -//! 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) +//! 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 PruneSizeAsMiB(const common::SettingsValue& prune_setting) { - return std::max(1, prune_size.toInt()); + int value = SettingToInt(prune_setting, 0); + return value > 1 ? value : DEFAULT_PRUNE_TARGET_MiB; } struct ProxySetting { @@ -122,6 +149,33 @@ static const QLatin1String fontchoice_str_embedded{"embedded"}; static const QLatin1String fontchoice_str_best_system{"best_system"}; static const QString fontchoice_str_custom_prefix{QStringLiteral("custom, ")}; +static const std::map> UntranslatedOutputTypeDescriptions{ + {OutputType::LEGACY, { + QT_TRANSLATE_NOOP("Output type name", "Base58 (Legacy)"), + QT_TRANSLATE_NOOP("Output type description", "Widest compatibility and best for health of the Bitcoin network, but may result in higher fees later. Recommended."), + }}, + {OutputType::P2SH_SEGWIT, { + QT_TRANSLATE_NOOP("Output type name", "Base58 (P2SH Segwit)"), + QT_TRANSLATE_NOOP("Output type description", "Compatible with most older wallets, and may result in lower fees than Legacy."), + }}, + {OutputType::BECH32, { + QT_TRANSLATE_NOOP("Output type name", "Native Segwit (Bech32)"), + QT_TRANSLATE_NOOP("Output type description", "Lower fees than Base58, but some old wallets don't support it."), + }}, + {OutputType::BECH32M, { + QT_TRANSLATE_NOOP("Output type name", "Taproot (Bech32m)"), + QT_TRANSLATE_NOOP("Output type description", "Lowest fees, but wallet support is still limited."), + }}, +}; + +std::pair GetOutputTypeDescription(const OutputType type) +{ + auto& untr = UntranslatedOutputTypeDescriptions.at(type); + QString text = QCoreApplication::translate("Output type name", untr.first); + QString tooltip = QCoreApplication::translate("Output type description", untr.second); + return std::make_pair(text, tooltip); +} + QString OptionsModel::FontChoiceToString(const OptionsModel::FontChoice& f) { if (std::holds_alternative(f)) { @@ -216,9 +270,14 @@ bool OptionsModel::Init(bilingual_str& error) // These are shared with the core or have a command-line parameter // and we want command-line parameters to overwrite the GUI settings. + std::unordered_set checked_settings; for (OptionID option : {DatabaseCache, ThreadsScriptVerif, SpendZeroConfChange, ExternalSignerPath, MapPortUPnP, - MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language}) { + MapPortNatpmp, Listen, Server, PruneTristate, ProxyUse, ProxyUseTor, Language}) { + // isSettingIgnored will have a false positive here during first-run prune changes + if (option == PruneTristate && m_prune_forced_by_gui) continue; + std::string setting = SettingName(option); + checked_settings.insert(setting); if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting); try { getOption(option); @@ -231,6 +290,18 @@ bool OptionsModel::Init(bilingual_str& error) } } + if (m_prune_forced_by_gui) checked_settings.insert("prune"); + for (OptionID option = OptionID(0); option < OptionIDRowCount; option = OptionID(option + 1)) { + std::string setting; + try { + setting = SettingName(option); + } catch (const std::logic_error&) { + continue; // Ignore GUI-only settings + } + if (!checked_settings.insert(setting).second) continue; + if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting); + } + // If setting doesn't exist create it with defaults. // Main @@ -251,6 +322,9 @@ bool OptionsModel::Init(bilingual_str& error) if (!gArgs.SoftSetArg("-port", settings.value("nNetworkPort").toString().toStdString())) addOverriddenOption("-port"); + // rwconf settings that require a restart + f_peerbloomfilters = gArgs.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS); + // Display if (settings.contains("FontForMoney")) { m_font_money = FontChoiceFromString(settings.value("FontForMoney").toString()); @@ -321,6 +395,11 @@ void OptionsModel::Reset() // Set strDataDir settings.setValue("strDataDir", dataDir); + // Set prune option iff it was configured in rwconf + if (gArgs.RWConfigHasPruneOption()) { + SetPruneTargetMiB(gArgs.GetIntArg("-prune", 0)); + } + // Set that this was reset settings.setValue("fReset", true); @@ -370,29 +449,30 @@ static QString GetDefaultProxyAddress() return QString("%1:%2").arg(DEFAULT_GUI_PROXY_HOST).arg(DEFAULT_GUI_PROXY_PORT); } -void OptionsModel::SetPruneTargetGB(int prune_target_gb) +void OptionsModel::SetPruneTargetMiB(int prune_target_mib) { const common::SettingsValue cur_value = node().getPersistentSetting("prune"); - const common::SettingsValue new_value = PruneSetting(prune_target_gb > 0, prune_target_gb); + const common::SettingsValue new_value = prune_target_mib; // 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); + m_prune_forced_by_gui = true; // 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)) { + if (cur_value.write() != new_value.write()) { // Call UpdateRwSetting() instead of setOption() to avoid setting // RestartRequired flag - UpdateRwSetting(node(), Prune, "", new_value); + UpdateRwSetting(node(), PruneTristate, "", new_value); + gArgs.ModifyRWConfigFile("prune", new_value.getValStr()); } // Keep previous pruning size, if pruning was disabled. if (PruneEnabled(cur_value)) { - UpdateRwSetting(node(), Prune, "-prev", PruneEnabled(new_value) ? common::SettingsValue{} : cur_value); + UpdateRwSetting(node(), PruneTristate, "-prev", PruneEnabled(new_value) ? common::SettingsValue{} : cur_value); } } @@ -484,6 +564,11 @@ QVariant OptionsModel::getOption(OptionID option, const std::string& suffix) con return QString::fromStdString(SettingToString(setting(), "")); case SubFeeFromAmount: return m_sub_fee_from_amount; + case addresstype: + { + const OutputType default_address_type = ParseOutputType(gArgs.GetArg("-addresstype", "")).value_or(wallet::DEFAULT_ADDRESS_TYPE); + return QString::fromStdString(FormatOutputType(default_address_type)); + } #endif case DisplayUnit: return QVariant::fromValue(m_display_bitcoin_unit); @@ -501,12 +586,12 @@ QVariant OptionsModel::getOption(OptionID option, const std::string& suffix) con return fCoinControlFeatures; case EnablePSBTControls: return settings.value("enable_psbt_controls"); - case Prune: - return PruneEnabled(setting()); - case PruneSize: - return PruneEnabled(setting()) ? PruneSizeGB(setting()) : + case PruneTristate: + return PruneSettingAsTristate(setting()); + case PruneSizeMiB: + return PruneEnabled(setting()) ? PruneSizeAsMiB(setting()) : suffix.empty() ? getOption(option, "-prev") : - DEFAULT_PRUNE_TARGET_GB; + DEFAULT_PRUNE_TARGET_MiB; case DatabaseCache: return qlonglong(SettingToInt(setting(), nDefaultDbCache)); case ThreadsScriptVerif: @@ -517,6 +602,12 @@ QVariant OptionsModel::getOption(OptionID option, const std::string& suffix) con return SettingToBool(setting(), false); case MaskValues: return m_mask_values; + case maxuploadtarget: + return qlonglong(node().context()->connman->GetMaxOutboundTarget() / 1024 / 1024); + case peerbloomfilters: + return f_peerbloomfilters; + case peerblockfilters: + return gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS); default: return QVariant(); } @@ -667,6 +758,27 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value, const std:: m_sub_fee_from_amount = value.toBool(); settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount); break; + case addresstype: + { + const std::string newvalue_str = value.toString().toStdString(); + const OutputType oldvalue = ParseOutputType(gArgs.GetArg("-addresstype", "")).value_or(wallet::DEFAULT_ADDRESS_TYPE); + const OutputType newvalue = ParseOutputType(newvalue_str).value_or(oldvalue); + if (newvalue != oldvalue) { + gArgs.ModifyRWConfigFile("addresstype", newvalue_str); + gArgs.ForceSetArg("-addresstype", newvalue_str); + for (auto& wallet_interface : m_node.walletLoader().getWallets()) { + wallet::CWallet *wallet; + if (wallet_interface && (wallet = wallet_interface->wallet())) { + wallet->m_default_address_type = newvalue; + } else { + setRestartRequired(true); + continue; + } + } + Q_EMIT addresstypeChanged(newvalue); + } + break; + } #endif case DisplayUnit: setDisplayUnit(value); @@ -716,22 +828,28 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value, const std:: m_enable_psbt_controls = value.toBool(); settings.setValue("enable_psbt_controls", m_enable_psbt_controls); break; - case Prune: + case PruneTristate: 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", {}); + const bool is_autoprune = (value.value() == Qt::Checked); + const auto prune_setting = PruneSettingFromMiB(value.value(), getOption(PruneSizeMiB).toInt()); + if (suffix.empty() && !is_autoprune) setOption(option, Qt::Checked, "-prev"); + update(prune_setting); + if (suffix.empty()) gArgs.ModifyRWConfigFile("prune", prune_setting.getValStr()); + if (suffix.empty() && is_autoprune) UpdateRwSetting(node(), option, "-prev", {}); if (suffix.empty()) setRestartRequired(true); } break; - case PruneSize: + case PruneSizeMiB: if (changed()) { - if (suffix.empty() && !getOption(Prune).toBool()) { + const bool is_autoprune = (Qt::Checked == getOption(PruneTristate).value()); + if (suffix.empty() && !is_autoprune) { setOption(option, value, "-prev"); } else { - update(PruneSetting(true, ParsePruneSizeGB(value))); + const auto prune_setting = PruneSettingFromMiB(Qt::Checked, value.toInt()); + update(prune_setting); + if (suffix.empty()) gArgs.ModifyRWConfigFile("prune", prune_setting.getValStr()); } - if (suffix.empty() && getOption(Prune).toBool()) setRestartRequired(true); + if (suffix.empty() && is_autoprune) setRestartRequired(true); } break; case DatabaseCache: @@ -757,6 +875,38 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value, const std:: m_mask_values = value.toBool(); settings.setValue("mask_values", m_mask_values); break; + case maxuploadtarget: + { + if (changed()) { + gArgs.ModifyRWConfigFile("maxuploadtarget", value.toString().toStdString()); + node().context()->connman->SetMaxOutboundTarget(value.toLongLong() * 1024 * 1024); + } + break; + } + case peerbloomfilters: + if (changed()) { + gArgs.ModifyRWConfigFile("peerbloomfilters", strprintf("%d", value.toBool())); + f_peerbloomfilters = value.toBool(); + setRestartRequired(true); + } + break; + case peerblockfilters: + { + bool nv = value.toBool(); + if (gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS) != nv) { + gArgs.ModifyRWConfigFile("peerblockfilters", strprintf("%d", nv)); + gArgs.ModifyRWConfigFile("peercfilters", strprintf("%d", nv), /*also_settings_json=*/ false); // for downgrade compatibility with Knots 0.19 + gArgs.ForceSetArg("peerblockfilters", nv); + if (nv && !GetBlockFilterIndex(BlockFilterType::BASIC)) { + // TODO: When other options are possible, we need to append a list! + // TODO: Some way to unset/delete this... + gArgs.ModifyRWConfigFile("blockfilterindex", "basic"); + gArgs.ForceSetArg("blockfilterindex", "basic"); + } + setRestartRequired(true); + } + break; + } default: break; } @@ -833,6 +983,14 @@ void OptionsModel::checkAndMigrate() ProxySetting parsed = ParseProxyString(value.toString()); setOption(ProxyIPTor, parsed.ip); setOption(ProxyPortTor, parsed.port); + } else if (option == PruneSizeMiB) { + // Stored as GB + const int64_t prune_size_gb = value.toInt(); + const int prune_size_mib = std::max(prune_size_gb * GB_BYTES / MiB_BYTES, MIN_DISK_SPACE_FOR_BLOCK_FILES / MiB_BYTES); + setOption(option, prune_size_mib); + } else if (option == PruneTristate) { + // Stored as bool + setOption(option, value.toBool() ? Qt::Checked : Qt::Unchecked); } else { setOption(option, value); } @@ -850,8 +1008,8 @@ void OptionsModel::checkAndMigrate() migrate_setting(MapPortNatpmp, "fUseNatpmp"); migrate_setting(Listen, "fListen"); migrate_setting(Server, "server"); - migrate_setting(PruneSize, "nPruneSize"); - migrate_setting(Prune, "bPrune"); + migrate_setting(PruneSizeMiB, "nPruneSize"); + migrate_setting(PruneTristate, "bPrune"); migrate_setting(ProxyIP, "addrProxy"); migrate_setting(ProxyUse, "fUseProxy"); migrate_setting(ProxyIPTor, "addrSeparateProxyTor"); diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 027dd42fe1..d9c1ddbc5f 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -11,11 +11,16 @@ #include #include +#include #include +#include +#include #include struct bilingual_str; +enum class OutputType; + namespace interfaces { class Node; } @@ -23,15 +28,7 @@ class Node; extern const char *DEFAULT_GUI_PROXY_HOST; static constexpr uint16_t DEFAULT_GUI_PROXY_PORT = 9050; -/** - * Convert configured prune target MiB to displayed GB. Round up to avoid underestimating max disk usage. - */ -static inline int PruneMiBtoGB(int64_t mib) { return (mib * 1024 * 1024 + GB_BYTES - 1) / GB_BYTES; } - -/** - * Convert displayed prune target GB to configured MiB. Round down so roundtrip GB -> MiB -> GB conversion is stable. - */ -static inline int64_t PruneGBtoMiB(int gb) { return gb * GB_BYTES / 1024 / 1024; } +std::pair GetOutputTypeDescription(const OutputType type); /** Interface from Qt to configuration data structure for Bitcoin client. To Qt, the options are presented as a list with the different options @@ -69,15 +66,19 @@ public: CoinControlFeatures, // bool SubFeeFromAmount, // bool ThreadsScriptVerif, // int - Prune, // bool - PruneSize, // int + PruneTristate, // Qt::CheckState + PruneSizeMiB, // int DatabaseCache, // int ExternalSignerPath, // QString SpendZeroConfChange, // bool + addresstype, // QString Listen, // bool Server, // bool EnablePSBTControls, // bool MaskValues, // bool + maxuploadtarget, + peerbloomfilters, // bool + peerblockfilters, // bool OptionIDRowCount, }; @@ -118,7 +119,7 @@ public: bool hasSigner(); /* Explicit setters */ - void SetPruneTargetGB(int prune_target_gb); + void SetPruneTargetMiB(int prune_target_mib); /* Restart flag helper */ void setRestartRequired(bool fRequired); @@ -145,10 +146,14 @@ private: /* settings that were overridden by command-line */ QString strOverriddenByCommandLine; + bool m_prune_forced_by_gui{false}; static QString FontChoiceToString(const OptionsModel::FontChoice&); static FontChoice FontChoiceFromString(const QString&); + /* rwconf settings that require a restart */ + bool f_peerbloomfilters; + // Add option to list of GUI options overridden through command line/config file void addOverriddenOption(const std::string &option); @@ -158,6 +163,7 @@ private: Q_SIGNALS: void displayUnitChanged(BitcoinUnit unit); void coinControlFeaturesChanged(bool); + void addresstypeChanged(OutputType); void showTrayIconChanged(bool); void fontForMoneyChanged(const QFont&); void fontForQRCodesChanged(const FontChoice&); diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 772b99bdbe..716c0d194c 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -93,19 +93,25 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model) columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(tableView, AMOUNT_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this); // Populate address type dropdown and select default - auto add_address_type = [&](OutputType type, const QString& text, const QString& tooltip) { + auto add_address_type = [&](OutputType type) { + const auto [text, tooltip] = GetOutputTypeDescription(type); const auto index = ui->addressType->count(); ui->addressType->addItem(text, (int) type); ui->addressType->setItemData(index, tooltip, Qt::ToolTipRole); if (model->wallet().getDefaultAddressType() == type) ui->addressType->setCurrentIndex(index); }; - add_address_type(OutputType::LEGACY, tr("Base58 (Legacy)"), tr("Not recommended due to higher fees and less protection against typos.")); - add_address_type(OutputType::P2SH_SEGWIT, tr("Base58 (P2SH-SegWit)"), tr("Generates an address compatible with older wallets.")); - add_address_type(OutputType::BECH32, tr("Bech32 (SegWit)"), tr("Generates a native segwit address (BIP-173). Some old wallets don't support it.")); + add_address_type(OutputType::LEGACY); + add_address_type(OutputType::P2SH_SEGWIT); + add_address_type(OutputType::BECH32); if (model->wallet().taprootEnabled()) { - add_address_type(OutputType::BECH32M, tr("Bech32m (Taproot)"), tr("Bech32m (BIP-350) is an upgrade to Bech32, wallet support is still limited.")); + add_address_type(OutputType::BECH32M); } + connect(_model->getOptionsModel(), &OptionsModel::addresstypeChanged, [this](const OutputType type) { + const int index = ui->addressType->findData((int) type); + if (index != -1) ui->addressType->setCurrentIndex(index); + }); + // Set the button to be enabled or disabled based on whether the wallet can give out new addresses. ui->receiveButton->setEnabled(model->wallet().canGetAddresses());