diff options
Diffstat (limited to 'src')
37 files changed, 512 insertions, 370 deletions
diff --git a/src/banman.cpp b/src/banman.cpp index b28e3f7f7c..2a6e0e010f 100644 --- a/src/banman.cpp +++ b/src/banman.cpp @@ -16,6 +16,19 @@ BanMan::BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t default_ban_time) : m_client_interface(client_interface), m_ban_db(std::move(ban_file)), m_default_ban_time(default_ban_time) { + LoadBanlist(); + DumpBanlist(); +} + +BanMan::~BanMan() +{ + DumpBanlist(); +} + +void BanMan::LoadBanlist() +{ + LOCK(m_cs_banned); + if (m_client_interface) m_client_interface->InitMessage(_("Loading banlist…").translated); int64_t n_start = GetTimeMillis(); @@ -29,13 +42,6 @@ BanMan::BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t m_banned = {}; m_is_dirty = true; } - - DumpBanlist(); -} - -BanMan::~BanMan() -{ - DumpBanlist(); } void BanMan::DumpBanlist() @@ -173,23 +179,24 @@ void BanMan::GetBanned(banmap_t& banmap) void BanMan::SweepBanned() { + AssertLockHeld(m_cs_banned); + int64_t now = GetTime(); bool notify_ui = false; - { - LOCK(m_cs_banned); - banmap_t::iterator it = m_banned.begin(); - while (it != m_banned.end()) { - CSubNet sub_net = (*it).first; - CBanEntry ban_entry = (*it).second; - if (!sub_net.IsValid() || now > ban_entry.nBanUntil) { - m_banned.erase(it++); - m_is_dirty = true; - notify_ui = true; - LogPrint(BCLog::NET, "Removed banned node address/subnet: %s\n", sub_net.ToString()); - } else - ++it; + banmap_t::iterator it = m_banned.begin(); + while (it != m_banned.end()) { + CSubNet sub_net = (*it).first; + CBanEntry ban_entry = (*it).second; + if (!sub_net.IsValid() || now > ban_entry.nBanUntil) { + m_banned.erase(it++); + m_is_dirty = true; + notify_ui = true; + LogPrint(BCLog::NET, "Removed banned node address/subnet: %s\n", sub_net.ToString()); + } else { + ++it; } } + // update UI if (notify_ui && m_client_interface) { m_client_interface->BannedListChanged(); diff --git a/src/banman.h b/src/banman.h index f268fffa5a..77b043f081 100644 --- a/src/banman.h +++ b/src/banman.h @@ -80,11 +80,12 @@ public: void DumpBanlist(); private: + void LoadBanlist() EXCLUSIVE_LOCKS_REQUIRED(!m_cs_banned); bool BannedSetIsDirty(); //!set the "dirty" flag for the banlist void SetBannedSetDirty(bool dirty = true); //!clean unused entries (if bantime has expired) - void SweepBanned(); + void SweepBanned() EXCLUSIVE_LOCKS_REQUIRED(m_cs_banned); RecursiveMutex m_cs_banned; banmap_t m_banned GUARDED_BY(m_cs_banned); diff --git a/src/base58.h b/src/base58.h index 9ba5af73e0..d2a8d5e3bc 100644 --- a/src/base58.h +++ b/src/base58.h @@ -14,7 +14,6 @@ #ifndef BITCOIN_BASE58_H #define BITCOIN_BASE58_H -#include <attributes.h> #include <span.h> #include <string> diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index de8ab9807c..b2958bcc9f 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -58,7 +58,7 @@ static void CoinSelection(benchmark::Bench& bench) // Create coins std::vector<COutput> coins; for (const auto& wtx : wtxs) { - coins.emplace_back(COutPoint(wtx->GetHash(), 0), wtx->tx->vout.at(0), /*depth=*/6 * 24, GetTxSpendSize(wallet, *wtx, 0), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true); + coins.emplace_back(COutPoint(wtx->GetHash(), 0), wtx->tx->vout.at(0), /*depth=*/6 * 24, GetTxSpendSize(wallet, *wtx, 0), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0); } const CoinEligibilityFilter filter_standard(1, 6, 0); @@ -88,7 +88,7 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup> CMutableTransaction tx; tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; - COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 0, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ true); + COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 0, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ true, /*fees=*/ 0); set.emplace_back(); set.back().Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false); } diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 88cbab1d11..b9e5a81f8d 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -412,8 +412,8 @@ private: bool is_addr_relay_enabled; bool is_bip152_hb_from; bool is_bip152_hb_to; - bool is_block_relay; bool is_outbound; + bool is_tx_relay; bool operator<(const Peer& rhs) const { return std::tie(is_outbound, min_ping) < std::tie(rhs.is_outbound, rhs.min_ping); } }; std::vector<Peer> m_peers; @@ -477,7 +477,7 @@ public: const int8_t network_id{NetworkStringToId(network)}; if (network_id == UNKNOWN_NETWORK) continue; const bool is_outbound{!peer["inbound"].get_bool()}; - const bool is_block_relay{!peer["relaytxes"].get_bool()}; + const bool is_tx_relay{peer["relaytxes"].isNull() ? true : peer["relaytxes"].get_bool()}; const std::string conn_type{peer["connection_type"].get_str()}; ++m_counts.at(is_outbound).at(network_id); // in/out by network ++m_counts.at(is_outbound).at(NETWORKS.size()); // in/out overall @@ -505,7 +505,7 @@ public: const bool is_addr_relay_enabled{peer["addr_relay_enabled"].isNull() ? false : peer["addr_relay_enabled"].get_bool()}; const bool is_bip152_hb_from{peer["bip152_hb_from"].get_bool()}; const bool is_bip152_hb_to{peer["bip152_hb_to"].get_bool()}; - m_peers.push_back({addr, sub_version, conn_type, network, age, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_block_relay, is_outbound}); + m_peers.push_back({addr, sub_version, conn_type, network, age, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_outbound, is_tx_relay}); m_max_addr_length = std::max(addr.length() + 1, m_max_addr_length); m_max_addr_processed_length = std::max(ToString(addr_processed).length(), m_max_addr_processed_length); m_max_addr_rate_limited_length = std::max(ToString(addr_rate_limited).length(), m_max_addr_rate_limited_length); @@ -538,7 +538,7 @@ public: PingTimeToString(peer.ping), peer.last_send ? ToString(time_now - peer.last_send) : "", peer.last_recv ? ToString(time_now - peer.last_recv) : "", - peer.last_trxn ? ToString((time_now - peer.last_trxn) / 60) : peer.is_block_relay ? "*" : "", + peer.last_trxn ? ToString((time_now - peer.last_trxn) / 60) : peer.is_tx_relay ? "" : "*", peer.last_blck ? ToString((time_now - peer.last_blck) / 60) : "", strprintf("%s%s", peer.is_bip152_hb_to ? "." : " ", peer.is_bip152_hb_from ? "*" : " "), m_max_addr_processed_length, // variable spacing diff --git a/src/core_io.h b/src/core_io.h index aa1381c374..c91c8199d8 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -6,7 +6,6 @@ #define BITCOIN_CORE_IO_H #include <consensus/amount.h> -#include <attributes.h> #include <string> #include <vector> diff --git a/src/hash.h b/src/hash.h index 9f582842c1..0ccef2105f 100644 --- a/src/hash.h +++ b/src/hash.h @@ -6,7 +6,6 @@ #ifndef BITCOIN_HASH_H #define BITCOIN_HASH_H -#include <attributes.h> #include <crypto/common.h> #include <crypto/ripemd160.h> #include <crypto/sha256.h> diff --git a/src/init.cpp b/src/init.cpp index b1fe915189..e436d5ea8e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -86,7 +86,6 @@ #include <vector> #ifndef WIN32 -#include <attributes.h> #include <cerrno> #include <signal.h> #include <sys/stat.h> diff --git a/src/netaddress.h b/src/netaddress.h index 77e6171054..47ba045334 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -9,7 +9,6 @@ #include <config/bitcoin-config.h> #endif -#include <attributes.h> #include <compat.h> #include <crypto/siphash.h> #include <prevector.h> diff --git a/src/node/transaction.h b/src/node/transaction.h index b7cf225636..0604754a46 100644 --- a/src/node/transaction.h +++ b/src/node/transaction.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_NODE_TRANSACTION_H #define BITCOIN_NODE_TRANSACTION_H -#include <attributes.h> #include <policy/feerate.h> #include <primitives/transaction.h> #include <util/error.h> diff --git a/src/outputtype.h b/src/outputtype.h index 66fe489bb0..6b4e695760 100644 --- a/src/outputtype.h +++ b/src/outputtype.h @@ -6,7 +6,6 @@ #ifndef BITCOIN_OUTPUTTYPE_H #define BITCOIN_OUTPUTTYPE_H -#include <attributes.h> #include <script/signingprovider.h> #include <script/standard.h> diff --git a/src/psbt.h b/src/psbt.h index 8a9cbd33d2..8fda889bb4 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_PSBT_H #define BITCOIN_PSBT_H -#include <attributes.h> #include <node/transaction.h> #include <policy/feerate.h> #include <primitives/transaction.h> diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index d59a4345f3..a82bd5f73e 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -19,6 +19,11 @@ #include <QMenu> #include <QMessageBox> #include <QSortFilterProxyModel> +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) +#include <QRegularExpression> +#else +#include <QRegExp> +#endif class AddressBookSortFilterProxyModel final : public QSortFilterProxyModel { @@ -46,12 +51,13 @@ protected: auto address = model->index(row, AddressTableModel::Address, parent); - if (filterRegExp().indexIn(model->data(address).toString()) < 0 && - filterRegExp().indexIn(model->data(label).toString()) < 0) { - return false; - } - - return true; +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + const auto pattern = filterRegularExpression(); +#else + const auto pattern = filterRegExp(); +#endif + return (model->data(address).toString().contains(pattern) || + model->data(label).toString().contains(pattern)); } }; diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 8cac28400f..53fbac0106 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -261,7 +261,7 @@ void BitcoinApplication::createPaymentServer() void BitcoinApplication::createOptionsModel(bool resetSettings) { - optionsModel = new OptionsModel(this, resetSettings); + optionsModel = new OptionsModel(node(), this, resetSettings); } void BitcoinApplication::createWindow(const NetworkStyle *networkStyle) @@ -292,7 +292,6 @@ void BitcoinApplication::createNode(interfaces::Init& init) { assert(!m_node); m_node = init.makeNode(); - if (optionsModel) optionsModel->setNode(*m_node); if (m_splash) m_splash->setNode(*m_node); } @@ -308,7 +307,9 @@ void BitcoinApplication::startThread() /* communication to and from thread */ connect(&m_executor.value(), &InitExecutor::initializeResult, this, &BitcoinApplication::initializeResult); - connect(&m_executor.value(), &InitExecutor::shutdownResult, this, &QCoreApplication::quit); + connect(&m_executor.value(), &InitExecutor::shutdownResult, this, [] { + QCoreApplication::exit(0); + }); connect(&m_executor.value(), &InitExecutor::runawayException, this, &BitcoinApplication::handleRunawayException); connect(this, &BitcoinApplication::requestedInitialize, &m_executor.value(), &InitExecutor::initialize); connect(this, &BitcoinApplication::requestedShutdown, &m_executor.value(), &InitExecutor::shutdown); @@ -633,6 +634,12 @@ int GuiMain(int argc, char* argv[]) // Allow parameter interaction before we create the options model app.parameterSetup(); GUIUtil::LogQtInfo(); + + if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) + app.createSplashScreen(networkStyle.data()); + + app.createNode(*init); + // Load GUI settings from QSettings app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false)); @@ -641,11 +648,6 @@ int GuiMain(int argc, char* argv[]) app.InitPruneSetting(prune_MiB); } - if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) - app.createSplashScreen(networkStyle.data()); - - app.createNode(*init); - int rv = EXIT_SUCCESS; try { diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 40b9ed5483..612d3009c1 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -30,8 +30,8 @@ const char *DEFAULT_GUI_PROXY_HOST = "127.0.0.1"; static const QString GetDefaultProxyAddress(); -OptionsModel::OptionsModel(QObject *parent, bool resetSettings) : - QAbstractListModel(parent) +OptionsModel::OptionsModel(interfaces::Node& node, QObject *parent, bool resetSettings) : + QAbstractListModel(parent), m_node{node} { Init(resetSettings); } @@ -335,260 +335,272 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const { if(role == Qt::EditRole) { - QSettings settings; - switch(index.row()) - { - case StartAtStartup: - return GUIUtil::GetStartOnSystemStartup(); - case ShowTrayIcon: - return m_show_tray_icon; - case MinimizeToTray: - return fMinimizeToTray; - case MapPortUPnP: + return getOption(OptionID(index.row())); + } + return QVariant(); +} + +// write QSettings values +bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role) +{ + bool successful = true; /* set to false on parse error */ + if(role == Qt::EditRole) + { + successful = setOption(OptionID(index.row()), value); + } + + Q_EMIT dataChanged(index, index); + + return successful; +} + +QVariant OptionsModel::getOption(OptionID option) const +{ + QSettings settings; + switch (option) { + case StartAtStartup: + return GUIUtil::GetStartOnSystemStartup(); + case ShowTrayIcon: + return m_show_tray_icon; + case MinimizeToTray: + return fMinimizeToTray; + case MapPortUPnP: #ifdef USE_UPNP - return settings.value("fUseUPnP"); + return settings.value("fUseUPnP"); #else - return false; + return false; #endif // USE_UPNP - case MapPortNatpmp: + case MapPortNatpmp: #ifdef USE_NATPMP - return settings.value("fUseNatpmp"); + return settings.value("fUseNatpmp"); #else - return false; + return false; #endif // USE_NATPMP - case MinimizeOnClose: - return fMinimizeOnClose; - - // default proxy - case ProxyUse: - return settings.value("fUseProxy", false); - case ProxyIP: - return GetProxySetting(settings, "addrProxy").ip; - case ProxyPort: - return GetProxySetting(settings, "addrProxy").port; - - // separate Tor proxy - case ProxyUseTor: - return settings.value("fUseSeparateProxyTor", false); - case ProxyIPTor: - return GetProxySetting(settings, "addrSeparateProxyTor").ip; - case ProxyPortTor: - return GetProxySetting(settings, "addrSeparateProxyTor").port; + case MinimizeOnClose: + return fMinimizeOnClose; + + // default proxy + case ProxyUse: + return settings.value("fUseProxy", false); + case ProxyIP: + return GetProxySetting(settings, "addrProxy").ip; + case ProxyPort: + return GetProxySetting(settings, "addrProxy").port; + + // separate Tor proxy + case ProxyUseTor: + return settings.value("fUseSeparateProxyTor", false); + case ProxyIPTor: + return GetProxySetting(settings, "addrSeparateProxyTor").ip; + case ProxyPortTor: + return GetProxySetting(settings, "addrSeparateProxyTor").port; #ifdef ENABLE_WALLET - case SpendZeroConfChange: - return settings.value("bSpendZeroConfChange"); - case ExternalSignerPath: - return settings.value("external_signer_path"); - case SubFeeFromAmount: - return m_sub_fee_from_amount; + case SpendZeroConfChange: + return settings.value("bSpendZeroConfChange"); + case ExternalSignerPath: + return settings.value("external_signer_path"); + case SubFeeFromAmount: + return m_sub_fee_from_amount; #endif - case DisplayUnit: - return QVariant::fromValue(m_display_bitcoin_unit); - case ThirdPartyTxUrls: - return strThirdPartyTxUrls; - case Language: - return settings.value("language"); - case UseEmbeddedMonospacedFont: - return m_use_embedded_monospaced_font; - case CoinControlFeatures: - return fCoinControlFeatures; - case EnablePSBTControls: - return settings.value("enable_psbt_controls"); - case Prune: - return settings.value("bPrune"); - case PruneSize: - return settings.value("nPruneSize"); - case DatabaseCache: - return settings.value("nDatabaseCache"); - case ThreadsScriptVerif: - return settings.value("nThreadsScriptVerif"); - case Listen: - return settings.value("fListen"); - case Server: - return settings.value("server"); - default: - return QVariant(); - } + case DisplayUnit: + return QVariant::fromValue(m_display_bitcoin_unit); + case ThirdPartyTxUrls: + return strThirdPartyTxUrls; + case Language: + return settings.value("language"); + case UseEmbeddedMonospacedFont: + return m_use_embedded_monospaced_font; + case CoinControlFeatures: + return fCoinControlFeatures; + case EnablePSBTControls: + return settings.value("enable_psbt_controls"); + case Prune: + return settings.value("bPrune"); + case PruneSize: + return settings.value("nPruneSize"); + case DatabaseCache: + return settings.value("nDatabaseCache"); + case ThreadsScriptVerif: + return settings.value("nThreadsScriptVerif"); + case Listen: + return settings.value("fListen"); + case Server: + return settings.value("server"); + default: + return QVariant(); } - return QVariant(); } -// write QSettings values -bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role) +bool OptionsModel::setOption(OptionID option, const QVariant& value) { bool successful = true; /* set to false on parse error */ - if(role == Qt::EditRole) - { - QSettings settings; - switch(index.row()) - { - case StartAtStartup: - successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); - break; - case ShowTrayIcon: - m_show_tray_icon = value.toBool(); - settings.setValue("fHideTrayIcon", !m_show_tray_icon); - Q_EMIT showTrayIconChanged(m_show_tray_icon); - break; - case MinimizeToTray: - fMinimizeToTray = value.toBool(); - settings.setValue("fMinimizeToTray", fMinimizeToTray); - break; - case MapPortUPnP: // core option - can be changed on-the-fly - settings.setValue("fUseUPnP", value.toBool()); - break; - case MapPortNatpmp: // core option - can be changed on-the-fly - settings.setValue("fUseNatpmp", value.toBool()); - break; - case MinimizeOnClose: - fMinimizeOnClose = value.toBool(); - settings.setValue("fMinimizeOnClose", fMinimizeOnClose); - break; - - // default proxy - case ProxyUse: - if (settings.value("fUseProxy") != value) { - settings.setValue("fUseProxy", value.toBool()); - setRestartRequired(true); - } - break; - case ProxyIP: { - auto ip_port = GetProxySetting(settings, "addrProxy"); - if (!ip_port.is_set || ip_port.ip != value.toString()) { - ip_port.ip = value.toString(); - SetProxySetting(settings, "addrProxy", ip_port); - setRestartRequired(true); - } - } + QSettings settings; + + switch (option) { + case StartAtStartup: + successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); break; - case ProxyPort: { - auto ip_port = GetProxySetting(settings, "addrProxy"); - if (!ip_port.is_set || ip_port.port != value.toString()) { - ip_port.port = value.toString(); - SetProxySetting(settings, "addrProxy", ip_port); - setRestartRequired(true); - } - } + case ShowTrayIcon: + m_show_tray_icon = value.toBool(); + settings.setValue("fHideTrayIcon", !m_show_tray_icon); + Q_EMIT showTrayIconChanged(m_show_tray_icon); + break; + case MinimizeToTray: + fMinimizeToTray = value.toBool(); + settings.setValue("fMinimizeToTray", fMinimizeToTray); + break; + case MapPortUPnP: // core option - can be changed on-the-fly + settings.setValue("fUseUPnP", value.toBool()); + break; + case MapPortNatpmp: // core option - can be changed on-the-fly + settings.setValue("fUseNatpmp", value.toBool()); + break; + case MinimizeOnClose: + fMinimizeOnClose = value.toBool(); + settings.setValue("fMinimizeOnClose", fMinimizeOnClose); break; - // separate Tor proxy - case ProxyUseTor: - if (settings.value("fUseSeparateProxyTor") != value) { - settings.setValue("fUseSeparateProxyTor", value.toBool()); - setRestartRequired(true); - } - break; - case ProxyIPTor: { - auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); - if (!ip_port.is_set || ip_port.ip != value.toString()) { - ip_port.ip = value.toString(); - SetProxySetting(settings, "addrSeparateProxyTor", ip_port); - setRestartRequired(true); - } + // default proxy + case ProxyUse: + if (settings.value("fUseProxy") != value) { + settings.setValue("fUseProxy", value.toBool()); + setRestartRequired(true); } break; - case ProxyPortTor: { - auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); - if (!ip_port.is_set || ip_port.port != value.toString()) { - ip_port.port = value.toString(); - SetProxySetting(settings, "addrSeparateProxyTor", ip_port); - setRestartRequired(true); - } + case ProxyIP: { + auto ip_port = GetProxySetting(settings, "addrProxy"); + if (!ip_port.is_set || ip_port.ip != value.toString()) { + ip_port.ip = value.toString(); + SetProxySetting(settings, "addrProxy", ip_port); + setRestartRequired(true); + } + } + break; + case ProxyPort: { + auto ip_port = GetProxySetting(settings, "addrProxy"); + if (!ip_port.is_set || ip_port.port != value.toString()) { + ip_port.port = value.toString(); + SetProxySetting(settings, "addrProxy", ip_port); + setRestartRequired(true); + } + } + break; + + // separate Tor proxy + case ProxyUseTor: + if (settings.value("fUseSeparateProxyTor") != value) { + settings.setValue("fUseSeparateProxyTor", value.toBool()); + setRestartRequired(true); } break; + case ProxyIPTor: { + auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); + if (!ip_port.is_set || ip_port.ip != value.toString()) { + ip_port.ip = value.toString(); + SetProxySetting(settings, "addrSeparateProxyTor", ip_port); + setRestartRequired(true); + } + } + break; + case ProxyPortTor: { + auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); + if (!ip_port.is_set || ip_port.port != value.toString()) { + ip_port.port = value.toString(); + SetProxySetting(settings, "addrSeparateProxyTor", ip_port); + setRestartRequired(true); + } + } + break; #ifdef ENABLE_WALLET - case SpendZeroConfChange: - if (settings.value("bSpendZeroConfChange") != value) { - settings.setValue("bSpendZeroConfChange", value); - setRestartRequired(true); - } - break; - case ExternalSignerPath: - if (settings.value("external_signer_path") != value.toString()) { - settings.setValue("external_signer_path", value.toString()); - setRestartRequired(true); - } - break; - case SubFeeFromAmount: - m_sub_fee_from_amount = value.toBool(); - settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount); - break; + case SpendZeroConfChange: + if (settings.value("bSpendZeroConfChange") != value) { + settings.setValue("bSpendZeroConfChange", value); + setRestartRequired(true); + } + break; + case ExternalSignerPath: + if (settings.value("external_signer_path") != value.toString()) { + settings.setValue("external_signer_path", value.toString()); + setRestartRequired(true); + } + break; + case SubFeeFromAmount: + m_sub_fee_from_amount = value.toBool(); + settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount); + break; #endif - case DisplayUnit: - setDisplayUnit(value); - break; - case ThirdPartyTxUrls: - if (strThirdPartyTxUrls != value.toString()) { - strThirdPartyTxUrls = value.toString(); - settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls); - setRestartRequired(true); - } - break; - case Language: - if (settings.value("language") != value) { - settings.setValue("language", value); - setRestartRequired(true); - } - break; - case UseEmbeddedMonospacedFont: - m_use_embedded_monospaced_font = value.toBool(); - settings.setValue("UseEmbeddedMonospacedFont", m_use_embedded_monospaced_font); - Q_EMIT useEmbeddedMonospacedFontChanged(m_use_embedded_monospaced_font); - break; - case CoinControlFeatures: - fCoinControlFeatures = value.toBool(); - settings.setValue("fCoinControlFeatures", fCoinControlFeatures); - Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); - break; - case EnablePSBTControls: - m_enable_psbt_controls = value.toBool(); - settings.setValue("enable_psbt_controls", m_enable_psbt_controls); - break; - case Prune: - if (settings.value("bPrune") != value) { - settings.setValue("bPrune", value); - setRestartRequired(true); - } - break; - case PruneSize: - if (settings.value("nPruneSize") != value) { - settings.setValue("nPruneSize", value); - setRestartRequired(true); - } - break; - case DatabaseCache: - if (settings.value("nDatabaseCache") != value) { - settings.setValue("nDatabaseCache", value); - setRestartRequired(true); - } - break; - case ThreadsScriptVerif: - if (settings.value("nThreadsScriptVerif") != value) { - settings.setValue("nThreadsScriptVerif", value); - setRestartRequired(true); - } - break; - case Listen: - if (settings.value("fListen") != value) { - settings.setValue("fListen", value); - setRestartRequired(true); - } - break; - case Server: - if (settings.value("server") != value) { - settings.setValue("server", value); - setRestartRequired(true); - } - break; - default: - break; + case DisplayUnit: + setDisplayUnit(value); + break; + case ThirdPartyTxUrls: + if (strThirdPartyTxUrls != value.toString()) { + strThirdPartyTxUrls = value.toString(); + settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls); + setRestartRequired(true); } + break; + case Language: + if (settings.value("language") != value) { + settings.setValue("language", value); + setRestartRequired(true); + } + break; + case UseEmbeddedMonospacedFont: + m_use_embedded_monospaced_font = value.toBool(); + settings.setValue("UseEmbeddedMonospacedFont", m_use_embedded_monospaced_font); + Q_EMIT useEmbeddedMonospacedFontChanged(m_use_embedded_monospaced_font); + break; + case CoinControlFeatures: + fCoinControlFeatures = value.toBool(); + settings.setValue("fCoinControlFeatures", fCoinControlFeatures); + Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); + break; + case EnablePSBTControls: + m_enable_psbt_controls = value.toBool(); + settings.setValue("enable_psbt_controls", m_enable_psbt_controls); + break; + case Prune: + if (settings.value("bPrune") != value) { + settings.setValue("bPrune", value); + setRestartRequired(true); + } + break; + case PruneSize: + if (settings.value("nPruneSize") != value) { + settings.setValue("nPruneSize", value); + setRestartRequired(true); + } + break; + case DatabaseCache: + if (settings.value("nDatabaseCache") != value) { + settings.setValue("nDatabaseCache", value); + setRestartRequired(true); + } + break; + case ThreadsScriptVerif: + if (settings.value("nThreadsScriptVerif") != value) { + settings.setValue("nThreadsScriptVerif", value); + setRestartRequired(true); + } + break; + case Listen: + if (settings.value("fListen") != value) { + settings.setValue("fListen", value); + setRestartRequired(true); + } + break; + case Server: + if (settings.value("server") != value) { + settings.setValue("server", value); + setRestartRequired(true); + } + break; + default: + break; } - Q_EMIT dataChanged(index, index); - return successful; } diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 510ebb5cfd..92f80ecf21 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -41,7 +41,7 @@ class OptionsModel : public QAbstractListModel Q_OBJECT public: - explicit OptionsModel(QObject *parent = nullptr, bool resetSettings = false); + explicit OptionsModel(interfaces::Node& node, QObject *parent = nullptr, bool resetSettings = false); enum OptionID { StartAtStartup, // bool @@ -80,6 +80,8 @@ public: int rowCount(const QModelIndex & parent = QModelIndex()) const override; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override; + QVariant getOption(OptionID option) const; + bool setOption(OptionID option, const QVariant& value); /** Updates current unit in memory, settings and emits displayUnitChanged(new_unit) signal */ void setDisplayUnit(const QVariant& new_unit); @@ -103,11 +105,10 @@ public: void setRestartRequired(bool fRequired); bool isRestartRequired() const; - interfaces::Node& node() const { assert(m_node); return *m_node; } - void setNode(interfaces::Node& node) { assert(!m_node); m_node = &node; } + interfaces::Node& node() const { return m_node; } private: - interfaces::Node* m_node = nullptr; + interfaces::Node& m_node; /* Qt-only settings */ bool m_show_tray_icon; bool fMinimizeToTray; diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index a60e0647e3..ededde4da9 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -24,6 +24,7 @@ #include <chrono> #include <QApplication> +#include <QLineEdit> #include <QMessageBox> #include <QTableView> #include <QTimer> @@ -102,11 +103,13 @@ void TestAddAddressesToSendBook(interfaces::Node& node) QString s_label("already here (s)"); // Define a new address (which should add to the address book successfully). - QString new_address; + QString new_address_a; + QString new_address_b; std::tie(r_key_dest, preexisting_r_address) = build_address(); std::tie(s_key_dest, preexisting_s_address) = build_address(); - std::tie(std::ignore, new_address) = build_address(); + std::tie(std::ignore, new_address_a) = build_address(); + std::tie(std::ignore, new_address_b) = build_address(); { LOCK(wallet->cs_wallet); @@ -124,7 +127,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node) // Initialize relevant QT models. std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other")); - OptionsModel optionsModel; + OptionsModel optionsModel(node); ClientModel clientModel(node, &optionsModel); WalletContext& context = *node.walletLoader().context(); AddWallet(context, wallet); @@ -159,9 +162,52 @@ void TestAddAddressesToSendBook(interfaces::Node& node) // Submit a new address which should add successfully - we expect the // warning message to be blank. EditAddressAndSubmit( - &editAddressDialog, QString("new"), new_address, QString("")); + &editAddressDialog, QString("io - new A"), new_address_a, QString("")); check_addbook_size(3); QCOMPARE(table_view->model()->rowCount(), 2); + + EditAddressAndSubmit( + &editAddressDialog, QString("io - new B"), new_address_b, QString("")); + check_addbook_size(4); + QCOMPARE(table_view->model()->rowCount(), 3); + + auto search_line = address_book.findChild<QLineEdit*>("searchLineEdit"); + + search_line->setText(r_label); + QCOMPARE(table_view->model()->rowCount(), 0); + + search_line->setText(s_label); + QCOMPARE(table_view->model()->rowCount(), 1); + + search_line->setText("io"); + QCOMPARE(table_view->model()->rowCount(), 2); + + // Check wilcard "?". + search_line->setText("io?new"); + QCOMPARE(table_view->model()->rowCount(), 0); + search_line->setText("io???new"); + QCOMPARE(table_view->model()->rowCount(), 2); + + // Check wilcard "*". + search_line->setText("io*new"); + QCOMPARE(table_view->model()->rowCount(), 2); + search_line->setText("*"); + QCOMPARE(table_view->model()->rowCount(), 3); + + search_line->setText(preexisting_r_address); + QCOMPARE(table_view->model()->rowCount(), 0); + + search_line->setText(preexisting_s_address); + QCOMPARE(table_view->model()->rowCount(), 1); + + search_line->setText(new_address_a); + QCOMPARE(table_view->model()->rowCount(), 1); + + search_line->setText(new_address_b); + QCOMPARE(table_view->model()->rowCount(), 1); + + search_line->setText(""); + QCOMPARE(table_view->model()->rowCount(), 3); } } // namespace diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index b191082ab0..9648ef6188 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -119,6 +119,6 @@ AppTests::HandleCallback::~HandleCallback() assert(it != callbacks.end()); callbacks.erase(it); if (callbacks.empty()) { - m_app_tests.m_app.quit(); + m_app_tests.m_app.exit(0); } } diff --git a/src/qt/test/optiontests.cpp b/src/qt/test/optiontests.cpp index 4a943a6343..2ef9230c59 100644 --- a/src/qt/test/optiontests.cpp +++ b/src/qt/test/optiontests.cpp @@ -13,8 +13,7 @@ #include <univalue.h> -//! Entry point for BitcoinApplication tests. -void OptionTests::optionTests() +void OptionTests::integerGetArgBug() { // Test regression https://github.com/bitcoin/bitcoin/issues/24457. Ensure // that setting integer prune value doesn't cause an exception to be thrown @@ -24,7 +23,7 @@ void OptionTests::optionTests() settings.rw_settings["prune"] = 3814; }); gArgs.WriteSettingsFile(); - OptionsModel{}; + OptionsModel{m_node}; gArgs.LockSettings([&](util::Settings& settings) { settings.rw_settings.erase("prune"); }); @@ -49,7 +48,7 @@ void OptionTests::parametersInteraction() QSettings settings; settings.setValue("fListen", false); - OptionsModel{}; + OptionsModel{m_node}; const bool expected{false}; diff --git a/src/qt/test/optiontests.h b/src/qt/test/optiontests.h index 39c1612c8f..257a0b65be 100644 --- a/src/qt/test/optiontests.h +++ b/src/qt/test/optiontests.h @@ -16,7 +16,7 @@ public: explicit OptionTests(interfaces::Node& node) : m_node(node) {} private Q_SLOTS: - void optionTests(); + void integerGetArgBug(); void parametersInteraction(); private: diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 70b7f83872..bc06f0f23b 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -184,7 +184,7 @@ void TestGUI(interfaces::Node& node) std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other")); SendCoinsDialog sendCoinsDialog(platformStyle.get()); TransactionView transactionView(platformStyle.get()); - OptionsModel optionsModel; + OptionsModel optionsModel(node); ClientModel clientModel(node, &optionsModel); WalletContext& context = *node.walletLoader().context(); AddWallet(context, wallet); diff --git a/src/script/standard.h b/src/script/standard.h index f0b143c52b..6a15ba4e3d 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_SCRIPT_STANDARD_H #define BITCOIN_SCRIPT_STANDARD_H +#include <attributes.h> #include <pubkey.h> #include <script/interpreter.h> #include <uint256.h> diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 82e4e1c90f..b333a9f72d 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -2,7 +2,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <attributes.h> #include <clientversion.h> #include <coins.h> #include <script/standard.h> diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index 580105e442..3fc6fa1cd5 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -6,7 +6,6 @@ #define BITCOIN_TEST_FUZZ_UTIL_H #include <arith_uint256.h> -#include <attributes.h> #include <chainparamsbase.h> #include <coins.h> #include <compat.h> diff --git a/src/util/bip32.h b/src/util/bip32.h index 8f86f2aaa6..aa4eac3791 100644 --- a/src/util/bip32.h +++ b/src/util/bip32.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_UTIL_BIP32_H #define BITCOIN_UTIL_BIP32_H -#include <attributes.h> #include <string> #include <vector> diff --git a/src/util/moneystr.h b/src/util/moneystr.h index 8180604342..3d33bd7f99 100644 --- a/src/util/moneystr.h +++ b/src/util/moneystr.h @@ -9,7 +9,6 @@ #ifndef BITCOIN_UTIL_MONEYSTR_H #define BITCOIN_UTIL_MONEYSTR_H -#include <attributes.h> #include <consensus/amount.h> #include <optional> diff --git a/src/util/strencodings.h b/src/util/strencodings.h index ee58383528..9a96bbe67b 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -9,7 +9,6 @@ #ifndef BITCOIN_UTIL_STRENCODINGS_H #define BITCOIN_UTIL_STRENCODINGS_H -#include <attributes.h> #include <span.h> #include <util/string.h> diff --git a/src/util/system.h b/src/util/system.h index 64585cbfac..8db3ab9913 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -14,7 +14,6 @@ #include <config/bitcoin-config.h> #endif -#include <attributes.h> #include <compat.h> #include <compat/assumptions.h> #include <fs.h> diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index fffab39cc1..613c5b65ef 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -5,6 +5,7 @@ #include <validationinterface.h> +#include <attributes.h> #include <chain.h> #include <consensus/validation.h> #include <logging.h> diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index 0b236e2e48..e710787a26 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -328,24 +328,18 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, ******************************************************************************/ void OutputGroup::Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only) { - // Compute the effective value first - const CAmount coin_fee = output.input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.input_bytes); - const CAmount ev = output.txout.nValue - coin_fee; - // Filter for positive only here before adding the coin - if (positive_only && ev <= 0) return; + if (positive_only && output.GetEffectiveValue() <= 0) return; m_outputs.push_back(output); COutput& coin = m_outputs.back(); - coin.fee = coin_fee; - fee += coin.fee; + fee += coin.GetFee(); coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes); long_term_fee += coin.long_term_fee; - coin.effective_value = ev; - effective_value += coin.effective_value; + effective_value += coin.GetEffectiveValue(); m_from_me &= coin.from_me; m_value += coin.txout.nValue; @@ -380,8 +374,8 @@ CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount waste = 0; CAmount selected_effective_value = 0; for (const COutput& coin : inputs) { - waste += coin.fee - coin.long_term_fee; - selected_effective_value += use_effective_value ? coin.effective_value : coin.txout.nValue; + waste += coin.GetFee() - coin.long_term_fee; + selected_effective_value += use_effective_value ? coin.GetEffectiveValue() : coin.txout.nValue; } if (change_cost) { diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index 25c672eda0..9135e48104 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -20,6 +20,14 @@ static constexpr CAmount CHANGE_UPPER{1000000}; /** A UTXO under consideration for use in funding a new transaction. */ struct COutput { +private: + /** The output's value minus fees required to spend it.*/ + std::optional<CAmount> effective_value; + + /** The fee required to spend this output at the transaction's target feerate. */ + std::optional<CAmount> fee; + +public: /** The outpoint identifying this UTXO */ COutPoint outpoint; @@ -55,16 +63,10 @@ struct COutput { /** Whether the transaction containing this output is sent from the owning wallet */ bool from_me; - /** The output's value minus fees required to spend it. Initialized as the output's absolute value. */ - CAmount effective_value; - - /** The fee required to spend this output at the transaction's target feerate. */ - CAmount fee{0}; - /** The fee required to spend this output at the consolidation feerate. */ CAmount long_term_fee{0}; - COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me) + COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me, const std::optional<CFeeRate> feerate = std::nullopt) : outpoint{outpoint}, txout{txout}, depth{depth}, @@ -73,9 +75,22 @@ struct COutput { solvable{solvable}, safe{safe}, time{time}, - from_me{from_me}, - effective_value{txout.nValue} - {} + from_me{from_me} + { + if (feerate) { + fee = input_bytes < 0 ? 0 : feerate.value().GetFee(input_bytes); + effective_value = txout.nValue - fee.value(); + } + } + + COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me, const CAmount fees) + : COutput(outpoint, txout, depth, input_bytes, spendable, solvable, safe, time, from_me) + { + // if input_bytes is unknown, then fees should be 0, if input_bytes is known, then the fees should be a positive integer or 0 (input_bytes known and fees = 0 only happens in the tests) + assert((input_bytes < 0 && fees == 0) || (input_bytes > 0 && fees >= 0)); + fee = fees; + effective_value = txout.nValue - fee.value(); + } std::string ToString() const; @@ -83,6 +98,18 @@ struct COutput { { return outpoint < rhs.outpoint; } + + CAmount GetFee() const + { + assert(fee.has_value()); + return fee.value(); + } + + CAmount GetEffectiveValue() const + { + assert(effective_value.has_value()); + return effective_value.value(); + } }; /** Parameters for one iteration of Coin Selection. */ diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index 95296b4c9d..2649fa586c 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -18,28 +18,31 @@ namespace wallet { static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { - std::set<CScript> output_scripts; - + std::set<CTxDestination> addresses; if (by_label) { // Get the set of addresses assigned to label - std::string label = LabelFromValue(params[0]); - for (const auto& address : wallet.GetLabelAddresses(label)) { - auto output_script{GetScriptForDestination(address)}; - if (wallet.IsMine(output_script)) { - output_scripts.insert(output_script); - } - } + addresses = wallet.GetLabelAddresses(LabelFromValue(params[0])); + if (addresses.empty()) throw JSONRPCError(RPC_WALLET_ERROR, "Label not found in wallet"); } else { // Get the address CTxDestination dest = DecodeDestination(params[0].get_str()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } - CScript script_pub_key = GetScriptForDestination(dest); - if (!wallet.IsMine(script_pub_key)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet"); + addresses.insert(dest); + } + + // Filter by own scripts only + std::set<CScript> output_scripts; + for (const auto& address : addresses) { + auto output_script{GetScriptForDestination(address)}; + if (wallet.IsMine(output_script)) { + output_scripts.insert(output_script); } - output_scripts.insert(script_pub_key); + } + + if (output_scripts.empty()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet"); } // Minimum confirmations @@ -635,7 +638,7 @@ RPCHelpMan listunspent() cctl.m_max_depth = nMaxDepth; cctl.m_include_unsafe_inputs = include_unsafe; LOCK(pwallet->cs_wallet); - AvailableCoins(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); + AvailableCoinsListUnspent(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); } LOCK(pwallet->cs_wallet); diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index c7b57ba4be..d1a0ba50f6 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -1398,7 +1398,7 @@ RPCHelpMan sendall() total_input_value += tx->tx->vout[input.prevout.n].nValue; } } else { - AvailableCoins(*pwallet, all_the_utxos, &coin_control, /*nMinimumAmount=*/0); + AvailableCoins(*pwallet, all_the_utxos, &coin_control, fee_rate, /*nMinimumAmount=*/0); for (const COutput& output : all_the_utxos) { CHECK_NONFATAL(output.input_bytes > 0); if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) { diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index e5fd7b0eb4..fe36cfcc6b 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -84,7 +84,7 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control); } -void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) +void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, std::optional<CFeeRate> feerate, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) { AssertLockHeld(wallet.cs_wallet); @@ -192,7 +192,7 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly)); - vCoins.emplace_back(COutPoint(wtx.GetHash(), i), wtx.tx->vout.at(i), nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me); + vCoins.emplace_back(COutPoint(wtx.GetHash(), i), wtx.tx->vout.at(i), nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate); // Checks the sum amount of all UTXO's. if (nMinimumSumAmount != MAX_MONEY) { @@ -211,13 +211,18 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C } } +void AvailableCoinsListUnspent(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) +{ + AvailableCoins(wallet, vCoins, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); +} + CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl) { LOCK(wallet.cs_wallet); CAmount balance = 0; std::vector<COutput> vCoins; - AvailableCoins(wallet, vCoins, coinControl); + AvailableCoinsListUnspent(wallet, vCoins, coinControl); for (const COutput& out : vCoins) { if (out.spendable) { balance += out.txout.nValue; @@ -257,7 +262,7 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet) std::map<CTxDestination, std::vector<COutput>> result; std::vector<COutput> availableCoins; - AvailableCoins(wallet, availableCoins); + AvailableCoinsListUnspent(wallet, availableCoins); for (const COutput& coin : availableCoins) { CTxDestination address; @@ -477,12 +482,11 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec } /* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */ - COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false); - output.effective_value = output.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(output.input_bytes); + COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate); if (coin_selection_params.m_subtract_fee_outputs) { value_to_select -= output.txout.nValue; } else { - value_to_select -= output.effective_value; + value_to_select -= output.GetEffectiveValue(); } preset_coins.insert(outpoint); /* Set ancestors and descendants to 0 as they don't matter for preset inputs since no actual selection is being done. @@ -788,7 +792,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( // Get available coins std::vector<COutput> vAvailableCoins; - AvailableCoins(wallet, vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0); + AvailableCoins(wallet, vAvailableCoins, &coin_control, coin_selection_params.m_effective_feerate, 1, MAX_MONEY, MAX_MONEY, 0); // Choose coins to use std::optional<SelectionResult> result = SelectCoins(wallet, vAvailableCoins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params); diff --git a/src/wallet/spend.h b/src/wallet/spend.h index 8af712110d..988058a25a 100644 --- a/src/wallet/spend.h +++ b/src/wallet/spend.h @@ -37,7 +37,13 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* walle /** * populate vCoins with vector of available COutputs. */ -void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, std::optional<CFeeRate> feerate = std::nullopt, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + +/** + * Wrapper function for AvailableCoins which skips the `feerate` parameter. Use this function + * to list all available coins (e.g. listunspent RPC) while not intending to fund a transaction. + */ +void AvailableCoinsListUnspent(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr); diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index d851101260..76f28917a4 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -41,7 +41,7 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<COutput>& se tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; tx.nLockTime = nextLockTime++; // so all transactions get different hashes - set.emplace_back(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false); + set.emplace_back(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0); } static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result) @@ -50,7 +50,7 @@ static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result) tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; tx.nLockTime = nextLockTime++; // so all transactions get different hashes - COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false); + COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0); OutputGroup group; group.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ true); result.AddInput(group); @@ -62,14 +62,12 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fe tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; tx.nLockTime = nextLockTime++; // so all transactions get different hashes - COutput coin(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false); - coin.effective_value = nValue - fee; - coin.fee = fee; + COutput coin(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ 148, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, fee); coin.long_term_fee = long_term_fee; set.insert(coin); } -static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false) +static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount& nValue, CFeeRate feerate = CFeeRate(0), int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false) { CMutableTransaction tx; tx.nLockTime = nextLockTime++; // so all transactions get different hashes @@ -88,7 +86,7 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{})); assert(ret.second); CWalletTx& wtx = (*ret.first).second; - coins.emplace_back(COutPoint(wtx.GetHash(), nInput), wtx.tx->vout.at(nInput), nAge, GetTxSpendSize(wallet, wtx, nInput), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe); + coins.emplace_back(COutPoint(wtx.GetHash(), nInput), wtx.tx->vout.at(nInput), nAge, GetTxSpendSize(wallet, wtx, nInput), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate); } /** Check if SelectionResult a is equivalent to SelectionResult b. @@ -311,13 +309,13 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) std::vector<COutput> coins; - add_coin(coins, *wallet, 1); + add_coin(coins, *wallet, 1, coin_selection_params_bnb.m_effective_feerate); coins.at(0).input_bytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail BOOST_CHECK(!SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change)); // Test fees subtracted from output: coins.clear(); - add_coin(coins, *wallet, 1 * CENT); + add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate); coins.at(0).input_bytes = 40; coin_selection_params_bnb.m_subtract_fee_outputs = true; const auto result9 = SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change); @@ -334,9 +332,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) std::vector<COutput> coins; - add_coin(coins, *wallet, 5 * CENT, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 3 * CENT, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 2 * CENT, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); CCoinControl coin_control; coin_control.fAllowOtherInputs = true; coin_control.Select(coins.at(0).outpoint); @@ -353,36 +351,49 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) std::vector<COutput> coins; - add_coin(coins, *wallet, 10 * CENT, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 9 * CENT, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 1 * CENT, 6 * 24, false, 0, true); - // single coin should be selected when effective fee > long term fee + coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000); + coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000); + + add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + expected_result.Clear(); add_coin(10 * CENT, 2, expected_result); CCoinControl coin_control; - coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000); - coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000); const auto result11 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb); BOOST_CHECK(EquivalentResult(expected_result, *result11)); + coins.clear(); // more coins should be selected when effective fee < long term fee + coin_selection_params_bnb.m_effective_feerate = CFeeRate(3000); + coin_selection_params_bnb.m_long_term_feerate = CFeeRate(5000); + + add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + expected_result.Clear(); add_coin(9 * CENT, 2, expected_result); add_coin(1 * CENT, 2, expected_result); - coin_selection_params_bnb.m_effective_feerate = CFeeRate(3000); - coin_selection_params_bnb.m_long_term_feerate = CFeeRate(5000); const auto result12 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb); BOOST_CHECK(EquivalentResult(expected_result, *result12)); + coins.clear(); // pre selected coin should be selected even if disadvantageous + coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000); + coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000); + + add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + expected_result.Clear(); add_coin(9 * CENT, 2, expected_result); add_coin(1 * CENT, 2, expected_result); coin_control.fAllowOtherInputs = true; coin_control.Select(coins.at(1).outpoint); // pre select 9 coin - coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000); - coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000); const auto result13 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb); BOOST_CHECK(EquivalentResult(expected_result, *result13)); } @@ -409,7 +420,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) // with an empty wallet we can't even pay one cent BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT)); - add_coin(coins, *wallet, 1*CENT, 4); // add a new 1 cent coin + add_coin(coins, *wallet, 1*CENT, CFeeRate(0), 4); // add a new 1 cent coin // with a new 1 cent coin, we still can't find a mature 1 cent BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT)); @@ -430,7 +441,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 3 * CENT); add_coin(coins, *wallet, 5*CENT); // add a mature 5 cent coin, - add_coin(coins, *wallet, 10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses + add_coin(coins, *wallet, 10*CENT, CFeeRate(0), 3, true); // a new 10 cent coin sent from one of our own addresses add_coin(coins, *wallet, 20*CENT); // and a mature 20 cent coin // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38 @@ -857,5 +868,40 @@ BOOST_AUTO_TEST_CASE(waste_test) BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, /* change cost */ 0, new_target)); } +BOOST_AUTO_TEST_CASE(effective_value_test) +{ + const int input_bytes = 148; + const CFeeRate feerate(1000); + const CAmount nValue = 10000; + const int nInput = 0; + + CMutableTransaction tx; + tx.vout.resize(1); + tx.vout[nInput].nValue = nValue; + + // standard case, pass feerate in constructor + COutput output1(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, feerate); + const CAmount expected_ev1 = 9852; // 10000 - 148 + BOOST_CHECK_EQUAL(output1.GetEffectiveValue(), expected_ev1); + + // input bytes unknown (input_bytes = -1), pass feerate in constructor + COutput output2(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, feerate); + BOOST_CHECK_EQUAL(output2.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1 + + // negative effective value, pass feerate in constructor + COutput output3(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, CFeeRate(100000)); + const CAmount expected_ev3 = -4800; // 10000 - 14800 + BOOST_CHECK_EQUAL(output3.GetEffectiveValue(), expected_ev3); + + // standard case, pass fees in constructor + const CAmount fees = 148; + COutput output4(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, fees); + BOOST_CHECK_EQUAL(output4.GetEffectiveValue(), expected_ev1); + + // input bytes unknown (input_bytes = -1), pass fees in constructor + COutput output5(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0); + BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1 +} + BOOST_AUTO_TEST_SUITE_END() } // namespace wallet diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index d4406fd5dd..70863f5464 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -584,7 +584,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup) { LOCK(wallet->cs_wallet); std::vector<COutput> available; - AvailableCoins(*wallet, available); + AvailableCoinsListUnspent(*wallet, available); BOOST_CHECK_EQUAL(available.size(), 2U); } for (const auto& group : list) { @@ -596,7 +596,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup) { LOCK(wallet->cs_wallet); std::vector<COutput> available; - AvailableCoins(*wallet, available); + AvailableCoinsListUnspent(*wallet, available); BOOST_CHECK_EQUAL(available.size(), 0U); } // Confirm ListCoins still returns same result as before, despite coins |