diff options
-rw-r--r-- | depends/Makefile | 24 | ||||
-rw-r--r-- | depends/hosts/default.mk | 9 | ||||
-rw-r--r-- | depends/packages/boost.mk | 17 | ||||
-rw-r--r-- | doc/release-notes.md | 26 | ||||
-rw-r--r-- | src/blockfilter.h | 8 | ||||
-rw-r--r-- | src/net_processing.cpp | 91 | ||||
-rw-r--r-- | src/protocol.cpp | 29 | ||||
-rw-r--r-- | src/protocol.h | 18 | ||||
-rw-r--r-- | src/qt/guiutil.cpp | 8 | ||||
-rw-r--r-- | src/rpc/blockchain.cpp | 2 | ||||
-rw-r--r-- | src/rpc/util.cpp | 8 | ||||
-rw-r--r-- | src/test/getarg_tests.cpp | 133 | ||||
-rw-r--r-- | src/test/raii_event_tests.cpp | 19 | ||||
-rw-r--r-- | src/txmempool.h | 6 | ||||
-rw-r--r-- | src/validation.cpp | 14 | ||||
-rw-r--r-- | test/config.ini.in | 1 | ||||
-rwxr-xr-x | test/functional/mempool_persist.py | 10 | ||||
-rwxr-xr-x | test/functional/mempool_unbroadcast.py | 12 | ||||
-rwxr-xr-x | test/functional/p2p_blockfilters.py | 74 | ||||
-rw-r--r-- | test/functional/test_framework/address.py | 49 | ||||
-rwxr-xr-x | test/functional/test_framework/messages.py | 51 | ||||
-rwxr-xr-x | test/functional/test_framework/mininode.py | 11 | ||||
-rwxr-xr-x | test/functional/test_framework/test_framework.py | 5 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 1 | ||||
-rwxr-xr-x | test/functional/wallet_resendwallettransactions.py | 15 |
25 files changed, 497 insertions, 144 deletions
diff --git a/depends/Makefile b/depends/Makefile index 5ad82bb56a..3d0784cb6b 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -4,6 +4,30 @@ print-%: @echo $* = $($*) +# When invoking a sub-make, keep only the command line variable definitions +# matching the pattern in the filter function. +# +# e.g. invoking: +# $ make A=1 C=1 print-MAKEOVERRIDES print-MAKEFLAGS +# +# with the following in the Makefile: +# MAKEOVERRIDES := $(filter A=% B=%,$(MAKEOVERRIDES)) +# +# will print: +# MAKEOVERRIDES = A=1 +# MAKEFLAGS = -- A=1 +# +# this is because as the GNU make manual says: +# The command line variable definitions really appear in the variable +# MAKEOVERRIDES, and MAKEFLAGS contains a reference to this variable. +# +# and since the GNU make manual also says: +# variables defined on the command line are passed to the sub-make through +# MAKEFLAGS +# +# this means that sub-makes will be invoked as if: +# $(MAKE) A=1 blah blah +MAKEOVERRIDES := $(filter V=%,$(MAKEOVERRIDES)) SOURCES_PATH ?= $(BASEDIR)/sources WORK_PATH = $(BASEDIR)/work BASE_CACHE ?= $(BASEDIR)/built diff --git a/depends/hosts/default.mk b/depends/hosts/default.mk index 144e5f88b7..258619a9d0 100644 --- a/depends/hosts/default.mk +++ b/depends/hosts/default.mk @@ -13,9 +13,18 @@ default_host_OTOOL = $(host_toolchain)otool default_host_NM = $(host_toolchain)nm define add_host_tool_func +ifneq ($(filter $(origin $1),undefined default),) +# Do not consider the well-known var $1 if it is undefined or is taking a value +# that is predefined by "make" (e.g. the make variable "CC" has a predefined +# value of "cc") $(host_os)_$1?=$$(default_host_$1) $(host_arch)_$(host_os)_$1?=$$($(host_os)_$1) $(host_arch)_$(host_os)_$(release_type)_$1?=$$($(host_os)_$1) +else +$(host_os)_$1=$(or $($1),$($(host_os)_$1),$(default_host_$1)) +$(host_arch)_$(host_os)_$1=$(or $($1),$($(host_arch)_$(host_os)_$1),$$($(host_os)_$1)) +$(host_arch)_$(host_os)_$(release_type)_$1=$(or $($1),$($(host_arch)_$(host_os)_$(release_type)_$1),$$($(host_os)_$1)) +endif host_$1=$$($(host_arch)_$(host_os)_$1) endef diff --git a/depends/packages/boost.mk b/depends/packages/boost.mk index 970c81041e..3a7e605b4f 100644 --- a/depends/packages/boost.mk +++ b/depends/packages/boost.mk @@ -9,9 +9,9 @@ $(package)_config_opts_release=variant=release $(package)_config_opts_debug=variant=debug $(package)_config_opts=--layout=tagged --build-type=complete --user-config=user-config.jam $(package)_config_opts+=threading=multi link=static -sNO_BZIP2=1 -sNO_ZLIB=1 -$(package)_config_opts_linux=threadapi=pthread runtime-link=shared -$(package)_config_opts_darwin=--toolset=clang-darwin runtime-link=shared -$(package)_config_opts_mingw32=binary-format=pe target-os=windows threadapi=win32 runtime-link=static +$(package)_config_opts_linux=target-os=linux threadapi=pthread runtime-link=shared +$(package)_config_opts_darwin=target-os=darwin runtime-link=shared +$(package)_config_opts_mingw32=target-os=windows binary-format=pe threadapi=win32 runtime-link=static $(package)_config_opts_x86_64_mingw32=address-model=64 $(package)_config_opts_i686_mingw32=address-model=32 $(package)_config_opts_i686_linux=address-model=32 architecture=x86 @@ -20,8 +20,11 @@ $(package)_config_opts_aarch64_android=address-model=64 $(package)_config_opts_x86_64_android=address-model=64 $(package)_config_opts_armv7a_android=address-model=32 $(package)_toolset_$(host_os)=gcc +$(package)_toolset_darwin=clang +ifneq (,$(findstring clang,$($(package)_cxx))) + $(package)_toolset_$(host_os)=clang +endif $(package)_archiver_$(host_os)=$($(package)_ar) -$(package)_toolset_darwin=clang-darwin $(package)_config_libraries=filesystem,system,thread,test $(package)_cxxflags=-std=c++11 -fvisibility=hidden $(package)_cxxflags_linux=-fPIC @@ -33,13 +36,13 @@ define $(package)_preprocess_cmds endef define $(package)_config_cmds - ./bootstrap.sh --without-icu --with-libraries=$($(package)_config_libraries) + ./bootstrap.sh --without-icu --with-libraries=$($(package)_config_libraries) --with-toolset=$($(package)_toolset_$(host_os)) endef define $(package)_build_cmds - ./b2 -d2 -j2 -d1 --prefix=$($(package)_staging_prefix_dir) $($(package)_config_opts) stage + ./b2 -d2 -j2 -d1 --prefix=$($(package)_staging_prefix_dir) $($(package)_config_opts) toolset=$($(package)_toolset_$(host_os)) stage endef define $(package)_stage_cmds - ./b2 -d0 -j4 --prefix=$($(package)_staging_prefix_dir) $($(package)_config_opts) install + ./b2 -d0 -j4 --prefix=$($(package)_staging_prefix_dir) $($(package)_config_opts) toolset=$($(package)_toolset_$(host_os)) install endef diff --git a/doc/release-notes.md b/doc/release-notes.md index 0d668a6302..0a2d0fbfc3 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -65,9 +65,28 @@ Notable changes P2P and network changes ----------------------- +- The mempool now tracks whether transactions submitted via the wallet or RPCs + have been successfully broadcast. Every 10-15 minutes, the node will try to + announce unbroadcast transactions until a peer requests it via a `getdata` + message or the transaction is removed from the mempool for other reasons. + The node will not track the broadcast status of transactions submitted to the + node using P2P relay. This version reduces the initial broadcast guarantees + for wallet transactions submitted via P2P to a node running the wallet. (#18038) + Updated RPCs ------------ +- `getmempoolinfo` now returns an additional `unbroadcastcount` field. The + mempool tracks locally submitted transactions until their initial broadcast + is acknowledged by a peer. This field returns the count of transactions + waiting for acknowledgement. + +- Mempool RPCs such as `getmempoolentry` and `getrawmempool` with + `verbose=true` now return an additional `unbroadcast` field. This indicates + whether initial broadcast of the transaction has been acknowledged by a + peer. `getmempoolancestors` and `getmempooldescendants` are also updated. + + Changes to Wallet or GUI related RPCs can be found in the GUI or Wallet section below. New RPCs @@ -87,6 +106,13 @@ New settings Wallet ------ +- To improve wallet privacy, the frequency of wallet rebroadcast attempts is + reduced from approximately once every 15 minutes to once every 12-36 hours. + To maintain a similar level of guarantee for initial broadcast of wallet + transactions, the mempool tracks these transactions as a part of the newly + introduced unbroadcast set. See the "P2P and network changes" section for + more information on the unbroadcast set. (#18038) + #### Wallet RPC changes - The `upgradewallet` RPC replaces the `-upgradewallet` command line option. diff --git a/src/blockfilter.h b/src/blockfilter.h index ff8744b217..96cefbf3b2 100644 --- a/src/blockfilter.h +++ b/src/blockfilter.h @@ -144,8 +144,8 @@ public: template <typename Stream> void Serialize(Stream& s) const { - s << m_block_hash - << static_cast<uint8_t>(m_filter_type) + s << static_cast<uint8_t>(m_filter_type) + << m_block_hash << m_filter.GetEncoded(); } @@ -154,8 +154,8 @@ public: std::vector<unsigned char> encoded_filter; uint8_t filter_type; - s >> m_block_hash - >> filter_type + s >> filter_type + >> m_block_hash >> encoded_filter; m_filter_type = static_cast<BlockFilterType>(filter_type); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 159036a237..404b33a977 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -129,6 +129,8 @@ static constexpr unsigned int INVENTORY_BROADCAST_MAX = 7 * INVENTORY_BROADCAST_ static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60; /** Maximum feefilter broadcast delay after significant change. */ static constexpr unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60; +/** Maximum number of compact filters that may be requested with one getcfilters. See BIP 157. */ +static constexpr uint32_t MAX_GETCFILTERS_SIZE = 1000; /** Maximum number of cf hashes that may be requested with one getcfheaders. See BIP 157. */ static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000; @@ -827,7 +829,8 @@ void PeerLogicValidation::ReattemptInitialBroadcast(CScheduler& scheduler) const } } - // schedule next run for 10-15 minutes in the future + // Schedule next run for 10-15 minutes in the future. + // We add randomness on every cycle to avoid the possibility of P2P fingerprinting. const std::chrono::milliseconds delta = std::chrono::minutes{10} + GetRandMillis(std::chrono::minutes{5}); scheduler.scheduleFromNow([&] { ReattemptInitialBroadcast(scheduler); }, delta); } @@ -1998,7 +2001,7 @@ void static ProcessOrphanTx(CConnman* connman, CTxMemPool& mempool, std::set<uin * @param[out] filter_index The filter index, if the request can be serviced. * @return True if the request can be serviced. */ -static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_params, +static bool PrepareBlockFilterRequest(CNode& pfrom, const CChainParams& chain_params, BlockFilterType filter_type, uint32_t start_height, const uint256& stop_hash, uint32_t max_height_diff, const CBlockIndex*& stop_index, @@ -2009,8 +2012,8 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS)); if (!supported_filter_type) { LogPrint(BCLog::NET, "peer %d requested unsupported block filter type: %d\n", - pfrom->GetId(), static_cast<uint8_t>(filter_type)); - pfrom->fDisconnect = true; + pfrom.GetId(), static_cast<uint8_t>(filter_type)); + pfrom.fDisconnect = true; return false; } @@ -2021,8 +2024,8 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa // Check that the stop block exists and the peer would be allowed to fetch it. if (!stop_index || !BlockRequestAllowed(stop_index, chain_params.GetConsensus())) { LogPrint(BCLog::NET, "peer %d requested invalid block hash: %s\n", - pfrom->GetId(), stop_hash.ToString()); - pfrom->fDisconnect = true; + pfrom.GetId(), stop_hash.ToString()); + pfrom.fDisconnect = true; return false; } } @@ -2031,14 +2034,14 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa if (start_height > stop_height) { LogPrint(BCLog::NET, "peer %d sent invalid getcfilters/getcfheaders with " /* Continued */ "start height %d and stop height %d\n", - pfrom->GetId(), start_height, stop_height); - pfrom->fDisconnect = true; + pfrom.GetId(), start_height, stop_height); + pfrom.fDisconnect = true; return false; } if (stop_height - start_height >= max_height_diff) { LogPrint(BCLog::NET, "peer %d requested too many cfilters/cfheaders: %d / %d\n", - pfrom->GetId(), stop_height - start_height + 1, max_height_diff); - pfrom->fDisconnect = true; + pfrom.GetId(), stop_height - start_height + 1, max_height_diff); + pfrom.fDisconnect = true; return false; } @@ -2052,6 +2055,49 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa } /** + * Handle a cfilters request. + * + * May disconnect from the peer in the case of a bad request. + * + * @param[in] pfrom The peer that we received the request from + * @param[in] vRecv The raw message received + * @param[in] chain_params Chain parameters + * @param[in] connman Pointer to the connection manager + */ +static void ProcessGetCFilters(CNode& pfrom, CDataStream& vRecv, const CChainParams& chain_params, + CConnman& connman) +{ + uint8_t filter_type_ser; + uint32_t start_height; + uint256 stop_hash; + + vRecv >> filter_type_ser >> start_height >> stop_hash; + + const BlockFilterType filter_type = static_cast<BlockFilterType>(filter_type_ser); + + const CBlockIndex* stop_index; + BlockFilterIndex* filter_index; + if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, start_height, stop_hash, + MAX_GETCFILTERS_SIZE, stop_index, filter_index)) { + return; + } + + std::vector<BlockFilter> filters; + + if (!filter_index->LookupFilterRange(start_height, stop_index, filters)) { + LogPrint(BCLog::NET, "Failed to find block filter in index: filter_type=%s, start_height=%d, stop_hash=%s\n", + BlockFilterTypeName(filter_type), start_height, stop_hash.ToString()); + return; + } + + for (const auto& filter : filters) { + CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion()) + .Make(NetMsgType::CFILTER, filter); + connman.PushMessage(&pfrom, std::move(msg)); + } +} + +/** * Handle a cfheaders request. * * May disconnect from the peer in the case of a bad request. @@ -2061,8 +2107,8 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa * @param[in] chain_params Chain parameters * @param[in] connman Pointer to the connection manager */ -static void ProcessGetCFHeaders(CNode* pfrom, CDataStream& vRecv, const CChainParams& chain_params, - CConnman* connman) +static void ProcessGetCFHeaders(CNode& pfrom, CDataStream& vRecv, const CChainParams& chain_params, + CConnman& connman) { uint8_t filter_type_ser; uint32_t start_height; @@ -2097,13 +2143,13 @@ static void ProcessGetCFHeaders(CNode* pfrom, CDataStream& vRecv, const CChainPa return; } - CSerializedNetMsg msg = CNetMsgMaker(pfrom->GetSendVersion()) + CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion()) .Make(NetMsgType::CFHEADERS, filter_type_ser, stop_index->GetBlockHash(), prev_header, filter_hashes); - connman->PushMessage(pfrom, std::move(msg)); + connman.PushMessage(&pfrom, std::move(msg)); } /** @@ -2116,8 +2162,8 @@ static void ProcessGetCFHeaders(CNode* pfrom, CDataStream& vRecv, const CChainPa * @param[in] chain_params Chain parameters * @param[in] connman Pointer to the connection manager */ -static void ProcessGetCFCheckPt(CNode* pfrom, CDataStream& vRecv, const CChainParams& chain_params, - CConnman* connman) +static void ProcessGetCFCheckPt(CNode& pfrom, CDataStream& vRecv, const CChainParams& chain_params, + CConnman& connman) { uint8_t filter_type_ser; uint256 stop_hash; @@ -2149,12 +2195,12 @@ static void ProcessGetCFCheckPt(CNode* pfrom, CDataStream& vRecv, const CChainPa } } - CSerializedNetMsg msg = CNetMsgMaker(pfrom->GetSendVersion()) + CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion()) .Make(NetMsgType::CFCHECKPT, filter_type_ser, stop_index->GetBlockHash(), headers); - connman->PushMessage(pfrom, std::move(msg)); + connman.PushMessage(&pfrom, std::move(msg)); } bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, ChainstateManager& chainman, CTxMemPool& mempool, CConnman* connman, BanMan* banman, const std::atomic<bool>& interruptMsgProc) @@ -3466,13 +3512,18 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec return true; } + if (msg_type == NetMsgType::GETCFILTERS) { + ProcessGetCFilters(*pfrom, vRecv, chainparams, *connman); + return true; + } + if (msg_type == NetMsgType::GETCFHEADERS) { - ProcessGetCFHeaders(pfrom, vRecv, chainparams, connman); + ProcessGetCFHeaders(*pfrom, vRecv, chainparams, *connman); return true; } if (msg_type == NetMsgType::GETCFCHECKPT) { - ProcessGetCFCheckPt(pfrom, vRecv, chainparams, connman); + ProcessGetCFCheckPt(*pfrom, vRecv, chainparams, *connman); return true; } diff --git a/src/protocol.cpp b/src/protocol.cpp index 737baff36b..83a24b9d95 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -40,6 +40,8 @@ const char *SENDCMPCT="sendcmpct"; const char *CMPCTBLOCK="cmpctblock"; const char *GETBLOCKTXN="getblocktxn"; const char *BLOCKTXN="blocktxn"; +const char *GETCFILTERS="getcfilters"; +const char *CFILTER="cfilter"; const char *GETCFHEADERS="getcfheaders"; const char *CFHEADERS="cfheaders"; const char *GETCFCHECKPT="getcfcheckpt"; @@ -75,6 +77,8 @@ const static std::string allNetMessageTypes[] = { NetMsgType::CMPCTBLOCK, NetMsgType::GETBLOCKTXN, NetMsgType::BLOCKTXN, + NetMsgType::GETCFILTERS, + NetMsgType::CFILTER, NetMsgType::GETCFHEADERS, NetMsgType::CFHEADERS, NetMsgType::GETCFCHECKPT, @@ -195,9 +199,15 @@ const std::vector<std::string> &getAllNetMessageTypes() return allNetMessageTypesVec; } -std::string serviceFlagToStr(const uint64_t mask, const int bit) +/** + * Convert a service flag (NODE_*) to a human readable string. + * It supports unknown service flags which will be returned as "UNKNOWN[...]". + * @param[in] bit the service flag is calculated as (1 << bit) + */ +static std::string serviceFlagToStr(size_t bit) { - switch (ServiceFlags(mask)) { + const uint64_t service_flag = 1ULL << bit; + switch ((ServiceFlags)service_flag) { case NODE_NONE: abort(); // impossible case NODE_NETWORK: return "NETWORK"; case NODE_GETUTXO: return "GETUTXO"; @@ -211,10 +221,23 @@ std::string serviceFlagToStr(const uint64_t mask, const int bit) stream.imbue(std::locale::classic()); stream << "UNKNOWN["; if (bit < 8) { - stream << mask; + stream << service_flag; } else { stream << "2^" << bit; } stream << "]"; return stream.str(); } + +std::vector<std::string> serviceFlagsToStr(uint64_t flags) +{ + std::vector<std::string> str_flags; + + for (size_t i = 0; i < sizeof(flags) * 8; ++i) { + if (flags & (1ULL << i)) { + str_flags.emplace_back(serviceFlagToStr(i)); + } + } + + return str_flags; +} diff --git a/src/protocol.h b/src/protocol.h index c12bec9bf3..985f44640b 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -226,6 +226,17 @@ extern const char* GETBLOCKTXN; */ extern const char* BLOCKTXN; /** + * getcfilters requests compact filters for a range of blocks. + * Only available with service bit NODE_COMPACT_FILTERS as described by + * BIP 157 & 158. + */ +extern const char* GETCFILTERS; +/** + * cfilter is a response to a getcfilters request containing a single compact + * filter. + */ +extern const char* CFILTER; +/** * getcfheaders requests a compact filter header and the filter hashes for a * range of blocks, which can then be used to reconstruct the filter headers * for those blocks. @@ -288,7 +299,12 @@ enum ServiceFlags : uint64_t { // BIP process. }; -std::string serviceFlagToStr(uint64_t mask, int bit); +/** + * Convert service flags (a bitmask of NODE_*) to human readable strings. + * It supports unknown service flags which will be returned as "UNKNOWN[...]". + * @param[in] flags multiple NODE_* bitwise-OR-ed together + */ +std::vector<std::string> serviceFlagsToStr(uint64_t flags); /** * Gets the set of service flags which are "desirable" for a given peer. diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index e78e400f07..ce44d4f3a5 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -755,12 +755,8 @@ QString formatServicesStr(quint64 mask) { QStringList strList; - for (int i = 0; i < 64; i++) { - uint64_t check = 1ull << i; - if (mask & check) - { - strList.append(QString::fromStdString(serviceFlagToStr(mask, i))); - } + for (const auto& flag : serviceFlagsToStr(mask)) { + strList.append(QString::fromStdString(flag)); } if (strList.size()) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 4eb47d7b15..b0936cef5a 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -414,7 +414,7 @@ static std::vector<RPCResult> MempoolEntryDescription() { return { RPCResult{RPCResult::Type::ARR, "spentby", "unconfirmed transactions spending outputs from this transaction", {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}}, RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction could be replaced due to BIP125 (replace-by-fee)"}, - RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet confirmed)"}, + RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"}, };} static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs) diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 0e13d8dfce..39bf05fbbd 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -841,14 +841,10 @@ std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, Fl UniValue GetServicesNames(ServiceFlags services) { - const uint64_t services_n = services; UniValue servicesNames(UniValue::VARR); - for (int i = 0; i < 64; ++i) { - const uint64_t mask = 1ull << i; - if (services_n & mask) { - servicesNames.push_back(serviceFlagToStr(mask, i)); - } + for (const auto& flag : serviceFlagsToStr(services)) { + servicesNames.push_back(flag); } return servicesNames; diff --git a/src/test/getarg_tests.cpp b/src/test/getarg_tests.cpp index 512e48f8e5..45c9b90ee9 100644 --- a/src/test/getarg_tests.cpp +++ b/src/test/getarg_tests.cpp @@ -13,9 +13,18 @@ #include <boost/algorithm/string.hpp> #include <boost/test/unit_test.hpp> -BOOST_FIXTURE_TEST_SUITE(getarg_tests, BasicTestingSetup) +namespace getarg_tests{ + class LocalTestingSetup : BasicTestingSetup { + protected: + void SetupArgs(const std::vector<std::pair<std::string, unsigned int>>& args); + void ResetArgs(const std::string& strArg); + ArgsManager m_args; + }; +} + +BOOST_FIXTURE_TEST_SUITE(getarg_tests, LocalTestingSetup) -static void ResetArgs(const std::string& strArg) +void LocalTestingSetup :: ResetArgs(const std::string& strArg) { std::vector<std::string> vecArg; if (strArg.size()) @@ -30,14 +39,14 @@ static void ResetArgs(const std::string& strArg) vecChar.push_back(s.c_str()); std::string error; - BOOST_CHECK(gArgs.ParseParameters(vecChar.size(), vecChar.data(), error)); + BOOST_CHECK(m_args.ParseParameters(vecChar.size(), vecChar.data(), error)); } -static void SetupArgs(const std::vector<std::pair<std::string, unsigned int>>& args) +void LocalTestingSetup :: SetupArgs(const std::vector<std::pair<std::string, unsigned int>>& args) { - gArgs.ClearArgs(); + m_args.ClearArgs(); for (const auto& arg : args) { - gArgs.AddArg(arg.first, "", arg.second, OptionsCategory::OPTIONS); + m_args.AddArg(arg.first, "", arg.second, OptionsCategory::OPTIONS); } } @@ -46,52 +55,52 @@ BOOST_AUTO_TEST_CASE(boolarg) const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_ANY); SetupArgs({foo}); ResetArgs("-foo"); - BOOST_CHECK(gArgs.GetBoolArg("-foo", false)); - BOOST_CHECK(gArgs.GetBoolArg("-foo", true)); + BOOST_CHECK(m_args.GetBoolArg("-foo", false)); + BOOST_CHECK(m_args.GetBoolArg("-foo", true)); - BOOST_CHECK(!gArgs.GetBoolArg("-fo", false)); - BOOST_CHECK(gArgs.GetBoolArg("-fo", true)); + BOOST_CHECK(!m_args.GetBoolArg("-fo", false)); + BOOST_CHECK(m_args.GetBoolArg("-fo", true)); - BOOST_CHECK(!gArgs.GetBoolArg("-fooo", false)); - BOOST_CHECK(gArgs.GetBoolArg("-fooo", true)); + BOOST_CHECK(!m_args.GetBoolArg("-fooo", false)); + BOOST_CHECK(m_args.GetBoolArg("-fooo", true)); ResetArgs("-foo=0"); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", false)); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", true)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", false)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", true)); ResetArgs("-foo=1"); - BOOST_CHECK(gArgs.GetBoolArg("-foo", false)); - BOOST_CHECK(gArgs.GetBoolArg("-foo", true)); + BOOST_CHECK(m_args.GetBoolArg("-foo", false)); + BOOST_CHECK(m_args.GetBoolArg("-foo", true)); // New 0.6 feature: auto-map -nosomething to !-something: ResetArgs("-nofoo"); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", false)); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", true)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", false)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", true)); ResetArgs("-nofoo=1"); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", false)); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", true)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", false)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", true)); ResetArgs("-foo -nofoo"); // -nofoo should win - BOOST_CHECK(!gArgs.GetBoolArg("-foo", false)); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", true)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", false)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", true)); ResetArgs("-foo=1 -nofoo=1"); // -nofoo should win - BOOST_CHECK(!gArgs.GetBoolArg("-foo", false)); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", true)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", false)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", true)); ResetArgs("-foo=0 -nofoo=0"); // -nofoo=0 should win - BOOST_CHECK(gArgs.GetBoolArg("-foo", false)); - BOOST_CHECK(gArgs.GetBoolArg("-foo", true)); + BOOST_CHECK(m_args.GetBoolArg("-foo", false)); + BOOST_CHECK(m_args.GetBoolArg("-foo", true)); // New 0.6 feature: treat -- same as -: ResetArgs("--foo=1"); - BOOST_CHECK(gArgs.GetBoolArg("-foo", false)); - BOOST_CHECK(gArgs.GetBoolArg("-foo", true)); + BOOST_CHECK(m_args.GetBoolArg("-foo", false)); + BOOST_CHECK(m_args.GetBoolArg("-foo", true)); ResetArgs("--nofoo=1"); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", false)); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", true)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", false)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", true)); } @@ -101,24 +110,24 @@ BOOST_AUTO_TEST_CASE(stringarg) const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_ANY); SetupArgs({foo, bar}); ResetArgs(""); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", ""), ""); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", "eleven"), "eleven"); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", ""), ""); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", "eleven"), "eleven"); ResetArgs("-foo -bar"); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", ""), ""); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", "eleven"), ""); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", ""), ""); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", "eleven"), ""); ResetArgs("-foo="); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", ""), ""); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", "eleven"), ""); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", ""), ""); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", "eleven"), ""); ResetArgs("-foo=11"); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", ""), "11"); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", "eleven"), "11"); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", ""), "11"); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", "eleven"), "11"); ResetArgs("-foo=eleven"); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", ""), "eleven"); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", "eleven"), "eleven"); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", ""), "eleven"); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", "eleven"), "eleven"); } @@ -128,20 +137,20 @@ BOOST_AUTO_TEST_CASE(intarg) const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_ANY); SetupArgs({foo, bar}); ResetArgs(""); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 11), 11); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 0), 0); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", 11), 11); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", 0), 0); ResetArgs("-foo -bar"); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 11), 0); - BOOST_CHECK_EQUAL(gArgs.GetArg("-bar", 11), 0); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", 11), 0); + BOOST_CHECK_EQUAL(m_args.GetArg("-bar", 11), 0); ResetArgs("-foo=11 -bar=12"); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 0), 11); - BOOST_CHECK_EQUAL(gArgs.GetArg("-bar", 11), 12); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", 0), 11); + BOOST_CHECK_EQUAL(m_args.GetArg("-bar", 11), 12); ResetArgs("-foo=NaN -bar=NotANumber"); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 1), 0); - BOOST_CHECK_EQUAL(gArgs.GetArg("-bar", 11), 0); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", 1), 0); + BOOST_CHECK_EQUAL(m_args.GetArg("-bar", 11), 0); } BOOST_AUTO_TEST_CASE(doubledash) @@ -150,11 +159,11 @@ BOOST_AUTO_TEST_CASE(doubledash) const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_ANY); SetupArgs({foo, bar}); ResetArgs("--foo"); - BOOST_CHECK_EQUAL(gArgs.GetBoolArg("-foo", false), true); + BOOST_CHECK_EQUAL(m_args.GetBoolArg("-foo", false), true); ResetArgs("--foo=verbose --bar=1"); - BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", ""), "verbose"); - BOOST_CHECK_EQUAL(gArgs.GetArg("-bar", 0), 1); + BOOST_CHECK_EQUAL(m_args.GetArg("-foo", ""), "verbose"); + BOOST_CHECK_EQUAL(m_args.GetArg("-bar", 0), 1); } BOOST_AUTO_TEST_CASE(boolargno) @@ -163,24 +172,24 @@ BOOST_AUTO_TEST_CASE(boolargno) const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_ANY); SetupArgs({foo, bar}); ResetArgs("-nofoo"); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", true)); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", false)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", true)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", false)); ResetArgs("-nofoo=1"); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", true)); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", false)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", true)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", false)); ResetArgs("-nofoo=0"); - BOOST_CHECK(gArgs.GetBoolArg("-foo", true)); - BOOST_CHECK(gArgs.GetBoolArg("-foo", false)); + BOOST_CHECK(m_args.GetBoolArg("-foo", true)); + BOOST_CHECK(m_args.GetBoolArg("-foo", false)); ResetArgs("-foo --nofoo"); // --nofoo should win - BOOST_CHECK(!gArgs.GetBoolArg("-foo", true)); - BOOST_CHECK(!gArgs.GetBoolArg("-foo", false)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", true)); + BOOST_CHECK(!m_args.GetBoolArg("-foo", false)); ResetArgs("-nofoo -foo"); // foo always wins: - BOOST_CHECK(gArgs.GetBoolArg("-foo", true)); - BOOST_CHECK(gArgs.GetBoolArg("-foo", false)); + BOOST_CHECK(m_args.GetBoolArg("-foo", true)); + BOOST_CHECK(m_args.GetBoolArg("-foo", false)); } BOOST_AUTO_TEST_CASE(logargs) @@ -200,7 +209,7 @@ BOOST_AUTO_TEST_CASE(logargs) }); // Log the arguments - gArgs.LogArgs(); + m_args.LogArgs(); LogInstance().DeleteCallback(print_connection); // Check that what should appear does, and what shouldn't doesn't. diff --git a/src/test/raii_event_tests.cpp b/src/test/raii_event_tests.cpp index 04bf7c20c1..8c2712f764 100644 --- a/src/test/raii_event_tests.cpp +++ b/src/test/raii_event_tests.cpp @@ -4,9 +4,6 @@ #include <event2/event.h> -#ifdef EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED -// It would probably be ideal to define dummy test(s) that report skipped, but boost::test doesn't seem to make that practical (at least not in versions available with common distros) - #include <map> #include <stdlib.h> @@ -16,6 +13,10 @@ #include <boost/test/unit_test.hpp> +BOOST_FIXTURE_TEST_SUITE(raii_event_tests, BasicTestingSetup) + +#ifdef EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED + static std::map<void*, short> tags; static std::map<void*, uint16_t> orders; static uint16_t tagSequence = 0; @@ -34,8 +35,6 @@ static void tag_free(void* mem) { free(mem); } -BOOST_FIXTURE_TEST_SUITE(raii_event_tests, BasicTestingSetup) - BOOST_AUTO_TEST_CASE(raii_event_creation) { event_set_mem_functions(tag_malloc, realloc, tag_free); @@ -87,6 +86,14 @@ BOOST_AUTO_TEST_CASE(raii_event_order) event_set_mem_functions(malloc, realloc, free); } -BOOST_AUTO_TEST_SUITE_END() +#else + +BOOST_AUTO_TEST_CASE(raii_event_tests_SKIPPED) +{ + // It would probably be ideal to report skipped, but boost::test doesn't seem to make that practical (at least not in versions available with common distros) + BOOST_TEST_MESSAGE("Skipping raii_event_tess: libevent doesn't support event_set_mem_functions"); +} #endif // EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/txmempool.h b/src/txmempool.h index 4568eb928d..583f7614b7 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -704,7 +704,7 @@ public: /** Adds a transaction to the unbroadcast set */ void AddUnbroadcastTx(const uint256& txid) { LOCK(cs); - /** Sanity Check: the transaction should also be in the mempool */ + // Sanity Check: the transaction should also be in the mempool if (exists(txid)) { m_unbroadcast_txids.insert(txid); } @@ -714,12 +714,12 @@ public: void RemoveUnbroadcastTx(const uint256& txid, const bool unchecked = false); /** Returns transactions in unbroadcast set */ - const std::set<uint256> GetUnbroadcastTxs() const { + std::set<uint256> GetUnbroadcastTxs() const { LOCK(cs); return m_unbroadcast_txids; } - // Returns if a txid is in the unbroadcast set + /** Returns whether a txid is in the unbroadcast set */ bool IsUnbroadcastTx(const uint256& txid) const { LOCK(cs); return (m_unbroadcast_txids.count(txid) != 0); diff --git a/src/validation.cpp b/src/validation.cpp index dbdf5028fd..396fc0a1b5 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -5067,12 +5067,18 @@ bool LoadMempool(CTxMemPool& pool) pool.PrioritiseTransaction(i.first, i.second); } - std::set<uint256> unbroadcast_txids; - file >> unbroadcast_txids; - unbroadcast = unbroadcast_txids.size(); + // TODO: remove this try except in v0.22 + try { + std::set<uint256> unbroadcast_txids; + file >> unbroadcast_txids; + unbroadcast = unbroadcast_txids.size(); - for (const auto& txid : unbroadcast_txids) { + for (const auto& txid : unbroadcast_txids) { pool.AddUnbroadcastTx(txid); + } + } catch (const std::exception&) { + // mempool.dat files created prior to v0.21 will not have an + // unbroadcast set. No need to log a failure if parsing fails here. } } catch (const std::exception& e) { diff --git a/test/config.ini.in b/test/config.ini.in index 9687206ee1..be1bfe8752 100644 --- a/test/config.ini.in +++ b/test/config.ini.in @@ -7,6 +7,7 @@ [environment] PACKAGE_NAME=@PACKAGE_NAME@ +PACKAGE_BUGREPORT=@PACKAGE_BUGREPORT@ SRCDIR=@abs_top_srcdir@ BUILDDIR=@abs_top_builddir@ EXEEXT=@EXEEXT@ diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py index 3969da2eb0..5d00648aed 100755 --- a/test/functional/mempool_persist.py +++ b/test/functional/mempool_persist.py @@ -84,7 +84,9 @@ class MempoolPersistTest(BitcoinTestFramework): assert_greater_than_or_equal(tx_creation_time_higher, tx_creation_time) # disconnect nodes & make a txn that remains in the unbroadcast set. - disconnect_nodes(self.nodes[0], 2) + disconnect_nodes(self.nodes[0], 1) + assert(len(self.nodes[0].getpeerinfo()) == 0) + assert(len(self.nodes[0].p2ps) == 0) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), Decimal("12")) connect_nodes(self.nodes[0], 2) @@ -157,8 +159,10 @@ class MempoolPersistTest(BitcoinTestFramework): # clear out mempool node0.generate(1) - # disconnect nodes to make a txn that remains in the unbroadcast set. - disconnect_nodes(node0, 1) + # ensure node0 doesn't have any connections + # make a transaction that will remain in the unbroadcast set + assert(len(node0.getpeerinfo()) == 0) + assert(len(node0.p2ps) == 0) node0.sendtoaddress(self.nodes[1].getnewaddress(), Decimal("12")) # shutdown, then startup with wallet disabled diff --git a/test/functional/mempool_unbroadcast.py b/test/functional/mempool_unbroadcast.py index dedf5b8a47..365d011157 100755 --- a/test/functional/mempool_unbroadcast.py +++ b/test/functional/mempool_unbroadcast.py @@ -16,6 +16,7 @@ from test_framework.util import ( disconnect_nodes, ) +MAX_INITIAL_BROADCAST_DELAY = 15 * 60 # 15 minutes in seconds class MempoolUnbroadcastTest(BitcoinTestFramework): def set_test_params(self): @@ -72,7 +73,7 @@ class MempoolUnbroadcastTest(BitcoinTestFramework): connect_nodes(node, 1) # fast forward into the future & ensure that the second node has the txns - node.mockscheduler(15 * 60) # 15 min in seconds + node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY) self.sync_mempools(timeout=30) mempool = self.nodes[1].getrawmempool() assert rpc_tx_hsh in mempool @@ -86,15 +87,16 @@ class MempoolUnbroadcastTest(BitcoinTestFramework): self.log.info("Add another connection & ensure transactions aren't broadcast again") conn = node.add_p2p_connection(P2PTxInvStore()) - node.mockscheduler(15 * 60) - time.sleep(5) + node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY) + time.sleep(2) # allow sufficient time for possibility of broadcast assert_equal(len(conn.get_invs()), 0) + disconnect_nodes(node, 1) + node.disconnect_p2ps() + def test_txn_removal(self): self.log.info("Test that transactions removed from mempool are removed from unbroadcast set") node = self.nodes[0] - disconnect_nodes(node, 1) - node.disconnect_p2ps # since the node doesn't have any connections, it will not receive # any GETDATAs & thus the transaction will remain in the unbroadcast set. diff --git a/test/functional/p2p_blockfilters.py b/test/functional/p2p_blockfilters.py index 9ff76b4b3d..6d947ac660 100755 --- a/test/functional/p2p_blockfilters.py +++ b/test/functional/p2p_blockfilters.py @@ -13,6 +13,7 @@ from test_framework.messages import ( hash256, msg_getcfcheckpt, msg_getcfheaders, + msg_getcfilters, ser_uint256, uint256_from_str, ) @@ -25,6 +26,21 @@ from test_framework.util import ( wait_until, ) +class CFiltersClient(P2PInterface): + def __init__(self): + super().__init__() + # Store the cfilters received. + self.cfilters = [] + + def pop_cfilters(self): + cfilters = self.cfilters + self.cfilters = [] + return cfilters + + def on_cfilter(self, message): + """Store cfilters received in a list.""" + self.cfilters.append(message) + class CompactFiltersTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -37,8 +53,8 @@ class CompactFiltersTest(BitcoinTestFramework): def run_test(self): # Node 0 supports COMPACT_FILTERS, node 1 does not. - node0 = self.nodes[0].add_p2p_connection(P2PInterface()) - node1 = self.nodes[1].add_p2p_connection(P2PInterface()) + node0 = self.nodes[0].add_p2p_connection(CFiltersClient()) + node1 = self.nodes[1].add_p2p_connection(CFiltersClient()) # Nodes 0 & 1 share the same first 999 blocks in the chain. self.nodes[0].generate(999) @@ -112,7 +128,8 @@ class CompactFiltersTest(BitcoinTestFramework): ) node0.send_and_ping(request) response = node0.last_message['cfheaders'] - assert_equal(len(response.hashes), 1000) + main_cfhashes = response.hashes + assert_equal(len(main_cfhashes), 1000) assert_equal( compute_last_header(response.prev_header, response.hashes), int(main_cfcheckpt, 16) @@ -126,12 +143,50 @@ class CompactFiltersTest(BitcoinTestFramework): ) node0.send_and_ping(request) response = node0.last_message['cfheaders'] - assert_equal(len(response.hashes), 1000) + stale_cfhashes = response.hashes + assert_equal(len(stale_cfhashes), 1000) assert_equal( compute_last_header(response.prev_header, response.hashes), int(stale_cfcheckpt, 16) ) + self.log.info("Check that peers can fetch cfilters.") + stop_hash = self.nodes[0].getblockhash(10) + request = msg_getcfilters( + filter_type=FILTER_TYPE_BASIC, + start_height=1, + stop_hash=int(stop_hash, 16) + ) + node0.send_message(request) + node0.sync_with_ping() + response = node0.pop_cfilters() + assert_equal(len(response), 10) + + self.log.info("Check that cfilter responses are correct.") + for cfilter, cfhash, height in zip(response, main_cfhashes, range(1, 11)): + block_hash = self.nodes[0].getblockhash(height) + assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC) + assert_equal(cfilter.block_hash, int(block_hash, 16)) + computed_cfhash = uint256_from_str(hash256(cfilter.filter_data)) + assert_equal(computed_cfhash, cfhash) + + self.log.info("Check that peers can fetch cfilters for stale blocks.") + request = msg_getcfilters( + filter_type=FILTER_TYPE_BASIC, + start_height=1000, + stop_hash=int(stale_block_hash, 16) + ) + node0.send_message(request) + node0.sync_with_ping() + response = node0.pop_cfilters() + assert_equal(len(response), 1) + + cfilter = response[0] + assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC) + assert_equal(cfilter.block_hash, int(stale_block_hash, 16)) + computed_cfhash = uint256_from_str(hash256(cfilter.filter_data)) + assert_equal(computed_cfhash, stale_cfhashes[999]) + self.log.info("Requests to node 1 without NODE_COMPACT_FILTERS results in disconnection.") requests = [ msg_getcfcheckpt( @@ -143,6 +198,11 @@ class CompactFiltersTest(BitcoinTestFramework): start_height=1000, stop_hash=int(main_block_hash, 16) ), + msg_getcfilters( + filter_type=FILTER_TYPE_BASIC, + start_height=1000, + stop_hash=int(main_block_hash, 16) + ), ] for request in requests: node1 = self.nodes[1].add_p2p_connection(P2PInterface()) @@ -151,6 +211,12 @@ class CompactFiltersTest(BitcoinTestFramework): self.log.info("Check that invalid requests result in disconnection.") requests = [ + # Requesting too many filters results in disconnection. + msg_getcfilters( + filter_type=FILTER_TYPE_BASIC, + start_height=0, + stop_hash=int(main_block_hash, 16) + ), # Requesting too many filter headers results in disconnection. msg_getcfheaders( filter_type=FILTER_TYPE_BASIC, diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py index 8f410f233e..9506b63f82 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -5,12 +5,15 @@ """Encode and decode BASE58, P2PKH and P2SH addresses.""" import enum +import unittest from .script import hash256, hash160, sha256, CScript, OP_0 from .util import hex_str_to_bytes from . import segwit_addr +from test_framework.util import assert_equal + ADDRESS_BCRT1_UNSPENDABLE = 'bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj' ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR = 'addr(bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj)#juyq9d97' # Coins sent to this address can be spent with a witness stack of just OP_TRUE @@ -41,7 +44,32 @@ def byte_to_base58(b, version): str = str[2:] return result -# TODO: def base58_decode + +def base58_to_byte(s, verify_checksum=True): + if not s: + return b'' + n = 0 + for c in s: + n *= 58 + assert c in chars + digit = chars.index(c) + n += digit + h = '%x' % n + if len(h) % 2: + h = '0' + h + res = n.to_bytes((n.bit_length() + 7) // 8, 'big') + pad = 0 + for c in s: + if c == chars[0]: + pad += 1 + else: + break + res = b'\x00' * pad + res + if verify_checksum: + assert_equal(hash256(res[:-4])[:4], res[-4:]) + + return res[1:-4], int(res[0]) + def keyhash_to_p2pkh(hash, main = False): assert len(hash) == 20 @@ -100,3 +128,22 @@ def check_script(script): if (type(script) is bytes or type(script) is CScript): return script assert False + + +class TestFrameworkScript(unittest.TestCase): + def test_base58encodedecode(self): + def check_base58(data, version): + self.assertEqual(base58_to_byte(byte_to_base58(data, version)), (data, version)) + + check_base58(b'\x1f\x8e\xa1p*{\xd4\x94\x1b\xca\tA\xb8R\xc4\xbb\xfe\xdb.\x05', 111) + check_base58(b':\x0b\x05\xf4\xd7\xf6l;\xa7\x00\x9fE50)l\x84\\\xc9\xcf', 111) + check_base58(b'A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111) + check_base58(b'\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111) + check_base58(b'\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111) + check_base58(b'\0\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111) + check_base58(b'\x1f\x8e\xa1p*{\xd4\x94\x1b\xca\tA\xb8R\xc4\xbb\xfe\xdb.\x05', 0) + check_base58(b':\x0b\x05\xf4\xd7\xf6l;\xa7\x00\x9fE50)l\x84\\\xc9\xcf', 0) + check_base58(b'A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0) + check_base58(b'\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0) + check_base58(b'\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0) + check_base58(b'\0\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0) diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index d178e79541..4d1dd4422e 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -1516,6 +1516,57 @@ class msg_no_witness_blocktxn(msg_blocktxn): def serialize(self): return self.block_transactions.serialize(with_witness=False) + +class msg_getcfilters: + __slots__ = ("filter_type", "start_height", "stop_hash") + msgtype = b"getcfilters" + + def __init__(self, filter_type, start_height, stop_hash): + self.filter_type = filter_type + self.start_height = start_height + self.stop_hash = stop_hash + + def deserialize(self, f): + self.filter_type = struct.unpack("<B", f.read(1))[0] + self.start_height = struct.unpack("<I", f.read(4))[0] + self.stop_hash = deser_uint256(f) + + def serialize(self): + r = b"" + r += struct.pack("<B", self.filter_type) + r += struct.pack("<I", self.start_height) + r += ser_uint256(self.stop_hash) + return r + + def __repr__(self): + return "msg_getcfilters(filter_type={:#x}, start_height={}, stop_hash={:x})".format( + self.filter_type, self.start_height, self.stop_hash) + +class msg_cfilter: + __slots__ = ("filter_type", "block_hash", "filter_data") + msgtype = b"cfilter" + + def __init__(self, filter_type=None, block_hash=None, filter_data=None): + self.filter_type = filter_type + self.block_hash = block_hash + self.filter_data = filter_data + + def deserialize(self, f): + self.filter_type = struct.unpack("<B", f.read(1))[0] + self.block_hash = deser_uint256(f) + self.filter_data = deser_string(f) + + def serialize(self): + r = b"" + r += struct.pack("<B", self.filter_type) + r += ser_uint256(self.block_hash) + r += ser_string(self.filter_data) + return r + + def __repr__(self): + return "msg_cfilter(filter_type={:#x}, block_hash={:x})".format( + self.filter_type, self.block_hash) + class msg_getcfheaders: __slots__ = ("filter_type", "start_height", "stop_hash") msgtype = b"getcfheaders" diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index d1e982ac3e..45063aaff2 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -31,8 +31,9 @@ from test_framework.messages import ( msg_block, MSG_BLOCK, msg_blocktxn, - msg_cfheaders, msg_cfcheckpt, + msg_cfheaders, + msg_cfilter, msg_cmpctblock, msg_feefilter, msg_filteradd, @@ -69,8 +70,9 @@ MESSAGEMAP = { b"addr": msg_addr, b"block": msg_block, b"blocktxn": msg_blocktxn, - b"cfheaders": msg_cfheaders, b"cfcheckpt": msg_cfcheckpt, + b"cfheaders": msg_cfheaders, + b"cfilter": msg_cfilter, b"cmpctblock": msg_cmpctblock, b"feefilter": msg_feefilter, b"filteradd": msg_filteradd, @@ -332,8 +334,9 @@ class P2PInterface(P2PConnection): def on_addr(self, message): pass def on_block(self, message): pass def on_blocktxn(self, message): pass - def on_cfheaders(self, message): pass def on_cfcheckpt(self, message): pass + def on_cfheaders(self, message): pass + def on_cfilter(self, message): pass def on_cmpctblock(self, message): pass def on_feefilter(self, message): pass def on_filteradd(self, message): pass @@ -655,6 +658,8 @@ class P2PTxInvStore(P2PInterface): # save txid self.tx_invs_received[i.hash] += 1 + super().on_inv(message) + def get_invs(self): with mininode_lock: return list(self.tx_invs_received.keys()) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 716fa1d845..5469f808d1 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -290,7 +290,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): exit_code = TEST_EXIT_SKIPPED else: self.log.error("Test failed. Test logging available at %s/test_framework.log", self.options.tmpdir) + self.log.error("") self.log.error("Hint: Call {} '{}' to consolidate all logs".format(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../combine_logs.py"), self.options.tmpdir)) + self.log.error("") + self.log.error("If this failure happened unexpectedly or intermittently, please file a bug and provide a link or upload of the combined log.") + self.log.error(self.config['environment']['PACKAGE_BUGREPORT']) + self.log.error("") exit_code = TEST_EXIT_FAILED # Logging.shutdown will not remove stream- and filehandlers, so we must # do it explicitly. Handlers are removed so the next test run can apply diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 7821355e29..0812470b0c 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -67,6 +67,7 @@ TEST_EXIT_PASSED = 0 TEST_EXIT_SKIPPED = 77 TEST_FRAMEWORK_MODULES = [ + "address", "script", ] diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py index b384998d56..3417616d77 100755 --- a/test/functional/wallet_resendwallettransactions.py +++ b/test/functional/wallet_resendwallettransactions.py @@ -49,16 +49,21 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): block.solve() node.submitblock(ToHex(block)) - # Transaction should not be rebroadcast node.syncwithvalidationinterfacequeue() - node.p2ps[1].sync_with_ping() - assert_equal(node.p2ps[1].tx_invs_received[txid], 0) + now = int(time.time()) + + # Transaction should not be rebroadcast within first 12 hours + # Leave 2 mins for buffer + twelve_hrs = 12 * 60 * 60 + two_min = 2 * 60 + node.setmocktime(now + twelve_hrs - two_min) + time.sleep(2) # ensure enough time has passed for rebroadcast attempt to occur + assert_equal(txid in node.p2ps[1].get_invs(), False) self.log.info("Bump time & check that transaction is rebroadcast") # Transaction should be rebroadcast approximately 24 hours in the future, # but can range from 12-36. So bump 36 hours to be sure. - rebroadcast_time = int(time.time()) + 36 * 60 * 60 - node.setmocktime(rebroadcast_time) + node.setmocktime(now + 36 * 60 * 60) wait_until(lambda: node.p2ps[1].tx_invs_received[txid] >= 1, lock=mininode_lock) |