diff options
28 files changed, 576 insertions, 94 deletions
diff --git a/Makefile.am b/Makefile.am index 66be768277..be62c3e6a9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -78,6 +78,7 @@ COVERAGE_INFO = $(COV_TOOL_WRAPPER) baseline.info \ dist-hook: -$(GIT) archive --format=tar HEAD -- src/clientversion.cpp | $(AMTAR) -C $(top_distdir) -xf - +if TARGET_WINDOWS $(BITCOIN_WIN_INSTALLER): all-recursive $(MKDIR_P) $(top_builddir)/release STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIND_BIN) $(top_builddir)/release @@ -90,6 +91,10 @@ $(BITCOIN_WIN_INSTALLER): all-recursive echo error: could not build $@ @echo built $@ +deploy: $(BITCOIN_WIN_INSTALLER) +endif + +if TARGET_DARWIN $(OSX_APP)/Contents/PkgInfo: $(MKDIR_P) $(@D) @echo "APPL????" > $@ @@ -133,7 +138,7 @@ $(OSX_BACKGROUND_IMAGE): $(OSX_BACKGROUND_IMAGE).png $(OSX_BACKGROUND_IMAGE)@2x. tiffutil -cathidpicheck $^ -out $@ deploydir: $(OSX_DMG) -else +else !BUILD_DARWIN APP_DIST_DIR=$(top_builddir)/dist APP_DIST_EXTRAS=$(APP_DIST_DIR)/.background/$(OSX_BACKGROUND_IMAGE) $(APP_DIST_DIR)/.DS_Store $(APP_DIST_DIR)/Applications @@ -160,15 +165,11 @@ $(APP_DIST_DIR)/$(OSX_APP)/Contents/MacOS/Bitcoin-Qt: $(OSX_APP_BUILT) $(OSX_PAC INSTALLNAMETOOL=$(INSTALLNAMETOOL) OTOOL=$(OTOOL) STRIP=$(STRIP) $(PYTHON) $(OSX_DEPLOY_SCRIPT) $(OSX_APP) $(OSX_VOLNAME) -translations-dir=$(QT_TRANSLATION_DIR) deploydir: $(APP_DIST_EXTRAS) -endif +endif !BUILD_DARWIN -if TARGET_DARWIN appbundle: $(OSX_APP_BUILT) deploy: $(OSX_DMG) endif -if TARGET_WINDOWS -deploy: $(BITCOIN_WIN_INSTALLER) -endif $(BITCOIN_QT_BIN): FORCE $(MAKE) -C src qt/$(@F) diff --git a/configure.ac b/configure.ac index 1b06035815..3de60cd6d4 100644 --- a/configure.ac +++ b/configure.ac @@ -881,11 +881,16 @@ if test x$use_hardening != xno; then dnl Use CHECK_LINK_FLAG & --fatal-warnings to ensure we won't use the flag in this case. AX_CHECK_LINK_FLAG([-fcf-protection=full],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fcf-protection=full"],, [[$LDFLAG_WERROR]]) - dnl stack-clash-protection does not work properly when building for Windows. - dnl We use the test case from https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90458 - dnl to determine if it can be enabled. - AX_CHECK_COMPILE_FLAG([-fstack-clash-protection],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fstack-clash-protection"],[],["-O0"], - [AC_LANG_SOURCE([[class D {public: unsigned char buf[32768];}; int main() {D d; return 0;}]])]) + case $host in + *mingw*) + dnl stack-clash-protection doesn't currently work, and likely should just be skipped for Windows. + dnl See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90458 for more details. + ;; + *) + AX_CHECK_COMPILE_FLAG([-fstack-clash-protection],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fstack-clash-protection"]) + ;; + esac + dnl When enable_debug is yes, all optimizations are disabled. dnl However, FORTIFY_SOURCE requires that there is some level of optimization, otherwise it does nothing and just creates a compiler warning. diff --git a/contrib/init/bitcoind.service b/contrib/init/bitcoind.service index 5999928aa4..93de353bb4 100644 --- a/contrib/init/bitcoind.service +++ b/contrib/init/bitcoind.service @@ -18,7 +18,7 @@ After=network-online.target Wants=network-online.target [Service] -ExecStart=/usr/bin/bitcoind -daemon \ +ExecStart=/usr/bin/bitcoind -daemonwait \ -pid=/run/bitcoind/bitcoind.pid \ -conf=/etc/bitcoin/bitcoin.conf \ -datadir=/var/lib/bitcoind @@ -33,6 +33,7 @@ ExecStartPre=/bin/chgrp bitcoin /etc/bitcoin Type=forking PIDFile=/run/bitcoind/bitcoind.pid Restart=on-failure +TimeoutStartSec=infinity TimeoutStopSec=600 # Directory creation and permissions diff --git a/depends/packages/miniupnpc.mk b/depends/packages/miniupnpc.mk index 49a584e462..99f5b0a8db 100644 --- a/depends/packages/miniupnpc.mk +++ b/depends/packages/miniupnpc.mk @@ -1,9 +1,9 @@ package=miniupnpc -$(package)_version=2.0.20180203 +$(package)_version=2.2.2 $(package)_download_path=https://miniupnp.tuxfamily.org/files/ $(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=90dda8c7563ca6cd4a83e23b3c66dbbea89603a1675bfdb852897c2c9cc220b7 -$(package)_patches=dont_use_wingen.patch +$(package)_sha256_hash=888fb0976ba61518276fe1eda988589c700a3f2a69d71089260d75562afd3687 +$(package)_patches=dont_leak_info.patch define $(package)_set_vars $(package)_build_opts=CC="$($(package)_cc)" @@ -13,9 +13,7 @@ $(package)_build_env+=CFLAGS="$($(package)_cflags) $($(package)_cppflags)" AR="$ endef define $(package)_preprocess_cmds - mkdir dll && \ - sed -e 's|MINIUPNPC_VERSION_STRING \"version\"|MINIUPNPC_VERSION_STRING \"$($(package)_version)\"|' -e 's|OS/version|$(host)|' miniupnpcstrings.h.in > miniupnpcstrings.h && \ - patch -p1 < $($(package)_patch_dir)/dont_use_wingen.patch + patch -p1 < $($(package)_patch_dir)/dont_leak_info.patch endef define $(package)_build_cmds diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index f853aa0d79..18adfaadc3 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -111,6 +111,7 @@ $(package)_config_opts += -no-feature-xml $(package)_config_opts_darwin = -no-dbus $(package)_config_opts_darwin += -no-opengl $(package)_config_opts_darwin += -pch +$(package)_config_opts_darwin += -no-feature-corewlan $(package)_config_opts_darwin += -device-option QMAKE_MACOSX_DEPLOYMENT_TARGET=$(OSX_MIN_VERSION) ifneq ($(build_os),darwin) diff --git a/depends/patches/miniupnpc/dont_leak_info.patch b/depends/patches/miniupnpc/dont_leak_info.patch new file mode 100644 index 0000000000..512f9c50ea --- /dev/null +++ b/depends/patches/miniupnpc/dont_leak_info.patch @@ -0,0 +1,32 @@ +commit 8815452257437ba36607d0e2381c01142d1c7bb0 +Author: fanquake <fanquake@gmail.com> +Date: Thu Nov 19 10:51:19 2020 +0800 + + Don't leak OS and miniupnpc version info in User-Agent + +diff --git a//minisoap.c b/minisoap.c +index 7860667..775580b 100644 +--- a/minisoap.c ++++ b/minisoap.c +@@ -90,7 +90,7 @@ int soapPostSubmit(SOCKET fd, + headerssize = snprintf(headerbuf, sizeof(headerbuf), + "POST %s HTTP/%s\r\n" + "Host: %s%s\r\n" +- "User-Agent: " OS_STRING ", " UPNP_VERSION_STRING ", MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" ++ "User-Agent: " UPNP_VERSION_STRING "\r\n" + "Content-Length: %d\r\n" + "Content-Type: text/xml\r\n" + "SOAPAction: \"%s\"\r\n" +diff --git a/miniwget.c b/miniwget.c +index d5b7970..05aeb9c 100644 +--- a/miniwget.c ++++ b/miniwget.c +@@ -444,7 +444,7 @@ miniwget3(const char * host, + "GET %s HTTP/%s\r\n" + "Host: %s:%d\r\n" + "Connection: Close\r\n" +- "User-Agent: " OS_STRING ", " UPNP_VERSION_STRING ", MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" ++ "User-Agent: " UPNP_VERSION_STRING "\r\n" + + "\r\n", + path, httpversion, host, port); diff --git a/depends/patches/miniupnpc/dont_use_wingen.patch b/depends/patches/miniupnpc/dont_use_wingen.patch deleted file mode 100644 index a1cc9b50d1..0000000000 --- a/depends/patches/miniupnpc/dont_use_wingen.patch +++ /dev/null @@ -1,26 +0,0 @@ -commit e8077044df239bcf0d9e9980b0e1afb9f1f5c446 -Author: fanquake <fanquake@gmail.com> -Date: Tue Aug 18 20:50:19 2020 +0800 - - Don't use wingenminiupnpcstrings when generating miniupnpcstrings.h - - The wingenminiupnpcstrings tool is used on Windows to generate version - information. This information is irrelevant for us, and trying to use - wingenminiupnpcstrings would cause builds to fail, so just don't use it. - - We should be able to drop this once we are using 2.1 or later. See - upstream commit: 9663c55c61408fdcc39a82987d2243f816b22932. - -diff --git a/Makefile.mingw b/Makefile.mingw -index 574720e..fcc17bb 100644 ---- a/Makefile.mingw -+++ b/Makefile.mingw -@@ -74,7 +74,7 @@ wingenminiupnpcstrings: wingenminiupnpcstrings.o - - wingenminiupnpcstrings.o: wingenminiupnpcstrings.c - --miniupnpcstrings.h: miniupnpcstrings.h.in wingenminiupnpcstrings -+miniupnpcstrings.h: miniupnpcstrings.h.in - wingenminiupnpcstrings $< $@ - - minixml.o: minixml.c minixml.h diff --git a/doc/dependencies.md b/doc/dependencies.md index 1397fe9d0c..22161856ce 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -17,7 +17,7 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | libnatpmp | git commit [4536032...](https://github.com/miniupnp/libnatpmp/tree/4536032ae32268a45c073a4d5e91bbab4534773a) | | No | | | | libpng | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | | librsvg | | | | | | -| MiniUPnPc | [2.0.20180203](https://miniupnp.tuxfamily.org/files) | | No | | | +| MiniUPnPc | [2.2.2](https://miniupnp.tuxfamily.org/files) | | No | | | | PCRE | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | | Python (tests) | | [3.6](https://www.python.org/downloads) | | | | | qrencode | [3.4.4](https://fukuchi.org/works/qrencode) | | No | | | diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 7ed0eafb14..e00f17a83f 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -297,6 +297,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/transaction.cpp \ test/fuzz/tx_in.cpp \ test/fuzz/tx_out.cpp \ + test/fuzz/tx_pool.cpp \ test/fuzz/txrequest.cpp \ test/fuzz/validation_load_mempool.cpp \ test/fuzz/versionbits.cpp diff --git a/src/coins.h b/src/coins.h index feb441fd6a..5a6f73652b 100644 --- a/src/coins.h +++ b/src/coins.h @@ -75,6 +75,9 @@ public: ::Unserialize(s, Using<TxOutCompression>(out)); } + /** Either this coin never existed (see e.g. coinEmpty in coins.cpp), or it + * did exist and has been spent. + */ bool IsSpent() const { return out.IsNull(); } diff --git a/src/init.cpp b/src/init.cpp index 7d5420e3be..f4b7699233 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1308,7 +1308,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA LogPrintf("Config file: %s\n", config_file_path.string()); } else if (args.IsArgSet("-conf")) { // Warn if no conf file exists at path provided by user - InitWarning(strprintf(_("The specified config file %s does not exist\n"), config_file_path.string())); + InitWarning(strprintf(_("The specified config file %s does not exist"), config_file_path.string())); } else { // Not categorizing as "Warning" because it's the default behavior LogPrintf("Config file: %s (not found, skipping)\n", config_file_path.string()); diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 32890ed0fe..b4afdbcc22 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -759,14 +759,14 @@ QString formatNiceTimeOffset(qint64 secs) QString formatBytes(uint64_t bytes) { - if(bytes < 1024) + if (bytes < 1'000) return QObject::tr("%1 B").arg(bytes); - if(bytes < 1024 * 1024) - return QObject::tr("%1 KB").arg(bytes / 1024); - if(bytes < 1024 * 1024 * 1024) - return QObject::tr("%1 MB").arg(bytes / 1024 / 1024); + if (bytes < 1'000'000) + return QObject::tr("%1 kB").arg(bytes / 1'000); + if (bytes < 1'000'000'000) + return QObject::tr("%1 MB").arg(bytes / 1'000'000); - return QObject::tr("%1 GB").arg(bytes / 1024 / 1024 / 1024); + return QObject::tr("%1 GB").arg(bytes / 1'000'000'000); } qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal minPointSize, qreal font_size) { diff --git a/src/qt/trafficgraphwidget.cpp b/src/qt/trafficgraphwidget.cpp index dabdf9d887..7e12410c80 100644 --- a/src/qt/trafficgraphwidget.cpp +++ b/src/qt/trafficgraphwidget.cpp @@ -79,7 +79,7 @@ void TrafficGraphWidget::paintEvent(QPaintEvent *) int base = floor(log10(fMax)); float val = pow(10.0f, base); - const QString units = tr("KB/s"); + const QString units = tr("kB/s"); const float yMarginText = 2.0; // draw lines @@ -128,10 +128,10 @@ void TrafficGraphWidget::updateRates() quint64 bytesIn = clientModel->node().getTotalBytesRecv(), bytesOut = clientModel->node().getTotalBytesSent(); - float inRate = (bytesIn - nLastBytesIn) / 1024.0f * 1000 / timer->interval(); - float outRate = (bytesOut - nLastBytesOut) / 1024.0f * 1000 / timer->interval(); - vSamplesIn.push_front(inRate); - vSamplesOut.push_front(outRate); + float in_rate_kilobytes_per_sec = static_cast<float>(bytesIn - nLastBytesIn) / timer->interval(); + float out_rate_kilobytes_per_sec = static_cast<float>(bytesOut - nLastBytesOut) / timer->interval(); + vSamplesIn.push_front(in_rate_kilobytes_per_sec); + vSamplesOut.push_front(out_rate_kilobytes_per_sec); nLastBytesIn = bytesIn; nLastBytesOut = bytesOut; diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index b55f1c72b1..0baf30aef6 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -104,7 +104,7 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman) [&] { const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider); if (opt_service) { - addr_man.SetServices(*opt_service, ServiceFlags{fuzzed_data_provider.ConsumeIntegral<uint64_t>()}); + addr_man.SetServices(*opt_service, ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS)); } }, [&] { diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index 04fc6da9b1..96e1cfa08f 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -68,8 +68,8 @@ void fuzz_target(FuzzBufferType buffer, const std::string& LIMIT_TO_MESSAGE_TYPE { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - ConnmanTestMsg& connman = *(ConnmanTestMsg*)g_setup->m_node.connman.get(); - TestChainState& chainstate = *(TestChainState*)&g_setup->m_node.chainman->ActiveChainstate(); + ConnmanTestMsg& connman = *static_cast<ConnmanTestMsg*>(g_setup->m_node.connman.get()); + TestChainState& chainstate = *static_cast<TestChainState*>(&g_setup->m_node.chainman->ActiveChainstate()); SetMockTime(1610000000); // any time to successfully reset ibd chainstate.ResetIbd(); diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp index ee402dba38..00050391b0 100644 --- a/src/test/fuzz/process_messages.cpp +++ b/src/test/fuzz/process_messages.cpp @@ -35,8 +35,8 @@ FUZZ_TARGET_INIT(process_messages, initialize_process_messages) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - ConnmanTestMsg& connman = *(ConnmanTestMsg*)g_setup->m_node.connman.get(); - TestChainState& chainstate = *(TestChainState*)&g_setup->m_node.chainman->ActiveChainstate(); + ConnmanTestMsg& connman = *static_cast<ConnmanTestMsg*>(g_setup->m_node.connman.get()); + TestChainState& chainstate = *static_cast<TestChainState*>(&g_setup->m_node.chainman->ActiveChainstate()); SetMockTime(1610000000); // any time to successfully reset ibd chainstate.ResetIbd(); diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp new file mode 100644 index 0000000000..f84d6702a7 --- /dev/null +++ b/src/test/fuzz/tx_pool.cpp @@ -0,0 +1,285 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <consensus/validation.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <test/util/mining.h> +#include <test/util/script.h> +#include <test/util/setup_common.h> +#include <util/rbf.h> +#include <validation.h> +#include <validationinterface.h> + +namespace { + +const TestingSetup* g_setup; +std::vector<COutPoint> g_outpoints_coinbase_init_mature; +std::vector<COutPoint> g_outpoints_coinbase_init_immature; + +struct MockedTxPool : public CTxMemPool { + void RollingFeeUpdate() + { + lastRollingFeeUpdate = GetTime(); + blockSinceLastRollingFeeBump = true; + } +}; + +void initialize_tx_pool() +{ + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); + g_setup = testing_setup.get(); + + for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) { + CTxIn in = MineBlock(g_setup->m_node, P2WSH_OP_TRUE); + // Remember the txids to avoid expensive disk acess later on + auto& outpoints = i < COINBASE_MATURITY ? + g_outpoints_coinbase_init_mature : + g_outpoints_coinbase_init_immature; + outpoints.push_back(in.prevout); + } + SyncWithValidationInterfaceQueue(); +} + +struct TransactionsDelta final : public CValidationInterface { + std::set<CTransactionRef>& m_removed; + std::set<CTransactionRef>& m_added; + + explicit TransactionsDelta(std::set<CTransactionRef>& r, std::set<CTransactionRef>& a) + : m_removed{r}, m_added{a} {} + + void TransactionAddedToMempool(const CTransactionRef& tx, uint64_t /* mempool_sequence */) override + { + Assert(m_added.insert(tx).second); + } + + void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override + { + Assert(m_removed.insert(tx).second); + } +}; + +void SetMempoolConstraints(ArgsManager& args, FuzzedDataProvider& fuzzed_data_provider) +{ + args.ForceSetArg("-limitancestorcount", + ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50))); + args.ForceSetArg("-limitancestorsize", + ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202))); + args.ForceSetArg("-limitdescendantcount", + ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50))); + args.ForceSetArg("-limitdescendantsize", + ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202))); + args.ForceSetArg("-maxmempool", + ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200))); + args.ForceSetArg("-mempoolexpiry", + ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999))); +} + +FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + const auto& node = g_setup->m_node; + auto& chainstate = node.chainman->ActiveChainstate(); + + SetMockTime(ConsumeTime(fuzzed_data_provider)); + SetMempoolConstraints(*node.args, fuzzed_data_provider); + + // All RBF-spendable outpoints + std::set<COutPoint> outpoints_rbf; + // All outpoints counting toward the total supply (subset of outpoints_rbf) + std::set<COutPoint> outpoints_supply; + for (const auto& outpoint : g_outpoints_coinbase_init_mature) { + Assert(outpoints_supply.insert(outpoint).second); + } + outpoints_rbf = outpoints_supply; + + // The sum of the values of all spendable outpoints + constexpr CAmount SUPPLY_TOTAL{COINBASE_MATURITY * 50 * COIN}; + + CTxMemPool tx_pool_{/* estimator */ nullptr, /* check_ratio */ 1}; + MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_); + + // Helper to query an amount + const CCoinsViewMemPool amount_view{WITH_LOCK(::cs_main, return &chainstate.CoinsTip()), tx_pool}; + const auto GetAmount = [&](const COutPoint& outpoint) { + Coin c; + Assert(amount_view.GetCoin(outpoint, c)); + return c.out.nValue; + }; + + while (fuzzed_data_provider.ConsumeBool()) { + { + // Total supply is the mempool fee + all outpoints + CAmount supply_now{WITH_LOCK(tx_pool.cs, return tx_pool.GetTotalFee())}; + for (const auto& op : outpoints_supply) { + supply_now += GetAmount(op); + } + Assert(supply_now == SUPPLY_TOTAL); + } + Assert(!outpoints_supply.empty()); + + // Create transaction to add to the mempool + const CTransactionRef tx = [&] { + CMutableTransaction tx_mut; + tx_mut.nVersion = CTransaction::CURRENT_VERSION; + tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>(); + const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size()); + const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size() * 2); + + CAmount amount_in{0}; + for (int i = 0; i < num_in; ++i) { + // Pop random outpoint + auto pop = outpoints_rbf.begin(); + std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints_rbf.size() - 1)); + const auto outpoint = *pop; + outpoints_rbf.erase(pop); + amount_in += GetAmount(outpoint); + + // Create input + const auto sequence = ConsumeSequence(fuzzed_data_provider); + const auto script_sig = CScript{}; + const auto script_wit_stack = std::vector<std::vector<uint8_t>>{WITNESS_STACK_ELEM_OP_TRUE}; + CTxIn in; + in.prevout = outpoint; + in.nSequence = sequence; + in.scriptSig = script_sig; + in.scriptWitness.stack = script_wit_stack; + + tx_mut.vin.push_back(in); + } + const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1000, amount_in); + const auto amount_out = (amount_in - amount_fee) / num_out; + for (int i = 0; i < num_out; ++i) { + tx_mut.vout.emplace_back(amount_out, P2WSH_OP_TRUE); + } + const auto tx = MakeTransactionRef(tx_mut); + // Restore previously removed outpoints + for (const auto& in : tx->vin) { + Assert(outpoints_rbf.insert(in.prevout).second); + } + return tx; + }(); + + if (fuzzed_data_provider.ConsumeBool()) { + SetMockTime(ConsumeTime(fuzzed_data_provider)); + } + if (fuzzed_data_provider.ConsumeBool()) { + SetMempoolConstraints(*node.args, fuzzed_data_provider); + } + if (fuzzed_data_provider.ConsumeBool()) { + tx_pool.RollingFeeUpdate(); + } + if (fuzzed_data_provider.ConsumeBool()) { + const auto& txid = fuzzed_data_provider.ConsumeBool() ? + tx->GetHash() : + PickValue(fuzzed_data_provider, outpoints_rbf).hash; + const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN); + tx_pool.PrioritiseTransaction(txid, delta); + } + + // Remember all removed and added transactions + std::set<CTransactionRef> removed; + std::set<CTransactionRef> added; + auto txr = std::make_shared<TransactionsDelta>(removed, added); + RegisterSharedValidationInterface(txr); + const bool bypass_limits = fuzzed_data_provider.ConsumeBool(); + ::fRequireStandard = fuzzed_data_provider.ConsumeBool(); + const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx_pool, tx, bypass_limits)); + const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID; + SyncWithValidationInterfaceQueue(); + UnregisterSharedValidationInterface(txr); + + Assert(accepted != added.empty()); + Assert(accepted == res.m_state.IsValid()); + Assert(accepted != res.m_state.IsInvalid()); + if (accepted) { + Assert(added.size() == 1); // For now, no package acceptance + Assert(tx == *added.begin()); + } else { + // Do not consider rejected transaction removed + removed.erase(tx); + } + + // Helper to insert spent and created outpoints of a tx into collections + using Sets = std::vector<std::reference_wrapper<std::set<COutPoint>>>; + const auto insert_tx = [](Sets created_by_tx, Sets consumed_by_tx, const auto& tx) { + for (size_t i{0}; i < tx.vout.size(); ++i) { + for (auto& set : created_by_tx) { + Assert(set.get().emplace(tx.GetHash(), i).second); + } + } + for (const auto& in : tx.vin) { + for (auto& set : consumed_by_tx) { + Assert(set.get().insert(in.prevout).second); + } + } + }; + // Add created outpoints, remove spent outpoints + { + // Outpoints that no longer exist at all + std::set<COutPoint> consumed_erased; + // Outpoints that no longer count toward the total supply + std::set<COutPoint> consumed_supply; + for (const auto& removed_tx : removed) { + insert_tx(/* created_by_tx */ {consumed_erased}, /* consumed_by_tx */ {outpoints_supply}, /* tx */ *removed_tx); + } + for (const auto& added_tx : added) { + insert_tx(/* created_by_tx */ {outpoints_supply, outpoints_rbf}, /* consumed_by_tx */ {consumed_supply}, /* tx */ *added_tx); + } + for (const auto& p : consumed_erased) { + Assert(outpoints_supply.erase(p) == 1); + Assert(outpoints_rbf.erase(p) == 1); + } + for (const auto& p : consumed_supply) { + Assert(outpoints_supply.erase(p) == 1); + } + } + } + WITH_LOCK(::cs_main, tx_pool.check(chainstate)); + const auto info_all = tx_pool.infoAll(); + if (!info_all.empty()) { + const auto& tx_to_remove = *PickValue(fuzzed_data_provider, info_all).tx; + WITH_LOCK(tx_pool.cs, tx_pool.removeRecursive(tx_to_remove, /* dummy */ MemPoolRemovalReason::BLOCK)); + std::vector<uint256> all_txids; + tx_pool.queryHashes(all_txids); + assert(all_txids.size() < info_all.size()); + WITH_LOCK(::cs_main, tx_pool.check(chainstate)); + } + SyncWithValidationInterfaceQueue(); +} + +FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + const auto& node = g_setup->m_node; + + std::vector<uint256> txids; + for (const auto& outpoint : g_outpoints_coinbase_init_mature) { + txids.push_back(outpoint.hash); + } + for (int i{0}; i <= 3; ++i) { + // Add some immature and non-existent outpoints + txids.push_back(g_outpoints_coinbase_init_immature.at(i).hash); + txids.push_back(ConsumeUInt256(fuzzed_data_provider)); + } + + CTxMemPool tx_pool{/* estimator */ nullptr, /* check_ratio */ 1}; + + while (fuzzed_data_provider.ConsumeBool()) { + const auto mut_tx = ConsumeTransaction(fuzzed_data_provider, txids); + + const auto tx = MakeTransactionRef(mut_tx); + const bool bypass_limits = fuzzed_data_provider.ConsumeBool(); + ::fRequireStandard = fuzzed_data_provider.ConsumeBool(); + const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(node.chainman->ActiveChainstate(), tx_pool, tx, bypass_limits)); + const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID; + if (accepted) { + txids.push_back(tx->GetHash()); + } + + SyncWithValidationInterfaceQueue(); + } +} +} // namespace diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp index 0a541e4186..93418ab1ff 100644 --- a/src/test/fuzz/util.cpp +++ b/src/test/fuzz/util.cpp @@ -3,8 +3,11 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <test/fuzz/util.h> +#include <test/util/script.h> +#include <util/rbf.h> #include <version.h> + void FillNode(FuzzedDataProvider& fuzzed_data_provider, CNode& node, bool init_version) noexcept { const ServiceFlags remote_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS); @@ -23,3 +26,78 @@ void FillNode(FuzzedDataProvider& fuzzed_data_provider, CNode& node, bool init_v node.m_tx_relay->fRelayTxes = filter_txs; } } + +CMutableTransaction ConsumeTransaction(FuzzedDataProvider& fuzzed_data_provider, const std::optional<std::vector<uint256>>& prevout_txids, const int max_num_in, const int max_num_out) noexcept +{ + CMutableTransaction tx_mut; + const auto p2wsh_op_true = fuzzed_data_provider.ConsumeBool(); + tx_mut.nVersion = fuzzed_data_provider.ConsumeBool() ? + CTransaction::CURRENT_VERSION : + fuzzed_data_provider.ConsumeIntegral<int32_t>(); + tx_mut.nLockTime = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); + const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, max_num_in); + const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, max_num_out); + for (int i = 0; i < num_in; ++i) { + const auto& txid_prev = prevout_txids ? + PickValue(fuzzed_data_provider, *prevout_txids) : + ConsumeUInt256(fuzzed_data_provider); + const auto index_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, max_num_out); + const auto sequence = ConsumeSequence(fuzzed_data_provider); + const auto script_sig = p2wsh_op_true ? CScript{} : ConsumeScript(fuzzed_data_provider); + CScriptWitness script_wit; + if (p2wsh_op_true) { + script_wit.stack = std::vector<std::vector<uint8_t>>{WITNESS_STACK_ELEM_OP_TRUE}; + } else { + script_wit = ConsumeScriptWitness(fuzzed_data_provider); + } + CTxIn in; + in.prevout = COutPoint{txid_prev, index_out}; + in.nSequence = sequence; + in.scriptSig = script_sig; + in.scriptWitness = script_wit; + + tx_mut.vin.push_back(in); + } + for (int i = 0; i < num_out; ++i) { + const auto amount = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-10, 50 * COIN + 10); + const auto script_pk = p2wsh_op_true ? + P2WSH_OP_TRUE : + ConsumeScript(fuzzed_data_provider, /* max_length */ 128, /* maybe_p2wsh */ true); + tx_mut.vout.emplace_back(amount, script_pk); + } + return tx_mut; +} + +CScriptWitness ConsumeScriptWitness(FuzzedDataProvider& fuzzed_data_provider, const size_t max_stack_elem_size) noexcept +{ + CScriptWitness ret; + const auto n_elements = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, max_stack_elem_size); + for (size_t i = 0; i < n_elements; ++i) { + ret.stack.push_back(ConsumeRandomLengthByteVector(fuzzed_data_provider)); + } + return ret; +} + +CScript ConsumeScript(FuzzedDataProvider& fuzzed_data_provider, const size_t max_length, const bool maybe_p2wsh) noexcept +{ + const std::vector<uint8_t> b = ConsumeRandomLengthByteVector(fuzzed_data_provider); + CScript r_script{b.begin(), b.end()}; + if (maybe_p2wsh && fuzzed_data_provider.ConsumeBool()) { + uint256 script_hash; + CSHA256().Write(&r_script[0], r_script.size()).Finalize(script_hash.begin()); + r_script.clear(); + r_script << OP_0 << ToByteVector(script_hash); + } + return r_script; +} + +uint32_t ConsumeSequence(FuzzedDataProvider& fuzzed_data_provider) noexcept +{ + return fuzzed_data_provider.ConsumeBool() ? + fuzzed_data_provider.PickValueInArray({ + CTxIn::SEQUENCE_FINAL, + CTxIn::SEQUENCE_FINAL - 1, + MAX_BIP125_RBF_SEQUENCE, + }) : + fuzzed_data_provider.ConsumeIntegral<uint32_t>(); +} diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index cdddad82b3..df459fe60d 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -48,6 +48,16 @@ void CallOneOf(FuzzedDataProvider& fuzzed_data_provider, Callables... callables) return ((i++ == call_index ? callables() : void()), ...); } +template <typename Collection> +const auto& PickValue(FuzzedDataProvider& fuzzed_data_provider, const Collection& col) +{ + const auto sz = col.size(); + assert(sz >= 1); + auto it = col.begin(); + std::advance(it, fuzzed_data_provider.ConsumeIntegralInRange<decltype(sz)>(0, sz - 1)); + return *it; +} + [[nodiscard]] inline std::vector<uint8_t> ConsumeRandomLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const size_t max_length = 4096) noexcept { const std::string s = fuzzed_data_provider.ConsumeRandomLengthString(max_length); @@ -125,11 +135,13 @@ template <typename WeakEnumType, size_t size> return fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(time_min, time_max); } -[[nodiscard]] inline CScript ConsumeScript(FuzzedDataProvider& fuzzed_data_provider) noexcept -{ - const std::vector<uint8_t> b = ConsumeRandomLengthByteVector(fuzzed_data_provider); - return {b.begin(), b.end()}; -} +[[nodiscard]] CMutableTransaction ConsumeTransaction(FuzzedDataProvider& fuzzed_data_provider, const std::optional<std::vector<uint256>>& prevout_txids, const int max_num_in = 10, const int max_num_out = 10) noexcept; + +[[nodiscard]] CScriptWitness ConsumeScriptWitness(FuzzedDataProvider& fuzzed_data_provider, const size_t max_stack_elem_size = 32) noexcept; + +[[nodiscard]] CScript ConsumeScript(FuzzedDataProvider& fuzzed_data_provider, const size_t max_length = 4096, const bool maybe_p2wsh = false) noexcept; + +[[nodiscard]] uint32_t ConsumeSequence(FuzzedDataProvider& fuzzed_data_provider) noexcept; [[nodiscard]] inline CScriptNum ConsumeScriptNum(FuzzedDataProvider& fuzzed_data_provider) noexcept { diff --git a/src/test/fuzz/versionbits.cpp b/src/test/fuzz/versionbits.cpp index a898e2782d..88c1a1a9cb 100644 --- a/src/test/fuzz/versionbits.cpp +++ b/src/test/fuzz/versionbits.cpp @@ -25,18 +25,18 @@ private: const Consensus::Params dummy_params{}; public: - const int64_t m_begin = 0; - const int64_t m_end = 0; - const int m_period = 0; - const int m_threshold = 0; - const int m_bit = 0; + const int64_t m_begin; + const int64_t m_end; + const int m_period; + const int m_threshold; + const int m_bit; TestConditionChecker(int64_t begin, int64_t end, int period, int threshold, int bit) : m_begin{begin}, m_end{end}, m_period{period}, m_threshold{threshold}, m_bit{bit} { assert(m_period > 0); assert(0 <= m_threshold && m_threshold <= m_period); - assert(0 <= m_bit && m_bit <= 32 && m_bit < VERSIONBITS_NUM_BITS); + assert(0 <= m_bit && m_bit < 32 && m_bit < VERSIONBITS_NUM_BITS); } bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return Condition(pindex->nVersion); } @@ -49,9 +49,10 @@ public: int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, dummy_params, m_cache); } BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateStatisticsFor(pindexPrev, dummy_params); } - bool Condition(int64_t version) const + bool Condition(int32_t version) const { - return ((version >> m_bit) & 1) != 0 && (version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS; + uint32_t mask = ((uint32_t)1) << m_bit; + return (((version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (version & mask) != 0); } bool Condition(const CBlockIndex* pindex) const { return Condition(pindex->nVersion); } @@ -94,18 +95,20 @@ public: } }; +std::unique_ptr<const CChainParams> g_params; + void initialize() { - SelectParams(CBaseChainParams::MAIN); + // this is actually comparatively slow, so only do it once + g_params = CreateChainParams(ArgsManager{}, CBaseChainParams::MAIN); + assert(g_params != nullptr); } -} // namespace -constexpr uint32_t MAX_TIME = 4102444800; // 2100-01-01 +constexpr uint32_t MAX_START_TIME = 4102444800; // 2100-01-01 FUZZ_TARGET_INIT(versionbits, initialize) { - const CChainParams& params = Params(); - + const CChainParams& params = *g_params; const int64_t interval = params.GetConsensus().nPowTargetSpacing; assert(interval > 1); // need to be able to halve it assert(interval < std::numeric_limits<int32_t>::max()); @@ -122,9 +125,9 @@ FUZZ_TARGET_INIT(versionbits, initialize) // too many blocks at 10min each might cause uint32_t time to overflow if // block_start_time is at the end of the range above - assert(std::numeric_limits<uint32_t>::max() - MAX_TIME > interval * max_blocks); + assert(std::numeric_limits<uint32_t>::max() - MAX_START_TIME > interval * max_blocks); - const int64_t block_start_time = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(params.GenesisBlock().nTime, MAX_TIME); + const int64_t block_start_time = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(params.GenesisBlock().nTime, MAX_START_TIME); // what values for version will we use to signal / not signal? const int32_t ver_signal = fuzzed_data_provider.ConsumeIntegral<int32_t>(); @@ -173,8 +176,10 @@ FUZZ_TARGET_INIT(versionbits, initialize) if (checker.Condition(ver_nosignal)) return; if (ver_nosignal < 0) return; - // TOP_BITS should ensure version will be positive + // TOP_BITS should ensure version will be positive and meet min + // version requirement assert(ver_signal > 0); + assert(ver_signal >= VERSIONBITS_LAST_OLD_BLOCK_VERSION); // Now that we have chosen time and versions, setup to mine blocks Blocks blocks(block_start_time, interval, ver_signal, ver_nosignal); @@ -203,7 +208,7 @@ FUZZ_TARGET_INIT(versionbits, initialize) } // don't risk exceeding max_blocks or times may wrap around - if (blocks.size() + period*2 > max_blocks) break; + if (blocks.size() + 2 * period > max_blocks) break; } // NOTE: fuzzed_data_provider may be fully consumed at this point and should not be used further @@ -316,7 +321,7 @@ FUZZ_TARGET_INIT(versionbits, initialize) assert(false); } - if (blocks.size() >= max_periods * period) { + if (blocks.size() >= period * max_periods) { // we chose the timeout (and block times) so that by the time we have this many blocks it's all over assert(state == ThresholdState::ACTIVE || state == ThresholdState::FAILED); } @@ -343,3 +348,4 @@ FUZZ_TARGET_INIT(versionbits, initialize) } } } +} // namespace diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 470a8a3a44..5ac09b05db 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -77,6 +77,9 @@ BOOST_AUTO_TEST_CASE(util_check) const int two = *Assert(p_two); Assert(two == 2); Assert(true); + // Check that Assume can be used as unary expression + const bool result{Assume(two == 2)}; + Assert(result); } BOOST_AUTO_TEST_CASE(util_criticalsection) @@ -1450,7 +1453,6 @@ BOOST_AUTO_TEST_CASE(test_ParseInt32) BOOST_CHECK(!ParseInt32("1a", &n)); BOOST_CHECK(!ParseInt32("aap", &n)); BOOST_CHECK(!ParseInt32("0x1", &n)); // no hex - BOOST_CHECK(!ParseInt32("0x1", &n)); // no hex BOOST_CHECK(!ParseInt32(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseInt32("-2147483649", nullptr)); @@ -1513,7 +1515,6 @@ BOOST_AUTO_TEST_CASE(test_ParseUInt8) BOOST_CHECK(!ParseUInt8("1a", &n)); BOOST_CHECK(!ParseUInt8("aap", &n)); BOOST_CHECK(!ParseUInt8("0x1", &n)); // no hex - BOOST_CHECK(!ParseUInt8("0x1", &n)); // no hex BOOST_CHECK(!ParseUInt8(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseUInt8("-255", &n)); @@ -1549,7 +1550,6 @@ BOOST_AUTO_TEST_CASE(test_ParseUInt16) BOOST_CHECK(!ParseUInt16("1a", &n)); BOOST_CHECK(!ParseUInt16("aap", &n)); BOOST_CHECK(!ParseUInt16("0x1", &n)); // no hex - BOOST_CHECK(!ParseUInt16("0x1", &n)); // no hex BOOST_CHECK(!ParseUInt16(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseUInt16("-65535", &n)); @@ -1587,7 +1587,6 @@ BOOST_AUTO_TEST_CASE(test_ParseUInt32) BOOST_CHECK(!ParseUInt32("1a", &n)); BOOST_CHECK(!ParseUInt32("aap", &n)); BOOST_CHECK(!ParseUInt32("0x1", &n)); // no hex - BOOST_CHECK(!ParseUInt32("0x1", &n)); // no hex BOOST_CHECK(!ParseUInt32(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseUInt32("-2147483648", &n)); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index faccd1ade0..67549fc13d 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -895,7 +895,7 @@ std::optional<CTxMemPool::txiter> CTxMemPool::GetIter(const uint256& txid) const { auto it = mapTx.find(txid); if (it != mapTx.end()) return it; - return {}; + return std::nullopt; } CTxMemPool::setEntries CTxMemPool::GetIterSet(const std::set<uint256>& hashes) const diff --git a/src/txmempool.h b/src/txmempool.h index 9d4ea760e7..c3a9bd851d 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -476,7 +476,7 @@ enum class MemPoolRemovalReason { */ class CTxMemPool { -private: +protected: const int m_check_ratio; //!< Value n means that 1 times in n we check. std::atomic<unsigned int> nTransactionsUpdated{0}; //!< Used by getblocktemplate to trigger CreateNewBlock() invocation CBlockPolicyEstimator* minerPolicyEstimator; diff --git a/src/util/check.h b/src/util/check.h index bc62da3440..e60088a2c6 100644 --- a/src/util/check.h +++ b/src/util/check.h @@ -69,7 +69,7 @@ T get_pure_r_value(T&& val) #ifdef ABORT_ON_FAILED_ASSUME #define Assume(val) Assert(val) #else -#define Assume(val) ((void)(val)) +#define Assume(val) ([&]() -> decltype(get_pure_r_value(val)) { auto&& check = (val); return std::forward<decltype(get_pure_r_value(val))>(check); }()) #endif #endif // BITCOIN_UTIL_CHECK_H diff --git a/src/validation.cpp b/src/validation.cpp index 96d12f10e2..d1b9efe7ba 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -5186,7 +5186,7 @@ std::optional<uint256> ChainstateManager::SnapshotBlockhash() const { // If a snapshot chainstate exists, it will always be our active. return m_active_chainstate->m_from_snapshot_blockhash; } - return {}; + return std::nullopt; } std::vector<CChainState*> ChainstateManager::GetAll() diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index 4543f6fb4c..6a59bc2b38 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -76,7 +76,7 @@ bool VerifyWallets(interfaces::Chain& chain) bilingual_str error_string; if (!MakeWalletDatabase(wallet_file, options, status, error_string)) { if (status == DatabaseStatus::FAILED_NOT_FOUND) { - chain.initWarning(Untranslated(strprintf("Skipping -wallet path that doesn't exist. %s\n", error_string.original))); + chain.initWarning(Untranslated(strprintf("Skipping -wallet path that doesn't exist. %s", error_string.original))); } else { chain.initError(error_string); return false; diff --git a/test/functional/feature_anchors.py b/test/functional/feature_anchors.py new file mode 100755 index 0000000000..a60a723b3e --- /dev/null +++ b/test/functional/feature_anchors.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test Anchors functionality""" + +import os + +from test_framework.p2p import P2PInterface +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +def check_node_connections(*, node, num_in, num_out): + info = node.getnetworkinfo() + assert_equal(info["connections_in"], num_in) + assert_equal(info["connections_out"], num_out) + + +class AnchorsTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def setup_network(self): + self.setup_nodes() + + def run_test(self): + self.log.info("Add 2 block-relay-only connections to node 0") + for i in range(2): + self.log.debug(f"block-relay-only: {i}") + self.nodes[0].add_outbound_p2p_connection( + P2PInterface(), p2p_idx=i, connection_type="block-relay-only" + ) + + self.log.info("Add 5 inbound connections to node 0") + for i in range(5): + self.log.debug(f"inbound: {i}") + self.nodes[0].add_p2p_connection(P2PInterface()) + + self.log.info("Check node 0 connections") + check_node_connections(node=self.nodes[0], num_in=5, num_out=2) + + # 127.0.0.1 + ip = "7f000001" + + # Since the ip is always 127.0.0.1 for this case, + # we store only the port to identify the peers + block_relay_nodes_port = [] + inbound_nodes_port = [] + for p in self.nodes[0].getpeerinfo(): + addr_split = p["addr"].split(":") + if p["connection_type"] == "block-relay-only": + block_relay_nodes_port.append(hex(int(addr_split[1]))[2:]) + else: + inbound_nodes_port.append(hex(int(addr_split[1]))[2:]) + + self.log.info("Stop node 0") + self.stop_node(0) + + node0_anchors_path = os.path.join( + self.nodes[0].datadir, "regtest", "anchors.dat" + ) + + # It should contain only the block-relay-only addresses + self.log.info("Check the addresses in anchors.dat") + + with open(node0_anchors_path, "rb") as file_handler: + anchors = file_handler.read().hex() + + for port in block_relay_nodes_port: + ip_port = ip + port + assert ip_port in anchors + for port in inbound_nodes_port: + ip_port = ip + port + assert ip_port not in anchors + + self.log.info("Start node 0") + self.start_node(0) + + self.log.info("When node starts, check if anchors.dat doesn't exist anymore") + assert not os.path.exists(node0_anchors_path) + + +if __name__ == "__main__": + AnchorsTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 79ad2cf161..28d3518715 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -279,6 +279,7 @@ BASE_SCRIPTS = [ 'p2p_ping.py', 'rpc_scantxoutset.py', 'feature_logging.py', + 'feature_anchors.py', 'p2p_node_network_limited.py', 'p2p_permissions.py', 'feature_blocksdir.py', |