aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am1
-rw-r--r--depends/funcs.mk13
-rw-r--r--depends/packages/native_cdrkit.mk2
-rw-r--r--depends/packages/native_libdmg-hfsplus.mk2
-rw-r--r--doc/developer-notes.md66
-rw-r--r--doc/release-notes-19671.md6
-rw-r--r--doc/zmq.md14
-rw-r--r--src/dummywallet.cpp1
-rw-r--r--src/init.cpp23
-rw-r--r--src/net.cpp112
-rw-r--r--src/net.h91
-rw-r--r--src/net_processing.cpp168
-rw-r--r--src/protocol.cpp2
-rw-r--r--src/protocol.h24
-rw-r--r--src/qt/walletmodel.cpp2
-rw-r--r--src/sync.cpp5
-rw-r--r--src/sync.h10
-rw-r--r--src/test/denialofservice_tests.cpp4
-rw-r--r--src/test/fuzz/net.cpp4
-rw-r--r--src/test/fuzz/process_message.cpp2
-rw-r--r--src/test/fuzz/process_messages.cpp2
-rw-r--r--src/test/net_tests.cpp14
-rw-r--r--src/validation.h2
-rw-r--r--src/wallet/init.cpp22
-rw-r--r--src/wallet/rpcwallet.cpp2
-rw-r--r--src/wallet/salvage.cpp9
-rw-r--r--src/wallet/scriptpubkeyman.h2
-rw-r--r--src/wallet/wallet.cpp57
-rw-r--r--src/wallet/wallet.h5
-rw-r--r--src/wallet/walletdb.cpp27
-rw-r--r--src/wallet/walletdb.h6
-rw-r--r--src/zmq/zmqpublishnotifier.cpp8
-rwxr-xr-xtest/functional/example_test.py3
-rwxr-xr-xtest/functional/feature_backwards_compatibility.py6
-rwxr-xr-xtest/functional/feature_versionbits_warning.py4
-rwxr-xr-xtest/functional/interface_zmq.py92
-rwxr-xr-xtest/functional/mempool_compatibility.py2
-rwxr-xr-xtest/functional/p2p_leak.py8
-rwxr-xr-xtest/functional/p2p_segwit.py3
-rw-r--r--test/functional/test_framework/key.py17
-rwxr-xr-xtest/functional/test_framework/messages.py9
-rw-r--r--test/functional/test_framework/muhash.py110
-rwxr-xr-xtest/functional/test_framework/p2p.py8
-rwxr-xr-xtest/functional/test_framework/test_framework.py6
-rwxr-xr-xtest/functional/test_framework/test_node.py8
-rw-r--r--test/functional/test_framework/util.py47
-rwxr-xr-xtest/functional/test_runner.py3
-rwxr-xr-xtest/functional/wallet_basic.py2
-rwxr-xr-xtest/functional/wallet_multiwallet.py5
-rwxr-xr-xtest/functional/wallet_upgradewallet.py2
-rwxr-xr-xtest/functional/wallet_zapwallettxes.py79
-rwxr-xr-xtest/lint/check-doc.py2
-rwxr-xr-xtest/lint/lint-cpp.sh21
53 files changed, 692 insertions, 453 deletions
diff --git a/Makefile.am b/Makefile.am
index 1d6358b1d5..8cdd3ff647 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -181,6 +181,7 @@ $(BITCOIN_WALLET_BIN): FORCE
if USE_LCOV
LCOV_FILTER_PATTERN = \
+ -p "/usr/local/" \
-p "/usr/include/" \
-p "/usr/lib/" \
-p "/usr/lib64/" \
diff --git a/depends/funcs.mk b/depends/funcs.mk
index 6fc20543bb..81ecbedf5c 100644
--- a/depends/funcs.mk
+++ b/depends/funcs.mk
@@ -157,12 +157,17 @@ ifneq ($($(1)_ldflags),)
$(1)_autoconf += LDFLAGS="$$($(1)_ldflags)"
endif
-$(1)_cmake=cmake -DCMAKE_INSTALL_PREFIX=$($($(1)_type)_prefix)
+$(1)_cmake=env CC="$$($(1)_cc)" \
+ CFLAGS="$$($(1)_cppflags) $$($(1)_cflags)" \
+ CXX="$$($(1)_cxx)" \
+ CXXFLAGS="$$($(1)_cppflags) $$($(1)_cxxflags)" \
+ LDFLAGS="$$($(1)_ldflags)" \
+ cmake -DCMAKE_INSTALL_PREFIX:PATH="$$($($(1)_type)_prefix)"
ifneq ($($(1)_type),build)
ifneq ($(host),$(build))
-$(1)_cmake += -DCMAKE_SYSTEM_NAME=$($(host_os)_cmake_system) -DCMAKE_SYSROOT=$(host_prefix)
-$(1)_cmake += -DCMAKE_C_COMPILER_TARGET=$(host) -DCMAKE_C_COMPILER=$(firstword $($($(1)_type)_CC)) -DCMAKE_C_FLAGS="$(wordlist 2,1000,$($($(1)_type)_CC))"
-$(1)_cmake += -DCMAKE_CXX_COMPILER_TARGET=$(host) -DCMAKE_CXX_COMPILER=$(firstword $($($(1)_type)_CXX)) -DCMAKE_CXX_FLAGS="$(wordlist 2,1000,$($($(1)_type)_CXX))"
+$(1)_cmake += -DCMAKE_SYSTEM_NAME=$($(host_os)_cmake_system)
+$(1)_cmake += -DCMAKE_C_COMPILER_TARGET=$(host)
+$(1)_cmake += -DCMAKE_CXX_COMPILER_TARGET=$(host)
endif
endif
endef
diff --git a/depends/packages/native_cdrkit.mk b/depends/packages/native_cdrkit.mk
index 14c37a0fc7..7bdf2d7dfd 100644
--- a/depends/packages/native_cdrkit.mk
+++ b/depends/packages/native_cdrkit.mk
@@ -12,7 +12,7 @@ endef
# Starting with 10.1, GCC defaults to -fno-common, resulting in linking errors.
# Pass -fcommon to retain the legacy behaviour.
define $(package)_config_cmds
- cmake -DCMAKE_INSTALL_PREFIX=$(build_prefix) -DCMAKE_C_FLAGS="-fcommon"
+ $($(package)_cmake) -DCMAKE_C_FLAGS="$$($(1)_cflags) -fcommon"
endef
define $(package)_build_cmds
diff --git a/depends/packages/native_libdmg-hfsplus.mk b/depends/packages/native_libdmg-hfsplus.mk
index c0f0ce74de..035b767188 100644
--- a/depends/packages/native_libdmg-hfsplus.mk
+++ b/depends/packages/native_libdmg-hfsplus.mk
@@ -12,7 +12,7 @@ define $(package)_preprocess_cmds
endef
define $(package)_config_cmds
- cmake -DCMAKE_INSTALL_PREFIX:PATH=$(build_prefix) -DCMAKE_C_FLAGS="-Wl,--build-id=none" ..
+ $($(package)_cmake) -DCMAKE_C_FLAGS="$$($(1)_cflags) -Wl,--build-id=none" ..
endef
define $(package)_build_cmds
diff --git a/doc/developer-notes.md b/doc/developer-notes.md
index 6ae7e770e8..ef9ecbb085 100644
--- a/doc/developer-notes.md
+++ b/doc/developer-notes.md
@@ -746,6 +746,72 @@ the upper cycle, etc.
Threads and synchronization
----------------------------
+- Prefer `Mutex` type to `RecursiveMutex` one
+
+- Consistently use [Clang Thread Safety Analysis](https://clang.llvm.org/docs/ThreadSafetyAnalysis.html) annotations to
+ get compile-time warnings about potential race conditions in code. Combine annotations in function declarations with
+ run-time asserts in function definitions:
+
+```C++
+// txmempool.h
+class CTxMemPool
+{
+public:
+ ...
+ mutable RecursiveMutex cs;
+ ...
+ void UpdateTransactionsFromBlock(...) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, cs);
+ ...
+}
+
+// txmempool.cpp
+void CTxMemPool::UpdateTransactionsFromBlock(...)
+{
+ AssertLockHeld(::cs_main);
+ AssertLockHeld(cs);
+ ...
+}
+```
+
+```C++
+// validation.h
+class ChainstateManager
+{
+public:
+ ...
+ bool ProcessNewBlock(...) EXCLUSIVE_LOCKS_REQUIRED(!::cs_main);
+ ...
+}
+
+// validation.cpp
+bool ChainstateManager::ProcessNewBlock(...)
+{
+ AssertLockNotHeld(::cs_main);
+ ...
+ LOCK(::cs_main);
+ ...
+}
+```
+
+- When Clang Thread Safety Analysis is unable to determine if a mutex is locked, use `LockAssertion` class instances:
+
+```C++
+// net_processing.h
+void RelayTransaction(...) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
+// net_processing.cpp
+void RelayTransaction(...)
+{
+ AssertLockHeld(::cs_main);
+
+ connman.ForEachNode([&txid, &wtxid](CNode* pnode) {
+ LockAssertion lock(::cs_main);
+ ...
+ });
+}
+
+```
+
- Build and run tests with `-DDEBUG_LOCKORDER` to verify that no potential
deadlocks are introduced. As of 0.12, this is defined by default when
configuring with `--enable-debug`.
diff --git a/doc/release-notes-19671.md b/doc/release-notes-19671.md
new file mode 100644
index 0000000000..fb2d56d9a5
--- /dev/null
+++ b/doc/release-notes-19671.md
@@ -0,0 +1,6 @@
+Wallet
+------
+
+* The `-zapwallettxes` startup option has been removed and its functionality removed from the wallet.
+ This option was originally intended to allow for the fee bumping of transactions that did not
+ signal RBF. This functionality has been superseded with the abandon transaction feature.
diff --git a/doc/zmq.md b/doc/zmq.md
index 3a1194de1c..ac26fc0a48 100644
--- a/doc/zmq.md
+++ b/doc/zmq.md
@@ -98,6 +98,20 @@ ZMQ_SUBSCRIBE option set to one or either of these prefixes (for
instance, just `hash`); without doing so will result in no messages
arriving. Please see [`contrib/zmq/zmq_sub.py`](/contrib/zmq/zmq_sub.py) for a working example.
+The ZMQ_PUB socket's ZMQ_TCP_KEEPALIVE option is enabled. This means that
+the underlying SO_KEEPALIVE option is enabled when using a TCP transport.
+The effective TCP keepalive values are managed through the underlying
+operating system configuration and must be configured prior to connection establishment.
+
+For example, when running on GNU/Linux, one might use the following
+to lower the keepalive setting to 10 minutes:
+
+sudo sysctl -w net.ipv4.tcp_keepalive_time=600
+
+Setting the keepalive values appropriately for your operating environment may
+improve connectivity in situations where long-lived connections are silently
+dropped by network middle boxes.
+
## Remarks
From the perspective of bitcoind, the ZeroMQ socket is write-only; PUB
diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp
index e54c2daaeb..8d2dcd0279 100644
--- a/src/dummywallet.cpp
+++ b/src/dummywallet.cpp
@@ -46,7 +46,6 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const
"-walletdir=<dir>",
"-walletnotify=<cmd>",
"-walletrbf",
- "-zapwallettxes=<mode>",
"-dblogsize=<n>",
"-flushwallet",
"-privdb",
diff --git a/src/init.cpp b/src/init.cpp
index 4b689d6153..bb93f5c797 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1833,20 +1833,15 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
}
#if HAVE_SYSTEM
- if (args.IsArgSet("-blocknotify")) {
- const std::string block_notify = args.GetArg("-blocknotify", "");
- const auto BlockNotifyCallback = [block_notify](SynchronizationState sync_state, const CBlockIndex* pBlockIndex) {
- if (sync_state != SynchronizationState::POST_INIT || !pBlockIndex)
- return;
-
- std::string strCmd = block_notify;
- if (!strCmd.empty()) {
- boost::replace_all(strCmd, "%s", pBlockIndex->GetBlockHash().GetHex());
- std::thread t(runCommand, strCmd);
- t.detach(); // thread runs free
- }
- };
- uiInterface.NotifyBlockTip_connect(BlockNotifyCallback);
+ const std::string block_notify = args.GetArg("-blocknotify", "");
+ if (!block_notify.empty()) {
+ uiInterface.NotifyBlockTip_connect([block_notify](SynchronizationState sync_state, const CBlockIndex* pBlockIndex) {
+ if (sync_state != SynchronizationState::POST_INIT || !pBlockIndex) return;
+ std::string command = block_notify;
+ boost::replace_all(command, "%s", pBlockIndex->GetBlockHash().GetHex());
+ std::thread t(runCommand, command);
+ t.detach(); // thread runs free
+ });
}
#endif
diff --git a/src/net.cpp b/src/net.cpp
index 883e57bdf0..e35d05cec0 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -816,6 +816,7 @@ struct NodeEvictionCandidate
CAddress addr;
uint64_t nKeyedNetGroup;
bool prefer_evict;
+ bool m_is_local;
};
static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
@@ -828,6 +829,12 @@ static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a, cons
return a.nTimeConnected > b.nTimeConnected;
}
+static bool CompareLocalHostTimeConnected(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
+{
+ if (a.m_is_local != b.m_is_local) return b.m_is_local;
+ return a.nTimeConnected > b.nTimeConnected;
+}
+
static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) {
return a.nKeyedNetGroup < b.nKeyedNetGroup;
}
@@ -849,6 +856,14 @@ static bool CompareNodeTXTime(const NodeEvictionCandidate &a, const NodeEviction
return a.nTimeConnected > b.nTimeConnected;
}
+// Pick out the potential block-relay only peers, and sort them by last block time.
+static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
+{
+ if (a.fRelayTxes != b.fRelayTxes) return a.fRelayTxes;
+ if (a.nLastBlockTime != b.nLastBlockTime) return a.nLastBlockTime < b.nLastBlockTime;
+ if (a.fRelevantServices != b.fRelevantServices) return b.fRelevantServices;
+ return a.nTimeConnected > b.nTimeConnected;
+}
//! Sort an array by the specified comparator, then erase the last K elements.
template<typename T, typename Comparator>
@@ -891,7 +906,7 @@ bool CConnman::AttemptToEvictConnection()
node->nLastBlockTime, node->nLastTXTime,
HasAllDesirableServiceFlags(node->nServices),
peer_relay_txes, peer_filter_not_null, node->addr, node->nKeyedNetGroup,
- node->m_prefer_evict};
+ node->m_prefer_evict, node->addr.IsLocal()};
vEvictionCandidates.push_back(candidate);
}
}
@@ -904,15 +919,34 @@ bool CConnman::AttemptToEvictConnection()
// Protect the 8 nodes with the lowest minimum ping time.
// An attacker cannot manipulate this metric without physically moving nodes closer to the target.
EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8);
- // Protect 4 nodes that most recently sent us transactions.
+ // Protect 4 nodes that most recently sent us novel transactions accepted into our mempool.
// An attacker cannot manipulate this metric without performing useful work.
EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4);
- // Protect 4 nodes that most recently sent us blocks.
+ // Protect up to 8 non-tx-relay peers that have sent us novel blocks.
+ std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNodeBlockRelayOnlyTime);
+ size_t erase_size = std::min(size_t(8), vEvictionCandidates.size());
+ vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return !n.fRelayTxes && n.fRelevantServices; }), vEvictionCandidates.end());
+
+ // Protect 4 nodes that most recently sent us novel blocks.
// An attacker cannot manipulate this metric without performing useful work.
EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4);
+
// Protect the half of the remaining nodes which have been connected the longest.
// This replicates the non-eviction implicit behavior, and precludes attacks that start later.
- EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, vEvictionCandidates.size() / 2);
+ // Reserve half of these protected spots for localhost peers, even if
+ // they're not longest-uptime overall. This helps protect tor peers, which
+ // tend to be otherwise disadvantaged under our eviction criteria.
+ size_t initial_size = vEvictionCandidates.size();
+ size_t total_protect_size = initial_size / 2;
+
+ // Pick out up to 1/4 peers that are localhost, sorted by longest uptime.
+ std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareLocalHostTimeConnected);
+ size_t local_erase_size = total_protect_size / 2;
+ vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - local_erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return n.m_is_local; }), vEvictionCandidates.end());
+ // Calculate how many we removed, and update our total number of peers that
+ // we want to protect based on uptime accordingly.
+ total_protect_size -= initial_size - vEvictionCandidates.size();
+ EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size);
if (vEvictionCandidates.empty()) return false;
@@ -1843,41 +1877,45 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
// but inbound and manual peers do not use our outbound slots. Inbound peers
// also have the added issue that they could be attacker controlled and used
// to prevent us from connecting to particular hosts if we used them here.
- switch(pnode->m_conn_type){
+ switch (pnode->m_conn_type) {
case ConnectionType::INBOUND:
case ConnectionType::MANUAL:
break;
- case ConnectionType::OUTBOUND:
+ case ConnectionType::OUTBOUND_FULL_RELAY:
case ConnectionType::BLOCK_RELAY:
case ConnectionType::ADDR_FETCH:
case ConnectionType::FEELER:
setConnected.insert(pnode->addr.GetGroup(addrman.m_asmap));
- }
+ } // no default case, so the compiler can warn about missing cases
}
}
- // Feeler Connections
- //
- // Design goals:
- // * Increase the number of connectable addresses in the tried table.
- //
- // Method:
- // * Choose a random address from new and attempt to connect to it if we can connect
- // successfully it is added to tried.
- // * Start attempting feeler connections only after node finishes making outbound
- // connections.
- // * Only make a feeler connection once every few minutes.
- //
+ ConnectionType conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
+ int64_t nTime = GetTimeMicros();
bool fFeeler = false;
- if (nOutboundFullRelay >= m_max_outbound_full_relay && nOutboundBlockRelay >= m_max_outbound_block_relay && !GetTryNewOutboundPeer()) {
- int64_t nTime = GetTimeMicros(); // The current time right now (in microseconds).
- if (nTime > nNextFeeler) {
- nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL);
- fFeeler = true;
- } else {
- continue;
- }
+ // Determine what type of connection to open. Opening
+ // OUTBOUND_FULL_RELAY connections gets the highest priority until we
+ // meet our full-relay capacity. Then we open BLOCK_RELAY connection
+ // until we hit our block-relay-only peer limit.
+ // GetTryNewOutboundPeer() gets set when a stale tip is detected, so we
+ // try opening an additional OUTBOUND_FULL_RELAY connection. If none of
+ // these conditions are met, check the nNextFeeler timer to decide if
+ // we should open a FEELER.
+
+ if (nOutboundFullRelay < m_max_outbound_full_relay) {
+ // OUTBOUND_FULL_RELAY
+ } else if (nOutboundBlockRelay < m_max_outbound_block_relay) {
+ conn_type = ConnectionType::BLOCK_RELAY;
+ } else if (GetTryNewOutboundPeer()) {
+ // OUTBOUND_FULL_RELAY
+ } else if (nTime > nNextFeeler) {
+ nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL);
+ conn_type = ConnectionType::FEELER;
+ fFeeler = true;
+ } else {
+ // skip to next iteration of while loop
+ continue;
}
addrman.ResolveCollisions();
@@ -1944,23 +1982,6 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToString());
}
- ConnectionType conn_type;
- // Determine what type of connection to open. If fFeeler is not
- // set, open OUTBOUND connections until we meet our full-relay
- // capacity. Then open BLOCK_RELAY connections until we hit our
- // block-relay peer limit. Otherwise, default to opening an
- // OUTBOUND connection.
- if (fFeeler) {
- conn_type = ConnectionType::FEELER;
- } else if (nOutboundFullRelay < m_max_outbound_full_relay) {
- conn_type = ConnectionType::OUTBOUND;
- } else if (nOutboundBlockRelay < m_max_outbound_block_relay) {
- conn_type = ConnectionType::BLOCK_RELAY;
- } else {
- // GetTryNewOutboundPeer() is true
- conn_type = ConnectionType::OUTBOUND;
- }
-
OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, conn_type);
}
}
@@ -2784,6 +2805,9 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn
hashContinue = uint256();
if (conn_type_in != ConnectionType::BLOCK_RELAY) {
m_tx_relay = MakeUnique<TxRelay>();
+ }
+
+ if (RelayAddrsWithConn()) {
m_addr_known = MakeUnique<CRollingBloomFilter>(5000, 0.001);
}
diff --git a/src/net.h b/src/net.h
index c72eada3ff..60c3dc6aef 100644
--- a/src/net.h
+++ b/src/net.h
@@ -118,12 +118,54 @@ struct CSerializedNetMsg
* information we have available at the time of opening or accepting the
* connection. Aside from INBOUND, all types are initiated by us. */
enum class ConnectionType {
- INBOUND, /**< peer initiated connections */
- OUTBOUND, /**< full relay connections (blocks, addrs, txns) made automatically. Addresses selected from AddrMan. */
- MANUAL, /**< connections to addresses added via addnode or the connect command line argument */
- FEELER, /**< short lived connections used to test address validity */
- BLOCK_RELAY, /**< only relay blocks to these automatic outbound connections. Addresses selected from AddrMan. */
- ADDR_FETCH, /**< short lived connections used to solicit addrs when starting the node without a populated AddrMan */
+ /**
+ * Inbound connections are those initiated by a peer. This is the only
+ * property we know at the time of connection, until P2P messages are
+ * exchanged.
+ */
+ INBOUND,
+
+ /**
+ * These are the default connections that we use to connect with the
+ * network. There is no restriction on what is relayed- by default we relay
+ * blocks, addresses & transactions. We automatically attempt to open
+ * MAX_OUTBOUND_FULL_RELAY_CONNECTIONS using addresses from our AddrMan.
+ */
+ OUTBOUND_FULL_RELAY,
+
+
+ /**
+ * We open manual connections to addresses that users explicitly inputted
+ * via the addnode RPC, or the -connect command line argument. Even if a
+ * manual connection is misbehaving, we do not automatically disconnect or
+ * add it to our discouragement filter.
+ */
+ MANUAL,
+
+ /**
+ * Feeler connections are short lived connections used to increase the
+ * number of connectable addresses in our AddrMan. Approximately every
+ * FEELER_INTERVAL, we attempt to connect to a random address from the new
+ * table. If successful, we add it to the tried table.
+ */
+ FEELER,
+
+ /**
+ * We use block-relay-only connections to help prevent against partition
+ * attacks. By not relaying transactions or addresses, these connections
+ * are harder to detect by a third party, thus helping obfuscate the
+ * network topology. We automatically attempt to open
+ * MAX_BLOCK_RELAY_ONLY_CONNECTIONS using addresses from our AddrMan.
+ */
+ BLOCK_RELAY,
+
+ /**
+ * AddrFetch connections are short lived connections used to solicit
+ * addresses from peers. These are initiated to addresses submitted via the
+ * -seednode command line argument, or under certain conditions when the
+ * AddrMan is empty.
+ */
+ ADDR_FETCH,
};
class NetEventsInterface;
@@ -209,7 +251,7 @@ public:
bool GetNetworkActive() const { return fNetworkActive; };
bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; };
void SetNetworkActive(bool active);
- void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, ConnectionType conn_type = ConnectionType::OUTBOUND);
+ void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant* grantOutbound, const char* strDest, ConnectionType conn_type);
bool CheckIncomingNonce(uint64_t nonce);
bool ForNode(NodeId id, std::function<bool(CNode* pnode)> func);
@@ -823,8 +865,8 @@ public:
std::atomic_bool fPauseSend{false};
bool IsOutboundOrBlockRelayConn() const {
- switch(m_conn_type) {
- case ConnectionType::OUTBOUND:
+ switch (m_conn_type) {
+ case ConnectionType::OUTBOUND_FULL_RELAY:
case ConnectionType::BLOCK_RELAY:
return true;
case ConnectionType::INBOUND:
@@ -832,13 +874,13 @@ public:
case ConnectionType::ADDR_FETCH:
case ConnectionType::FEELER:
return false;
- }
+ } // no default case, so the compiler can warn about missing cases
assert(false);
}
bool IsFullOutboundConn() const {
- return m_conn_type == ConnectionType::OUTBOUND;
+ return m_conn_type == ConnectionType::OUTBOUND_FULL_RELAY;
}
bool IsManualConn() const {
@@ -861,17 +903,23 @@ public:
return m_conn_type == ConnectionType::INBOUND;
}
+ /* Whether we send addr messages over this connection */
+ bool RelayAddrsWithConn() const
+ {
+ return m_conn_type != ConnectionType::BLOCK_RELAY;
+ }
+
bool ExpectServicesFromConn() const {
- switch(m_conn_type) {
+ switch (m_conn_type) {
case ConnectionType::INBOUND:
case ConnectionType::MANUAL:
case ConnectionType::FEELER:
return false;
- case ConnectionType::OUTBOUND:
+ case ConnectionType::OUTBOUND_FULL_RELAY:
case ConnectionType::BLOCK_RELAY:
case ConnectionType::ADDR_FETCH:
return true;
- }
+ } // no default case, so the compiler can warn about missing cases
assert(false);
}
@@ -886,13 +934,11 @@ public:
// flood relay
std::vector<CAddress> vAddrToSend;
- std::unique_ptr<CRollingBloomFilter> m_addr_known = nullptr;
+ std::unique_ptr<CRollingBloomFilter> m_addr_known{nullptr};
bool fGetAddr{false};
std::chrono::microseconds m_next_addr_send GUARDED_BY(cs_sendProcessing){0};
std::chrono::microseconds m_next_local_addr_send GUARDED_BY(cs_sendProcessing){0};
- bool IsAddrRelayPeer() const { return m_addr_known != nullptr; }
-
// List of block ids we still have announce.
// There is no final sorting before sending, as they are always sent immediately
// and in the order requested.
@@ -932,8 +978,17 @@ public:
// Used for headers announcements - unfiltered blocks to relay
std::vector<uint256> vBlockHashesToAnnounce GUARDED_BY(cs_inventory);
- // Block and TXN accept times
+ /** UNIX epoch time of the last block received from this peer that we had
+ * not yet seen (e.g. not already received from another peer), that passed
+ * preliminary validity checks and was saved to disk, even if we don't
+ * connect the block or it eventually fails connection. Used as an inbound
+ * peer eviction criterium in CConnman::AttemptToEvictConnection. */
std::atomic<int64_t> nLastBlockTime{0};
+
+ /** UNIX epoch time of the last transaction received from this peer that we
+ * had not yet seen (e.g. not already received from another peer) and that
+ * was accepted into our mempool. Used as an inbound peer eviction criterium
+ * in CConnman::AttemptToEvictConnection. */
std::atomic<int64_t> nLastTXTime{0};
// Ping time measurement:
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index adad428413..ce4ac3cd75 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -663,13 +663,12 @@ static void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman& connma
}
}
connman.ForNode(nodeid, [&connman](CNode* pfrom){
- AssertLockHeld(cs_main);
+ LockAssertion lock(::cs_main);
uint64_t nCMPCTBLOCKVersion = (pfrom->GetLocalServices() & NODE_WITNESS) ? 2 : 1;
if (lNodesAnnouncingHeaderAndIDs.size() >= 3) {
// As per BIP152, we only get 3 of our peers to announce
// blocks using compact encodings.
connman.ForNode(lNodesAnnouncingHeaderAndIDs.front(), [&connman, nCMPCTBLOCKVersion](CNode* pnodeStop){
- AssertLockHeld(cs_main);
connman.PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetSendVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/false, nCMPCTBLOCKVersion));
return true;
});
@@ -883,8 +882,9 @@ void PeerLogicValidation::InitializeNode(CNode *pnode) {
LOCK(g_peer_mutex);
g_peer_map.emplace_hint(g_peer_map.end(), nodeid, std::move(peer));
}
- if(!pnode->IsInboundConn())
+ if (!pnode->IsInboundConn()) {
PushNodeVersion(*pnode, m_connman, GetTime());
+ }
}
void PeerLogicValidation::ReattemptInitialBroadcast(CScheduler& scheduler) const
@@ -1371,7 +1371,7 @@ void PeerLogicValidation::NewPoWValidBlock(const CBlockIndex *pindex, const std:
}
m_connman.ForEachNode([this, &pcmpctblock, pindex, &msgMaker, fWitnessEnabled, &hashBlock](CNode* pnode) {
- AssertLockHeld(cs_main);
+ LockAssertion lock(::cs_main);
// TODO: Avoid the repeated-serialization here
if (pnode->nVersion < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect)
@@ -1466,54 +1466,48 @@ void PeerLogicValidation::BlockChecked(const CBlock& block, const BlockValidatio
//
-bool static AlreadyHave(const CInv& inv, const CTxMemPool& mempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+bool static AlreadyHaveTx(const GenTxid& gtxid, const CTxMemPool& mempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
- switch (inv.type)
- {
- case MSG_TX:
- case MSG_WITNESS_TX:
- case MSG_WTX:
- {
- assert(recentRejects);
- if (::ChainActive().Tip()->GetBlockHash() != hashRecentRejectsChainTip)
- {
- // If the chain tip has changed previously rejected transactions
- // might be now valid, e.g. due to a nLockTime'd tx becoming valid,
- // or a double-spend. Reset the rejects filter and give those
- // txs a second chance.
- hashRecentRejectsChainTip = ::ChainActive().Tip()->GetBlockHash();
- recentRejects->reset();
- }
-
- {
- LOCK(g_cs_orphans);
- if (!inv.IsMsgWtx() && mapOrphanTransactions.count(inv.hash)) {
- return true;
- } else if (inv.IsMsgWtx() && g_orphans_by_wtxid.count(inv.hash)) {
- return true;
- }
- }
+ assert(recentRejects);
+ if (::ChainActive().Tip()->GetBlockHash() != hashRecentRejectsChainTip) {
+ // If the chain tip has changed previously rejected transactions
+ // might be now valid, e.g. due to a nLockTime'd tx becoming valid,
+ // or a double-spend. Reset the rejects filter and give those
+ // txs a second chance.
+ hashRecentRejectsChainTip = ::ChainActive().Tip()->GetBlockHash();
+ recentRejects->reset();
+ }
- {
- LOCK(g_cs_recent_confirmed_transactions);
- if (g_recent_confirmed_transactions->contains(inv.hash)) return true;
- }
+ const uint256& hash = gtxid.GetHash();
- return recentRejects->contains(inv.hash) || mempool.exists(ToGenTxid(inv));
+ {
+ LOCK(g_cs_orphans);
+ if (!gtxid.IsWtxid() && mapOrphanTransactions.count(hash)) {
+ return true;
+ } else if (gtxid.IsWtxid() && g_orphans_by_wtxid.count(hash)) {
+ return true;
}
- case MSG_BLOCK:
- case MSG_WITNESS_BLOCK:
- return LookupBlockIndex(inv.hash) != nullptr;
}
- // Don't know what it is, just say we already got one
- return true;
+
+ {
+ LOCK(g_cs_recent_confirmed_transactions);
+ if (g_recent_confirmed_transactions->contains(hash)) return true;
+ }
+
+ return recentRejects->contains(hash) || mempool.exists(gtxid);
+}
+
+bool static AlreadyHaveBlock(const uint256& block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+{
+ return LookupBlockIndex(block_hash) != nullptr;
}
void RelayTransaction(const uint256& txid, const uint256& wtxid, const CConnman& connman)
{
connman.ForEachNode([&txid, &wtxid](CNode* pnode)
{
- AssertLockHeld(cs_main);
+ LockAssertion lock(::cs_main);
+
CNodeState &state = *State(pnode->GetId());
if (state.m_wtxid_relay) {
pnode->PushTxInventory(wtxid);
@@ -1538,7 +1532,7 @@ static void RelayAddress(const CAddress& addr, bool fReachable, const CConnman&
assert(nRelayNodes <= best.size());
auto sortfunc = [&best, &hasher, nRelayNodes](CNode* pnode) {
- if (pnode->IsAddrRelayPeer()) {
+ if (pnode->RelayAddrsWithConn()) {
uint64_t hashKey = CSipHasher(hasher).Write(pnode->GetId()).Finalize();
for (unsigned int i = 0; i < nRelayNodes; i++) {
if (hashKey > best[i].first) {
@@ -1608,7 +1602,7 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
// disconnect node in case we have reached the outbound limit for serving historical blocks
if (send &&
connman.OutboundTargetReached(true) &&
- (((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.type == MSG_FILTERED_BLOCK) &&
+ (((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.IsMsgFilteredBlk()) &&
!pfrom.HasPermission(PF_DOWNLOAD) // nodes with the download permission may exceed target
) {
LogPrint(BCLog::NET, "historical block serving limit reached, disconnect peer=%d\n", pfrom.GetId());
@@ -1634,7 +1628,7 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
std::shared_ptr<const CBlock> pblock;
if (a_recent_block && a_recent_block->GetHash() == pindex->GetBlockHash()) {
pblock = a_recent_block;
- } else if (inv.type == MSG_WITNESS_BLOCK) {
+ } else if (inv.IsMsgWitnessBlk()) {
// Fast-path: in this case it is possible to serve the block directly from disk,
// as the network format matches the format on disk
std::vector<uint8_t> block_data;
@@ -1651,12 +1645,11 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
pblock = pblockRead;
}
if (pblock) {
- if (inv.type == MSG_BLOCK)
+ if (inv.IsMsgBlk()) {
connman.PushMessage(&pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::BLOCK, *pblock));
- else if (inv.type == MSG_WITNESS_BLOCK)
+ } else if (inv.IsMsgWitnessBlk()) {
connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCK, *pblock));
- else if (inv.type == MSG_FILTERED_BLOCK)
- {
+ } else if (inv.IsMsgFilteredBlk()) {
bool sendMerkleBlock = false;
CMerkleBlock merkleBlock;
if (pfrom.m_tx_relay != nullptr) {
@@ -1680,9 +1673,7 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
}
// else
// no response
- }
- else if (inv.type == MSG_CMPCT_BLOCK)
- {
+ } else if (inv.IsMsgCmpctBlk()) {
// If a peer is asking for old blocks, we're almost guaranteed
// they won't have a useful mempool to match against a compact block,
// and we don't feel like constructing the object for them, so
@@ -1810,7 +1801,7 @@ void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnm
// expensive to process.
if (it != pfrom.vRecvGetData.end() && !pfrom.fPauseSend) {
const CInv &inv = *it++;
- if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK || inv.type == MSG_CMPCT_BLOCK || inv.type == MSG_WITNESS_BLOCK) {
+ if (inv.IsGenBlkMsg()) {
ProcessGetBlockData(pfrom, chainparams, inv, connman);
}
// else: If the first item on the queue is an unknown type, we erase it
@@ -2027,7 +2018,7 @@ static void ProcessHeadersMessage(CNode& pfrom, CConnman& connman, ChainstateMan
}
}
- if (!pfrom.fDisconnect && pfrom.IsOutboundOrBlockRelayConn() && nodestate->pindexBestKnownBlock != nullptr && pfrom.m_tx_relay != nullptr) {
+ if (!pfrom.fDisconnect && pfrom.IsFullOutboundConn() && nodestate->pindexBestKnownBlock != nullptr) {
// If this is an outbound full-relay peer, check to see if we should protect
// it from the bad/lagging chain logic.
// Note that block-relay-only peers are already implicitly protected, so we
@@ -2468,9 +2459,23 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
UpdatePreferredDownload(pfrom, State(pfrom.GetId()));
}
- if (!pfrom.IsInboundConn() && pfrom.IsAddrRelayPeer())
- {
- // Advertise our address
+ if (!pfrom.IsInboundConn() && !pfrom.IsBlockOnlyConn()) {
+ // For outbound peers, we try to relay our address (so that other
+ // nodes can try to find us more quickly, as we have no guarantee
+ // that an outbound peer is even aware of how to reach us) and do a
+ // one-time address fetch (to help populate/update our addrman). If
+ // we're starting up for the first time, our addrman may be pretty
+ // empty and no one will know who we are, so these mechanisms are
+ // important to help us connect to the network.
+ //
+ // We also update the addrman to record connection success for
+ // these peers (which include OUTBOUND_FULL_RELAY and FEELER
+ // connections) so that addrman will have an up-to-date notion of
+ // which peers are online and available.
+ //
+ // We skip these operations for BLOCK_RELAY peers to avoid
+ // potentially leaking information about our BLOCK_RELAY
+ // connections via the addrman or address relay.
if (fListen && !::ChainstateActive().IsInitialBlockDownload())
{
CAddress addr = GetLocalAddress(&pfrom.addr, pfrom.GetLocalServices());
@@ -2489,6 +2494,9 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
// Get recent addresses
m_connman.PushMessage(&pfrom, CNetMsgMaker(nSendVersion).Make(NetMsgType::GETADDR));
pfrom.fGetAddr = true;
+
+ // Moves address from New to Tried table in Addrman, resolves
+ // tried-table collisions, etc.
m_connman.MarkAddressGood(pfrom.addr);
}
@@ -2594,7 +2602,7 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
std::vector<CAddress> vAddr;
vRecv >> vAddr;
- if (!pfrom.IsAddrRelayPeer()) {
+ if (!pfrom.RelayAddrsWithConn()) {
return;
}
if (vAddr.size() > MAX_ADDR_TO_SEND)
@@ -2692,14 +2700,11 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
LOCK(cs_main);
- uint32_t nFetchFlags = GetFetchFlags(pfrom);
const auto current_time = GetTime<std::chrono::microseconds>();
uint256* best_block{nullptr};
- for (CInv &inv : vInv)
- {
- if (interruptMsgProc)
- return;
+ for (CInv& inv : vInv) {
+ if (interruptMsgProc) return;
// Ignore INVs that don't match wtxidrelay setting.
// Note that orphan parent fetching always uses MSG_TX GETDATAs regardless of the wtxidrelay setting.
@@ -2710,14 +2715,10 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
if (inv.IsMsgWtx()) continue;
}
- bool fAlreadyHave = AlreadyHave(inv, m_mempool);
- LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId());
+ if (inv.IsMsgBlk()) {
+ const bool fAlreadyHave = AlreadyHaveBlock(inv.hash);
+ LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId());
- if (inv.IsMsgTx()) {
- inv.type |= nFetchFlags;
- }
-
- if (inv.type == MSG_BLOCK) {
UpdateBlockAvailability(pfrom.GetId(), inv.hash);
if (!fAlreadyHave && !fImporting && !fReindex && !mapBlocksInFlight.count(inv.hash)) {
// Headers-first is the primary method of announcement on
@@ -2727,15 +2728,21 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
// then fetch the blocks we need to catch up.
best_block = &inv.hash;
}
- } else {
+ } else if (inv.IsGenTxMsg()) {
+ const GenTxid gtxid = ToGenTxid(inv);
+ const bool fAlreadyHave = AlreadyHaveTx(gtxid, mempool);
+ LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId());
+
pfrom.AddKnownTx(inv.hash);
if (fBlocksOnly) {
LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol, disconnecting peer=%d\n", inv.hash.ToString(), pfrom.GetId());
pfrom.fDisconnect = true;
return;
} else if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
- RequestTx(State(pfrom.GetId()), ToGenTxid(inv), current_time);
+ RequestTx(State(pfrom.GetId()), gtxid, current_time);
}
+ } else {
+ LogPrint(BCLog::NET, "Unknown inv type \"%s\" received from peer=%d\n", inv.ToString(), pfrom.GetId());
}
}
@@ -3006,7 +3013,7 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
// already; and an adversary can already relay us old transactions
// (older than our recency filter) if trying to DoS us, without any need
// for witness malleation.
- if (!AlreadyHave(CInv(MSG_WTX, wtxid), m_mempool) &&
+ if (!AlreadyHaveTx(GenTxid(/* is_wtxid=*/true, wtxid), m_mempool) &&
AcceptToMemoryPool(m_mempool, state, ptx, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
m_mempool.check(&::ChainstateActive().CoinsTip());
RelayTransaction(tx.GetHash(), tx.GetWitnessHash(), m_connman);
@@ -3050,7 +3057,6 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
}
}
if (!fRejectedParents) {
- uint32_t nFetchFlags = GetFetchFlags(pfrom);
const auto current_time = GetTime<std::chrono::microseconds>();
for (const uint256& parent_txid : unique_parents) {
@@ -3059,9 +3065,9 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
// wtxidrelay peers.
// Eventually we should replace this with an improved
// protocol for getting all unconfirmed parents.
- CInv _inv(MSG_TX | nFetchFlags, parent_txid);
+ const GenTxid gtxid{/* is_wtxid=*/false, parent_txid};
pfrom.AddKnownTx(parent_txid);
- if (!AlreadyHave(_inv, m_mempool)) RequestTx(State(pfrom.GetId()), ToGenTxid(_inv), current_time);
+ if (!AlreadyHaveTx(gtxid, m_mempool)) RequestTx(State(pfrom.GetId()), gtxid, current_time);
}
AddOrphanTx(ptx, pfrom.GetId());
@@ -3534,7 +3540,7 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
LogPrint(BCLog::NET, "Ignoring \"getaddr\" from outbound connection. peer=%d\n", pfrom.GetId());
return;
}
- if (!pfrom.IsAddrRelayPeer()) {
+ if (!pfrom.RelayAddrsWithConn()) {
LogPrint(BCLog::NET, "Ignoring \"getaddr\" from block-relay-only connection. peer=%d\n", pfrom.GetId());
return;
}
@@ -3993,7 +3999,7 @@ void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds)
int64_t oldest_block_announcement = std::numeric_limits<int64_t>::max();
m_connman.ForEachNode([&](CNode* pnode) {
- AssertLockHeld(cs_main);
+ LockAssertion lock(::cs_main);
// Ignore non-outbound peers, or nodes marked for disconnect already
if (!pnode->IsOutboundOrBlockRelayConn() || pnode->fDisconnect) return;
@@ -4010,7 +4016,7 @@ void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds)
});
if (worst_peer != -1) {
bool disconnected = m_connman.ForNode(worst_peer, [&](CNode *pnode) {
- AssertLockHeld(cs_main);
+ LockAssertion lock(::cs_main);
// Only disconnect a peer that has been connected to us for
// some reasonable fraction of our check-frequency, to give
@@ -4134,7 +4140,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
int64_t nNow = GetTimeMicros();
auto current_time = GetTime<std::chrono::microseconds>();
- if (pto->IsAddrRelayPeer() && !::ChainstateActive().IsInitialBlockDownload() && pto->m_next_local_addr_send < current_time) {
+ if (pto->RelayAddrsWithConn() && !::ChainstateActive().IsInitialBlockDownload() && pto->m_next_local_addr_send < current_time) {
AdvertiseLocal(pto);
pto->m_next_local_addr_send = PoissonNextSend(current_time, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL);
}
@@ -4142,7 +4148,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
//
// Message: addr
//
- if (pto->IsAddrRelayPeer() && pto->m_next_addr_send < current_time) {
+ if (pto->RelayAddrsWithConn() && pto->m_next_addr_send < current_time) {
pto->m_next_addr_send = PoissonNextSend(current_time, AVG_ADDRESS_BROADCAST_INTERVAL);
std::vector<CAddress> vAddr;
vAddr.reserve(pto->vAddrToSend.size());
@@ -4611,7 +4617,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
// processing at a later time, see below)
tx_process_time.erase(tx_process_time.begin());
CInv inv(gtxid.IsWtxid() ? MSG_WTX : (MSG_TX | GetFetchFlags(*pto)), gtxid.GetHash());
- if (!AlreadyHave(inv, m_mempool)) {
+ if (!AlreadyHaveTx(ToGenTxid(inv), m_mempool)) {
// If this transaction was last requested more than 1 minute ago,
// then request.
const auto last_request_time = GetTxRequestTime(gtxid);
diff --git a/src/protocol.cpp b/src/protocol.cpp
index c989aa3902..1f2e628545 100644
--- a/src/protocol.cpp
+++ b/src/protocol.cpp
@@ -163,7 +163,7 @@ CInv::CInv()
hash.SetNull();
}
-CInv::CInv(int typeIn, const uint256& hashIn) : type(typeIn), hash(hashIn) {}
+CInv::CInv(uint32_t typeIn, const uint256& hashIn) : type(typeIn), hash(hashIn) {}
bool operator<(const CInv& a, const CInv& b)
{
diff --git a/src/protocol.h b/src/protocol.h
index 2e6c767cdd..7fb84cddf1 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -247,7 +247,7 @@ extern const char* CFCHECKPT;
* txid.
* @since protocol version 70016 as described by BIP 339.
*/
-extern const char *WTXIDRELAY;
+extern const char* WTXIDRELAY;
}; // namespace NetMsgType
/* Get a vector of all valid message types (see above) */
@@ -408,7 +408,7 @@ class CInv
{
public:
CInv();
- CInv(int typeIn, const uint256& hashIn);
+ CInv(uint32_t typeIn, const uint256& hashIn);
SERIALIZE_METHODS(CInv, obj) { READWRITE(obj.type, obj.hash); }
@@ -418,14 +418,24 @@ public:
std::string ToString() const;
// Single-message helper methods
- bool IsMsgTx() const { return type == MSG_TX; }
- bool IsMsgWtx() const { return type == MSG_WTX; }
- bool IsMsgWitnessTx() const { return type == MSG_WITNESS_TX; }
+ bool IsMsgTx() const { return type == MSG_TX; }
+ bool IsMsgBlk() const { return type == MSG_BLOCK; }
+ bool IsMsgWtx() const { return type == MSG_WTX; }
+ bool IsMsgFilteredBlk() const { return type == MSG_FILTERED_BLOCK; }
+ bool IsMsgCmpctBlk() const { return type == MSG_CMPCT_BLOCK; }
+ bool IsMsgWitnessBlk() const { return type == MSG_WITNESS_BLOCK; }
// Combined-message helper methods
- bool IsGenTxMsg() const { return type == MSG_TX || type == MSG_WTX || type == MSG_WITNESS_TX; }
+ bool IsGenTxMsg() const
+ {
+ return type == MSG_TX || type == MSG_WTX || type == MSG_WITNESS_TX;
+ }
+ bool IsGenBlkMsg() const
+ {
+ return type == MSG_BLOCK || type == MSG_FILTERED_BLOCK || type == MSG_CMPCT_BLOCK || type == MSG_WITNESS_BLOCK;
+ }
- int type;
+ uint32_t type;
uint256 hash;
};
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index 0556895948..6a3f903206 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -413,7 +413,7 @@ void WalletModel::subscribeToCoreSignals()
m_handler_transaction_changed = m_wallet->handleTransactionChanged(std::bind(NotifyTransactionChanged, this, std::placeholders::_1, std::placeholders::_2));
m_handler_show_progress = m_wallet->handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2));
m_handler_watch_only_changed = m_wallet->handleWatchOnlyChanged(std::bind(NotifyWatchonlyChanged, this, std::placeholders::_1));
- m_handler_can_get_addrs_changed = m_wallet->handleCanGetAddressesChanged(boost::bind(NotifyCanGetAddressesChanged, this));
+ m_handler_can_get_addrs_changed = m_wallet->handleCanGetAddressesChanged(std::bind(NotifyCanGetAddressesChanged, this));
}
void WalletModel::unsubscribeFromCoreSignals()
diff --git a/src/sync.cpp b/src/sync.cpp
index 4be13a3c48..322198a852 100644
--- a/src/sync.cpp
+++ b/src/sync.cpp
@@ -238,12 +238,15 @@ void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine,
template void AssertLockHeldInternal(const char*, const char*, int, Mutex*);
template void AssertLockHeldInternal(const char*, const char*, int, RecursiveMutex*);
-void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs)
+template <typename MutexType>
+void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs)
{
if (!LockHeld(cs)) return;
tfm::format(std::cerr, "Assertion failed: lock %s held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld());
abort();
}
+template void AssertLockNotHeldInternal(const char*, const char*, int, Mutex*);
+template void AssertLockNotHeldInternal(const char*, const char*, int, RecursiveMutex*);
void DeleteLock(void* cs)
{
diff --git a/src/sync.h b/src/sync.h
index 05ff2ee8a9..7b397a8003 100644
--- a/src/sync.h
+++ b/src/sync.h
@@ -53,8 +53,9 @@ void LeaveCritical();
void CheckLastCritical(void* cs, std::string& lockname, const char* guardname, const char* file, int line);
std::string LocksHeld();
template <typename MutexType>
-void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) ASSERT_EXCLUSIVE_LOCK(cs);
-void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs);
+void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) EXCLUSIVE_LOCKS_REQUIRED(cs);
+template <typename MutexType>
+void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs);
void DeleteLock(void* cs);
bool LockStackEmpty();
@@ -69,8 +70,9 @@ inline void EnterCritical(const char* pszName, const char* pszFile, int nLine, v
inline void LeaveCritical() {}
inline void CheckLastCritical(void* cs, std::string& lockname, const char* guardname, const char* file, int line) {}
template <typename MutexType>
-inline void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) ASSERT_EXCLUSIVE_LOCK(cs) {}
-inline void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, void* cs) {}
+inline void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) EXCLUSIVE_LOCKS_REQUIRED(cs) {}
+template <typename MutexType>
+void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLine, MutexType* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) {}
inline void DeleteLock(void* cs) {}
inline bool LockStackEmpty() { return true; }
#endif
diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp
index 93bfa88024..bf0659587c 100644
--- a/src/test/denialofservice_tests.cpp
+++ b/src/test/denialofservice_tests.cpp
@@ -84,7 +84,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
// Mock an outbound peer
CAddress addr1(ip(0xa0b0c001), NODE_NONE);
- CNode dummyNode1(id++, ServiceFlags(NODE_NETWORK|NODE_WITNESS), 0, INVALID_SOCKET, addr1, 0, 0, CAddress(), "", ConnectionType::OUTBOUND);
+ CNode dummyNode1(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), 0, INVALID_SOCKET, addr1, 0, 0, CAddress(), "", ConnectionType::OUTBOUND_FULL_RELAY);
dummyNode1.SetSendVersion(PROTOCOL_VERSION);
peerLogic->InitializeNode(&dummyNode1);
@@ -136,7 +136,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerLogicValidation &peerLogic, CConnmanTest* connman)
{
CAddress addr(ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE);
- vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK|NODE_WITNESS), 0, INVALID_SOCKET, addr, 0, 0, CAddress(), "", ConnectionType::OUTBOUND));
+ vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), 0, INVALID_SOCKET, addr, 0, 0, CAddress(), "", ConnectionType::OUTBOUND_FULL_RELAY));
CNode &node = *vNodes.back();
node.SetSendVersion(PROTOCOL_VERSION);
diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp
index 1ff9d6b286..cd0c93b8d0 100644
--- a/src/test/fuzz/net.cpp
+++ b/src/test/fuzz/net.cpp
@@ -46,7 +46,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
fuzzed_data_provider.ConsumeIntegral<uint64_t>(),
*address_bind,
fuzzed_data_provider.ConsumeRandomLengthString(32),
- fuzzed_data_provider.PickValueInArray({ConnectionType::INBOUND, ConnectionType::OUTBOUND, ConnectionType::MANUAL, ConnectionType::FEELER, ConnectionType::BLOCK_RELAY, ConnectionType::ADDR_FETCH})};
+ fuzzed_data_provider.PickValueInArray({ConnectionType::INBOUND, ConnectionType::OUTBOUND_FULL_RELAY, ConnectionType::MANUAL, ConnectionType::FEELER, ConnectionType::BLOCK_RELAY, ConnectionType::ADDR_FETCH})};
while (fuzzed_data_provider.ConsumeBool()) {
switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 12)) {
case 0: {
@@ -147,7 +147,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
const int ref_count = node.GetRefCount();
assert(ref_count >= 0);
(void)node.GetSendVersion();
- (void)node.IsAddrRelayPeer();
+ (void)node.RelayAddrsWithConn();
const NetPermissionFlags net_permission_flags = fuzzed_data_provider.ConsumeBool() ?
fuzzed_data_provider.PickValueInArray<NetPermissionFlags>({NetPermissionFlags::PF_NONE, NetPermissionFlags::PF_BLOOMFILTER, NetPermissionFlags::PF_RELAY, NetPermissionFlags::PF_FORCERELAY, NetPermissionFlags::PF_NOBAN, NetPermissionFlags::PF_MEMPOOL, NetPermissionFlags::PF_ISIMPLICIT, NetPermissionFlags::PF_ALL}) :
diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp
index ec09acc6c6..52efe5ddfa 100644
--- a/src/test/fuzz/process_message.cpp
+++ b/src/test/fuzz/process_message.cpp
@@ -68,7 +68,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
return;
}
CDataStream random_bytes_data_stream{fuzzed_data_provider.ConsumeRemainingBytes<unsigned char>(), SER_NETWORK, PROTOCOL_VERSION};
- CNode& p2p_node = *MakeUnique<CNode>(0, ServiceFlags(NODE_NETWORK | NODE_WITNESS | NODE_BLOOM), 0, INVALID_SOCKET, CAddress{CService{in_addr{0x0100007f}, 7777}, NODE_NETWORK}, 0, 0, CAddress{}, std::string{}, ConnectionType::OUTBOUND).release();
+ CNode& p2p_node = *MakeUnique<CNode>(0, ServiceFlags(NODE_NETWORK | NODE_WITNESS | NODE_BLOOM), 0, INVALID_SOCKET, CAddress{CService{in_addr{0x0100007f}, 7777}, NODE_NETWORK}, 0, 0, CAddress{}, std::string{}, ConnectionType::OUTBOUND_FULL_RELAY).release();
p2p_node.fSuccessfullyConnected = true;
p2p_node.nVersion = PROTOCOL_VERSION;
p2p_node.SetSendVersion(PROTOCOL_VERSION);
diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp
index ef427442e9..33385c06cf 100644
--- a/src/test/fuzz/process_messages.cpp
+++ b/src/test/fuzz/process_messages.cpp
@@ -44,7 +44,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
const auto num_peers_to_add = fuzzed_data_provider.ConsumeIntegralInRange(1, 3);
for (int i = 0; i < num_peers_to_add; ++i) {
const ServiceFlags service_flags = ServiceFlags(fuzzed_data_provider.ConsumeIntegral<uint64_t>());
- const ConnectionType conn_type = fuzzed_data_provider.PickValueInArray({ConnectionType::INBOUND, ConnectionType::OUTBOUND, ConnectionType::MANUAL, ConnectionType::FEELER, ConnectionType::BLOCK_RELAY, ConnectionType::ADDR_FETCH});
+ const ConnectionType conn_type = fuzzed_data_provider.PickValueInArray({ConnectionType::INBOUND, ConnectionType::OUTBOUND_FULL_RELAY, ConnectionType::MANUAL, ConnectionType::FEELER, ConnectionType::BLOCK_RELAY, ConnectionType::ADDR_FETCH});
peers.push_back(MakeUnique<CNode>(i, service_flags, 0, INVALID_SOCKET, CAddress{CService{in_addr{0x0100007f}, 7777}, NODE_NETWORK}, 0, 0, CAddress{}, std::string{}, conn_type).release());
CNode& p2p_node = *peers.back();
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index 917ae571f5..85ebc89673 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -183,10 +183,20 @@ BOOST_AUTO_TEST_CASE(cnode_simple_test)
CAddress addr = CAddress(CService(ipv4Addr, 7777), NODE_NETWORK);
std::string pszDest;
- std::unique_ptr<CNode> pnode1 = MakeUnique<CNode>(id++, NODE_NETWORK, height, hSocket, addr, 0, 0, CAddress(), pszDest, ConnectionType::OUTBOUND);
+ std::unique_ptr<CNode> pnode1 = MakeUnique<CNode>(id++, NODE_NETWORK, height, hSocket, addr, 0, 0, CAddress(), pszDest, ConnectionType::OUTBOUND_FULL_RELAY);
+ BOOST_CHECK(pnode1->IsFullOutboundConn() == true);
+ BOOST_CHECK(pnode1->IsManualConn() == false);
+ BOOST_CHECK(pnode1->IsBlockOnlyConn() == false);
+ BOOST_CHECK(pnode1->IsFeelerConn() == false);
+ BOOST_CHECK(pnode1->IsAddrFetchConn() == false);
BOOST_CHECK(pnode1->IsInboundConn() == false);
std::unique_ptr<CNode> pnode2 = MakeUnique<CNode>(id++, NODE_NETWORK, height, hSocket, addr, 1, 1, CAddress(), pszDest, ConnectionType::INBOUND);
+ BOOST_CHECK(pnode2->IsFullOutboundConn() == false);
+ BOOST_CHECK(pnode2->IsManualConn() == false);
+ BOOST_CHECK(pnode2->IsBlockOnlyConn() == false);
+ BOOST_CHECK(pnode2->IsFeelerConn() == false);
+ BOOST_CHECK(pnode2->IsAddrFetchConn() == false);
BOOST_CHECK(pnode2->IsInboundConn() == true);
}
@@ -283,7 +293,7 @@ BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test)
in_addr ipv4AddrPeer;
ipv4AddrPeer.s_addr = 0xa0b0c001;
CAddress addr = CAddress(CService(ipv4AddrPeer, 7777), NODE_NETWORK);
- std::unique_ptr<CNode> pnode = MakeUnique<CNode>(0, NODE_NETWORK, 0, INVALID_SOCKET, addr, 0, 0, CAddress{}, std::string{}, ConnectionType::OUTBOUND);
+ std::unique_ptr<CNode> pnode = MakeUnique<CNode>(0, NODE_NETWORK, 0, INVALID_SOCKET, addr, 0, 0, CAddress{}, std::string{}, ConnectionType::OUTBOUND_FULL_RELAY);
pnode->fSuccessfullyConnected.store(true);
// the peer claims to be reaching us via IPv6
diff --git a/src/validation.h b/src/validation.h
index bb59b57f6b..cac9473c7a 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -245,7 +245,7 @@ bool TestLockPointValidity(const LockPoints* lp) EXCLUSIVE_LOCKS_REQUIRED(cs_mai
*
* See consensus/consensus.h for flag definitions.
*/
-bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flags, LockPoints* lp = nullptr, bool useExistingLockPoints = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flags, LockPoints* lp = nullptr, bool useExistingLockPoints = false) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs);
/**
* Closure representing one script verification
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 1456e1595e..3910599ca7 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -67,13 +67,13 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
argsman.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes. %s in cmd is replaced by TxID and %w is replaced by wallet name. %w is not currently implemented on windows. On systems where %w is supported, it should NOT be quoted because this would break shell escaping used to invoke the command.", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
#endif
argsman.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
- argsman.AddArg("-zapwallettxes=<mode>", "Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup"
- " (1 = keep tx meta data e.g. payment request information, 2 = drop tx meta data)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
argsman.AddArg("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
argsman.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
argsman.AddArg("-walletrejectlongchains", strprintf("Wallet will not create transactions that violate mempool chain limits (default: %u)", DEFAULT_WALLET_REJECT_LONG_CHAINS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
+
+ argsman.AddHiddenArgs({"-zapwallettxes"});
}
bool WalletInit::ParameterInteraction() const
@@ -86,26 +86,12 @@ bool WalletInit::ParameterInteraction() const
return true;
}
- const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1;
-
if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && gArgs.SoftSetBoolArg("-walletbroadcast", false)) {
LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -walletbroadcast=0\n", __func__);
}
- bool zapwallettxes = gArgs.GetBoolArg("-zapwallettxes", false);
- // -zapwallettxes implies dropping the mempool on startup
- if (zapwallettxes && gArgs.SoftSetBoolArg("-persistmempool", false)) {
- LogPrintf("%s: parameter interaction: -zapwallettxes enabled -> setting -persistmempool=0\n", __func__);
- }
-
- // -zapwallettxes implies a rescan
- if (zapwallettxes) {
- if (is_multiwallet) {
- return InitError(strprintf(Untranslated("%s is only allowed with a single wallet file"), "-zapwallettxes"));
- }
- if (gArgs.SoftSetBoolArg("-rescan", true)) {
- LogPrintf("%s: parameter interaction: -zapwallettxes enabled -> setting -rescan=1\n", __func__);
- }
+ if (gArgs.IsArgSet("-zapwallettxes")) {
+ return InitError(Untranslated("-zapwallettxes has been removed. If you are attempting to remove a stuck transaction from your wallet, please use abandontransaction instead."));
}
if (gArgs.GetBoolArg("-sysperms", false))
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 74259099c9..887c5b632b 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -2483,7 +2483,7 @@ static UniValue loadwallet(const JSONRPCRequest& request)
RPCHelpMan{"loadwallet",
"\nLoads a wallet from a wallet file or directory."
"\nNote that all wallet command-line options used when starting bitcoind will be"
- "\napplied to the new wallet (eg -zapwallettxes, rescan, etc).\n",
+ "\napplied to the new wallet (eg -rescan, etc).\n",
{
{"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."},
{"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp
index c0755db751..934e3d5c86 100644
--- a/src/wallet/salvage.cpp
+++ b/src/wallet/salvage.cpp
@@ -16,6 +16,11 @@ static const char *HEADER_END = "HEADER=END";
static const char *DATA_END = "DATA=END";
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
+static bool KeyFilter(const std::string& type)
+{
+ return WalletBatch::IsKeyType(type) || type == DBKeys::HDCHAIN;
+}
+
bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
std::string filename;
@@ -129,9 +134,9 @@ bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::v
{
// Required in LoadKeyMetadata():
LOCK(dummyWallet.cs_wallet);
- fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, strType, strErr);
+ fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, strType, strErr, KeyFilter);
}
- if (!WalletBatch::IsKeyType(strType) && strType != DBKeys::HDCHAIN) {
+ if (!KeyFilter(strType)) {
continue;
}
if (!fReadOK)
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index a96d971734..14fb1fa89f 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -535,7 +535,7 @@ private:
//! keeps track of whether Unlock has run a thorough check before
bool m_decryption_thoroughly_checked = false;
- bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey);
+ bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
KeyMap GetKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index fcdf3b9c7d..afe676078c 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -3235,25 +3235,6 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
return DBErrors::LOAD_OK;
}
-DBErrors CWallet::ZapWalletTx(std::list<CWalletTx>& vWtx)
-{
- DBErrors nZapWalletTxRet = WalletBatch(*database,"cr+").ZapWalletTx(vWtx);
- if (nZapWalletTxRet == DBErrors::NEED_REWRITE)
- {
- if (database->Rewrite("\x04pool"))
- {
- for (const auto& spk_man_pair : m_spk_managers) {
- spk_man_pair.second->RewriteDB();
- }
- }
- }
-
- if (nZapWalletTxRet != DBErrors::LOAD_OK)
- return nZapWalletTxRet;
-
- return DBErrors::LOAD_OK;
-}
-
bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::string& strPurpose)
{
bool fUpdated = false;
@@ -3832,20 +3813,6 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
{
const std::string walletFile = WalletDataFilePath(location.GetPath()).string();
- // needed to restore wallet transaction meta data after -zapwallettxes
- std::list<CWalletTx> vWtx;
-
- if (gArgs.GetBoolArg("-zapwallettxes", false)) {
- chain.initMessage(_("Zapping all transactions from wallet...").translated);
-
- std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(&chain, location, CreateWalletDatabase(location.GetPath()));
- DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx);
- if (nZapWalletRet != DBErrors::LOAD_OK) {
- error = strprintf(_("Error loading %s: Wallet corrupted"), walletFile);
- return nullptr;
- }
- }
-
chain.initMessage(_("Loading wallet...").translated);
int64_t nStart = GetTimeMillis();
@@ -4122,30 +4089,6 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
}
walletInstance->chainStateFlushed(chain.getTipLocator());
walletInstance->database->IncrementUpdateCounter();
-
- // Restore wallet transaction metadata after -zapwallettxes=1
- if (gArgs.GetBoolArg("-zapwallettxes", false) && gArgs.GetArg("-zapwallettxes", "1") != "2")
- {
- WalletBatch batch(*walletInstance->database);
-
- for (const CWalletTx& wtxOld : vWtx)
- {
- uint256 hash = wtxOld.GetHash();
- std::map<uint256, CWalletTx>::iterator mi = walletInstance->mapWallet.find(hash);
- if (mi != walletInstance->mapWallet.end())
- {
- const CWalletTx* copyFrom = &wtxOld;
- CWalletTx* copyTo = &mi->second;
- copyTo->mapValue = copyFrom->mapValue;
- copyTo->vOrderForm = copyFrom->vOrderForm;
- copyTo->nTimeReceived = copyFrom->nTimeReceived;
- copyTo->nTimeSmart = copyFrom->nTimeSmart;
- copyTo->fFromMe = copyFrom->fFromMe;
- copyTo->nOrderPos = copyFrom->nOrderPos;
- batch.WriteTx(*copyTo);
- }
- }
- }
}
{
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 73e9aef8ca..06069029ea 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1076,7 +1076,6 @@ public:
void chainStateFlushed(const CBlockLocator& loc) override;
DBErrors LoadWallet(bool& fFirstRunRet);
- DBErrors ZapWalletTx(std::list<CWalletTx>& vWtx);
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose);
@@ -1180,7 +1179,7 @@ public:
* Obviously holding cs_main/cs_wallet when going into this call may cause
* deadlock
*/
- void BlockUntilSyncedToCurrentChain() const LOCKS_EXCLUDED(cs_main, cs_wallet);
+ void BlockUntilSyncedToCurrentChain() const EXCLUSIVE_LOCKS_REQUIRED(!::cs_main, !cs_wallet);
/** set a single wallet flag */
void SetWalletFlag(uint64_t flags);
@@ -1286,7 +1285,7 @@ public:
void LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal);
//! Create new DescriptorScriptPubKeyMans and add them to the wallet
- void SetupDescriptorScriptPubKeyMans();
+ void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Return the DescriptorScriptPubKeyMan for a WalletDescriptor if it is already in the wallet
DescriptorScriptPubKeyMan* GetDescriptorScriptPubKeyMan(const WalletDescriptor& desc) const;
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index fa6814d0d3..962ea66fa0 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -263,13 +263,17 @@ public:
static bool
ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
- CWalletScanState &wss, std::string& strType, std::string& strErr) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
+ CWalletScanState &wss, std::string& strType, std::string& strErr, const KeyFilterFn& filter_fn = nullptr) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
{
try {
// Unserialize
// Taking advantage of the fact that pair serialization
// is just the two items serialized one after the other
ssKey >> strType;
+ // If we have a filter, check if this matches the filter
+ if (filter_fn && !filter_fn(strType)) {
+ return true;
+ }
if (strType == DBKeys::NAME) {
std::string strAddress;
ssKey >> strAddress;
@@ -668,11 +672,11 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
return true;
}
-bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, std::string& strType, std::string& strErr)
+bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, std::string& strType, std::string& strErr, const KeyFilterFn& filter_fn)
{
CWalletScanState dummy_wss;
LOCK(pwallet->cs_wallet);
- return ReadKeyValue(pwallet, ssKey, ssValue, dummy_wss, strType, strErr);
+ return ReadKeyValue(pwallet, ssKey, ssValue, dummy_wss, strType, strErr, filter_fn);
}
bool WalletBatch::IsKeyType(const std::string& strType)
@@ -926,23 +930,6 @@ DBErrors WalletBatch::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<u
return DBErrors::LOAD_OK;
}
-DBErrors WalletBatch::ZapWalletTx(std::list<CWalletTx>& vWtx)
-{
- // build list of wallet TXs
- std::vector<uint256> vTxHash;
- DBErrors err = FindWalletTx(vTxHash, vWtx);
- if (err != DBErrors::LOAD_OK)
- return err;
-
- // erase each wallet TX
- for (const uint256& hash : vTxHash) {
- if (!EraseTx(hash))
- return DBErrors::CORRUPT;
- }
-
- return DBErrors::LOAD_OK;
-}
-
void MaybeCompactWalletDB()
{
static std::atomic<bool> fOneThread(false);
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 64d60b1f44..2548c17508 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -257,7 +257,6 @@ public:
DBErrors LoadWallet(CWallet* pwallet);
DBErrors FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWalletTx>& vWtx);
- DBErrors ZapWalletTx(std::list<CWalletTx>& vWtx);
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
/* Function to determine if a certain KV/key-type is a key (cryptographical key) type */
static bool IsKeyType(const std::string& strType);
@@ -280,8 +279,11 @@ private:
//! Compacts BDB state so that wallet.dat is self-contained (if there are changes)
void MaybeCompactWalletDB();
+//! Callback for filtering key types to deserialize in ReadKeyValue
+using KeyFilterFn = std::function<bool(const std::string&)>;
+
//! Unserialize a given Key-Value pair and load it into the wallet
-bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, std::string& strType, std::string& strErr);
+bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, std::string& strType, std::string& strErr, const KeyFilterFn& filter_fn = nullptr);
/** Return whether a wallet database is currently loaded. */
bool IsWalletLoaded(const fs::path& wallet_path);
diff --git a/src/zmq/zmqpublishnotifier.cpp b/src/zmq/zmqpublishnotifier.cpp
index 04806903c2..e2431cbbb7 100644
--- a/src/zmq/zmqpublishnotifier.cpp
+++ b/src/zmq/zmqpublishnotifier.cpp
@@ -86,6 +86,14 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext)
return false;
}
+ const int so_keepalive_option {1};
+ rc = zmq_setsockopt(psocket, ZMQ_TCP_KEEPALIVE, &so_keepalive_option, sizeof(so_keepalive_option));
+ if (rc != 0) {
+ zmqError("Failed to set SO_KEEPALIVE");
+ zmq_close(psocket);
+ return false;
+ }
+
rc = zmq_bind(psocket, address.c_str());
if (rc != 0)
{
diff --git a/test/functional/example_test.py b/test/functional/example_test.py
index 1832043989..083deb6460 100755
--- a/test/functional/example_test.py
+++ b/test/functional/example_test.py
@@ -207,10 +207,11 @@ class ExampleTest(BitcoinTestFramework):
self.log.info("Check that each block was received only once")
# The network thread uses a global lock on data access to the P2PConnection objects when sending and receiving
# messages. The test thread should acquire the global lock before accessing any P2PConnection data to avoid locking
- # and synchronization issues. Note wait_until() acquires this global lock when testing the predicate.
+ # and synchronization issues. Note p2p.wait_until() acquires this global lock internally when testing the predicate.
with p2p_lock:
for block in self.nodes[2].p2p.block_receive_map.values():
assert_equal(block, 1)
+
if __name__ == '__main__':
ExampleTest().main()
diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py
index 126c9fe929..dd17e888f9 100755
--- a/test/functional/feature_backwards_compatibility.py
+++ b/test/functional/feature_backwards_compatibility.py
@@ -6,7 +6,7 @@
Test various backwards compatibility scenarios. Download the previous node binaries:
-test/get_previous_releases.py -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
+test/get_previous_releases.py -b v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2
v0.15.2 is not required by this test, but it is used in wallet_upgradewallet.py.
Due to a hardfork in regtest, it can't be used to sync nodes.
@@ -40,7 +40,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # Pre-release: use to receive coins, swap wallets, etc
["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.19.1
["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.18.1
- ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.17.1
+ ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.17.2
["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.16.3
]
@@ -54,7 +54,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
None,
190100,
180100,
- 170100,
+ 170200,
160300,
])
diff --git a/test/functional/feature_versionbits_warning.py b/test/functional/feature_versionbits_warning.py
index e045adac32..e1016e1581 100755
--- a/test/functional/feature_versionbits_warning.py
+++ b/test/functional/feature_versionbits_warning.py
@@ -12,7 +12,7 @@ import re
from test_framework.blocktools import create_block, create_coinbase
from test_framework.messages import msg_block
-from test_framework.p2p import p2p_lock, P2PInterface
+from test_framework.p2p import P2PInterface
from test_framework.test_framework import BitcoinTestFramework
VB_PERIOD = 144 # versionbits period length for regtest
@@ -90,7 +90,7 @@ class VersionBitsWarningTest(BitcoinTestFramework):
# Generating one block guarantees that we'll get out of IBD
node.generatetoaddress(1, node_deterministic_address)
- self.wait_until(lambda: not node.getblockchaininfo()['initialblockdownload'], timeout=10, lock=p2p_lock)
+ self.wait_until(lambda: not node.getblockchaininfo()['initialblockdownload'])
# Generating one more block will be enough to generate an error.
node.generatetoaddress(1, node_deterministic_address)
# Check that get*info() shows the versionbits unknown rules warning
diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py
index 89c55f31f3..ef4780cacb 100755
--- a/test/functional/interface_zmq.py
+++ b/test/functional/interface_zmq.py
@@ -54,28 +54,31 @@ class ZMQTest (BitcoinTestFramework):
self.ctx.destroy(linger=None)
def test_basic(self):
- # All messages are received in the same socket which means
- # that this test fails if the publishing order changes.
- # Note that the publishing order is not defined in the documentation and
- # is subject to change.
import zmq
# Invalid zmq arguments don't take down the node, see #17185.
self.restart_node(0, ["-zmqpubrawtx=foo", "-zmqpubhashtx=bar"])
address = 'tcp://127.0.0.1:28332'
- socket = self.ctx.socket(zmq.SUB)
- socket.set(zmq.RCVTIMEO, 60000)
+ sockets = []
+ subs = []
+ services = [b"hashblock", b"hashtx", b"rawblock", b"rawtx"]
+ for service in services:
+ sockets.append(self.ctx.socket(zmq.SUB))
+ sockets[-1].set(zmq.RCVTIMEO, 60000)
+ subs.append(ZMQSubscriber(sockets[-1], service))
# Subscribe to all available topics.
- hashblock = ZMQSubscriber(socket, b"hashblock")
- hashtx = ZMQSubscriber(socket, b"hashtx")
- rawblock = ZMQSubscriber(socket, b"rawblock")
- rawtx = ZMQSubscriber(socket, b"rawtx")
+ hashblock = subs[0]
+ hashtx = subs[1]
+ rawblock = subs[2]
+ rawtx = subs[3]
self.restart_node(0, ["-zmqpub%s=%s" % (sub.topic.decode(), address) for sub in [hashblock, hashtx, rawblock, rawtx]])
connect_nodes(self.nodes[0], 1)
- socket.connect(address)
+ for socket in sockets:
+ socket.connect(address)
+
# Relax so that the subscriber is ready before publishing zmq messages
sleep(0.2)
@@ -96,15 +99,16 @@ class ZMQTest (BitcoinTestFramework):
tx.calc_sha256()
assert_equal(tx.hash, txid.hex())
+ # Should receive the generated raw block.
+ block = rawblock.receive()
+ assert_equal(genhashes[x], hash256_reversed(block[:80]).hex())
+
# Should receive the generated block hash.
hash = hashblock.receive().hex()
assert_equal(genhashes[x], hash)
# The block should only have the coinbase txid.
assert_equal([txid.hex()], self.nodes[1].getblock(hash)["tx"])
- # Should receive the generated raw block.
- block = rawblock.receive()
- assert_equal(genhashes[x], hash256_reversed(block[:80]).hex())
if self.is_wallet_compiled():
self.log.info("Wait for tx from second node")
@@ -119,6 +123,13 @@ class ZMQTest (BitcoinTestFramework):
hex = rawtx.receive()
assert_equal(payment_txid, hash256_reversed(hex).hex())
+ # Mining the block with this tx should result in second notification
+ # after coinbase tx notification
+ self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE)
+ hashtx.receive()
+ txid = hashtx.receive()
+ assert_equal(payment_txid, txid.hex())
+
self.log.info("Test the getzmqnotifications RPC")
assert_equal(self.nodes[0].getzmqnotifications(), [
@@ -131,30 +142,67 @@ class ZMQTest (BitcoinTestFramework):
assert_equal(self.nodes[1].getzmqnotifications(), [])
def test_reorg(self):
+ if not self.is_wallet_compiled():
+ self.log.info("Skipping reorg test because wallet is disabled")
+ return
+
import zmq
address = 'tcp://127.0.0.1:28333'
- socket = self.ctx.socket(zmq.SUB)
- socket.set(zmq.RCVTIMEO, 60000)
- hashblock = ZMQSubscriber(socket, b'hashblock')
+
+ services = [b"hashblock", b"hashtx"]
+ sockets = []
+ subs = []
+ for service in services:
+ sockets.append(self.ctx.socket(zmq.SUB))
+ # 2 second timeout to check end of notifications
+ sockets[-1].set(zmq.RCVTIMEO, 2000)
+ subs.append(ZMQSubscriber(sockets[-1], service))
+
+ # Subscribe to all available topics.
+ hashblock = subs[0]
+ hashtx = subs[1]
# Should only notify the tip if a reorg occurs
- self.restart_node(0, ['-zmqpub%s=%s' % (hashblock.topic.decode(), address)])
- socket.connect(address)
+ self.restart_node(0, ["-zmqpub%s=%s" % (sub.topic.decode(), address) for sub in [hashblock, hashtx]])
+ for socket in sockets:
+ socket.connect(address)
# Relax so that the subscriber is ready before publishing zmq messages
sleep(0.2)
- # Generate 1 block in nodes[0] and receive all notifications
- self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE)
+ # Generate 1 block in nodes[0] with 1 mempool tx and receive all notifications
+ payment_txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
+ disconnect_block = self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE)[0]
+ disconnect_cb = self.nodes[0].getblock(disconnect_block)["tx"][0]
assert_equal(self.nodes[0].getbestblockhash(), hashblock.receive().hex())
+ assert_equal(hashtx.receive().hex(), payment_txid)
+ assert_equal(hashtx.receive().hex(), disconnect_cb)
# Generate 2 blocks in nodes[1]
- self.nodes[1].generatetoaddress(2, ADDRESS_BCRT1_UNSPENDABLE)
+ connect_blocks = self.nodes[1].generatetoaddress(2, ADDRESS_BCRT1_UNSPENDABLE)
# nodes[0] will reorg chain after connecting back nodes[1]
connect_nodes(self.nodes[0], 1)
+ self.sync_blocks() # tx in mempool valid but not advertised
# Should receive nodes[1] tip
assert_equal(self.nodes[1].getbestblockhash(), hashblock.receive().hex())
+ # During reorg:
+ # Get old payment transaction notification from disconnect and disconnected cb
+ assert_equal(hashtx.receive().hex(), payment_txid)
+ assert_equal(hashtx.receive().hex(), disconnect_cb)
+ # And the payment transaction again due to mempool entry
+ assert_equal(hashtx.receive().hex(), payment_txid)
+ assert_equal(hashtx.receive().hex(), payment_txid)
+ # And the new connected coinbases
+ for i in [0, 1]:
+ assert_equal(hashtx.receive().hex(), self.nodes[1].getblock(connect_blocks[i])["tx"][0])
+
+ # If we do a simple invalidate we announce the disconnected coinbase
+ self.nodes[0].invalidateblock(connect_blocks[1])
+ assert_equal(hashtx.receive().hex(), self.nodes[1].getblock(connect_blocks[1])["tx"][0])
+ # And the current tip
+ assert_equal(hashtx.receive().hex(), self.nodes[1].getblock(connect_blocks[0])["tx"][0])
+
if __name__ == '__main__':
ZMQTest().main()
diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py
index 75ca8d3236..ad29d389e2 100755
--- a/test/functional/mempool_compatibility.py
+++ b/test/functional/mempool_compatibility.py
@@ -8,7 +8,7 @@ NOTE: The test is designed to prevent cases when compatibility is broken acciden
In case we need to break mempool compatibility we can continue to use the test by just bumping the version number.
Download node binaries:
-test/get_previous_releases.py -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
+test/get_previous_releases.py -b v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2
Only v0.15.2 is required by this test. The rest is used in other backwards compatibility tests.
"""
diff --git a/test/functional/p2p_leak.py b/test/functional/p2p_leak.py
index 4978aa3845..4b32d60db0 100755
--- a/test/functional/p2p_leak.py
+++ b/test/functional/p2p_leak.py
@@ -17,7 +17,7 @@ from test_framework.messages import (
msg_ping,
msg_version,
)
-from test_framework.p2p import p2p_lock, P2PInterface
+from test_framework.p2p import P2PInterface
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -113,9 +113,9 @@ class P2PLeakTest(BitcoinTestFramework):
# verack, since we never sent one
no_verack_idle_peer.wait_for_verack()
- self.wait_until(lambda: no_version_disconnect_peer.ever_connected, timeout=10, lock=p2p_lock)
- self.wait_until(lambda: no_version_idle_peer.ever_connected, timeout=10, lock=p2p_lock)
- self.wait_until(lambda: no_verack_idle_peer.version_received, timeout=10, lock=p2p_lock)
+ no_version_disconnect_peer.wait_until(lambda: no_version_disconnect_peer.ever_connected, check_connected=False)
+ no_version_idle_peer.wait_until(lambda: no_version_idle_peer.ever_connected)
+ no_verack_idle_peer.wait_until(lambda: no_verack_idle_peer.version_received)
# Mine a block and make sure that it's not sent to the connected peers
self.nodes[0].generate(nblocks=1)
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index 2155c1d0e7..9503391030 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -25,6 +25,7 @@ from test_framework.messages import (
MSG_BLOCK,
MSG_TX,
MSG_WITNESS_FLAG,
+ MSG_WITNESS_TX,
MSG_WTX,
NODE_NETWORK,
NODE_WITNESS,
@@ -2157,7 +2158,7 @@ class SegWitTest(BitcoinTestFramework):
self.wtx_node.wait_for_getdata([tx.sha256], 60)
with p2p_lock:
lgd = self.wtx_node.lastgetdata[:]
- assert_equal(lgd, [CInv(MSG_TX|MSG_WITNESS_FLAG, tx.sha256)])
+ assert_equal(lgd, [CInv(MSG_WITNESS_TX, tx.sha256)])
# Send tx through
test_transaction_acceptance(self.nodes[0], self.wtx_node, tx, with_witness=False, accepted=True)
diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py
index 912c0ca978..adbffb7dc7 100644
--- a/test/functional/test_framework/key.py
+++ b/test/functional/test_framework/key.py
@@ -8,22 +8,7 @@ keys, and is trivially vulnerable to side channel attacks. Do not use for
anything but tests."""
import random
-def modinv(a, n):
- """Compute the modular inverse of a modulo n
-
- See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers.
- """
- t1, t2 = 0, 1
- r1, r2 = n, a
- while r2 != 0:
- q = r1 // r2
- t1, t2 = t2, t1 - q * t2
- r1, r2 = r2, r1 - q * r2
- if r1 > 1:
- return None
- if t1 < 0:
- t1 += n
- return t1
+from .util import modinv
def jacobi_symbol(n, k):
"""Compute the Jacobi symbol of n modulo k
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index bd4a53876e..b4e609df3a 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -63,6 +63,7 @@ MSG_CMPCT_BLOCK = 4
MSG_WTX = 5
MSG_WITNESS_FLAG = 1 << 30
MSG_TYPE_MASK = 0xffffffff >> 2
+MSG_WITNESS_TX = MSG_TX | MSG_WITNESS_FLAG
FILTER_TYPE_BASIC = 0
@@ -244,8 +245,8 @@ class CInv:
MSG_TX | MSG_WITNESS_FLAG: "WitnessTx",
MSG_BLOCK | MSG_WITNESS_FLAG: "WitnessBlock",
MSG_FILTERED_BLOCK: "filtered Block",
- 4: "CompactBlock",
- 5: "WTX",
+ MSG_CMPCT_BLOCK: "CompactBlock",
+ MSG_WTX: "WTX",
}
def __init__(self, t=0, h=0):
@@ -253,12 +254,12 @@ class CInv:
self.hash = h
def deserialize(self, f):
- self.type = struct.unpack("<i", f.read(4))[0]
+ self.type = struct.unpack("<I", f.read(4))[0]
self.hash = deser_uint256(f)
def serialize(self):
r = b""
- r += struct.pack("<i", self.type)
+ r += struct.pack("<I", self.type)
r += ser_uint256(self.hash)
return r
diff --git a/test/functional/test_framework/muhash.py b/test/functional/test_framework/muhash.py
new file mode 100644
index 0000000000..97d02359cb
--- /dev/null
+++ b/test/functional/test_framework/muhash.py
@@ -0,0 +1,110 @@
+# Copyright (c) 2020 Pieter Wuille
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Native Python MuHash3072 implementation."""
+
+import hashlib
+import unittest
+
+from .util import modinv
+
+def rot32(v, bits):
+ """Rotate the 32-bit value v left by bits bits."""
+ bits %= 32 # Make sure the term below does not throw an exception
+ return ((v << bits) & 0xffffffff) | (v >> (32 - bits))
+
+def chacha20_doubleround(s):
+ """Apply a ChaCha20 double round to 16-element state array s.
+
+ See https://cr.yp.to/chacha/chacha-20080128.pdf and https://tools.ietf.org/html/rfc8439
+ """
+ QUARTER_ROUNDS = [(0, 4, 8, 12),
+ (1, 5, 9, 13),
+ (2, 6, 10, 14),
+ (3, 7, 11, 15),
+ (0, 5, 10, 15),
+ (1, 6, 11, 12),
+ (2, 7, 8, 13),
+ (3, 4, 9, 14)]
+
+ for a, b, c, d in QUARTER_ROUNDS:
+ s[a] = (s[a] + s[b]) & 0xffffffff
+ s[d] = rot32(s[d] ^ s[a], 16)
+ s[c] = (s[c] + s[d]) & 0xffffffff
+ s[b] = rot32(s[b] ^ s[c], 12)
+ s[a] = (s[a] + s[b]) & 0xffffffff
+ s[d] = rot32(s[d] ^ s[a], 8)
+ s[c] = (s[c] + s[d]) & 0xffffffff
+ s[b] = rot32(s[b] ^ s[c], 7)
+
+def chacha20_32_to_384(key32):
+ """Specialized ChaCha20 implementation with 32-byte key, 0 IV, 384-byte output."""
+ # See RFC 8439 section 2.3 for chacha20 parameters
+ CONSTANTS = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
+
+ key_bytes = [0]*8
+ for i in range(8):
+ key_bytes[i] = int.from_bytes(key32[(4 * i):(4 * (i+1))], 'little')
+
+ INITIALIZATION_VECTOR = [0] * 4
+ init = CONSTANTS + key_bytes + INITIALIZATION_VECTOR
+ out = bytearray()
+ for counter in range(6):
+ init[12] = counter
+ s = init.copy()
+ for _ in range(10):
+ chacha20_doubleround(s)
+ for i in range(16):
+ out.extend(((s[i] + init[i]) & 0xffffffff).to_bytes(4, 'little'))
+ return bytes(out)
+
+def data_to_num3072(data):
+ """Hash a 32-byte array data to a 3072-bit number using 6 Chacha20 operations."""
+ bytes384 = chacha20_32_to_384(data)
+ return int.from_bytes(bytes384, 'little')
+
+class MuHash3072:
+ """Class representing the MuHash3072 computation of a set.
+
+ See https://cseweb.ucsd.edu/~mihir/papers/inchash.pdf and https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-May/014337.html
+ """
+
+ MODULUS = 2**3072 - 1103717
+
+ def __init__(self):
+ """Initialize for an empty set."""
+ self.numerator = 1
+ self.denominator = 1
+
+ def insert(self, data):
+ """Insert a byte array data in the set."""
+ self.numerator = (self.numerator * data_to_num3072(data)) % self.MODULUS
+
+ def remove(self, data):
+ """Remove a byte array from the set."""
+ self.denominator = (self.denominator * data_to_num3072(data)) % self.MODULUS
+
+ def digest(self):
+ """Extract the final hash. Does not modify this object."""
+ val = (self.numerator * modinv(self.denominator, self.MODULUS)) % self.MODULUS
+ bytes384 = val.to_bytes(384, 'little')
+ return hashlib.sha256(bytes384).digest()
+
+class TestFrameworkMuhash(unittest.TestCase):
+ def test_muhash(self):
+ muhash = MuHash3072()
+ muhash.insert([0]*32)
+ muhash.insert([1] + [0]*31)
+ muhash.remove([2] + [0]*31)
+ finalized = muhash.digest()
+ # This mirrors the result in the C++ MuHash3072 unit test
+ self.assertEqual(finalized[::-1].hex(), "a44e16d5e34d259b349af21c06e65d653915d2e208e4e03f389af750dc0bfdc3")
+
+ def test_chacha20(self):
+ def chacha_check(key, result):
+ self.assertEqual(chacha20_32_to_384(key)[:64].hex(), result)
+
+ # Test vectors from https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7
+ # Since the nonce is hardcoded to 0 in our function we only use those vectors.
+ chacha_check([0]*32, "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586")
+ chacha_check([0]*31 + [1], "4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963")
diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py
index 57c77e60b5..963a507f53 100755
--- a/test/functional/test_framework/p2p.py
+++ b/test/functional/test_framework/p2p.py
@@ -69,7 +69,7 @@ from test_framework.messages import (
NODE_WITNESS,
sha256,
)
-from test_framework.util import wait_until
+from test_framework.util import wait_until_helper
logger = logging.getLogger("TestFramework.p2p")
@@ -293,7 +293,7 @@ class P2PInterface(P2PConnection):
# Track the most recent message of each type.
# To wait for a message to be received, pop that message from
- # this and use wait_until.
+ # this and use self.wait_until.
self.last_message = {}
# A count of the number of ping messages we've sent to the node
@@ -398,7 +398,7 @@ class P2PInterface(P2PConnection):
assert self.is_connected
return test_function_in()
- wait_until(test_function, timeout=timeout, lock=p2p_lock, timeout_factor=self.timeout_factor)
+ wait_until_helper(test_function, timeout=timeout, lock=p2p_lock, timeout_factor=self.timeout_factor)
def wait_for_disconnect(self, timeout=60):
test_function = lambda: not self.is_connected
@@ -522,7 +522,7 @@ class NetworkThread(threading.Thread):
def close(self, timeout=10):
"""Close the connections and network event loop."""
self.network_event_loop.call_soon_threadsafe(self.network_event_loop.stop)
- wait_until(lambda: not self.network_event_loop.is_running(), timeout=timeout)
+ wait_until_helper(lambda: not self.network_event_loop.is_running(), timeout=timeout)
self.network_event_loop.close()
self.join(timeout)
# Safe to remove event loop.
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 2a60f8e0c1..f41f5129b8 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -31,7 +31,7 @@ from .util import (
disconnect_nodes,
get_datadir_path,
initialize_datadir,
- wait_until,
+ wait_until_helper,
)
@@ -603,8 +603,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.sync_blocks(nodes)
self.sync_mempools(nodes)
- def wait_until(self, test_function, timeout=60, lock=None):
- return wait_until(test_function, timeout=timeout, lock=lock, timeout_factor=self.options.timeout_factor)
+ def wait_until(self, test_function, timeout=60):
+ return wait_until_helper(test_function, timeout=timeout, timeout_factor=self.options.timeout_factor)
# Private helper methods. These should not be accessed by the subclass test scripts.
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 5c7a883c43..d034986821 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -31,7 +31,7 @@ from .util import (
get_auth_cookie,
get_rpc_proxy,
rpc_url,
- wait_until,
+ wait_until_helper,
p2p_port,
EncodeDecimal,
)
@@ -231,7 +231,7 @@ class TestNode():
if self.version_is_at_least(190000):
# getmempoolinfo.loaded is available since commit
# bb8ae2c (version 0.19.0)
- wait_until(lambda: rpc.getmempoolinfo()['loaded'])
+ wait_until_helper(lambda: rpc.getmempoolinfo()['loaded'], timeout_factor=self.timeout_factor)
# Wait for the node to finish reindex, block import, and
# loading the mempool. Usually importing happens fast or
# even "immediate" when the node is started. However, there
@@ -359,7 +359,7 @@ class TestNode():
return True
def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT):
- wait_until(self.is_node_stopped, timeout=timeout, timeout_factor=self.timeout_factor)
+ wait_until_helper(self.is_node_stopped, timeout=timeout, timeout_factor=self.timeout_factor)
@contextlib.contextmanager
def assert_debug_log(self, expected_msgs, unexpected_msgs=None, timeout=2):
@@ -560,7 +560,7 @@ class TestNode():
for p in self.p2ps:
p.peer_disconnect()
del self.p2ps[:]
- wait_until(lambda: self.num_test_p2p_connections() == 0)
+ wait_until_helper(lambda: self.num_test_p2p_connections() == 0, timeout_factor=self.timeout_factor)
class TestNodeCLIAttr:
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index cfc4ee65d4..af7f0b62f4 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -15,6 +15,7 @@ import os
import random
import re
import time
+import unittest
from . import coverage
from .authproxy import AuthServiceProxy, JSONRPCException
@@ -225,14 +226,14 @@ def satoshi_round(amount):
return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
-def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, timeout_factor=1.0):
+def wait_until_helper(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, timeout_factor=1.0):
"""Sleep until the predicate resolves to be True.
Warning: Note that this method is not recommended to be used in tests as it is
- not aware of the context of the test framework. Using `wait_until()` counterpart
- from `BitcoinTestFramework` or `P2PInterface` class ensures an understandable
- amount of timeout and a common shared timeout_factor. Furthermore, `wait_until()`
- from `P2PInterface` class in `mininode.py` has a preset lock.
+ not aware of the context of the test framework. Using the `wait_until()` members
+ from `BitcoinTestFramework` or `P2PInterface` class ensures the timeout is
+ properly scaled. Furthermore, `wait_until()` from `P2PInterface` class in
+ `p2p.py` has a preset lock.
"""
if attempts == float('inf') and timeout == float('inf'):
timeout = 60
@@ -437,7 +438,7 @@ def disconnect_nodes(from_connection, node_num):
raise
# wait to disconnect
- wait_until(lambda: not get_peer_ids(), timeout=5)
+ wait_until_helper(lambda: not get_peer_ids(), timeout=5)
def connect_nodes(from_connection, node_num):
@@ -448,8 +449,8 @@ def connect_nodes(from_connection, node_num):
# See comments in net_processing:
# * Must have a version message before anything else
# * Must have a verack message before anything else
- wait_until(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo()))
- wait_until(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo()))
+ wait_until_helper(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo()))
+ wait_until_helper(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo()))
# Transaction/Block functions
@@ -625,3 +626,33 @@ def find_vout_for_address(node, txid, addr):
if any([addr == a for a in tx["vout"][i]["scriptPubKey"]["addresses"]]):
return i
raise RuntimeError("Vout not found for address: txid=%s, addr=%s" % (txid, addr))
+
+def modinv(a, n):
+ """Compute the modular inverse of a modulo n using the extended Euclidean
+ Algorithm. See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers.
+ """
+ # TODO: Change to pow(a, -1, n) available in Python 3.8
+ t1, t2 = 0, 1
+ r1, r2 = n, a
+ while r2 != 0:
+ q = r1 // r2
+ t1, t2 = t2, t1 - q * t2
+ r1, r2 = r2, r1 - q * r2
+ if r1 > 1:
+ return None
+ if t1 < 0:
+ t1 += n
+ return t1
+
+class TestFrameworkUtil(unittest.TestCase):
+ def test_modinv(self):
+ test_vectors = [
+ [7, 11],
+ [11, 29],
+ [90, 13],
+ [1891, 3797],
+ [6003722857, 77695236973],
+ ]
+
+ for a, n in test_vectors:
+ self.assertEqual(modinv(a, n), pow(a, n-2, n))
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 01232bda3c..578afe5f30 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -69,7 +69,9 @@ TEST_EXIT_SKIPPED = 77
TEST_FRAMEWORK_MODULES = [
"address",
"blocktools",
+ "muhash",
"script",
+ "util",
]
EXTENDED_SCRIPTS = [
@@ -105,7 +107,6 @@ BASE_SCRIPTS = [
'wallet_listtransactions.py',
# vv Tests less than 60s vv
'p2p_sendheaders.py',
- 'wallet_zapwallettxes.py',
'wallet_importmulti.py',
'mempool_limit.py',
'rpc_txoutproof.py',
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index c4db8e2119..147c43f2f7 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -534,8 +534,6 @@ class WalletTest(BitcoinTestFramework):
maintenance = [
'-rescan',
'-reindex',
- '-zapwallettxes=1',
- '-zapwallettxes=2',
]
chainlimit = 6
for m in maintenance:
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index 1872545cdb..5c9d7ff629 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -134,11 +134,6 @@ class MultiWalletTest(BitcoinTestFramework):
open(not_a_dir, 'a', encoding="utf8").close()
self.nodes[0].assert_start_raises_init_error(['-walletdir=' + not_a_dir], 'Error: Specified -walletdir "' + not_a_dir + '" is not a directory')
- self.log.info("Do not allow -zapwallettxes with multiwallet")
- self.nodes[0].assert_start_raises_init_error(['-zapwallettxes', '-wallet=w1', '-wallet=w2'], "Error: -zapwallettxes is only allowed with a single wallet file")
- self.nodes[0].assert_start_raises_init_error(['-zapwallettxes=1', '-wallet=w1', '-wallet=w2'], "Error: -zapwallettxes is only allowed with a single wallet file")
- self.nodes[0].assert_start_raises_init_error(['-zapwallettxes=2', '-wallet=w1', '-wallet=w2'], "Error: -zapwallettxes is only allowed with a single wallet file")
-
# if wallets/ doesn't exist, datadir should be the default wallet dir
wallet_dir2 = data_dir('walletdir')
os.rename(wallet_dir(), wallet_dir2)
diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py
index f3d6e74829..69aa603c6b 100755
--- a/test/functional/wallet_upgradewallet.py
+++ b/test/functional/wallet_upgradewallet.py
@@ -6,7 +6,7 @@
Test upgradewallet RPC. Download node binaries:
-test/get_previous_releases.py -b v0.19.1 v0.18.1 v0.17.1 v0.16.3 v0.15.2
+test/get_previous_releases.py -b v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2
Only v0.15.2 and v0.16.3 are required by this test. The others are used in feature_backwards_compatibility.py
"""
diff --git a/test/functional/wallet_zapwallettxes.py b/test/functional/wallet_zapwallettxes.py
deleted file mode 100755
index 1287092cac..0000000000
--- a/test/functional/wallet_zapwallettxes.py
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2014-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 the zapwallettxes functionality.
-
-- start two bitcoind nodes
-- create two transactions on node 0 - one is confirmed and one is unconfirmed.
-- restart node 0 and verify that both the confirmed and the unconfirmed
- transactions are still available.
-- restart node 0 with zapwallettxes and persistmempool, and verify that both
- the confirmed and the unconfirmed transactions are still available.
-- restart node 0 with just zapwallettxes and verify that the confirmed
- transactions are still available, but that the unconfirmed transaction has
- been zapped.
-"""
-from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import (
- assert_equal,
- assert_raises_rpc_error,
-)
-
-
-class ZapWalletTXesTest (BitcoinTestFramework):
- def set_test_params(self):
- self.setup_clean_chain = True
- self.num_nodes = 2
-
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
-
- def run_test(self):
- self.log.info("Mining blocks...")
- self.nodes[0].generate(1)
- self.sync_all()
- self.nodes[1].generate(100)
- self.sync_all()
-
- # This transaction will be confirmed
- txid1 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 10)
-
- self.nodes[0].generate(1)
- self.sync_all()
-
- # This transaction will not be confirmed
- txid2 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 20)
-
- # Confirmed and unconfirmed transactions are now in the wallet.
- assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
- assert_equal(self.nodes[0].gettransaction(txid2)['txid'], txid2)
-
- # Restart node0. Both confirmed and unconfirmed transactions remain in the wallet.
- self.restart_node(0)
-
- assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
- assert_equal(self.nodes[0].gettransaction(txid2)['txid'], txid2)
-
- # Restart node0 with zapwallettxes and persistmempool. The unconfirmed
- # transaction is zapped from the wallet, but is re-added when the mempool is reloaded.
- self.restart_node(0, ["-persistmempool=1", "-zapwallettxes=2"])
-
- self.wait_until(lambda: self.nodes[0].getmempoolinfo()['size'] == 1, timeout=3)
- self.nodes[0].syncwithvalidationinterfacequeue() # Flush mempool to wallet
-
- assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
- assert_equal(self.nodes[0].gettransaction(txid2)['txid'], txid2)
-
- # Restart node0 with zapwallettxes, but not persistmempool.
- # The unconfirmed transaction is zapped and is no longer in the wallet.
- self.restart_node(0, ["-zapwallettxes=2"])
-
- # tx1 is still be available because it was confirmed
- assert_equal(self.nodes[0].gettransaction(txid1)['txid'], txid1)
-
- # This will raise an exception because the unconfirmed transaction has been zapped
- assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', self.nodes[0].gettransaction, txid2)
-
-if __name__ == '__main__':
- ZapWalletTXesTest().main()
diff --git a/test/lint/check-doc.py b/test/lint/check-doc.py
index bd947d194c..f77242d335 100755
--- a/test/lint/check-doc.py
+++ b/test/lint/check-doc.py
@@ -23,7 +23,7 @@ CMD_GREP_WALLET_ARGS = r"git grep --function-context 'void WalletInit::AddWallet
CMD_GREP_WALLET_HIDDEN_ARGS = r"git grep --function-context 'void DummyWalletInit::AddWalletOptions' -- {}".format(CMD_ROOT_DIR)
CMD_GREP_DOCS = r"git grep --perl-regexp '{}' {}".format(REGEX_DOC, CMD_ROOT_DIR)
# list unsupported, deprecated and duplicate args as they need no documentation
-SET_DOC_OPTIONAL = set(['-h', '-help', '-dbcrashratio', '-forcecompactdb'])
+SET_DOC_OPTIONAL = set(['-h', '-help', '-dbcrashratio', '-forcecompactdb', '-zapwallettxes'])
def lint_missing_argument_documentation():
diff --git a/test/lint/lint-cpp.sh b/test/lint/lint-cpp.sh
new file mode 100755
index 0000000000..cac57b968d
--- /dev/null
+++ b/test/lint/lint-cpp.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2020 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#
+# Check for various C++ code patterns we want to avoid.
+
+export LC_ALL=C
+
+EXIT_CODE=0
+
+OUTPUT=$(git grep -E "boost::bind\(" -- "*.cpp" "*.h")
+if [[ ${OUTPUT} != "" ]]; then
+ echo "Use of boost::bind detected. Use std::bind instead."
+ echo
+ echo "${OUTPUT}"
+ EXIT_CODE=1
+fi
+
+exit ${EXIT_CODE} \ No newline at end of file