diff options
44 files changed, 789 insertions, 182 deletions
diff --git a/.travis.yml b/.travis.yml index 422e9149f4..d49c116a5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,8 +64,10 @@ before_script: script: - export CONTINUE=1 - if [ $SECONDS -gt 1200 ]; then export CONTINUE=0; fi # Likely the depends build took very long + - if [ $TRAVIS_REPO_SLUG = "bitcoin/bitcoin" ]; then export CONTINUE=1; fi # Whitelisted repo (90 minutes build time) - if [ $CONTINUE = "1" ]; then set -o errexit; source .travis/test_06_script_a.sh; else set +o errexit; echo "$CACHE_ERR_MSG"; false; fi - if [ $SECONDS -gt 2000 ]; then export CONTINUE=0; fi # Likely the build took very long; The tests take about 1000s, so we should abort if we have less than 50*60-1000=2000s left + - if [ $TRAVIS_REPO_SLUG = "bitcoin/bitcoin" ]; then export CONTINUE=1; fi # Whitelisted repo (90 minutes build time) - if [ $CONTINUE = "1" ]; then set -o errexit; source .travis/test_06_script_b.sh; else set +o errexit; echo "$CACHE_ERR_MSG"; false; fi after_script: - echo $TRAVIS_COMMIT_RANGE diff --git a/configure.ac b/configure.ac index 118d52d423..4fd285bc8d 100644 --- a/configure.ac +++ b/configure.ac @@ -239,7 +239,7 @@ AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no) # Enable debug AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], - [use debug compiler flags and macros (default is no)])], + [use compiler flags and macros suited for debugging (default is no)])], [enable_debug=$enableval], [enable_debug=no]) @@ -271,12 +271,9 @@ if test "x$enable_debug" = xyes; then if test "x$CXXFLAGS_overridden" = xno; then CXXFLAGS="" fi - # Prefer -Og, fall back to -O0 if that is unavailable. - AX_CHECK_COMPILE_FLAG( - [-Og], - [[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -Og"]], - [AX_CHECK_COMPILE_FLAG([-O0],[[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -O0"]],,[[$CXXFLAG_WERROR]])], - [[$CXXFLAG_WERROR]]) + + # Disable all optimizations + AX_CHECK_COMPILE_FLAG([-O0], [[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -O0"]],,[[$CXXFLAG_WERROR]]) # Prefer -g3, fall back to -g if that is unavailable. AX_CHECK_COMPILE_FLAG( @@ -1625,7 +1622,7 @@ if test x$need_bundled_univalue = xyes; then AC_CONFIG_SUBDIRS([src/univalue]) fi -ac_configure_args="${ac_configure_args} --disable-shared --with-pic --with-bignum=no --enable-module-recovery --disable-jni" +ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --with-bignum=no --enable-module-recovery --disable-jni" AC_CONFIG_SUBDIRS([src/secp256k1]) AC_OUTPUT diff --git a/contrib/debian/copyright b/contrib/debian/copyright index 2d5b0188d2..8fb9df9e6c 100644 --- a/contrib/debian/copyright +++ b/contrib/debian/copyright @@ -60,7 +60,7 @@ Files: src/qt/res/icons/connect*.png Copyright: Marco Falke Luke Dashjr License: Expat -Comment: Inspired by Stephan Hutchings Typicons +Comment: Inspired by Stephen Hutchings' Typicons Files: src/qt/res/icons/tx_mined.png src/qt/res/src/mine.svg @@ -72,7 +72,6 @@ Files: src/qt/res/icons/tx_mined.png src/qt/res/src/hd_enabled.svg Copyright: Jonas Schnelli License: Expat -Comment: Files: src/qt/res/icons/clock*.png src/qt/res/icons/eye_*.png @@ -81,9 +80,9 @@ Files: src/qt/res/icons/clock*.png src/qt/res/src/clock_*.svg src/qt/res/src/tx_*.svg src/qt/res/src/verify.svg -Copyright: Stephan Hutching, Jonas Schnelli +Copyright: Stephen Hutchings, Jonas Schnelli License: Expat -Comment: Modifications of Stephan Hutchings Typicons +Comment: Modifications of Stephen Hutchings' Typicons Files: src/qt/res/icons/about.png src/qt/res/icons/bitcoin.* diff --git a/contrib/gitian-keys/keys.txt b/contrib/gitian-keys/keys.txt index 33f0f7e5b0..9222a40b17 100644 --- a/contrib/gitian-keys/keys.txt +++ b/contrib/gitian-keys/keys.txt @@ -1,3 +1,4 @@ +9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C Aaron Clauson (sipsorcery) 617C90010B3BD370B0AC7D424BB42E31C79111B8 Akira Takizawa E944AE667CF960B1004BC32FCA662BE18B877A60 Andreas Schildbach 152812300785C96444D3334D17565732E08E5E41 Andrew Chow diff --git a/depends/packages/libxcb.mk b/depends/packages/libxcb.mk index c497c5ac20..90bc15f9f4 100644 --- a/depends/packages/libxcb.mk +++ b/depends/packages/libxcb.mk @@ -7,6 +7,18 @@ $(package)_dependencies=xcb_proto libXau define $(package)_set_vars $(package)_config_opts=--disable-static +# Because we pass -qt-xcb to Qt, it will compile in a set of xcb helper libraries and extensions, +# so we skip building all of the extensions here. +# More info is available from: https://doc.qt.io/qt-5.9/linux-requirements.html +$(package)_config_opts += --disable-composite --disable-damage --disable-dpms +$(package)_config_opts += --disable-dri2 --disable-dri3 --disable-glx +$(package)_config_opts += --disable-present --disable-randr --disable-record +$(package)_config_opts += --disable-render --disable-resource --disable-screensaver +$(package)_config_opts += --disable-shape --disable-shm --disable-sync +$(package)_config_opts += --disable-xevie --disable-xfixes --disable-xfree86-dri +$(package)_config_opts += --disable-xinerama --disable-xinput --disable-xkb +$(package)_config_opts += --disable-xprint --disable-selinux --disable-xtest +$(package)_config_opts += --disable-xv --disable-xvmc endef define $(package)_preprocess_cmds diff --git a/doc/build-unix.md b/doc/build-unix.md index da65bc347a..9aca0336c0 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -61,6 +61,14 @@ tuned to conserve memory with additional CXXFLAGS: ./configure CXXFLAGS="--param ggc-min-expand=1 --param ggc-min-heapsize=32768" +Alternatively, or in addition, debugging information can be skipped for compilation. The default compile flags are +`-g -O2`, and can be changed with: + + ./configure CXXFLAGS="-O2" + +Finally, clang (often less resource hungry) can be used instead of gcc, which is used by default: + + ./configure CXX=clang++ CC=clang ## Linux Distribution Specific Instructions @@ -78,7 +86,7 @@ Now, you can either build from self-compiled [depends](/depends/README.md) or in BerkeleyDB is required for the wallet. -Ubuntu and Debian have their own libdb-dev and libdb++-dev packages, but these will install +Ubuntu and Debian have their own `libdb-dev` and `libdb++-dev` packages, but these will install BerkeleyDB 5.1 or later. This will break binary wallet compatibility with the distributed executables, which are based on BerkeleyDB 4.8. If you do not care about wallet compatibility, pass `--with-incompatible-bdb` to configure. diff --git a/doc/release-notes/release-notes-0.18.1.md b/doc/release-notes/release-notes-0.18.1.md new file mode 100644 index 0000000000..483cc5075e --- /dev/null +++ b/doc/release-notes/release-notes-0.18.1.md @@ -0,0 +1,136 @@ +Bitcoin Core version 0.18.1 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-0.18.1/> + +This is a new minor version release, including new features, various bug +fixes and performance improvements, as well as updated translations. + +Please report bugs using the issue tracker at GitHub: + + <https://github.com/bitcoin/bitcoin/issues> + +To receive security and update notifications, please subscribe to: + + <https://bitcoincore.org/en/list/announcements/join/> + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has +completely shut down (which might take a few minutes for older +versions), then run the installer (on Windows) or just copy over +`/Applications/Bitcoin-Qt` (on Mac) or `bitcoind`/`bitcoin-qt` (on +Linux). + +The first time you run version 0.15.0 or newer, your chainstate database +will be converted to a new format, which will take anywhere from a few +minutes to half an hour, depending on the speed of your machine. + +Note that the block database format also changed in version 0.8.0 and +there is no automatic upgrade code from before version 0.8 to version +0.15.0 or later. Upgrading directly from 0.7.x and earlier without +redownloading the blockchain is not supported. However, as usual, old +wallet versions are still supported. + +Compatibility +============== + +Bitcoin Core is supported and extensively tested on operating systems +using the Linux kernel, macOS 10.10+, and Windows 7 and newer. It is not +recommended to use Bitcoin Core on unsupported systems. + +Bitcoin Core should also work on most other Unix-like systems but is not +as frequently tested on them. + +From 0.17.0 onwards, macOS <10.10 is no longer supported. 0.17.0 is +built using Qt 5.9.x, which doesn't support versions of macOS older than +10.10. Additionally, Bitcoin Core does not yet change appearance when +macOS "dark mode" is activated. + +Known issues +============ + +Wallet GUI +---------- + +For advanced users who have both (1) enabled coin control features, and +(2) are using multiple wallets loaded at the same time: The coin control +input selection dialog can erroneously retain wrong-wallet state when +switching wallets using the dropdown menu. For now, it is recommended +not to use coin control features with multiple wallets loaded. + +0.18.1 change log +================= + +### P2P protocol and network code +- #15990 Add tests and documentation for blocksonly (MarcoFalke) +- #16021 Avoid logging transaction decode errors to stderr (MarcoFalke) +- #16405 fix: tor: Call `event_base_loopbreak` from the event's callback (promag) +- #16412 Make poll in InterruptibleRecv only filter for POLLIN events (tecnovert) + +### Wallet +- #15913 Add -ignorepartialspends to list of ignored wallet options (luke-jr) + +### RPC and other APIs +- #15991 Bugfix: fix pruneblockchain returned prune height (jonasschnelli) +- #15899 Document iswitness flag and fix bug in converttopsbt (MarcoFalke) +- #16026 Ensure that uncompressed public keys in a multisig always returns a legacy address (achow101) +- #14039 Disallow extended encoding for non-witness transactions (sipa) +- #16210 add 2nd arg to signrawtransactionwithkey examples (dooglus) +- #16250 signrawtransactionwithkey: report error when missing redeemScript/witnessScript (ajtowns) + +### GUI +- #16044 fix the bug of OPEN CONFIGURATION FILE on Mac (shannon1916) +- #15957 Show "No wallets available" in open menu instead of nothing (meshcollider) +- #16118 Enable open wallet menu on setWalletController (promag) +- #16135 Set progressDialog to nullptr (promag) +- #16231 Fix open wallet menu initialization order (promag) +- #16254 Set `AA_EnableHighDpiScaling` attribute early (hebasto) +- #16122 Enable console line edit on setClientModel (promag) +- #16348 Assert QMetaObject::invokeMethod result (promag) + +### Build system +- #15985 Add test for GCC bug 90348 (sipa) +- #15947 Install bitcoin-wallet manpage (domob1812) +- #15983 build with -fstack-reuse=none (MarcoFalke) + +### Tests and QA +- #15826 Pure python EC (sipa) +- #15893 Add test for superfluous witness record in deserialization (instagibbs) +- #14818 Bugfix: test/functional/rpc_psbt: Remove check for specific error message that depends on uncertain assumptions (luke-jr) +- #15831 Add test that addmultisigaddress fails for watchonly addresses (MarcoFalke) + +### Documentation +- #15890 Remove text about txes always relayed from -whitelist (harding) + +### Miscellaneous +- #16095 Catch by reference not value in wallettool (kristapsk) +- #16205 Replace fprintf with tfm::format (MarcoFalke) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- Andrew Chow +- Anthony Towns +- Chris Moore +- Daniel Kraft +- David A. Harding +- fanquake +- Gregory Sanders +- Hennadii Stepanov +- John Newbery +- Jonas Schnelli +- João Barbosa +- Kristaps Kaupe +- Luke Dashjr +- MarcoFalke +- Michele Federici +- Pieter Wuille +- Samuel Dobson +- shannon1916 +- tecnovert +- Wladimir J. van der Laan + +As well as everyone that helped translating on [Transifex](https://www.transifex.com/projects/p/bitcoin/). diff --git a/src/Makefile.am b/src/Makefile.am index ef5b1900d9..141d8e68ea 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -150,6 +150,7 @@ BITCOIN_CORE_H = \ merkleblock.h \ miner.h \ net.h \ + net_permissions.h \ net_processing.h \ netaddress.h \ netbase.h \ @@ -454,6 +455,7 @@ libbitcoin_common_a_SOURCES = \ merkleblock.cpp \ netaddress.cpp \ netbase.cpp \ + net_permissions.cpp \ outputtype.cpp \ policy/feerate.cpp \ policy/policy.cpp \ diff --git a/src/init.cpp b/src/init.cpp index bb3ff8d88f..25c964205a 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -27,6 +27,7 @@ #include <key.h> #include <miner.h> #include <net.h> +#include <net_permissions.h> #include <net_processing.h> #include <netbase.h> #include <policy/feerate.h> @@ -1775,21 +1776,16 @@ bool AppInitMain(InitInterfaces& interfaces) connOptions.vBinds.push_back(addrBind); } for (const std::string& strBind : gArgs.GetArgs("-whitebind")) { - CService addrBind; - if (!Lookup(strBind.c_str(), addrBind, 0, false)) { - return InitError(ResolveErrMsg("whitebind", strBind)); - } - if (addrBind.GetPort() == 0) { - return InitError(strprintf(_("Need to specify a port with -whitebind: '%s'").translated, strBind)); - } - connOptions.vWhiteBinds.push_back(addrBind); + NetWhitebindPermissions whitebind; + std::string error; + if (!NetWhitebindPermissions::TryParse(strBind, whitebind, error)) return InitError(error); + connOptions.vWhiteBinds.push_back(whitebind); } for (const auto& net : gArgs.GetArgs("-whitelist")) { - CSubNet subnet; - LookupSubNet(net.c_str(), subnet); - if (!subnet.IsValid()) - return InitError(strprintf(_("Invalid netmask specified in -whitelist: '%s'").translated, net)); + NetWhitelistPermissions subnet; + std::string error; + if (!NetWhitelistPermissions::TryParse(net, subnet, error)) return InitError(error); connOptions.vWhitelistedRange.push_back(subnet); } diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index 1ad4308f29..b8b9ecded9 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -332,7 +332,6 @@ public: LOCK(cs_main); return ::fHavePruned; } - bool p2pEnabled() override { return g_connman != nullptr; } bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !isInitialBlockDownload(); } bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); } bool shutdownRequested() override { return ShutdownRequested(); } diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 1d6ed05522..da670a3370 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -187,9 +187,6 @@ public: //! Check if any block has been pruned. virtual bool havePruned() = 0; - //! Check if p2p enabled. - virtual bool p2pEnabled() = 0; - //! Check if the node is ready to broadcast transactions. virtual bool isReadyToBroadcast() = 0; diff --git a/src/net.cpp b/src/net.cpp index 7d6eb31a7c..0464a6e9ea 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -16,6 +16,7 @@ #include <crypto/common.h> #include <crypto/sha256.h> #include <netbase.h> +#include <net_permissions.h> #include <primitives/transaction.h> #include <scheduler.h> #include <ui_interface.h> @@ -67,7 +68,6 @@ enum BindFlags { BF_NONE = 0, BF_EXPLICIT = (1U << 0), BF_REPORT_ERROR = (1U << 1), - BF_WHITELIST = (1U << 2), }; // The set of sockets cannot be modified while waiting @@ -459,12 +459,10 @@ void CNode::CloseSocketDisconnect() } } -bool CConnman::IsWhitelistedRange(const CNetAddr &addr) { - for (const CSubNet& subnet : vWhitelistedRange) { - if (subnet.Match(addr)) - return true; +void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const { + for (const auto& subnet : vWhitelistedRange) { + if (subnet.m_subnet.Match(addr)) NetPermissions::AddFlag(flags, subnet.m_flags); } - return false; } std::string CNode::GetAddrName() const { @@ -528,7 +526,8 @@ void CNode::copyStats(CNodeStats &stats) X(mapRecvBytesPerMsgCmd); X(nRecvBytes); } - X(fWhitelisted); + X(m_legacyWhitelisted); + X(m_permissionFlags); { LOCK(cs_feeFilter); X(minFeeFilter); @@ -813,7 +812,7 @@ bool CConnman::AttemptToEvictConnection() LOCK(cs_vNodes); for (const CNode* node : vNodes) { - if (node->fWhitelisted) + if (node->HasPermission(PF_NOBAN)) continue; if (!node->fInbound) continue; @@ -904,7 +903,20 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { } } - bool whitelisted = hListenSocket.whitelisted || IsWhitelistedRange(addr); + NetPermissionFlags permissionFlags = NetPermissionFlags::PF_NONE; + hListenSocket.AddSocketPermissionFlags(permissionFlags); + AddWhitelistPermissionFlags(permissionFlags, addr); + const bool noban = NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_NOBAN); + bool legacyWhitelisted = false; + if (NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_ISIMPLICIT)) { + NetPermissions::ClearFlag(permissionFlags, PF_ISIMPLICIT); + if (gArgs.GetBoolArg("-whitelistforcerelay", false)) NetPermissions::AddFlag(permissionFlags, PF_FORCERELAY); + if (gArgs.GetBoolArg("-whitelistrelay", false)) NetPermissions::AddFlag(permissionFlags, PF_RELAY); + NetPermissions::AddFlag(permissionFlags, PF_MEMPOOL); + NetPermissions::AddFlag(permissionFlags, PF_NOBAN); + legacyWhitelisted = true; + } + { LOCK(cs_vNodes); for (const CNode* pnode : vNodes) { @@ -941,7 +953,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { // Don't accept connections from banned peers, but if our inbound slots aren't almost full, accept // if the only banning reason was an automatic misbehavior ban. - if (!whitelisted && bannedlevel > ((nInbound + 1 < nMaxInbound) ? 1 : 0)) + if (!noban && bannedlevel > ((nInbound + 1 < nMaxInbound) ? 1 : 0)) { LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToString()); CloseSocket(hSocket); @@ -962,9 +974,15 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); CAddress addr_bind = GetBindAddress(hSocket); - CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true); + ServiceFlags nodeServices = nLocalServices; + if (NetPermissions::HasFlag(permissionFlags, PF_BLOOMFILTER)) { + nodeServices = static_cast<ServiceFlags>(nodeServices | NODE_BLOOM); + } + CNode* pnode = new CNode(id, nodeServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true); pnode->AddRef(); - pnode->fWhitelisted = whitelisted; + pnode->m_permissionFlags = permissionFlags; + // If this flag is present, the user probably expect that RPC and QT report it as whitelisted (backward compatibility) + pnode->m_legacyWhitelisted = legacyWhitelisted; pnode->m_prefer_evict = bannedlevel > 0; m_msgproc->InitializeNode(pnode); @@ -1983,7 +2001,7 @@ void CConnman::ThreadMessageHandler() -bool CConnman::BindListenPort(const CService &addrBind, std::string& strError, bool fWhitelisted) +bool CConnman::BindListenPort(const CService& addrBind, std::string& strError, NetPermissionFlags permissions) { strError = ""; int nOne = 1; @@ -2044,9 +2062,9 @@ bool CConnman::BindListenPort(const CService &addrBind, std::string& strError, b return false; } - vhListenSocket.push_back(ListenSocket(hListenSocket, fWhitelisted)); + vhListenSocket.push_back(ListenSocket(hListenSocket, permissions)); - if (addrBind.IsRoutable() && fDiscover && !fWhitelisted) + if (addrBind.IsRoutable() && fDiscover && (permissions & PF_NOBAN) == 0) AddLocal(addrBind, LOCAL_BIND); return true; @@ -2130,11 +2148,11 @@ NodeId CConnman::GetNewNodeId() } -bool CConnman::Bind(const CService &addr, unsigned int flags) { +bool CConnman::Bind(const CService &addr, unsigned int flags, NetPermissionFlags permissions) { if (!(flags & BF_EXPLICIT) && !IsReachable(addr)) return false; std::string strError; - if (!BindListenPort(addr, strError, (flags & BF_WHITELIST) != 0)) { + if (!BindListenPort(addr, strError, permissions)) { if ((flags & BF_REPORT_ERROR) && clientInterface) { clientInterface->ThreadSafeMessageBox(strError, "", CClientUIInterface::MSG_ERROR); } @@ -2143,20 +2161,21 @@ bool CConnman::Bind(const CService &addr, unsigned int flags) { return true; } -bool CConnman::InitBinds(const std::vector<CService>& binds, const std::vector<CService>& whiteBinds) { +bool CConnman::InitBinds(const std::vector<CService>& binds, const std::vector<NetWhitebindPermissions>& whiteBinds) +{ bool fBound = false; for (const auto& addrBind : binds) { - fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR)); + fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR), NetPermissionFlags::PF_NONE); } for (const auto& addrBind : whiteBinds) { - fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST)); + fBound |= Bind(addrBind.m_service, (BF_EXPLICIT | BF_REPORT_ERROR), addrBind.m_flags); } if (binds.empty() && whiteBinds.empty()) { struct in_addr inaddr_any; inaddr_any.s_addr = INADDR_ANY; struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT; - fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE); - fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE); + fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE, NetPermissionFlags::PF_NONE); + fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE, NetPermissionFlags::PF_NONE); } return fBound; } @@ -15,6 +15,7 @@ #include <hash.h> #include <limitedmap.h> #include <netaddress.h> +#include <net_permissions.h> #include <policy/feerate.h> #include <protocol.h> #include <random.h> @@ -138,8 +139,9 @@ public: uint64_t nMaxOutboundLimit = 0; int64_t m_peer_connect_timeout = DEFAULT_PEER_CONNECT_TIMEOUT; std::vector<std::string> vSeedNodes; - std::vector<CSubNet> vWhitelistedRange; - std::vector<CService> vBinds, vWhiteBinds; + std::vector<NetWhitelistPermissions> vWhitelistedRange; + std::vector<NetWhitebindPermissions> vWhiteBinds; + std::vector<CService> vBinds; bool m_use_addrman_outgoing = true; std::vector<std::string> m_specified_outgoing; std::vector<std::string> m_added_nodes; @@ -314,15 +316,17 @@ public: private: struct ListenSocket { + public: SOCKET socket; - bool whitelisted; - - ListenSocket(SOCKET socket_, bool whitelisted_) : socket(socket_), whitelisted(whitelisted_) {} + inline void AddSocketPermissionFlags(NetPermissionFlags& flags) const { NetPermissions::AddFlag(flags, m_permissions); } + ListenSocket(SOCKET socket_, NetPermissionFlags permissions_) : socket(socket_), m_permissions(permissions_) {} + private: + NetPermissionFlags m_permissions; }; - bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false); - bool Bind(const CService &addr, unsigned int flags); - bool InitBinds(const std::vector<CService>& binds, const std::vector<CService>& whiteBinds); + bool BindListenPort(const CService& bindAddr, std::string& strError, NetPermissionFlags permissions); + bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions); + bool InitBinds(const std::vector<CService>& binds, const std::vector<NetWhitebindPermissions>& whiteBinds); void ThreadOpenAddedConnections(); void AddOneShot(const std::string& strDest); void ProcessOneShot(); @@ -347,7 +351,7 @@ private: bool AttemptToEvictConnection(); CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection); - bool IsWhitelistedRange(const CNetAddr &addr); + void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const; void DeleteNode(CNode* pnode); @@ -380,7 +384,7 @@ private: // Whitelisted ranges. Any node connecting from these is automatically // whitelisted (as well as those connecting to whitelisted binds). - std::vector<CSubNet> vWhitelistedRange; + std::vector<NetWhitelistPermissions> vWhitelistedRange; unsigned int nSendBufferMaxSize{0}; unsigned int nReceiveFloodSize{0}; @@ -448,7 +452,6 @@ void StartMapPort(); void InterruptMapPort(); void StopMapPort(); unsigned short GetListenPort(); -bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false); struct CombinerAll { @@ -555,7 +558,8 @@ public: mapMsgCmdSize mapSendBytesPerMsgCmd; uint64_t nRecvBytes; mapMsgCmdSize mapRecvBytesPerMsgCmd; - bool fWhitelisted; + NetPermissionFlags m_permissionFlags; + bool m_legacyWhitelisted; double dPingTime; double dPingWait; double dMinPing; @@ -657,7 +661,11 @@ public: */ std::string cleanSubVer GUARDED_BY(cs_SubVer){}; bool m_prefer_evict{false}; // This peer is preferred for eviction. - bool fWhitelisted{false}; // This peer can bypass DoS banning. + bool HasPermission(NetPermissionFlags permission) const { + return NetPermissions::HasFlag(m_permissionFlags, permission); + } + // This boolean is unusued in actual processing, only present for backward compatibility at RPC/QT level + bool m_legacyWhitelisted{false}; bool fFeeler{false}; // If true this node is being used as a short lived feeler. bool fOneShot{false}; bool m_manual_connection{false}; @@ -753,6 +761,7 @@ private: const ServiceFlags nLocalServices; const int nMyStartingHeight; int nSendVersion{0}; + NetPermissionFlags m_permissionFlags{ PF_NONE }; std::list<CNetMessage> vRecvMsg; // Used only by SocketHandler thread mutable CCriticalSection cs_addrName; diff --git a/src/net_permissions.cpp b/src/net_permissions.cpp new file mode 100644 index 0000000000..736f19293a --- /dev/null +++ b/src/net_permissions.cpp @@ -0,0 +1,106 @@ +// Copyright (c) 2009-2018 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 <net_permissions.h> +#include <util/system.h> +#include <util/translation.h> +#include <netbase.h> + +// The parse the following format "perm1,perm2@xxxxxx" +bool TryParsePermissionFlags(const std::string str, NetPermissionFlags& output, size_t& readen, std::string& error) +{ + NetPermissionFlags flags = PF_NONE; + const auto atSeparator = str.find('@'); + + // if '@' is not found (ie, "xxxxx"), the caller should apply implicit permissions + if (atSeparator == std::string::npos) { + NetPermissions::AddFlag(flags, PF_ISIMPLICIT); + readen = 0; + } + // else (ie, "perm1,perm2@xxxxx"), let's enumerate the permissions by splitting by ',' and calculate the flags + else { + readen = 0; + // permissions == perm1,perm2 + const auto permissions = str.substr(0, atSeparator); + while (readen < permissions.length()) { + const auto commaSeparator = permissions.find(',', readen); + const auto len = commaSeparator == std::string::npos ? permissions.length() - readen : commaSeparator - readen; + // permission == perm1 + const auto permission = permissions.substr(readen, len); + readen += len; // We read "perm1" + if (commaSeparator != std::string::npos) readen++; // We read "," + + if (permission == "bloomfilter" || permission == "bloom") NetPermissions::AddFlag(flags, PF_BLOOMFILTER); + else if (permission == "noban") NetPermissions::AddFlag(flags, PF_NOBAN); + else if (permission == "forcerelay") NetPermissions::AddFlag(flags, PF_FORCERELAY); + else if (permission == "mempool") NetPermissions::AddFlag(flags, PF_MEMPOOL); + else if (permission == "all") NetPermissions::AddFlag(flags, PF_ALL); + else if (permission == "relay") NetPermissions::AddFlag(flags, PF_RELAY); + else if (permission.length() == 0); // Allow empty entries + else { + error = strprintf(_("Invalid P2P permission: '%s'").translated, permission); + return false; + } + } + readen++; + } + + output = flags; + error = ""; + return true; +} + +std::vector<std::string> NetPermissions::ToStrings(NetPermissionFlags flags) +{ + std::vector<std::string> strings; + if (NetPermissions::HasFlag(flags, PF_BLOOMFILTER)) strings.push_back("bloomfilter"); + if (NetPermissions::HasFlag(flags, PF_NOBAN)) strings.push_back("noban"); + if (NetPermissions::HasFlag(flags, PF_FORCERELAY)) strings.push_back("forcerelay"); + if (NetPermissions::HasFlag(flags, PF_RELAY)) strings.push_back("relay"); + if (NetPermissions::HasFlag(flags, PF_MEMPOOL)) strings.push_back("mempool"); + return strings; +} + +bool NetWhitebindPermissions::TryParse(const std::string str, NetWhitebindPermissions& output, std::string& error) +{ + NetPermissionFlags flags; + size_t offset; + if (!TryParsePermissionFlags(str, flags, offset, error)) return false; + + const std::string strBind = str.substr(offset); + CService addrBind; + if (!Lookup(strBind.c_str(), addrBind, 0, false)) { + error = strprintf(_("Cannot resolve -%s address: '%s'").translated, "whitebind", strBind); + return false; + } + if (addrBind.GetPort() == 0) { + error = strprintf(_("Need to specify a port with -whitebind: '%s'").translated, strBind); + return false; + } + + output.m_flags = flags; + output.m_service = addrBind; + error = ""; + return true; +} + +bool NetWhitelistPermissions::TryParse(const std::string str, NetWhitelistPermissions& output, std::string& error) +{ + NetPermissionFlags flags; + size_t offset; + if (!TryParsePermissionFlags(str, flags, offset, error)) return false; + + const std::string net = str.substr(offset); + CSubNet subnet; + LookupSubNet(net.c_str(), subnet); + if (!subnet.IsValid()) { + error = strprintf(_("Invalid netmask specified in -whitelist: '%s'").translated, net); + return false; + } + + output.m_flags = flags; + output.m_subnet = subnet; + error = ""; + return true; +} diff --git a/src/net_permissions.h b/src/net_permissions.h new file mode 100644 index 0000000000..b3987de65f --- /dev/null +++ b/src/net_permissions.h @@ -0,0 +1,62 @@ +// Copyright (c) 2009-2018 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 <string> +#include <vector> +#include <netaddress.h> + +#ifndef BITCOIN_NET_PERMISSIONS_H +#define BITCOIN_NET_PERMISSIONS_H +enum NetPermissionFlags +{ + PF_NONE = 0, + // Can query bloomfilter even if -peerbloomfilters is false + PF_BLOOMFILTER = (1U << 1), + // Relay and accept transactions from this peer, even if -blocksonly is true + PF_RELAY = (1U << 3), + // Always relay transactions from this peer, even if already in mempool or rejected from policy + // Keep parameter interaction: forcerelay implies relay + PF_FORCERELAY = (1U << 2) | PF_RELAY, + // Can't be banned for misbehavior + PF_NOBAN = (1U << 4), + // Can query the mempool + PF_MEMPOOL = (1U << 5), + + // True if the user did not specifically set fine grained permissions + PF_ISIMPLICIT = (1U << 31), + PF_ALL = PF_BLOOMFILTER | PF_FORCERELAY | PF_RELAY | PF_NOBAN | PF_MEMPOOL, +}; +class NetPermissions +{ +public: + NetPermissionFlags m_flags; + static std::vector<std::string> ToStrings(NetPermissionFlags flags); + static inline bool HasFlag(const NetPermissionFlags& flags, NetPermissionFlags f) + { + return (flags & f) == f; + } + static inline void AddFlag(NetPermissionFlags& flags, NetPermissionFlags f) + { + flags = static_cast<NetPermissionFlags>(flags | f); + } + static inline void ClearFlag(NetPermissionFlags& flags, NetPermissionFlags f) + { + flags = static_cast<NetPermissionFlags>(flags & ~f); + } +}; +class NetWhitebindPermissions : public NetPermissions +{ +public: + static bool TryParse(const std::string str, NetWhitebindPermissions& output, std::string& error); + CService m_service; +}; + +class NetWhitelistPermissions : public NetPermissions +{ +public: + static bool TryParse(const std::string str, NetWhitelistPermissions& output, std::string& error); + CSubNet m_subnet; +}; + +#endif // BITCOIN_NET_PERMISSIONS_H
\ No newline at end of file diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 5efb4adee6..3db460d444 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -408,7 +408,7 @@ static void UpdatePreferredDownload(CNode* node, CNodeState* state) EXCLUSIVE_LO nPreferredDownload -= state->fPreferredDownload; // Whether this node should be marked as a preferred download node. - state->fPreferredDownload = (!node->fInbound || node->fWhitelisted) && !node->fOneShot && !node->fClient; + state->fPreferredDownload = (!node->fInbound || node->HasPermission(PF_NOBAN)) && !node->fOneShot && !node->fClient; nPreferredDownload += state->fPreferredDownload; } @@ -1398,7 +1398,7 @@ void static ProcessGetBlockData(CNode* pfrom, const CChainParams& chainparams, c const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); // disconnect node in case we have reached the outbound limit for serving historical blocks // never disconnect whitelisted nodes - if (send && connman->OutboundTargetReached(true) && ( ((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.type == MSG_FILTERED_BLOCK) && !pfrom->fWhitelisted) + if (send && connman->OutboundTargetReached(true) && ( ((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.type == MSG_FILTERED_BLOCK) && !pfrom->HasPermission(PF_NOBAN)) { LogPrint(BCLog::NET, "historical block serving limit reached, disconnect peer=%d\n", pfrom->GetId()); @@ -1407,7 +1407,7 @@ void static ProcessGetBlockData(CNode* pfrom, const CChainParams& chainparams, c send = false; } // Avoid leaking prune-height by never sending blocks below the NODE_NETWORK_LIMITED threshold - if (send && !pfrom->fWhitelisted && ( + if (send && !pfrom->HasPermission(PF_NOBAN) && ( (((pfrom->GetLocalServices() & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((pfrom->GetLocalServices() & NODE_NETWORK) != NODE_NETWORK) && (::ChainActive().Tip()->nHeight - pindex->nHeight > (int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2 /* add two blocks buffer extension for possible races */) ) )) { LogPrint(BCLog::NET, "Ignore block request below NODE_NETWORK_LIMITED threshold from peer=%d\n", pfrom->GetId()); @@ -2217,7 +2217,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr bool fBlocksOnly = !g_relay_txes; // Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true - if (pfrom->fWhitelisted && gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) + if (pfrom->HasPermission(PF_RELAY)) fBlocksOnly = false; LOCK(cs_main); @@ -2412,7 +2412,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } LOCK(cs_main); - if (::ChainstateActive().IsInitialBlockDownload() && !pfrom->fWhitelisted) { + if (::ChainstateActive().IsInitialBlockDownload() && !pfrom->HasPermission(PF_NOBAN)) { LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because node is in initial block download\n", pfrom->GetId()); return true; } @@ -2470,7 +2470,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (strCommand == NetMsgType::TX) { // Stop processing the transaction early if // We are in blocks only mode and peer is either not whitelisted or whitelistrelay is off - if (!g_relay_txes && (!pfrom->fWhitelisted || !gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY))) + if (!g_relay_txes && !pfrom->HasPermission(PF_RELAY)) { LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom->GetId()); return true; @@ -2565,7 +2565,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr AddToCompactExtraTransactions(ptx); } - if (pfrom->fWhitelisted && gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) { + if (pfrom->HasPermission(PF_FORCERELAY)) { // Always relay transactions received from whitelisted peers, even // if they were already in the mempool or rejected from it due // to policy, allowing the node to function as a gateway for @@ -3010,17 +3010,23 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } if (strCommand == NetMsgType::MEMPOOL) { - if (!(pfrom->GetLocalServices() & NODE_BLOOM) && !pfrom->fWhitelisted) + if (!(pfrom->GetLocalServices() & NODE_BLOOM) && !pfrom->HasPermission(PF_MEMPOOL)) { - LogPrint(BCLog::NET, "mempool request with bloom filters disabled, disconnect peer=%d\n", pfrom->GetId()); - pfrom->fDisconnect = true; + if (!pfrom->HasPermission(PF_NOBAN)) + { + LogPrint(BCLog::NET, "mempool request with bloom filters disabled, disconnect peer=%d\n", pfrom->GetId()); + pfrom->fDisconnect = true; + } return true; } - if (connman->OutboundTargetReached(false) && !pfrom->fWhitelisted) + if (connman->OutboundTargetReached(false) && !pfrom->HasPermission(PF_MEMPOOL)) { - LogPrint(BCLog::NET, "mempool request with bandwidth limit reached, disconnect peer=%d\n", pfrom->GetId()); - pfrom->fDisconnect = true; + if (!pfrom->HasPermission(PF_NOBAN)) + { + LogPrint(BCLog::NET, "mempool request with bandwidth limit reached, disconnect peer=%d\n", pfrom->GetId()); + pfrom->fDisconnect = true; + } return true; } @@ -3216,7 +3222,7 @@ bool PeerLogicValidation::SendRejectsAndCheckIfBanned(CNode* pnode, bool enable_ if (state.fShouldBan) { state.fShouldBan = false; - if (pnode->fWhitelisted) + if (pnode->HasPermission(PF_NOBAN)) LogPrintf("Warning: not punishing whitelisted peer %s!\n", pnode->addr.ToString()); else if (pnode->m_manual_connection) LogPrintf("Warning: not punishing manually-connected peer %s!\n", pnode->addr.ToString()); @@ -3786,7 +3792,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) pto->vInventoryBlockToSend.clear(); // Check whether periodic sends should happen - bool fSendTrickle = pto->fWhitelisted; + bool fSendTrickle = pto->HasPermission(PF_NOBAN); if (pto->nNextInvSend < nNow) { fSendTrickle = true; if (pto->fInbound) { @@ -3942,7 +3948,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // Note: If all our peers are inbound, then we won't // disconnect our sync peer for stalling; we have bigger // problems if we can't get any outbound peers. - if (!pto->fWhitelisted) { + if (!pto->HasPermission(PF_NOBAN)) { LogPrintf("Timeout downloading headers from peer=%d, disconnecting\n", pto->GetId()); pto->fDisconnect = true; return true; @@ -4060,7 +4066,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // // We don't want white listed peers to filter txs to us if we have -whitelistforcerelay if (pto->nVersion >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) && - !(pto->fWhitelisted && gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY))) { + !pto->HasPermission(PF_FORCERELAY)) { CAmount currentFilter = mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK(); int64_t timeNow = GetTimeMicros(); if (timeNow > pto->nextSendTimeFeeFilter) { diff --git a/src/netbase.cpp b/src/netbase.cpp index 6d4738c835..0148aea428 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -37,8 +37,8 @@ bool fNameLookup = DEFAULT_NAME_LOOKUP; static const int SOCKS5_RECV_TIMEOUT = 20 * 1000; static std::atomic<bool> interruptSocks5Recv(false); -enum Network ParseNetwork(std::string net) { - Downcase(net); +enum Network ParseNetwork(const std::string& net_in) { + std::string net = ToLower(net_in); if (net == "ipv4") return NET_IPV4; if (net == "ipv6") return NET_IPV6; if (net == "onion") return NET_ONION; diff --git a/src/netbase.h b/src/netbase.h index 708df5b8e2..313a575687 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -37,7 +37,7 @@ public: bool randomize_credentials; }; -enum Network ParseNetwork(std::string net); +enum Network ParseNetwork(const std::string& net); std::string GetNetworkName(enum Network net); bool SetProxy(enum Network net, const proxyType &addrProxy); bool GetProxy(enum Network net, proxyType &proxyInfoOut); diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 8e56496358..a28136a8e8 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -16,6 +16,9 @@ TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback) { + // BroadcastTransaction can be called by either sendrawtransaction RPC or wallet RPCs. + // g_connman is assigned both before chain clients and before RPC server is accepting calls, + // and reset after chain clients and RPC sever are stopped. g_connman should never be null here. assert(g_connman); std::promise<void> promise; uint256 hashTx = tx->GetHash(); diff --git a/src/obj-test/.gitignore b/src/obj-test/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/src/obj-test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 482bf0543d..5ce4f3c191 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -169,8 +169,11 @@ void BitcoinCore::shutdown() } } -BitcoinApplication::BitcoinApplication(interfaces::Node& node, int &argc, char **argv): - QApplication(argc, argv), +static int qt_argc = 1; +static const char* qt_argv = "bitcoin-qt"; + +BitcoinApplication::BitcoinApplication(interfaces::Node& node): + QApplication(qt_argc, const_cast<char **>(&qt_argv)), coreThread(nullptr), m_node(node), optionsModel(nullptr), @@ -433,7 +436,7 @@ int GuiMain(int argc, char* argv[]) QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); #endif - BitcoinApplication app(*node, argc, argv); + BitcoinApplication app(*node); // Register meta types used for QMetaObject::invokeMethod qRegisterMetaType< bool* >(); diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h index 40537c1813..3869193a3a 100644 --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -56,7 +56,7 @@ class BitcoinApplication: public QApplication { Q_OBJECT public: - explicit BitcoinApplication(interfaces::Node& node, int &argc, char **argv); + explicit BitcoinApplication(interfaces::Node& node); ~BitcoinApplication(); #ifdef ENABLE_WALLET diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index cdf84eae9a..19b11ba1cd 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -1120,7 +1120,7 @@ void RPCConsole::updateNodeDetail(const CNodeCombinedStats *stats) ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer)); ui->peerDirection->setText(stats->nodeStats.fInbound ? tr("Inbound") : tr("Outbound")); ui->peerHeight->setText(QString("%1").arg(QString::number(stats->nodeStats.nStartingHeight))); - ui->peerWhitelisted->setText(stats->nodeStats.fWhitelisted ? tr("Yes") : tr("No")); + ui->peerWhitelisted->setText(stats->nodeStats.m_legacyWhitelisted ? tr("Yes") : tr("No")); // This check fails for example if the lock was busy and // nodeStateStats couldn't be fetched. diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index dd5216d68c..796cf24b36 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -68,7 +68,7 @@ int main(int argc, char *argv[]) // Don't remove this, it's needed to access // QApplication:: and QCoreApplication:: in the tests - BitcoinApplication app(*node, argc, argv); + BitcoinApplication app(*node); app.setApplicationName("Bitcoin-Qt-test"); AppTests app_tests(app); diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 2aedb77798..a8e7bce6b5 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -99,6 +99,9 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wal // Instantiate model and register it. WalletModel* wallet_model = new WalletModel(std::move(wallet), m_node, m_platform_style, m_options_model, nullptr); + // Handler callback runs in a different thread so fix wallet model thread affinity. + wallet_model->moveToThread(thread()); + wallet_model->setParent(this); m_wallets.push_back(wallet_model); connect(wallet_model, &WalletModel::unload, [this, wallet_model] { @@ -119,25 +122,11 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wal connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent); // Notify walletAdded signal on the GUI thread. - if (QThread::currentThread() == thread()) { - addWallet(wallet_model); - } else { - // Handler callback runs in a different thread so fix wallet model thread affinity. - wallet_model->moveToThread(thread()); - bool invoked = QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, Q_ARG(WalletModel*, wallet_model)); - assert(invoked); - } + Q_EMIT walletAdded(wallet_model); return wallet_model; } -void WalletController::addWallet(WalletModel* wallet_model) -{ - // Take ownership of the wallet model and register it. - wallet_model->setParent(this); - Q_EMIT walletAdded(wallet_model); -} - void WalletController::removeAndDeleteWallet(WalletModel* wallet_model) { // Unregister wallet model. diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index 03039dd795..be1c282919 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -50,9 +50,6 @@ public: OpenWalletActivity* openWallet(const std::string& name, QWidget* parent = nullptr); void closeWallet(WalletModel* wallet_model, QWidget* parent = nullptr); -private Q_SLOTS: - void addWallet(WalletModel* wallet_model); - Q_SIGNALS: void walletAdded(WalletModel* wallet_model); void walletRemoved(WalletModel* wallet_model); diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 48bc88823a..92a0e33769 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -352,7 +352,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) "}\n" }, RPCExamples{ - HelpExampleCli("getblocktemplate", "{\"rules\": [\"segwit\"]}") + HelpExampleCli("getblocktemplate", "'{\"rules\": [\"segwit\"]}'") + HelpExampleRpc("getblocktemplate", "{\"rules\": [\"segwit\"]}") }, }.Check(request); diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 16b59e3d58..25dda924a4 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -9,6 +9,7 @@ #include <core_io.h> #include <net.h> #include <net_processing.h> +#include <net_permissions.h> #include <netbase.h> #include <policy/policy.h> #include <policy/settings.h> @@ -177,7 +178,12 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) } obj.pushKV("inflight", heights); } - obj.pushKV("whitelisted", stats.fWhitelisted); + obj.pushKV("whitelisted", stats.m_legacyWhitelisted); + UniValue permissions(UniValue::VARR); + for (const auto& permission : NetPermissions::ToStrings(stats.m_permissionFlags)) { + permissions.push_back(permission); + } + obj.pushKV("permissions", permissions); obj.pushKV("minfeefilter", ValueFromAmount(stats.minFeeFilter)); UniValue sendPerMsgCmd(UniValue::VOBJ); diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 50119ba184..327af62a4f 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -335,10 +335,12 @@ public: /** Base class for all Descriptor implementations. */ class DescriptorImpl : public Descriptor { - //! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size of Multisig). + //! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size for Multisig). const std::vector<std::unique_ptr<PubkeyProvider>> m_pubkey_args; //! The sub-descriptor argument (nullptr for everything but SH and WSH). - const std::unique_ptr<DescriptorImpl> m_script_arg; + //! In doc/descriptors.m this is referred to as SCRIPT expressions sh(SCRIPT) + //! and wsh(SCRIPT), and distinct from KEY expressions and ADDR expressions. + const std::unique_ptr<DescriptorImpl> m_subdescriptor_arg; //! The string name of the descriptor function. const std::string m_name; @@ -349,10 +351,10 @@ protected: /** A helper function to construct the scripts for this descriptor. * * This function is invoked once for every CScript produced by evaluating - * m_script_arg, or just once in case m_script_arg is nullptr. + * m_subdescriptor_arg, or just once in case m_subdescriptor_arg is nullptr. * @param pubkeys The evaluations of the m_pubkey_args field. - * @param script The evaluation of m_script_arg (or nullptr when m_script_arg is nullptr). + * @param script The evaluation of m_subdescriptor_arg (or nullptr when m_subdescriptor_arg is nullptr). * @param out A FlatSigningProvider to put scripts or public keys in that are necessary to the solver. * The script arguments to this function are automatically added, as is the origin info of the provided pubkeys. * @return A vector with scriptPubKeys for this descriptor. @@ -360,12 +362,12 @@ protected: virtual std::vector<CScript> MakeScripts(const std::vector<CPubKey>& pubkeys, const CScript* script, FlatSigningProvider& out) const = 0; public: - DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_script_arg(std::move(script)), m_name(name) {} + DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_subdescriptor_arg(std::move(script)), m_name(name) {} bool IsSolvable() const override { - if (m_script_arg) { - if (!m_script_arg->IsSolvable()) return false; + if (m_subdescriptor_arg) { + if (!m_subdescriptor_arg->IsSolvable()) return false; } return true; } @@ -375,8 +377,8 @@ public: for (const auto& pubkey : m_pubkey_args) { if (pubkey->IsRange()) return true; } - if (m_script_arg) { - if (m_script_arg->IsRange()) return true; + if (m_subdescriptor_arg) { + if (m_subdescriptor_arg->IsRange()) return true; } return false; } @@ -396,10 +398,10 @@ public: } ret += std::move(tmp); } - if (m_script_arg) { + if (m_subdescriptor_arg) { if (pos++) ret += ","; std::string tmp; - if (!m_script_arg->ToStringHelper(arg, tmp, priv)) return false; + if (!m_subdescriptor_arg->ToStringHelper(arg, tmp, priv)) return false; ret += std::move(tmp); } out = std::move(ret) + ")"; @@ -428,6 +430,8 @@ public: // Construct temporary data in `entries` and `subscripts`, to avoid producing output in case of failure. for (const auto& p : m_pubkey_args) { entries.emplace_back(); + // If we have a cache, we don't need GetPubKey to compute the public key. + // Pass in nullptr to signify only origin info is desired. if (!p->GetPubKey(pos, arg, cache_read ? nullptr : &entries.back().first, entries.back().second)) return false; if (cache_read) { // Cached expanded public key exists, use it. @@ -444,9 +448,9 @@ public: } } std::vector<CScript> subscripts; - if (m_script_arg) { + if (m_subdescriptor_arg) { FlatSigningProvider subprovider; - if (!m_script_arg->ExpandHelper(pos, arg, cache_read, subscripts, subprovider, cache_write)) return false; + if (!m_subdescriptor_arg->ExpandHelper(pos, arg, cache_read, subscripts, subprovider, cache_write)) return false; out = Merge(out, subprovider); } @@ -456,7 +460,7 @@ public: pubkeys.push_back(entry.first); out.origins.emplace(entry.first.GetID(), std::make_pair<CPubKey, KeyOriginInfo>(CPubKey(entry.first), std::move(entry.second))); } - if (m_script_arg) { + if (m_subdescriptor_arg) { for (const auto& subscript : subscripts) { out.scripts.emplace(CScriptID(subscript), subscript); std::vector<CScript> addscripts = MakeScripts(pubkeys, &subscript, out); @@ -488,9 +492,9 @@ public: if (!p->GetPrivKey(pos, provider, key)) continue; out.keys.emplace(key.GetPubKey().GetID(), key); } - if (m_script_arg) { + if (m_subdescriptor_arg) { FlatSigningProvider subprovider; - m_script_arg->ExpandPrivate(pos, provider, subprovider); + m_subdescriptor_arg->ExpandPrivate(pos, provider, subprovider); out = Merge(out, subprovider); } } diff --git a/src/script/descriptor.h b/src/script/descriptor.h index 29915c6c92..a34e9f0d8a 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -47,9 +47,9 @@ struct Descriptor { * * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored. * provider: the provider to query for private keys in case of hardened derivation. - * output_script: the expanded scriptPubKeys will be put here. + * output_scripts: the expanded scriptPubKeys will be put here. * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider). - * cache: vector which will be overwritten with cache data necessary to-evaluate the descriptor at this point without access to private keys. + * cache: vector which will be overwritten with cache data necessary to evaluate the descriptor at this point without access to private keys. */ virtual bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, std::vector<unsigned char>* cache = nullptr) const = 0; @@ -57,7 +57,7 @@ struct Descriptor { * * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored. * cache: vector from which cached expansion data will be read. - * output_script: the expanded scriptPubKeys will be put here. + * output_scripts: the expanded scriptPubKeys will be put here. * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider). */ virtual bool ExpandFromCache(int pos, const std::vector<unsigned char>& cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0; diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index 86c0cecbf1..a3d0831624 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <netbase.h> +#include <net_permissions.h> #include <test/setup_common.h> #include <util/strencodings.h> @@ -321,4 +322,82 @@ BOOST_AUTO_TEST_CASE(netbase_parsenetwork) BOOST_CHECK_EQUAL(ParseNetwork(""), NET_UNROUTABLE); } +BOOST_AUTO_TEST_CASE(netpermissions_test) +{ + std::string error; + NetWhitebindPermissions whitebindPermissions; + NetWhitelistPermissions whitelistPermissions; + + // Detect invalid white bind + BOOST_CHECK(!NetWhitebindPermissions::TryParse("", whitebindPermissions, error)); + BOOST_CHECK(error.find("Cannot resolve -whitebind address") != std::string::npos); + BOOST_CHECK(!NetWhitebindPermissions::TryParse("127.0.0.1", whitebindPermissions, error)); + BOOST_CHECK(error.find("Need to specify a port with -whitebind") != std::string::npos); + BOOST_CHECK(!NetWhitebindPermissions::TryParse("", whitebindPermissions, error)); + + // If no permission flags, assume backward compatibility + BOOST_CHECK(NetWhitebindPermissions::TryParse("1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK(error.empty()); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_ISIMPLICIT); + BOOST_CHECK(NetPermissions::HasFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT)); + NetPermissions::ClearFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT); + BOOST_CHECK(!NetPermissions::HasFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_NONE); + NetPermissions::AddFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT); + BOOST_CHECK(NetPermissions::HasFlag(whitebindPermissions.m_flags, PF_ISIMPLICIT)); + + // Can set one permission + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER); + BOOST_CHECK(NetWhitebindPermissions::TryParse("@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_NONE); + + // Happy path, can parse flags + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,forcerelay@1.2.3.4:32", whitebindPermissions, error)); + // forcerelay should also activate the relay permission + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER | PF_FORCERELAY | PF_RELAY); + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,relay,noban@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER | PF_RELAY | PF_NOBAN); + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,forcerelay,noban@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK(NetWhitebindPermissions::TryParse("all@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_ALL); + + // Allow dups + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,relay,noban,noban@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER | PF_RELAY | PF_NOBAN); + + // Allow empty + BOOST_CHECK(NetWhitebindPermissions::TryParse("bloom,relay,,noban@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_BLOOMFILTER | PF_RELAY | PF_NOBAN); + BOOST_CHECK(NetWhitebindPermissions::TryParse(",@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_NONE); + BOOST_CHECK(NetWhitebindPermissions::TryParse(",,@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK_EQUAL(whitebindPermissions.m_flags, PF_NONE); + + // Detect invalid flag + BOOST_CHECK(!NetWhitebindPermissions::TryParse("bloom,forcerelay,oopsie@1.2.3.4:32", whitebindPermissions, error)); + BOOST_CHECK(error.find("Invalid P2P permission") != std::string::npos); + + // Check whitelist error + BOOST_CHECK(!NetWhitelistPermissions::TryParse("bloom,forcerelay,noban@1.2.3.4:32", whitelistPermissions, error)); + BOOST_CHECK(error.find("Invalid netmask specified in -whitelist") != std::string::npos); + + // Happy path for whitelist parsing + BOOST_CHECK(NetWhitelistPermissions::TryParse("noban@1.2.3.4", whitelistPermissions, error)); + BOOST_CHECK_EQUAL(whitelistPermissions.m_flags, PF_NOBAN); + BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay@1.2.3.4/32", whitelistPermissions, error)); + BOOST_CHECK_EQUAL(whitelistPermissions.m_flags, PF_BLOOMFILTER | PF_FORCERELAY | PF_NOBAN | PF_RELAY); + BOOST_CHECK(error.empty()); + BOOST_CHECK_EQUAL(whitelistPermissions.m_subnet.ToString(), "1.2.3.4/32"); + BOOST_CHECK(NetWhitelistPermissions::TryParse("bloom,forcerelay,noban,relay,mempool@1.2.3.4/32", whitelistPermissions, error)); + + const auto strings = NetPermissions::ToStrings(PF_ALL); + BOOST_CHECK_EQUAL(strings.size(), 5); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "bloomfilter") != strings.end()); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "forcerelay") != strings.end()); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "relay") != strings.end()); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "noban") != strings.end()); + BOOST_CHECK(std::find(strings.begin(), strings.end(), "mempool") != strings.end()); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/timedata_tests.cpp b/src/test/timedata_tests.cpp index b4c0e6a0f4..7b00222ab7 100644 --- a/src/test/timedata_tests.cpp +++ b/src/test/timedata_tests.cpp @@ -2,8 +2,14 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // -#include <timedata.h> + +#include <netaddress.h> +#include <noui.h> #include <test/setup_common.h> +#include <timedata.h> +#include <warnings.h> + +#include <string> #include <boost/test/unit_test.hpp> @@ -34,4 +40,61 @@ BOOST_AUTO_TEST_CASE(util_MedianFilter) BOOST_CHECK_EQUAL(filter.median(), 7); } +static void MultiAddTimeData(int n, int64_t offset) +{ + static int cnt = 0; + for (int i = 0; i < n; ++i) { + CNetAddr addr; + addr.SetInternal(std::to_string(++cnt)); + AddTimeData(addr, offset); + } +} + + +BOOST_AUTO_TEST_CASE(addtimedata) +{ + BOOST_CHECK_EQUAL(GetTimeOffset(), 0); + + //Part 1: Add large offsets to test a warning message that our clock may be wrong. + MultiAddTimeData(3, DEFAULT_MAX_TIME_ADJUSTMENT + 1); + // Filter size is 1 + 3 = 4: It is always initialized with a single element (offset 0) + + noui_suppress(); + MultiAddTimeData(1, DEFAULT_MAX_TIME_ADJUSTMENT + 1); //filter size 5 + noui_reconnect(); + + BOOST_CHECK(GetWarnings("gui").find("clock is wrong") != std::string::npos); + + // nTimeOffset is not changed if the median of offsets exceeds DEFAULT_MAX_TIME_ADJUSTMENT + BOOST_CHECK_EQUAL(GetTimeOffset(), 0); + + // Part 2: Test positive and negative medians by adding more offsets + MultiAddTimeData(4, 100); // filter size 9 + BOOST_CHECK_EQUAL(GetTimeOffset(), 100); + MultiAddTimeData(10, -100); //filter size 19 + BOOST_CHECK_EQUAL(GetTimeOffset(), -100); + + // Part 3: Test behaviour when filter has reached maximum number of offsets + const int MAX_SAMPLES = 200; + int nfill = (MAX_SAMPLES - 3 - 19) / 2; //89 + MultiAddTimeData(nfill, 100); + MultiAddTimeData(nfill, -100); //filter size MAX_SAMPLES - 3 + BOOST_CHECK_EQUAL(GetTimeOffset(), -100); + + MultiAddTimeData(2, 100); + //filter size MAX_SAMPLES -1, median is the initial 0 offset + //since we added same number of positive/negative offsets + + BOOST_CHECK_EQUAL(GetTimeOffset(), 0); + + // After the number of offsets has reached MAX_SAMPLES -1 (=199), nTimeOffset will never change + // because it is only updated when the number of elements in the filter becomes odd. It was decided + // not to fix this because it prevents possible attacks. See the comment in AddTimeData() or issue #4521 + // for a more detailed explanation. + MultiAddTimeData(2, 100); // filter median is 100 now, but nTimeOffset will not change + BOOST_CHECK_EQUAL(GetTimeOffset(), 0); + + // We want this test to end with nTimeOffset==0, otherwise subsequent tests of the suite will fail. +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 15fe1148fe..7119f56fc3 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -1532,17 +1532,9 @@ BOOST_AUTO_TEST_CASE(test_ToLower) BOOST_CHECK_EQUAL(ToLower(0), 0); BOOST_CHECK_EQUAL(ToLower('\xff'), '\xff'); - std::string testVector; - Downcase(testVector); - BOOST_CHECK_EQUAL(testVector, ""); - - testVector = "#HODL"; - Downcase(testVector); - BOOST_CHECK_EQUAL(testVector, "#hodl"); - - testVector = "\x00\xfe\xff"; - Downcase(testVector); - BOOST_CHECK_EQUAL(testVector, "\x00\xfe\xff"); + BOOST_CHECK_EQUAL(ToLower(""), ""); + BOOST_CHECK_EQUAL(ToLower("#HODL"), "#hodl"); + BOOST_CHECK_EQUAL(ToLower("\x00\xfe\xff"), "\x00\xfe\xff"); } BOOST_AUTO_TEST_CASE(test_ToUpper) @@ -1553,6 +1545,10 @@ BOOST_AUTO_TEST_CASE(test_ToUpper) BOOST_CHECK_EQUAL(ToUpper('{'), '{'); BOOST_CHECK_EQUAL(ToUpper(0), 0); BOOST_CHECK_EQUAL(ToUpper('\xff'), '\xff'); + + BOOST_CHECK_EQUAL(ToUpper(""), ""); + BOOST_CHECK_EQUAL(ToUpper("#hodl"), "#HODL"); + BOOST_CHECK_EQUAL(ToUpper("\x00\xfe\xff"), "\x00\xfe\xff"); } BOOST_AUTO_TEST_CASE(test_Capitalize) diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 0acbb4f117..1e7d24c71c 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -546,9 +546,18 @@ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) return true; } -void Downcase(std::string& str) +std::string ToLower(const std::string& str) { - std::transform(str.begin(), str.end(), str.begin(), [](char c){return ToLower(c);}); + std::string r; + for (auto ch : str) r += ToLower((unsigned char)ch); + return r; +} + +std::string ToUpper(const std::string& str) +{ + std::string r; + for (auto ch : str) r += ToUpper((unsigned char)ch); + return r; } std::string Capitalize(std::string str) diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 7c4364a082..e35b2ab857 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -199,6 +199,8 @@ bool ConvertBits(const O& outfn, I it, I end) { * Converts the given character to its lowercase equivalent. * This function is locale independent. It only converts uppercase * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * * @param[in] c the character to convert to lowercase. * @return the lowercase equivalent of c; or the argument * if no conversion is possible. @@ -209,17 +211,22 @@ constexpr char ToLower(char c) } /** - * Converts the given string to its lowercase equivalent. + * Returns the lowercase equivalent of the given string. * This function is locale independent. It only converts uppercase * characters in the standard 7-bit ASCII range. - * @param[in,out] str the string to convert to lowercase. + * This is a feature, not a limitation. + * + * @param[in] str the string to convert to lowercase. + * @returns lowercased equivalent of str */ -void Downcase(std::string& str); +std::string ToLower(const std::string& str); /** * Converts the given character to its uppercase equivalent. * This function is locale independent. It only converts lowercase * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * * @param[in] c the character to convert to uppercase. * @return the uppercase equivalent of c; or the argument * if no conversion is possible. @@ -230,12 +237,24 @@ constexpr char ToUpper(char c) } /** + * Returns the uppercase equivalent of the given string. + * This function is locale independent. It only converts lowercase + * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * + * @param[in] str the string to convert to uppercase. + * @returns UPPERCASED EQUIVALENT OF str + */ +std::string ToUpper(const std::string& str); + +/** * Capitalizes the first character of the given string. - * This function is locale independent. It only capitalizes the - * first character of the argument if it has an uppercase equivalent - * in the standard 7-bit ASCII range. + * This function is locale independent. It only converts lowercase + * characters in the standard 7-bit ASCII range. + * This is a feature, not a limitation. + * * @param[in] str the string to capitalize. - * @return string with the first letter capitalized. + * @returns string with the first letter capitalized. */ std::string Capitalize(std::string str); diff --git a/src/util/system.cpp b/src/util/system.cpp index f8fcbc1206..f8bcc45a6a 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -388,7 +388,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin key.erase(is_index); } #ifdef WIN32 - std::transform(key.begin(), key.end(), key.begin(), ToLower); + key = ToLower(key); if (key[0] == '/') key[0] = '-'; #endif diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index f94214b6ee..cbab73d612 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -309,10 +309,6 @@ static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet if (nValue > curBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); - if (pwallet->GetBroadcastTransactions() && !pwallet->chain().p2pEnabled()) { - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - } - // Parse Bitcoin address CScript scriptPubKey = GetScriptForDestination(address); @@ -845,10 +841,6 @@ static UniValue sendmany(const JSONRPCRequest& request) auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - if (pwallet->GetBroadcastTransactions() && !pwallet->chain().p2pEnabled()) { - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - } - if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"\""); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b3269083ec..03acf23508 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2150,16 +2150,21 @@ void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { CWalletTx& wtx = *(item.second); std::string unused_err_string; - wtx.SubmitMemoryPoolAndRelay(unused_err_string, false); + wtx.SubmitMemoryPoolAndRelay(unused_err_string, false, locked_chain); } } -bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay) +bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain) { // Can't relay if wallet is not broadcasting if (!pwallet->GetBroadcastTransactions()) return false; // Don't relay abandoned transactions if (isAbandoned()) return false; + // Don't try to submit coinbase transactions. These would fail anyway but would + // cause log spam. + if (IsCoinBase()) return false; + // Don't try to submit conflicted or confirmed transactions. + if (GetDepthInMainChain(locked_chain) != 0) return false; // Submit transaction to mempool for relay pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString()); @@ -2374,11 +2379,12 @@ void CWallet::ResendWalletTransactions() // Relay transactions for (std::pair<const uint256, CWalletTx>& item : mapWallet) { CWalletTx& wtx = item.second; - // only rebroadcast unconfirmed txes older than 5 minutes before the - // last block was found + // Attempt to rebroadcast all txes more than 5 minutes older than + // the last block. SubmitMemoryPoolAndRelay() will not rebroadcast + // any confirmed or conflicting txs. if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; std::string unused_err_string; - if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) ++submitted_tx_count; + if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true, *locked_chain)) ++submitted_tx_count; } } // locked_chain and cs_wallet @@ -3323,7 +3329,7 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve if (fBroadcastTransactions) { std::string err_string; - if (!wtx.SubmitMemoryPoolAndRelay(err_string, true)) { + if (!wtx.SubmitMemoryPoolAndRelay(err_string, true, *locked_chain)) { WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 3a45c1ccc5..cf388ad827 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -580,7 +580,7 @@ public: int64_t GetTxTime() const; // Pass this transaction to node for mempool insertion and relay to peers if flag set to true - bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay); + bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain); // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py index 8e58c85c15..1ba781c539 100755 --- a/test/functional/interface_zmq.py +++ b/test/functional/interface_zmq.py @@ -7,15 +7,15 @@ import struct from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE from test_framework.test_framework import BitcoinTestFramework -from test_framework.messages import CTransaction -from test_framework.util import ( - assert_equal, - hash256, -) +from test_framework.messages import CTransaction, hash256 +from test_framework.util import assert_equal from io import BytesIO ADDRESS = "tcp://127.0.0.1:28332" +def hash256_reversed(byte_str): + return hash256(byte_str)[::-1] + class ZMQSubscriber: def __init__(self, socket, topic): self.sequence = 0 @@ -103,7 +103,7 @@ class ZMQTest (BitcoinTestFramework): # Should receive the generated raw block. block = self.rawblock.receive() - assert_equal(genhashes[x], hash256(block[:80]).hex()) + assert_equal(genhashes[x], hash256_reversed(block[:80]).hex()) if self.is_wallet_compiled(): self.log.info("Wait for tx from second node") @@ -116,7 +116,7 @@ class ZMQTest (BitcoinTestFramework): # Should receive the broadcasted raw transaction. hex = self.rawtx.receive() - assert_equal(payment_txid, hash256(hex).hex()) + assert_equal(payment_txid, hash256_reversed(hex).hex()) self.log.info("Test the getzmqnotifications RPC") diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py new file mode 100644 index 0000000000..1013055420 --- /dev/null +++ b/test/functional/p2p_permissions.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-2018 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 p2p permission message. + +Test that permissions are correctly calculated and applied +""" + +from test_framework.test_node import ErrorMatch +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + connect_nodes, + p2p_port, +) + +class P2PPermissionsTests(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.setup_clean_chain = True + self.extra_args = [[],[]] + + def run_test(self): + self.checkpermission( + # relay permission added + ["-whitelist=127.0.0.1", "-whitelistrelay"], + ["relay", "noban", "mempool"], + True) + + self.checkpermission( + # forcerelay and relay permission added + # Legacy parameter interaction which set whitelistrelay to true + # if whitelistforcerelay is true + ["-whitelist=127.0.0.1", "-whitelistforcerelay"], + ["forcerelay", "relay", "noban", "mempool"], + True) + + # Let's make sure permissions are merged correctly + # For this, we need to use whitebind instead of bind + # by modifying the configuration file. + ip_port = "127.0.0.1:{}".format(p2p_port(1)) + self.replaceinconfig(1, "bind=127.0.0.1", "whitebind=bloomfilter,forcerelay@" + ip_port) + self.checkpermission( + ["-whitelist=noban@127.0.0.1" ], + # Check parameter interaction forcerelay should activate relay + ["noban", "bloomfilter", "forcerelay", "relay" ], + False) + self.replaceinconfig(1, "whitebind=bloomfilter,forcerelay@" + ip_port, "bind=127.0.0.1") + + self.checkpermission( + # legacy whitelistrelay should be ignored + ["-whitelist=noban,mempool@127.0.0.1", "-whitelistrelay"], + ["noban", "mempool"], + False) + + self.checkpermission( + # legacy whitelistforcerelay should be ignored + ["-whitelist=noban,mempool@127.0.0.1", "-whitelistforcerelay"], + ["noban", "mempool"], + False) + + self.checkpermission( + # missing mempool permission to be considered legacy whitelisted + ["-whitelist=noban@127.0.0.1"], + ["noban"], + False) + + self.checkpermission( + # all permission added + ["-whitelist=all@127.0.0.1"], + ["forcerelay", "noban", "mempool", "bloomfilter", "relay"], + False) + + self.stop_node(1) + self.nodes[1].assert_start_raises_init_error(["-whitelist=oopsie@127.0.0.1"], "Invalid P2P permission", match=ErrorMatch.PARTIAL_REGEX) + self.nodes[1].assert_start_raises_init_error(["-whitelist=noban@127.0.0.1:230"], "Invalid netmask specified in", match=ErrorMatch.PARTIAL_REGEX) + self.nodes[1].assert_start_raises_init_error(["-whitebind=noban@127.0.0.1/10"], "Cannot resolve -whitebind address", match=ErrorMatch.PARTIAL_REGEX) + + def checkpermission(self, args, expectedPermissions, whitelisted): + self.restart_node(1, args) + connect_nodes(self.nodes[0], 1) + peerinfo = self.nodes[1].getpeerinfo()[0] + assert_equal(peerinfo['whitelisted'], whitelisted) + assert_equal(len(expectedPermissions), len(peerinfo['permissions'])) + for p in expectedPermissions: + if not p in peerinfo['permissions']: + raise AssertionError("Expected permissions %r is not granted." % p) + + def replaceinconfig(self, nodeid, old, new): + with open(self.nodes[nodeid].bitcoinconf, encoding="utf8") as f: + newText=f.read().replace(old, new) + with open(self.nodes[nodeid].bitcoinconf, 'w', encoding="utf8") as f: + f.write(newText) + +if __name__ == '__main__': + P2PPermissionsTests().main() diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index cac5281764..df027397d2 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -68,6 +68,7 @@ class TestNode(): self.index = i self.datadir = datadir + self.bitcoinconf = os.path.join(self.datadir, "bitcoin.conf") self.stdout_dir = os.path.join(self.datadir, "stdout") self.stderr_dir = os.path.join(self.datadir, "stderr") self.chain = chain diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 8730157c74..7beddc6407 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -7,7 +7,6 @@ from base64 import b64encode from binascii import unhexlify from decimal import Decimal, ROUND_DOWN -import hashlib import inspect import json import logging @@ -183,12 +182,6 @@ def check_json_precision(): def count_bytes(hex_string): return len(bytearray.fromhex(hex_string)) -def hash256(byte_str): - sha256 = hashlib.sha256() - sha256.update(byte_str) - sha256d = hashlib.sha256() - sha256d.update(sha256.digest()) - return sha256d.digest()[::-1] def hex_str_to_bytes(hex_str): return unhexlify(hex_str.encode('ascii')) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 082d006540..ea8dbbaf95 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -200,6 +200,7 @@ BASE_SCRIPTS = [ 'rpc_scantxoutset.py', 'feature_logging.py', 'p2p_node_network_limited.py', + 'p2p_permissions.py', 'feature_blocksdir.py', 'feature_config_args.py', 'rpc_help.py', |