diff options
57 files changed, 431 insertions, 371 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index ac39fe235b..0c43e61592 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -39,6 +39,7 @@ after_build: - ps: clcache -z before_test: - ps: ${conf_ini} = (Get-Content([IO.Path]::Combine(${env:APPVEYOR_BUILD_FOLDER}, "test", "config.ini.in"))) +- ps: ${conf_ini} = ${conf_ini}.Replace("@PACKAGE_NAME@", "Bitcoin Core") - ps: ${conf_ini} = ${conf_ini}.Replace("@abs_top_srcdir@", ${env:APPVEYOR_BUILD_FOLDER}) - ps: ${conf_ini} = ${conf_ini}.Replace("@abs_top_builddir@", ${env:APPVEYOR_BUILD_FOLDER}) - ps: ${conf_ini} = ${conf_ini}.Replace("@EXEEXT@", ".exe") diff --git a/Makefile.am b/Makefile.am index 85674f819a..ec0743c3fa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -43,6 +43,7 @@ DIST_DOCS = $(wildcard doc/*.md) $(wildcard doc/release-notes/*.md) DIST_CONTRIB = $(top_srcdir)/contrib/bitcoin-cli.bash-completion \ $(top_srcdir)/contrib/bitcoin-tx.bash-completion \ $(top_srcdir)/contrib/bitcoind.bash-completion \ + $(top_srcdir)/contrib/debian/copyright \ $(top_srcdir)/contrib/init \ $(top_srcdir)/contrib/install_db4.sh DIST_SHARE = \ diff --git a/build_msvc/bitcoin_config.h b/build_msvc/bitcoin_config.h index 4ac27dae3f..b5a05e2629 100644 --- a/build_msvc/bitcoin_config.h +++ b/build_msvc/bitcoin_config.h @@ -11,10 +11,10 @@ #define CLIENT_VERSION_IS_RELEASE false /* Major version */ -#define CLIENT_VERSION_MAJOR 1 +#define CLIENT_VERSION_MAJOR 0 /* Minor version */ -#define CLIENT_VERSION_MINOR 17 +#define CLIENT_VERSION_MINOR 18 /* Build revision */ #define CLIENT_VERSION_REVISION 99 @@ -29,7 +29,7 @@ #define COPYRIGHT_HOLDERS_SUBSTITUTION "Bitcoin Core" /* Copyright year */ -#define COPYRIGHT_YEAR 2018 +#define COPYRIGHT_YEAR 2019 /* Define to 1 to enable wallet functions */ #define ENABLE_WALLET 1 diff --git a/configure.ac b/configure.ac index a3ba8ce808..66437ff7ed 100644 --- a/configure.ac +++ b/configure.ac @@ -195,8 +195,8 @@ AC_ARG_ENABLE([glibc-back-compat], [use_glibc_compat=no]) AC_ARG_ENABLE([asm], - [AS_HELP_STRING([--enable-asm], - [Enable assembly routines (default is yes)])], + [AS_HELP_STRING([--disable-asm], + [disable assembly routines (enabled by default)])], [use_asm=$enableval], [use_asm=yes]) @@ -339,6 +339,13 @@ if test "x$CXXFLAGS_overridden" = "xno"; then AX_CHECK_COMPILE_FLAG([-Wimplicit-fallthrough],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-implicit-fallthrough"],,[[$CXXFLAG_WERROR]]) fi +enable_hwcrc32=no +enable_sse41=no +enable_avx2=no +enable_shani=no + +if test "x$use_asm" = "xyes"; then + # Check for optional instruction set support. Enabling these does _not_ imply that all code will # be compiled with them, rather that specific objects/libs may use them after checking for runtime # compatibility. @@ -416,6 +423,8 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ) CXXFLAGS="$TEMP_CXXFLAGS" +fi + CPPFLAGS="$CPPFLAGS -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS" AC_ARG_WITH([utils], diff --git a/doc/developer-notes.md b/doc/developer-notes.md index 62c764bb31..cf071167c4 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -620,8 +620,8 @@ class AddressBookPage Mode m_mode; } -AddressBookPage::AddressBookPage(Mode _mode) : - m_mode(_mode) +AddressBookPage::AddressBookPage(Mode _mode) + : m_mode(_mode) ... ``` diff --git a/doc/release-process.md b/doc/release-process.md index eb1f5ad222..7522310ce2 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -4,15 +4,14 @@ Release Process Before every release candidate: * Update translations (ping wumpus on IRC) see [translation_process.md](https://github.com/bitcoin/bitcoin/blob/master/doc/translation_process.md#synchronising-translations). - * Update manpages, see [gen-manpages.sh](https://github.com/bitcoin/bitcoin/blob/master/contrib/devtools/README.md#gen-manpagessh). -* Update release candidate version in `configure.ac` (`CLIENT_VERSION_RC`) +* Update release candidate version in `configure.ac` (`CLIENT_VERSION_RC`). Before every minor and major release: * Update [bips.md](bips.md) to account for changes since the last release. -* Update version in `configure.ac` (don't forget to set `CLIENT_VERSION_IS_RELEASE` to `true`) (don't forget to set `CLIENT_VERSION_RC` to `0`) -* Write release notes (see below) +* Update version in `configure.ac` (don't forget to set `CLIENT_VERSION_RC` to `0`). +* Write release notes (see below). * Update `src/chainparams.cpp` nMinimumChainWork with information from the getblockchaininfo rpc. * Update `src/chainparams.cpp` defaultAssumeValid with information from the getblockhash rpc. - The selected value must not be orphaned so it may be useful to set the value two blocks back from the tip. @@ -26,7 +25,13 @@ Before every major release: * Update [`src/chainparams.cpp`](/src/chainparams.cpp) m_assumed_blockchain_size and m_assumed_chain_state_size with the current size plus some overhead. * Update `src/chainparams.cpp` chainTxData with statistics about the transaction count and rate. Use the output of the RPC `getchaintxstats`, see [this pull request](https://github.com/bitcoin/bitcoin/pull/12270) for an example. Reviewers can verify the results by running `getchaintxstats <window_block_count> <window_last_block_hash>` with the `window_block_count` and `window_last_block_hash` from your output. -* Update version of `contrib/gitian-descriptors/*.yml`: usually one'd want to do this on master after branching off the release - but be sure to at least do it before a new major release +* Update version of `contrib/gitian-descriptors/*.yml`: usually one'd want to do this on master after branching off the release - but be sure to at least do it before a new major release. +* In `configure.ac` and `build_msvc/bitcoin_config.h` on _the master branch_: + - update `CLIENT_VERSION_MINOR` version +* In `configure.ac` and `build_msvc/bitcoin_config.h` on _a new release branch_ (see [this commit](https://github.com/bitcoin/bitcoin/commit/742f7dd972fca3dd4a33cfff90bf901b71a687e7)): + - update `CLIENT_VERSION_MINOR` version + - set `CLIENT_VERSION_REVISION` to `0` + - set `CLIENT_VERSION_IS_RELEASE` to `true` ### First time / New builders diff --git a/src/coins.h b/src/coins.h index d39ebf9062..482e233e8c 100644 --- a/src/coins.h +++ b/src/coins.h @@ -294,6 +294,10 @@ public: bool HaveInputs(const CTransaction& tx) const; private: + /** + * @note this is marked const, but may actually append to `cacheCoins`, increasing + * memory usage. + */ CCoinsMap::iterator FetchCoin(const COutPoint &outpoint) const; }; diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index 8a76021a5b..eeec6dec25 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -23,11 +23,33 @@ public: void DummyWalletInit::AddWalletOptions() const { - std::vector<std::string> opts = {"-addresstype", "-changetype", "-disablewallet", "-discardfee=<amt>", "-fallbackfee=<amt>", - "-keypool=<n>", "-mintxfee=<amt>", "-paytxfee=<amt>", "-rescan", "-salvagewallet", "-spendzeroconfchange", "-txconfirmtarget=<n>", - "-upgradewallet", "-wallet=<path>", "-walletbroadcast", "-walletdir=<dir>", "-walletnotify=<cmd>", "-walletrbf", "-zapwallettxes=<mode>", - "-dblogsize=<n>", "-flushwallet", "-privdb", "-walletrejectlongchains"}; - gArgs.AddHiddenArgs(opts); + gArgs.AddHiddenArgs({ + "-addresstype", + "-avoidpartialspends", + "-changetype", + "-disablewallet", + "-discardfee=<amt>", + "-fallbackfee=<amt>", + "-keypool=<n>", + "-maxtxfee=<amt>", + "-mintxfee=<amt>", + "-paytxfee=<amt>", + "-rescan", + "-salvagewallet", + "-spendzeroconfchange", + "-txconfirmtarget=<n>", + "-upgradewallet", + "-wallet=<path>", + "-walletbroadcast", + "-walletdir=<dir>", + "-walletnotify=<cmd>", + "-walletrbf", + "-zapwallettxes=<mode>", + "-dblogsize=<n>", + "-flushwallet", + "-privdb", + "-walletrejectlongchains", + }); } const WalletInitInterface& g_wallet_init_interface = DummyWalletInit(); diff --git a/src/init.cpp b/src/init.cpp index e072b494c2..8f0be3ebe9 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -66,7 +66,6 @@ #include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/split.hpp> #include <boost/thread.hpp> -#include <openssl/crypto.h> #if ENABLE_ZMQ #include <zmq/zmqabstractnotifier.h> @@ -345,14 +344,15 @@ static void registerSignalHandler(int signal, void(*handler)(int)) } #endif +static boost::signals2::connection rpc_notify_block_change_connection; static void OnRPCStarted() { - uiInterface.NotifyBlockTip_connect(&RPCNotifyBlockChange); + rpc_notify_block_change_connection = uiInterface.NotifyBlockTip_connect(&RPCNotifyBlockChange); } static void OnRPCStopped() { - uiInterface.NotifyBlockTip_disconnect(&RPCNotifyBlockChange); + rpc_notify_block_change_connection.disconnect(); RPCNotifyBlockChange(false, nullptr); g_best_block_cv.notify_all(); LogPrint(BCLog::RPC, "RPC stopped.\n"); @@ -510,8 +510,6 @@ void SetupServerArgs() gArgs.AddArg("-mocktime=<n>", "Replace actual time with <n> seconds since epoch (default: 0)", true, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE), true, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-maxtxfee=<amt>", strprintf("Maximum total fees (in %s) to use in a single wallet transaction; setting this too low may abort large transactions (default: %s)", // TODO move setting to wallet - CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), false, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-printpriority", strprintf("Log transaction fee per kB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY), true, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -daemon. To disable logging to file, set -nodebuglogfile)", false, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-shrinkdebugfile", "Shrink debug.log file on client startup (default: 1 when no -debug)", false, OptionsCategory::DEBUG_TEST); @@ -521,7 +519,7 @@ void SetupServerArgs() gArgs.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", !testnetChainParams->RequireStandard()), true, OptionsCategory::NODE_RELAY); gArgs.AddArg("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to define cost of relay, used for mempool limiting and BIP 125 replacement. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), true, OptionsCategory::NODE_RELAY); - gArgs.AddArg("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to defined dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)), true, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to define dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)), true, OptionsCategory::NODE_RELAY); gArgs.AddArg("-bytespersigop", strprintf("Equivalent bytes per sigop in transactions for relay and mining (default: %u)", DEFAULT_BYTES_PER_SIGOP), false, OptionsCategory::NODE_RELAY); gArgs.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), false, OptionsCategory::NODE_RELAY); gArgs.AddArg("-datacarriersize", strprintf("Maximum size of data in data carrier transactions we relay and mine (default: %u)", MAX_OP_RETURN_RELAY), false, OptionsCategory::NODE_RELAY); @@ -1151,22 +1149,6 @@ bool AppInitParameterInteraction() dustRelayFee = CFeeRate(n); } - // This is required by both the wallet and node - if (gArgs.IsArgSet("-maxtxfee")) - { - CAmount nMaxFee = 0; - if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) - return InitError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""))); - if (nMaxFee > HIGH_MAX_TX_FEE) - InitWarning(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction.")); - maxTxFee = nMaxFee; - if (CFeeRate(maxTxFee, 1000) < ::minRelayTxFee) - { - return InitError(strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"), - gArgs.GetArg("-maxtxfee", ""), ::minRelayTxFee.ToString())); - } - } - fRequireStandard = !gArgs.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard()); if (chainparams.RequireStandard() && !fRequireStandard) return InitError(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString())); @@ -1733,8 +1715,9 @@ bool AppInitMain(InitInterfaces& interfaces) // Either install a handler to notify us when genesis activates, or set fHaveGenesis directly. // No locking, as this happens before any background thread is started. + boost::signals2::connection block_notify_genesis_wait_connection; if (chainActive.Tip() == nullptr) { - uiInterface.NotifyBlockTip_connect(BlockNotifyGenesisWait); + block_notify_genesis_wait_connection = uiInterface.NotifyBlockTip_connect(BlockNotifyGenesisWait); } else { fHaveGenesis = true; } @@ -1758,7 +1741,7 @@ bool AppInitMain(InitInterfaces& interfaces) while (!fHaveGenesis && !ShutdownRequested()) { g_genesis_wait_cv.wait_for(lock, std::chrono::milliseconds(500)); } - uiInterface.NotifyBlockTip_disconnect(BlockNotifyGenesisWait); + block_notify_genesis_wait_connection.disconnect(); } if (ShutdownRequested()) { diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index 4f2eb924a2..839af650bb 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -325,7 +325,6 @@ public: CFeeRate relayMinFee() override { return ::minRelayTxFee; } CFeeRate relayIncrementalFee() override { return ::incrementalRelayFee; } CFeeRate relayDustFee() override { return ::dustRelayFee; } - CAmount maxTxFee() override { return ::maxTxFee; } bool getPruneMode() override { return ::fPruneMode; } bool p2pEnabled() override { return g_connman != nullptr; } bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !IsInitialBlockDownload(); } diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 180991526b..7564ad26ac 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -207,12 +207,6 @@ public: //! Relay dust fee setting (-dustrelayfee), reflecting lowest rate it's economical to spend. virtual CFeeRate relayDustFee() = 0; - //! Node max tx fee setting (-maxtxfee). - //! This could be replaced by a per-wallet max fee, as proposed at - //! https://github.com/bitcoin/bitcoin/issues/15355 - //! But for the time being, wallets call this to access the node setting. - virtual CAmount maxTxFee() = 0; - //! Check if pruning is enabled. virtual bool getPruneMode() = 0; diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index 73a5074133..f3ee8fe364 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -207,7 +207,6 @@ public: } } bool getNetworkActive() override { return g_connman && g_connman->GetNetworkActive(); } - CAmount getMaxTxFee() override { return ::maxTxFee; } CFeeRate estimateSmartFee(int num_blocks, bool conservative, int* returned_target = nullptr) override { FeeCalculation fee_calc; diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 76b93af234..1ccd2a31b7 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -159,9 +159,6 @@ public: //! Get network active. virtual bool getNetworkActive() = 0; - //! Get max tx fee. - virtual CAmount getMaxTxFee() = 0; - //! Estimate smart fee. virtual CFeeRate estimateSmartFee(int num_blocks, bool conservative, int* returned_target = nullptr) = 0; diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index ed73a71354..b57299d78d 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -469,6 +469,7 @@ public: bool IsWalletFlagSet(uint64_t flag) override { return m_wallet->IsWalletFlagSet(flag); } OutputType getDefaultAddressType() override { return m_wallet->m_default_address_type; } OutputType getDefaultChangeType() override { return m_wallet->m_default_change_type; } + CAmount getDefaultMaxTxFee() override { return m_wallet->m_default_max_tx_fee; } void remove() override { RemoveWallet(m_wallet); diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index cabc455b1f..7096f54047 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -247,6 +247,9 @@ public: // Get default change type. virtual OutputType getDefaultChangeType() = 0; + //! Get max tx fee. + virtual CAmount getDefaultMaxTxFee() = 0; + // Remove wallet. virtual void remove() = 0; diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 6f8542123d..63a3d06267 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -59,7 +59,7 @@ bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType) std::vector<std::vector<unsigned char> > vSolutions; whichType = Solver(scriptPubKey, vSolutions); - if (whichType == TX_NONSTANDARD || whichType == TX_WITNESS_UNKNOWN) { + if (whichType == TX_NONSTANDARD) { return false; } else if (whichType == TX_MULTISIG) { unsigned char m = vSolutions.front()[0]; diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index f6f8e31363..aad991e2f1 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -222,6 +222,10 @@ inline void UnserializeTransaction(TxType& tx, Stream& s) { for (size_t i = 0; i < tx.vin.size(); i++) { s >> tx.vin[i].scriptWitness.stack; } + if (!tx.HasWitness()) { + /* It's illegal to encode witnesses when all witness stacks are empty. */ + throw std::ios_base::failure("Superfluous witness record"); + } } if (flags) { /* Unknown flag in the serialization */ diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index ba0a5abdf3..45f21d50fc 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -175,7 +175,9 @@ bool parseBitcoinURI(QString uri, SendCoinsRecipient *out) QString formatBitcoinURI(const SendCoinsRecipient &info) { - QString ret = QString("bitcoin:%1").arg(info.address); + bool bech_32 = info.address.startsWith(QString::fromStdString(Params().Bech32HRP() + "1")); + + QString ret = QString("bitcoin:%1").arg(bech_32 ? info.address.toUpper() : info.address); int paramCount = 0; if (info.amount) @@ -244,6 +246,11 @@ QList<QModelIndex> getEntryData(QAbstractItemView *view, int column) return view->selectionModel()->selectedRows(column); } +QString getDefaultDataDirectory() +{ + return boostPathToQString(GetDefaultDataDir()); +} + QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut) diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index cbec73a882..bea4a83494 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -79,6 +79,11 @@ namespace GUIUtil void setClipboard(const QString& str); + /** + * Determine default data directory for operating system. + */ + QString getDefaultDataDirectory(); + /** Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when no suffix is provided by the user. diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index 499af9fa07..c595361934 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -168,7 +168,7 @@ QString Intro::getDataDirectory() void Intro::setDataDirectory(const QString &dataDir) { ui->dataDirectory->setText(dataDir); - if(dataDir == getDefaultDataDirectory()) + if(dataDir == GUIUtil::getDefaultDataDirectory()) { ui->dataDirDefault->setChecked(true); ui->dataDirectory->setEnabled(false); @@ -180,11 +180,6 @@ void Intro::setDataDirectory(const QString &dataDir) } } -QString Intro::getDefaultDataDirectory() -{ - return GUIUtil::boostPathToQString(GetDefaultDataDir()); -} - bool Intro::pickDataDirectory(interfaces::Node& node) { QSettings settings; @@ -193,7 +188,7 @@ bool Intro::pickDataDirectory(interfaces::Node& node) if(!gArgs.GetArg("-datadir", "").empty()) return true; /* 1) Default data directory for operating system */ - QString dataDir = getDefaultDataDirectory(); + QString dataDir = GUIUtil::getDefaultDataDirectory(); /* 2) Allow QSettings to override default dir */ dataDir = settings.value("strDataDir", dataDir).toString(); @@ -239,7 +234,7 @@ bool Intro::pickDataDirectory(interfaces::Node& node) * override -datadir in the bitcoin.conf file in the default data directory * (to be consistent with bitcoind behavior) */ - if(dataDir != getDefaultDataDirectory()) { + if(dataDir != GUIUtil::getDefaultDataDirectory()) { node.softSetArg("-datadir", GUIUtil::qstringToBoostPath(dataDir).string()); // use OS locale for path setting } return true; @@ -293,7 +288,7 @@ void Intro::on_ellipsisButton_clicked() void Intro::on_dataDirDefault_clicked() { - setDataDirectory(getDefaultDataDirectory()); + setDataDirectory(GUIUtil::getDefaultDataDirectory()); } void Intro::on_dataDirCustom_clicked() diff --git a/src/qt/intro.h b/src/qt/intro.h index b537c94f7d..c3b26808d4 100644 --- a/src/qt/intro.h +++ b/src/qt/intro.h @@ -48,11 +48,6 @@ public: */ static bool pickDataDirectory(interfaces::Node& node); - /** - * Determine default data directory for operating system. - */ - static QString getDefaultDataDirectory(); - Q_SIGNALS: void requestCheck(); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 62496a57f4..5b4fb4cc18 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -17,7 +17,6 @@ #include <net.h> #include <netbase.h> #include <txdb.h> // for -dbcache defaults -#include <qt/intro.h> #include <QNetworkProxy> #include <QSettings> @@ -110,7 +109,7 @@ void OptionsModel::Init(bool resetSettings) addOverriddenOption("-par"); if (!settings.contains("strDataDir")) - settings.setValue("strDataDir", Intro::getDefaultDataDirectory()); + settings.setValue("strDataDir", GUIUtil::getDefaultDataDirectory()); // Wallet #ifdef ENABLE_WALLET @@ -187,7 +186,7 @@ void OptionsModel::Reset() BackupSettings(GetDataDir(true) / "guisettings.ini.bak", settings); // Save the strDataDir setting - QString dataDir = Intro::getDefaultDataDirectory(); + QString dataDir = GUIUtil::getDefaultDataDirectory(); dataDir = settings.value("strDataDir", dataDir).toString(); // Remove all entries from our QSettings object diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index c7ced3c106..8b6dcf0445 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -21,8 +21,6 @@ #include <util/strencodings.h> #include <util/system.h> -#include <openssl/crypto.h> - #include <univalue.h> #ifdef ENABLE_WALLET diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 6e00ab755c..8a0b265834 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -578,7 +578,7 @@ void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn msgParams.second = CClientUIInterface::MSG_ERROR; break; case WalletModel::AbsurdFee: - msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->node().getMaxTxFee())); + msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee())); break; case WalletModel::PaymentRequestExpired: msgParams.first = tr("Payment request expired."); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index f4f3be8f43..fd392b7cf7 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -222,9 +222,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact } // reject absurdly high fee. (This can never happen because the - // wallet caps the fee at maxTxFee. This merely serves as a + // wallet caps the fee at m_default_max_tx_fee. This merely serves as a // belt-and-suspenders check) - if (nFeeRequired > m_node.getMaxTxFee()) + if (nFeeRequired > m_wallet->getDefaultMaxTxFee()) return AbsurdFee; } diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 1a7216ceeb..78d7bbc80c 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -15,6 +15,7 @@ #include <key_io.h> #include <keystore.h> #include <merkleblock.h> +#include <node/coin.h> #include <node/psbt.h> #include <node/transaction.h> #include <policy/policy.h> @@ -770,7 +771,14 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) keystore.AddKey(key); } - return SignTransaction(*g_rpc_interfaces->chain, mtx, request.params[2], &keystore, true, request.params[3]); + // Fetch previous transactions (inputs): + std::map<COutPoint, Coin> coins; + for (const CTxIn& txin : mtx.vin) { + coins[txin.prevout]; // Create empty map entry keyed by prevout. + } + FindCoins(coins); + + return SignTransaction(mtx, request.params[2], &keystore, coins, true, request.params[3]); } static UniValue sendrawtransaction(const JSONRPCRequest& request) diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 728fc62e25..8af491977c 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -149,18 +149,8 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std:: vErrorsRet.push_back(entry); } -// TODO(https://github.com/bitcoin/bitcoin/pull/10973#discussion_r267084237): -// The dependency on interfaces::Chain should be removed, so -// signrawtransactionwithkey doesn't need access to a Chain instance. -UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore *keystore, bool is_temp_keystore, const UniValue& hashType) +UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore* keystore, std::map<COutPoint, Coin>& coins, bool is_temp_keystore, const UniValue& hashType) { - // Fetch previous transactions (inputs): - std::map<COutPoint, Coin> coins; - for (const CTxIn& txin : mtx.vin) { - coins[txin.prevout]; // Create empty map entry keyed by prevout. - } - chain.findCoins(coins); - // Add previous txouts given in the RPC call: if (!prevTxsUnival.isNull()) { UniValue prevTxs = prevTxsUnival.get_array(); diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index 5529dedbd4..c115d33a77 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -5,16 +5,26 @@ #ifndef BITCOIN_RPC_RAWTRANSACTION_UTIL_H #define BITCOIN_RPC_RAWTRANSACTION_UTIL_H +#include <map> + class CBasicKeyStore; class UniValue; struct CMutableTransaction; +class Coin; +class COutPoint; -namespace interfaces { -class Chain; -} // namespace interfaces - -/** Sign a transaction with the given keystore and previous transactions */ -UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType); +/** + * Sign a transaction with the given keystore and previous transactions + * + * @param mtx The transaction to-be-signed + * @param prevTxs Array of previous txns outputs that tx depends on but may not yet be in the block chain + * @param keystore Temporary keystore containing signing keys + * @param coins Map of unspent outputs - coins in mempool and current chain UTXO set, may be extended by previous txns outputs after call + * @param tempKeystore Whether to use temporary keystore + * @param hashType The signature hash type + * @returns JSON object with details of signed transaction + */ +UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore* keystore, std::map<COutPoint, Coin>& coins, bool tempKeystore, const UniValue& hashType); /** Create a transaction from univalue parameters */ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf); diff --git a/src/script/ismine.h b/src/script/ismine.h index 601e70f709..55e28e225a 100644 --- a/src/script/ismine.h +++ b/src/script/ismine.h @@ -9,6 +9,7 @@ #include <script/standard.h> #include <stdint.h> +#include <bitset> class CKeyStore; class CScript; @@ -16,10 +17,11 @@ class CScript; /** IsMine() return codes */ enum isminetype { - ISMINE_NO = 0, - ISMINE_WATCH_ONLY = 1, - ISMINE_SPENDABLE = 2, - ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE + ISMINE_NO = 0, + ISMINE_WATCH_ONLY = 1 << 0, + ISMINE_SPENDABLE = 1 << 1, + ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE, + ISMINE_ENUM_ELEMENTS, }; /** used for bitflags of isminetype */ typedef uint8_t isminefilter; @@ -27,4 +29,23 @@ typedef uint8_t isminefilter; isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey); isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest); +/** + * Cachable amount subdivided into watchonly and spendable parts. + */ +struct CachableAmount +{ + // NO and ALL are never (supposed to be) cached + std::bitset<ISMINE_ENUM_ELEMENTS> m_cached; + CAmount m_value[ISMINE_ENUM_ELEMENTS]; + inline void Reset() + { + m_cached.reset(); + } + void Set(isminefilter filter, CAmount value) + { + m_cached.set(filter); + m_value[filter] = value; + } +}; + #endif // BITCOIN_SCRIPT_ISMINE_H diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 8a219a8284..0d05b6514f 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -18,8 +18,6 @@ #include <vector> #include <boost/test/unit_test.hpp> -#include <openssl/aes.h> -#include <openssl/evp.h> BOOST_FIXTURE_TEST_SUITE(crypto_tests, BasicTestingSetup) diff --git a/src/ui_interface.cpp b/src/ui_interface.cpp index c084c4e0e2..31a95486d7 100644 --- a/src/ui_interface.cpp +++ b/src/ui_interface.cpp @@ -28,10 +28,6 @@ struct UISignals { boost::signals2::connection CClientUIInterface::signal_name##_connect(std::function<signal_name##Sig> fn) \ { \ return g_ui_signals.signal_name.connect(fn); \ - } \ - void CClientUIInterface::signal_name##_disconnect(std::function<signal_name##Sig> fn) \ - { \ - return g_ui_signals.signal_name.disconnect(&fn); \ } ADD_SIGNALS_IMPL_WRAPPER(ThreadSafeMessageBox); diff --git a/src/ui_interface.h b/src/ui_interface.h index 60d85bc142..d408f6f889 100644 --- a/src/ui_interface.h +++ b/src/ui_interface.h @@ -81,8 +81,7 @@ public: #define ADD_SIGNALS_DECL_WRAPPER(signal_name, rtype, ...) \ rtype signal_name(__VA_ARGS__); \ using signal_name##Sig = rtype(__VA_ARGS__); \ - boost::signals2::connection signal_name##_connect(std::function<signal_name##Sig> fn); \ - void signal_name##_disconnect(std::function<signal_name##Sig> fn); + boost::signals2::connection signal_name##_connect(std::function<signal_name##Sig> fn); /** Show message box. */ ADD_SIGNALS_DECL_WRAPPER(ThreadSafeMessageBox, bool, const std::string& message, const std::string& caption, unsigned int style); diff --git a/src/validation.cpp b/src/validation.cpp index 599d6027c8..c0b3243c8d 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -252,7 +252,6 @@ uint256 hashAssumeValid; arith_uint256 nMinimumChainWork; CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE); -CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE; CBlockPolicyEstimator feeEstimator; CTxMemPool mempool(&feeEstimator); @@ -562,6 +561,13 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, CValidationSt return CheckInputs(tx, state, view, true, flags, cacheSigStore, true, txdata); } +/** + * @param[out] coins_to_uncache Return any outpoints which were not previously present in the + * coins cache, but were added as a result of validating the tx + * for mempool acceptance. This allows the caller to optionally + * remove the cache additions if the associated transaction ends + * up being rejected by the mempool. + */ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx, bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced, bool bypass_limits, const CAmount& nAbsurdFee, std::vector<COutPoint>& coins_to_uncache, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -657,6 +663,10 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool if (!pcoinsTip->HaveCoinInCache(txin.prevout)) { coins_to_uncache.push_back(txin.prevout); } + + // Note: this call may add txin.prevout to the coins cache + // (pcoinsTip.cacheCoins) by way of FetchCoin(). It should be removed + // later (via coins_to_uncache) if this tx turns out to be invalid. if (!view.HaveCoin(txin.prevout)) { // Are inputs missing because we already have the tx? for (size_t out = 0; out < tx.vout.size(); out++) { @@ -978,6 +988,11 @@ static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPo std::vector<COutPoint> coins_to_uncache; bool res = AcceptToMemoryPoolWorker(chainparams, pool, state, tx, pfMissingInputs, nAcceptTime, plTxnReplaced, bypass_limits, nAbsurdFee, coins_to_uncache, test_accept); if (!res) { + // Remove coins that were not present in the coins cache before calling ATMPW; + // this is to prevent memory DoS in case we receive a large number of + // invalid transactions that attempt to overrun the in-memory coins cache + // (`CCoinsViewCache::cacheCoins`). + for (const COutPoint& hashTx : coins_to_uncache) pcoinsTip->Uncache(hashTx); } diff --git a/src/validation.h b/src/validation.h index 041d8860b0..ef8fff19ac 100644 --- a/src/validation.h +++ b/src/validation.h @@ -53,12 +53,6 @@ static const bool DEFAULT_WHITELISTRELAY = true; static const bool DEFAULT_WHITELISTFORCERELAY = false; /** Default for -minrelaytxfee, minimum relay fee for transactions */ static const unsigned int DEFAULT_MIN_RELAY_TX_FEE = 1000; -//! -maxtxfee default -static const CAmount DEFAULT_TRANSACTION_MAXFEE = COIN / 10; -//! Discourage users to set fees higher than this amount (in satoshis) per kB -static const CAmount HIGH_TX_FEE_PER_KB = COIN / 100; -//! -maxtxfee will warn if called with a higher fee than this amount (in satoshis) -static const CAmount HIGH_MAX_TX_FEE = 100 * HIGH_TX_FEE_PER_KB; /** Default for -limitancestorcount, max number of in-mempool ancestors */ static const unsigned int DEFAULT_ANCESTOR_LIMIT = 25; /** Default for -limitancestorsize, maximum kilobytes of tx + all in-mempool ancestors */ @@ -163,8 +157,6 @@ extern bool fCheckpointsEnabled; extern size_t nCoinCacheUsage; /** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */ extern CFeeRate minRelayTxFee; -/** Absolute maximum transaction fee (in satoshis) used by wallet and mempool (rejects high fee in sendrawtransaction) */ -extern CAmount maxTxFee; /** If the tip is older than this (in seconds), the node is considered to be in initial block download. */ extern int64_t nMaxTipAge; extern bool fEnableReplacement; diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 4ec9dca420..78db4df5e5 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -146,9 +146,9 @@ Result CreateTotalBumpTransaction(const CWallet* wallet, const uint256& txid, co } // Check that in all cases the new fee doesn't violate maxTxFee - const CAmount max_tx_fee = wallet->chain().maxTxFee(); + const CAmount max_tx_fee = wallet->m_default_max_tx_fee; if (new_fee > max_tx_fee) { - errors.push_back(strprintf("Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s)", + errors.push_back(strprintf("Specified or calculated fee %s is too high (cannot be higher than -maxtxfee %s)", FormatMoney(new_fee), FormatMoney(max_tx_fee))); return Result::WALLET_ERROR; } diff --git a/src/wallet/fees.cpp b/src/wallet/fees.cpp index 560c86a70a..d9ae18ed60 100644 --- a/src/wallet/fees.cpp +++ b/src/wallet/fees.cpp @@ -22,7 +22,7 @@ CAmount GetMinimumFee(const CWallet& wallet, unsigned int nTxBytes, const CCoinC { CAmount fee_needed = GetMinimumFeeRate(wallet, coin_control, feeCalc).GetFee(nTxBytes); // Always obey the maximum - const CAmount max_tx_fee = wallet.chain().maxTxFee(); + const CAmount max_tx_fee = wallet.m_default_max_tx_fee; if (fee_needed > max_tx_fee) { fee_needed = max_tx_fee; if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE; diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index e7eea94e06..47ef01bfd1 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -48,6 +48,8 @@ void WalletInit::AddWalletOptions() const gArgs.AddArg("-fallbackfee=<amt>", strprintf("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)), false, OptionsCategory::WALLET); gArgs.AddArg("-keypool=<n>", strprintf("Set key pool size to <n> (default: %u)", DEFAULT_KEYPOOL_SIZE), false, OptionsCategory::WALLET); + gArgs.AddArg("-maxtxfee=<amt>", strprintf("Maximum total fees (in %s) to use in a single wallet transaction; setting this too low may abort large transactions (default: %s)", + CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), false, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-mintxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE)), false, OptionsCategory::WALLET); gArgs.AddArg("-paytxfee=<amt>", strprintf("Fee (in %s/kB) to add to transactions you send (default: %s)", @@ -124,10 +126,6 @@ bool WalletInit::ParameterInteraction() const if (gArgs.GetArg("-prune", 0) && gArgs.GetBoolArg("-rescan", false)) return InitError(_("Rescans are not possible in pruned mode. You will need to use -reindex which will download the whole blockchain again.")); - if (::minRelayTxFee.GetFeePerK() > HIGH_TX_FEE_PER_KB) - InitWarning(AmountHighWarn("-minrelaytxfee") + " " + - _("The wallet will avoid paying less than the minimum relay fee.")); - return true; } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 0d804dcc10..5a22508c4b 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3150,7 +3150,14 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); - return SignTransaction(pwallet->chain(), mtx, request.params[1], pwallet, false, request.params[2]); + // Fetch previous transactions (inputs): + std::map<COutPoint, Coin> coins; + for (const CTxIn& txin : mtx.vin) { + coins[txin.prevout]; // Create empty map entry keyed by prevout. + } + pwallet->chain().findCoins(coins); + + return SignTransaction(mtx, request.params[1], pwallet, coins, false, request.params[2]); } static UniValue bumpfee(const JSONRPCRequest& request) @@ -3683,9 +3690,20 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request) // Find all addresses that have the given label UniValue ret(UniValue::VOBJ); + std::set<std::string> addresses; for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) { if (item.second.name == label) { - ret.pushKV(EncodeDestination(item.first), AddressBookDataToJSON(item.second, false)); + std::string address = EncodeDestination(item.first); + // CWallet::mapAddressBook is not expected to contain duplicate + // address strings, but build a separate set as a precaution just in + // case it does. + bool unique = addresses.emplace(address).second; + assert(unique); + // UniValue::pushKV checks if the key exists in O(N) + // and since duplicate addresses are unexpected (checked with + // std::set in O(log(N))), UniValue::__pushKV is used instead, + // which currently is O(1). + ret.__pushKV(address, AddressBookDataToJSON(item.second, false)); } } diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index b7ca746f8b..34b9770e8b 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -69,8 +69,7 @@ static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = fa std::unique_ptr<CWalletTx> wtx = MakeUnique<CWalletTx>(&testWallet, MakeTransactionRef(std::move(tx))); if (fIsFromMe) { - wtx->fDebitCached = true; - wtx->nDebitCached = 1; + wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1); } COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); vCoins.push_back(output); @@ -115,7 +114,7 @@ inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins) { static std::vector<OutputGroup> static_groups; static_groups.clear(); - for (auto& coin : coins) static_groups.emplace_back(coin.GetInputCoin(), coin.nDepth, coin.tx->fDebitCached && coin.tx->nDebitCached == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0); + for (auto& coin : coins) static_groups.emplace_back(coin.GetInputCoin(), coin.nDepth, coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] && coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0); return static_groups; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d076aa5e6f..4ab94f0c2c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1771,6 +1771,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, const uint256& stop_block, const WalletRescanReserver& reserver, bool fUpdate) { int64_t nNow = GetTime(); + int64_t start_time = GetTimeMillis(); assert(reserver.isReserved()); @@ -1779,90 +1780,90 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString()); + fAbortRescan = false; + ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup + uint256 tip_hash; + // The way the 'block_height' is initialized is just a workaround for the gcc bug #47679 since version 4.6.0. + Optional<int> block_height = MakeOptional(false, int()); + double progress_begin; + double progress_end; { - fAbortRescan = false; - ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup - uint256 tip_hash; - // The way the 'block_height' is initialized is just a workaround for the gcc bug #47679 since version 4.6.0. - Optional<int> block_height = MakeOptional(false, int()); - double progress_begin; - double progress_end; - { - auto locked_chain = chain().lock(); - if (Optional<int> tip_height = locked_chain->getHeight()) { - tip_hash = locked_chain->getBlockHash(*tip_height); - } - block_height = locked_chain->getBlockHeight(block_hash); - progress_begin = chain().guessVerificationProgress(block_hash); - progress_end = chain().guessVerificationProgress(stop_block.IsNull() ? tip_hash : stop_block); - } - double progress_current = progress_begin; - while (block_height && !fAbortRescan && !chain().shutdownRequested()) { - if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) { - ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)((progress_current - progress_begin) / (progress_end - progress_begin) * 100)))); - } - if (GetTime() >= nNow + 60) { - nNow = GetTime(); - WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", *block_height, progress_current); - } + auto locked_chain = chain().lock(); + if (Optional<int> tip_height = locked_chain->getHeight()) { + tip_hash = locked_chain->getBlockHash(*tip_height); + } + block_height = locked_chain->getBlockHeight(block_hash); + progress_begin = chain().guessVerificationProgress(block_hash); + progress_end = chain().guessVerificationProgress(stop_block.IsNull() ? tip_hash : stop_block); + } + double progress_current = progress_begin; + while (block_height && !fAbortRescan && !chain().shutdownRequested()) { + if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) { + ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)((progress_current - progress_begin) / (progress_end - progress_begin) * 100)))); + } + if (GetTime() >= nNow + 60) { + nNow = GetTime(); + WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", *block_height, progress_current); + } - CBlock block; - if (chain().findBlock(block_hash, &block) && !block.IsNull()) { - auto locked_chain = chain().lock(); - LOCK(cs_wallet); - if (!locked_chain->getBlockHeight(block_hash)) { - // Abort scan if current block is no longer active, to prevent - // marking transactions as coming from the wrong block. - // TODO: This should return success instead of failure, see - // https://github.com/bitcoin/bitcoin/pull/14711#issuecomment-458342518 - result.last_failed_block = block_hash; - result.status = ScanResult::FAILURE; - break; - } - for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { - SyncTransaction(block.vtx[posInBlock], block_hash, posInBlock, fUpdate); - } - // scan succeeded, record block as most recent successfully scanned - result.last_scanned_block = block_hash; - result.last_scanned_height = *block_height; - } else { - // could not scan block, keep scanning but record this block as the most recent failure + CBlock block; + if (chain().findBlock(block_hash, &block) && !block.IsNull()) { + auto locked_chain = chain().lock(); + LOCK(cs_wallet); + if (!locked_chain->getBlockHeight(block_hash)) { + // Abort scan if current block is no longer active, to prevent + // marking transactions as coming from the wrong block. + // TODO: This should return success instead of failure, see + // https://github.com/bitcoin/bitcoin/pull/14711#issuecomment-458342518 result.last_failed_block = block_hash; result.status = ScanResult::FAILURE; + break; } - if (block_hash == stop_block) { + for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { + SyncTransaction(block.vtx[posInBlock], block_hash, posInBlock, fUpdate); + } + // scan succeeded, record block as most recent successfully scanned + result.last_scanned_block = block_hash; + result.last_scanned_height = *block_height; + } else { + // could not scan block, keep scanning but record this block as the most recent failure + result.last_failed_block = block_hash; + result.status = ScanResult::FAILURE; + } + if (block_hash == stop_block) { + break; + } + { + auto locked_chain = chain().lock(); + Optional<int> tip_height = locked_chain->getHeight(); + if (!tip_height || *tip_height <= block_height || !locked_chain->getBlockHeight(block_hash)) { + // break successfully when rescan has reached the tip, or + // previous block is no longer on the chain due to a reorg break; } - { - auto locked_chain = chain().lock(); - Optional<int> tip_height = locked_chain->getHeight(); - if (!tip_height || *tip_height <= block_height || !locked_chain->getBlockHeight(block_hash)) { - // break successfully when rescan has reached the tip, or - // previous block is no longer on the chain due to a reorg - break; - } - // increment block and verification progress - block_hash = locked_chain->getBlockHash(++*block_height); - progress_current = chain().guessVerificationProgress(block_hash); + // increment block and verification progress + block_hash = locked_chain->getBlockHash(++*block_height); + progress_current = chain().guessVerificationProgress(block_hash); - // handle updated tip hash - const uint256 prev_tip_hash = tip_hash; - tip_hash = locked_chain->getBlockHash(*tip_height); - if (stop_block.IsNull() && prev_tip_hash != tip_hash) { - // in case the tip has changed, update progress max - progress_end = chain().guessVerificationProgress(tip_hash); - } + // handle updated tip hash + const uint256 prev_tip_hash = tip_hash; + tip_hash = locked_chain->getBlockHash(*tip_height); + if (stop_block.IsNull() && prev_tip_hash != tip_hash) { + // in case the tip has changed, update progress max + progress_end = chain().guessVerificationProgress(tip_hash); } } - ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 100); // hide progress dialog in GUI - if (block_height && fAbortRescan) { - WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", *block_height, progress_current); - result.status = ScanResult::USER_ABORT; - } else if (block_height && chain().shutdownRequested()) { - WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", *block_height, progress_current); - result.status = ScanResult::USER_ABORT; - } + } + ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 100); // hide progress dialog in GUI + if (block_height && fAbortRescan) { + WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", *block_height, progress_current); + result.status = ScanResult::USER_ABORT; + } else if (block_height && chain().shutdownRequested()) { + WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", *block_height, progress_current); + result.status = ScanResult::USER_ABORT; + } else { + WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - start_time); } return result; } @@ -1931,33 +1932,26 @@ std::set<uint256> CWalletTx::GetConflicts() const return result; } +CAmount CWalletTx::GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate) const +{ + auto& amount = m_amounts[type]; + if (recalculate || !amount.m_cached[filter]) { + amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter) : pwallet->GetCredit(*tx, filter)); + } + return amount.m_value[filter]; +} + CAmount CWalletTx::GetDebit(const isminefilter& filter) const { if (tx->vin.empty()) return 0; CAmount debit = 0; - if(filter & ISMINE_SPENDABLE) - { - if (fDebitCached) - debit += nDebitCached; - else - { - nDebitCached = pwallet->GetDebit(*tx, ISMINE_SPENDABLE); - fDebitCached = true; - debit += nDebitCached; - } + if (filter & ISMINE_SPENDABLE) { + debit += GetCachableAmount(DEBIT, ISMINE_SPENDABLE); } - if(filter & ISMINE_WATCH_ONLY) - { - if(fWatchDebitCached) - debit += nWatchDebitCached; - else - { - nWatchDebitCached = pwallet->GetDebit(*tx, ISMINE_WATCH_ONLY); - fWatchDebitCached = true; - debit += nWatchDebitCached; - } + if (filter & ISMINE_WATCH_ONLY) { + debit += GetCachableAmount(DEBIT, ISMINE_WATCH_ONLY); } return debit; } @@ -1969,28 +1963,12 @@ CAmount CWalletTx::GetCredit(interfaces::Chain::Lock& locked_chain, const ismine return 0; CAmount credit = 0; - if (filter & ISMINE_SPENDABLE) - { + if (filter & ISMINE_SPENDABLE) { // GetBalance can assume transactions in mapWallet won't change - if (fCreditCached) - credit += nCreditCached; - else - { - nCreditCached = pwallet->GetCredit(*tx, ISMINE_SPENDABLE); - fCreditCached = true; - credit += nCreditCached; - } + credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE); } - if (filter & ISMINE_WATCH_ONLY) - { - if (fWatchCreditCached) - credit += nWatchCreditCached; - else - { - nWatchCreditCached = pwallet->GetCredit(*tx, ISMINE_WATCH_ONLY); - fWatchCreditCached = true; - credit += nWatchCreditCached; - } + if (filter & ISMINE_WATCH_ONLY) { + credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY); } return credit; } @@ -1998,11 +1976,7 @@ CAmount CWalletTx::GetCredit(interfaces::Chain::Lock& locked_chain, const ismine CAmount CWalletTx::GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache) const { if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { - if (fUseCache && fImmatureCreditCached) - return nImmatureCreditCached; - nImmatureCreditCached = pwallet->GetCredit(*tx, ISMINE_SPENDABLE); - fImmatureCreditCached = true; - return nImmatureCreditCached; + return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); } return 0; @@ -2013,23 +1987,15 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo if (pwallet == nullptr) return 0; + // Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future). + bool allow_cache = filter == ISMINE_SPENDABLE || filter == ISMINE_WATCH_ONLY; + // Must wait until coinbase is safely deep enough in the chain before valuing it if (IsImmatureCoinBase(locked_chain)) return 0; - CAmount* cache = nullptr; - bool* cache_used = nullptr; - - if (filter == ISMINE_SPENDABLE) { - cache = &nAvailableCreditCached; - cache_used = &fAvailableCreditCached; - } else if (filter == ISMINE_WATCH_ONLY) { - cache = &nAvailableWatchCreditCached; - cache_used = &fAvailableWatchCreditCached; - } - - if (fUseCache && cache_used && *cache_used) { - return *cache; + if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) { + return m_amounts[AVAILABLE_CREDIT].m_value[filter]; } CAmount nCredit = 0; @@ -2045,22 +2011,17 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo } } - if (cache) { - *cache = nCredit; - assert(cache_used); - *cache_used = true; + if (allow_cache) { + m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit); } + return nCredit; } CAmount CWalletTx::GetImmatureWatchOnlyCredit(interfaces::Chain::Lock& locked_chain, const bool fUseCache) const { if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { - if (fUseCache && fImmatureWatchCreditCached) - return nImmatureWatchCreditCached; - nImmatureWatchCreditCached = pwallet->GetCredit(*tx, ISMINE_WATCH_ONLY); - fImmatureWatchCreditCached = true; - return nImmatureWatchCreditCached; + return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); } return 0; @@ -4203,6 +4164,29 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, return nullptr; } } + + if (gArgs.IsArgSet("-maxtxfee")) + { + CAmount nMaxFee = 0; + if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) { + chain.initError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""))); + return nullptr; + } + if (nMaxFee > HIGH_MAX_TX_FEE) { + chain.initWarning(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction.")); + } + if (CFeeRate(nMaxFee, 1000) < chain.relayMinFee()) { + chain.initError(strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"), + gArgs.GetArg("-maxtxfee", ""), chain.relayMinFee().ToString())); + return nullptr; + } + walletInstance->m_default_max_tx_fee = nMaxFee; + } + + if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) + chain.initWarning(AmountHighWarn("-minrelaytxfee") + " " + + _("The wallet will avoid paying less than the minimum relay fee.")); + walletInstance->m_confirm_target = gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET); walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); walletInstance->m_signal_rbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF); @@ -4262,7 +4246,6 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, } } - nStart = GetTimeMillis(); { WalletRescanReserver reserver(walletInstance.get()); if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(locked_chain->getBlockHash(rescan_height), {} /* stop block */, reserver, true /* update */).status)) { @@ -4270,7 +4253,6 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, return nullptr; } } - walletInstance->WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - nStart); walletInstance->ChainStateFlushed(locked_chain->getTipLocator()); walletInstance->database->IncrementUpdateCounter(); @@ -4398,7 +4380,7 @@ bool CWalletTx::AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, CValid // user could call sendmoney in a loop and hit spurious out of funds errors // because we think that this newly generated transaction's change is // unavailable as we're not yet aware that it is in the mempool. - bool ret = locked_chain.submitToMemoryPool(tx, pwallet->chain().maxTxFee(), state); + bool ret = locked_chain.submitToMemoryPool(tx, pwallet->m_default_max_tx_fee, state); fInMempool |= ret; return ret; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 62ba0aa962..900af75f4f 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -73,6 +73,12 @@ static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6; static const bool DEFAULT_WALLET_RBF = false; static const bool DEFAULT_WALLETBROADCAST = true; static const bool DEFAULT_DISABLE_WALLET = false; +//! -maxtxfee default +constexpr CAmount DEFAULT_TRANSACTION_MAXFEE{COIN / 10}; +//! Discourage users to set fees higher than this amount (in satoshis) per kB +constexpr CAmount HIGH_TX_FEE_PER_KB{COIN / 100}; +//! -maxtxfee will warn if called with a higher fee than this amount (in satoshis) +constexpr CAmount HIGH_MAX_TX_FEE{100 * HIGH_TX_FEE_PER_KB}; //! Pre-calculated constants for input size estimation in *virtual size* static constexpr size_t DUMMY_NESTED_P2WPKH_INPUT_SIZE = 91; @@ -369,24 +375,11 @@ public: std::multimap<int64_t, CWalletTx*>::const_iterator m_it_wtxOrdered; // memory only - mutable bool fDebitCached; - mutable bool fCreditCached; - mutable bool fImmatureCreditCached; - mutable bool fAvailableCreditCached; - mutable bool fWatchDebitCached; - mutable bool fWatchCreditCached; - mutable bool fImmatureWatchCreditCached; - mutable bool fAvailableWatchCreditCached; + enum AmountType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, AMOUNTTYPE_ENUM_ELEMENTS }; + CAmount GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate = false) const; + mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS]; mutable bool fChangeCached; mutable bool fInMempool; - mutable CAmount nDebitCached; - mutable CAmount nCreditCached; - mutable CAmount nImmatureCreditCached; - mutable CAmount nAvailableCreditCached; - mutable CAmount nWatchDebitCached; - mutable CAmount nWatchCreditCached; - mutable CAmount nImmatureWatchCreditCached; - mutable CAmount nAvailableWatchCreditCached; mutable CAmount nChangeCached; CWalletTx(const CWallet* pwalletIn, CTransactionRef arg) : CMerkleTx(std::move(arg)) @@ -403,24 +396,8 @@ public: nTimeReceived = 0; nTimeSmart = 0; fFromMe = false; - fDebitCached = false; - fCreditCached = false; - fImmatureCreditCached = false; - fAvailableCreditCached = false; - fWatchDebitCached = false; - fWatchCreditCached = false; - fImmatureWatchCreditCached = false; - fAvailableWatchCreditCached = false; fChangeCached = false; fInMempool = false; - nDebitCached = 0; - nCreditCached = 0; - nImmatureCreditCached = 0; - nAvailableCreditCached = 0; - nWatchDebitCached = 0; - nWatchCreditCached = 0; - nAvailableWatchCreditCached = 0; - nImmatureWatchCreditCached = 0; nChangeCached = 0; nOrderPos = -1; } @@ -464,14 +441,10 @@ public: //! make sure balances are recalculated void MarkDirty() { - fCreditCached = false; - fAvailableCreditCached = false; - fImmatureCreditCached = false; - fWatchDebitCached = false; - fWatchCreditCached = false; - fAvailableWatchCreditCached = false; - fImmatureWatchCreditCached = false; - fDebitCached = false; + m_amounts[DEBIT].Reset(); + m_amounts[CREDIT].Reset(); + m_amounts[IMMATURE_CREDIT].Reset(); + m_amounts[AVAILABLE_CREDIT].Reset(); fChangeCached = false; } @@ -989,6 +962,8 @@ public: CFeeRate m_discard_rate{DEFAULT_DISCARD_FEE}; OutputType m_default_address_type{DEFAULT_ADDRESS_TYPE}; OutputType m_default_change_type{DEFAULT_CHANGE_TYPE}; + /** Absolute maximum transaction fee (in satoshis) used by default for the wallet */ + CAmount m_default_max_tx_fee{DEFAULT_TRANSACTION_MAXFEE}; bool NewKeyPool(); size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp index 2d8adf8ba6..b227a15556 100644 --- a/src/wallet/walletutil.cpp +++ b/src/wallet/walletutil.cpp @@ -31,6 +31,8 @@ fs::path GetWalletDir() static bool IsBerkeleyBtree(const fs::path& path) { + if (!fs::exists(path)) return false; + // A Berkeley DB Btree file has at least 4K. // This check also prevents opening lock files. boost::system::error_code ec; diff --git a/test/config.ini.in b/test/config.ini.in index 6b7ef70659..060c553da2 100644 --- a/test/config.ini.in +++ b/test/config.ini.in @@ -6,6 +6,7 @@ # test/functional/test_runner.py and test/util/bitcoin-util-test.py [environment] +PACKAGE_NAME=@PACKAGE_NAME@ SRCDIR=@abs_top_srcdir@ BUILDDIR=@abs_top_builddir@ EXEEXT=@EXEEXT@ diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py index 02deae92f3..d262dae5aa 100644 --- a/test/functional/data/invalid_txs.py +++ b/test/functional/data/invalid_txs.py @@ -71,9 +71,13 @@ class InputMissing(BadTxTemplate): reject_reason = "bad-txns-vin-empty" expect_disconnect = False + # We use a blank transaction here to make sure + # it is interpreted as a non-witness transaction. + # Otherwise the transaction will fail the + # "surpufluous witness" check during deserialization + # rather than the input count check. def get_tx(self): tx = CTransaction() - tx.vout.append(CTxOut(0, sc.CScript([sc.OP_TRUE] * 100))) tx.calc_sha256() return tx diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 61f705e239..72eb4f804f 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -146,20 +146,6 @@ class FullBlockTest(BitcoinTestFramework): badtx = template.get_tx() if TxTemplate != invalid_txs.InputMissing: self.sign_tx(badtx, attempt_spend_tx) - else: - # Segwit is active in regtest at this point, so to deserialize a - # transaction without any inputs correctly, we set the outputs - # to an empty list. This is a hack, as the serialization of an - # empty list of outputs is deserialized as flags==0 and thus - # deserialization of the outputs is skipped. - # A policy check requires "loose" txs to be of a minimum size, - # so vtx is not set to be empty in the TxTemplate class and we - # only apply the workaround where txs are not "loose", i.e. in - # blocks. - # - # The workaround has the purpose that both sides calculate - # the same tx hash in the merkle tree - badtx.vout = [] badtx.rehash() badblock = self.update_block(blockname, [badtx]) self.send_blocks( diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py index 9fb0d35a68..ba116e41f5 100755 --- a/test/functional/feature_filelock.py +++ b/test/functional/feature_filelock.py @@ -23,7 +23,7 @@ class FilelockTest(BitcoinTestFramework): self.log.info("Using datadir {}".format(datadir)) self.log.info("Check that we can't start a second bitcoind instance using the same datadir") - expected_msg = "Error: Cannot obtain a lock on data directory {}. Bitcoin Core is probably already running.".format(datadir) + expected_msg = "Error: Cannot obtain a lock on data directory {0}. {1} is probably already running.".format(datadir, self.config['environment']['PACKAGE_NAME']) self.nodes[1].assert_start_raises_init_error(extra_args=['-datadir={}'.format(self.nodes[0].datadir), '-noserver'], expected_msg=expected_msg) if self.is_wallet_compiled(): diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 8fb7c49640..e2b3b2d544 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -35,16 +35,17 @@ def mine_large_blocks(node, n): # followed by 950k of OP_NOP. This would be non-standard in a non-coinbase # transaction but is consensus valid. + # Set the nTime if this is the first time this function has been called. + # A static variable ensures that time is monotonicly increasing and is therefore + # different for each block created => blockhash is unique. + if "nTimes" not in mine_large_blocks.__dict__: + mine_large_blocks.nTime = 0 + # Get the block parameters for the first block big_script = CScript([OP_RETURN] + [OP_NOP] * 950000) best_block = node.getblock(node.getbestblockhash()) height = int(best_block["height"]) + 1 - try: - # Static variable ensures that time is monotonicly increasing and is therefore - # different for each block created => blockhash is unique. - mine_large_blocks.nTime = min(mine_large_blocks.nTime, int(best_block["time"])) + 1 - except AttributeError: - mine_large_blocks.nTime = int(best_block["time"]) + 1 + mine_large_blocks.nTime = max(mine_large_blocks.nTime, int(best_block["time"])) + 1 previousblockhash = int(best_block["hash"], 16) for _ in range(n): diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index 15b7ba9ae1..7bb7044cc0 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -16,7 +16,7 @@ class TestBitcoinCli(BitcoinTestFramework): """Main test logic""" cli_response = self.nodes[0].cli("-version").send_cli() - assert "Bitcoin Core RPC client version" in cli_response + assert "{} RPC client version".format(self.config['environment']['PACKAGE_NAME']) in cli_response self.log.info("Compare responses from getwalletinfo RPC and `bitcoin-cli getwalletinfo`") if self.is_wallet_compiled(): diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py index 700fdf6e04..481d697e63 100755 --- a/test/functional/p2p_invalid_messages.py +++ b/test/functional/p2p_invalid_messages.py @@ -3,11 +3,12 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test node responses to invalid network messages.""" +import asyncio import os import struct from test_framework import messages -from test_framework.mininode import P2PDataStore +from test_framework.mininode import P2PDataStore, NetworkThread from test_framework.test_framework import BitcoinTestFramework @@ -143,8 +144,15 @@ class InvalidMessagesTest(BitcoinTestFramework): def test_magic_bytes(self): conn = self.nodes[0].add_p2p_connection(P2PDataStore()) - conn._on_data = lambda: None # Need to ignore all incoming messages from now, since they come with "invalid" magic bytes - conn.magic_bytes = b'\x00\x11\x22\x32' + + def swap_magic_bytes(): + conn._on_data = lambda: None # Need to ignore all incoming messages from now, since they come with "invalid" magic bytes + conn.magic_bytes = b'\x00\x11\x22\x32' + + # Call .result() to block until the atomic swap is complete, otherwise + # we might run into races later on + asyncio.run_coroutine_threadsafe(asyncio.coroutine(swap_magic_bytes)(), NetworkThread.network_event_loop).result() + with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: INVALID MESSAGESTART ping']): conn.send_message(messages.msg_ping(nonce=0xff)) conn.wait_for_disconnect(timeout=1) diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index 0a53ceee86..000c30646a 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -36,6 +36,7 @@ from test_framework.messages import ( ser_vector, sha256, uint256_from_str, + FromHex, ) from test_framework.mininode import ( P2PInterface, @@ -77,6 +78,7 @@ from test_framework.util import ( disconnect_nodes, get_bip9_status, hex_str_to_bytes, + assert_raises_rpc_error, ) # The versionbit bit used to signal activation of SegWit @@ -269,6 +271,7 @@ class SegWitTest(BitcoinTestFramework): self.test_non_standard_witness() self.test_upgrade_after_activation() self.test_witness_sigops() + self.test_superfluous_witness() # Individual tests @@ -1357,7 +1360,8 @@ class SegWitTest(BitcoinTestFramework): def test_segwit_versions(self): """Test validity of future segwit version transactions. - Future segwit version transactions are non-standard, but valid in blocks. + Future segwit versions are non-standard to spend, but valid in blocks. + Sending to future segwit versions is always allowed. Can run this before and after segwit activation.""" NUM_SEGWIT_VERSIONS = 17 # will test OP_0, OP1, ..., OP_16 @@ -1397,7 +1401,7 @@ class SegWitTest(BitcoinTestFramework): assert len(self.nodes[0].getrawmempool()) == 0 # Finally, verify that version 0 -> version 1 transactions - # are non-standard + # are standard script_pubkey = CScript([CScriptOp(OP_1), witness_hash]) tx2 = CTransaction() tx2.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")] @@ -1405,10 +1409,9 @@ class SegWitTest(BitcoinTestFramework): tx2.wit.vtxinwit.append(CTxInWitness()) tx2.wit.vtxinwit[0].scriptWitness.stack = [witness_program] tx2.rehash() - # Gets accepted to test_node, because standardness of outputs isn't - # checked with fRequireStandard + # Gets accepted to both policy-enforcing nodes and others. test_transaction_acceptance(self.nodes[0], self.test_node, tx2, with_witness=True, accepted=True) - test_transaction_acceptance(self.nodes[1], self.std_node, tx2, with_witness=True, accepted=False) + test_transaction_acceptance(self.nodes[1], self.std_node, tx2, with_witness=True, accepted=True) temp_utxo.pop() # last entry in temp_utxo was the output we just spent temp_utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue)) @@ -2034,5 +2037,31 @@ class SegWitTest(BitcoinTestFramework): # TODO: test p2sh sigop counting + def test_superfluous_witness(self): + # Serialization of tx that puts witness flag to 1 always + def serialize_with_bogus_witness(tx): + flags = 1 + r = b"" + r += struct.pack("<i", tx.nVersion) + if flags: + dummy = [] + r += ser_vector(dummy) + r += struct.pack("<B", flags) + r += ser_vector(tx.vin) + r += ser_vector(tx.vout) + if flags & 1: + if (len(tx.wit.vtxinwit) != len(tx.vin)): + # vtxinwit must have the same length as vin + tx.wit.vtxinwit = tx.wit.vtxinwit[:len(tx.vin)] + for i in range(len(tx.wit.vtxinwit), len(tx.vin)): + tx.wit.vtxinwit.append(CTxInWitness()) + r += tx.wit.serialize() + r += struct.pack("<I", tx.nLockTime) + return r + + raw = self.nodes[0].createrawtransaction([{"txid":"00"*32, "vout":0}], {self.nodes[0].getnewaddress():1}) + tx = FromHex(CTransaction(), raw) + assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, serialize_with_bogus_witness(tx).hex()) + if __name__ == '__main__': SegWitTest().main() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index fb68f79bbd..8a7ea7aa58 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -150,10 +150,11 @@ class PSBTTest(BitcoinTestFramework): new_psbt = self.nodes[0].converttopsbt(rawtx['hex']) self.nodes[0].decodepsbt(new_psbt) - # Make sure that a psbt with signatures cannot be converted + # Make sure that a non-psbt with signatures cannot be converted + # Error could be either "TX decode failed" (segwit inputs causes parsing to fail) or "Inputs must not have scriptSigs and scriptWitnesses" signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex']) - assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex']) - assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex'], False) + assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex']) + assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex'], False) # Unless we allow it to convert and strip signatures self.nodes[0].converttopsbt(signedtx['hex'], True) diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index 7a063ac526..11ea968257 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -531,7 +531,7 @@ class P2PDataStore(P2PInterface): for b in blocks: self.send_message(msg_block(block=b)) else: - self.send_message(msg_headers([CBlockHeader(blocks[-1])])) + self.send_message(msg_headers([CBlockHeader(block) for block in blocks])) wait_until(lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout, lock=mininode_lock) if expect_disconnect: diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 4aeff24d12..555d55d97f 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -556,21 +556,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): def is_cli_compiled(self): """Checks whether bitcoin-cli was compiled.""" - config = configparser.ConfigParser() - config.read_file(open(self.options.configfile)) - - return config["components"].getboolean("ENABLE_CLI") + return self.config["components"].getboolean("ENABLE_CLI") def is_wallet_compiled(self): """Checks whether the wallet module was compiled.""" - config = configparser.ConfigParser() - config.read_file(open(self.options.configfile)) - - return config["components"].getboolean("ENABLE_WALLET") + return self.config["components"].getboolean("ENABLE_WALLET") def is_zmq_compiled(self): """Checks whether the zmq module was compiled.""" - config = configparser.ConfigParser() - config.read_file(open(self.options.configfile)) - - return config["components"].getboolean("ENABLE_ZMQ") + return self.config["components"].getboolean("ENABLE_ZMQ") diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 86f334e942..d406ee3229 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -69,6 +69,7 @@ TEST_EXIT_SKIPPED = 77 BASE_SCRIPTS = [ # Scripts that are run by the travis build process. # Longest test should go first, to favor running tests in parallel + 'feature_pruning.py', 'feature_fee_estimation.py', 'wallet_hd.py', 'wallet_backup.py', @@ -199,7 +200,6 @@ BASE_SCRIPTS = [ EXTENDED_SCRIPTS = [ # These tests are not run by the travis build process. # Longest test should go first, to favor running tests in parallel - 'feature_pruning.py', 'feature_dbcrash.py', ] diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py index 72df714d80..9de30d0374 100755 --- a/test/functional/wallet_import_rescan.py +++ b/test/functional/wallet_import_rescan.py @@ -166,11 +166,12 @@ class ImportRescanTest(BitcoinTestFramework): timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"] set_node_times(self.nodes, timestamp + TIMESTAMP_WINDOW + 1) self.nodes[0].generate(1) - self.sync_blocks() + self.sync_all() # For each variation of wallet key import, invoke the import RPC and # check the results from getbalance and listtransactions. for variant in IMPORT_VARIANTS: + self.log.info('Run import for variant {}'.format(variant)) variant.expect_disabled = variant.rescan == Rescan.yes and variant.prune and variant.call == Call.single expect_rescan = variant.rescan == Rescan.yes and not variant.expect_disabled variant.node = self.nodes[2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))] @@ -192,10 +193,11 @@ class ImportRescanTest(BitcoinTestFramework): # Generate a block containing the new transactions. self.nodes[0].generate(1) assert_equal(self.nodes[0].getrawmempool(), []) - self.sync_blocks() + self.sync_all() # Check the latest results from getbalance and listtransactions. for variant in IMPORT_VARIANTS: + self.log.info('Run check for variant {}'.format(variant)) if not variant.expect_disabled: variant.expected_balance += variant.sent_amount variant.expected_txs += 1 diff --git a/test/lint/check-doc.py b/test/lint/check-doc.py index 3b05d5055c..1d6122a13d 100755 --- a/test/lint/check-doc.py +++ b/test/lint/check-doc.py @@ -12,26 +12,23 @@ Author: @MarcoFalke from subprocess import check_output import re -import sys FOLDER_GREP = 'src' FOLDER_TEST = 'src/test/' REGEX_ARG = '(?:ForceSet|SoftSet|Get|Is)(?:Bool)?Args?(?:Set)?\("(-[^"]+)"' REGEX_DOC = 'AddArg\("(-[^"=]+?)(?:=|")' -CMD_ROOT_DIR = '`git rev-parse --show-toplevel`/{}'.format(FOLDER_GREP) +CMD_ROOT_DIR = '$(git rev-parse --show-toplevel)/{}'.format(FOLDER_GREP) CMD_GREP_ARGS = r"git grep --perl-regexp '{}' -- {} ':(exclude){}'".format(REGEX_ARG, CMD_ROOT_DIR, FOLDER_TEST) +CMD_GREP_WALLET_ARGS = r"git grep --function-context 'void WalletInit::AddWalletOptions' -- {} | grep AddArg".format(CMD_ROOT_DIR) +CMD_GREP_WALLET_HIDDEN_ARGS = r"git grep --function-context 'void DummyWalletInit::AddWalletOptions' -- {}".format(CMD_ROOT_DIR) CMD_GREP_DOCS = r"git grep --perl-regexp '{}' {}".format(REGEX_DOC, CMD_ROOT_DIR) # list unsupported, deprecated and duplicate args as they need no documentation SET_DOC_OPTIONAL = set(['-h', '-help', '-dbcrashratio', '-forcecompactdb']) -def main(): - if sys.version_info >= (3, 6): - used = check_output(CMD_GREP_ARGS, shell=True, universal_newlines=True, encoding='utf8') - docd = check_output(CMD_GREP_DOCS, shell=True, universal_newlines=True, encoding='utf8') - else: - used = check_output(CMD_GREP_ARGS, shell=True).decode('utf8').strip() - docd = check_output(CMD_GREP_DOCS, shell=True).decode('utf8').strip() +def lint_missing_argument_documentation(): + used = check_output(CMD_GREP_ARGS, shell=True).decode('utf8').strip() + docd = check_output(CMD_GREP_DOCS, shell=True).decode('utf8').strip() args_used = set(re.findall(re.compile(REGEX_ARG), used)) args_docd = set(re.findall(re.compile(REGEX_DOC), docd)).union(SET_DOC_OPTIONAL) @@ -45,7 +42,24 @@ def main(): print("Args unknown : {}".format(len(args_unknown))) print(args_unknown) - sys.exit(len(args_need_doc)) + assert 0 == len(args_need_doc), "Please document the following arguments: {}".format(args_need_doc) + + +def lint_missing_hidden_wallet_args(): + wallet_args = check_output(CMD_GREP_WALLET_ARGS, shell=True).decode('utf8').strip() + wallet_hidden_args = check_output(CMD_GREP_WALLET_HIDDEN_ARGS, shell=True).decode('utf8').strip() + + wallet_args = set(re.findall(re.compile(REGEX_DOC), wallet_args)) + wallet_hidden_args = set(re.findall(re.compile(r' "([^"=]+)'), wallet_hidden_args)) + + hidden_missing = wallet_args.difference(wallet_hidden_args) + if hidden_missing: + assert 0, "Please add {} to the hidden args in DummyWalletInit::AddWalletOptions".format(hidden_missing) + + +def main(): + lint_missing_argument_documentation() + lint_missing_hidden_wallet_args() if __name__ == "__main__": diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index e59a5ce126..e1a99abc49 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -36,7 +36,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "txmempool -> validation -> validationinterface -> txmempool" "qt/addressbookpage -> qt/bitcoingui -> qt/walletview -> qt/receivecoinsdialog -> qt/addressbookpage" "qt/addressbookpage -> qt/bitcoingui -> qt/walletview -> qt/signverifymessagedialog -> qt/addressbookpage" - "qt/guiutil -> qt/walletmodel -> qt/optionsmodel -> qt/intro -> qt/guiutil" "qt/addressbookpage -> qt/bitcoingui -> qt/walletview -> qt/sendcoinsdialog -> qt/sendcoinsentry -> qt/addressbookpage" ) |