aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bench/chacha_poly_aead.cpp9
-rw-r--r--src/bitcoin-tx.cpp4
-rw-r--r--src/bitcoin-wallet.cpp1
-rw-r--r--src/bitcoind.cpp17
-rw-r--r--src/init.cpp3
-rw-r--r--src/net.cpp103
-rw-r--r--src/net.h35
-rw-r--r--src/net_processing.cpp16
-rw-r--r--src/policy/policy.cpp2
-rw-r--r--src/policy/policy.h41
-rw-r--r--src/primitives/transaction.h6
-rw-r--r--src/qt/bitcoinunits.cpp6
-rw-r--r--src/shutdown.cpp89
-rw-r--r--src/shutdown.h17
-rw-r--r--src/test/key_tests.cpp22
-rw-r--r--src/test/net_tests.cpp145
-rw-r--r--src/validation.h2
-rw-r--r--src/wallet/test/coinselector_tests.cpp3
-rw-r--r--src/wallet/wallettool.cpp36
19 files changed, 422 insertions, 135 deletions
diff --git a/src/bench/chacha_poly_aead.cpp b/src/bench/chacha_poly_aead.cpp
index 3b1d3e697a..e994279a4d 100644
--- a/src/bench/chacha_poly_aead.cpp
+++ b/src/bench/chacha_poly_aead.cpp
@@ -31,12 +31,15 @@ static void CHACHA20_POLY1305_AEAD(benchmark::Bench& bench, size_t buffersize, b
uint32_t len = 0;
bench.batch(buffersize).unit("byte").run([&] {
// encrypt or decrypt the buffer with a static key
- assert(aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true));
+ const bool crypt_ok_1 = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true);
+ assert(crypt_ok_1);
if (include_decryption) {
// if we decrypt, include the GetLength
- assert(aead.GetLength(&len, seqnr_aad, aad_pos, in.data()));
- assert(aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true));
+ const bool get_length_ok = aead.GetLength(&len, seqnr_aad, aad_pos, in.data());
+ assert(get_length_ok);
+ const bool crypt_ok_2 = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true);
+ assert(crypt_ok_2);
}
// increase main sequence number
diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp
index f87b9c1d16..321d62fe4d 100644
--- a/src/bitcoin-tx.cpp
+++ b/src/bitcoin-tx.cpp
@@ -11,6 +11,7 @@
#include <consensus/consensus.h>
#include <core_io.h>
#include <key_io.h>
+#include <policy/policy.h>
#include <policy/rbf.h>
#include <primitives/transaction.h>
#include <script/script.h>
@@ -196,8 +197,9 @@ static CAmount ExtractAndValidateValue(const std::string& strValue)
static void MutateTxVersion(CMutableTransaction& tx, const std::string& cmdVal)
{
int64_t newVersion;
- if (!ParseInt64(cmdVal, &newVersion) || newVersion < 1 || newVersion > CTransaction::MAX_STANDARD_VERSION)
+ if (!ParseInt64(cmdVal, &newVersion) || newVersion < 1 || newVersion > TX_MAX_STANDARD_VERSION) {
throw std::runtime_error("Invalid TX version requested: '" + cmdVal + "'");
+ }
tx.nVersion = (int) newVersion;
}
diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp
index d258f9f933..68890fda2d 100644
--- a/src/bitcoin-wallet.cpp
+++ b/src/bitcoin-wallet.cpp
@@ -28,6 +28,7 @@ static void SetupWalletToolArgs(ArgsManager& argsman)
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-wallet=<wallet-name>", "Specify wallet name", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS);
argsman.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
+ argsman.AddArg("-descriptors", "Create descriptors wallet. Only for create", ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS);
argsman.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("info", "Get wallet info", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp
index 4c89db54cb..b7bcb534ef 100644
--- a/src/bitcoind.cpp
+++ b/src/bitcoind.cpp
@@ -28,15 +28,6 @@
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
UrlDecodeFn* const URL_DECODE = urlDecode;
-static void WaitForShutdown(NodeContext& node)
-{
- while (!ShutdownRequested())
- {
- UninterruptibleSleep(std::chrono::milliseconds{200});
- }
- Interrupt(node);
-}
-
static bool AppInit(int argc, char* argv[])
{
NodeContext node;
@@ -147,12 +138,10 @@ static bool AppInit(int argc, char* argv[])
PrintExceptionContinue(nullptr, "AppInit()");
}
- if (!fRet)
- {
- Interrupt(node);
- } else {
- WaitForShutdown(node);
+ if (fRet) {
+ WaitForShutdown();
}
+ Interrupt(node);
Shutdown(node);
return fRet;
diff --git a/src/init.cpp b/src/init.cpp
index e077f3dbb8..1220f39b14 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -917,6 +917,9 @@ bool AppInitBasicSetup(const ArgsManager& args)
// Enable heap terminate-on-corruption
HeapSetInformation(nullptr, HeapEnableTerminationOnCorruption, nullptr, 0);
#endif
+ if (!InitShutdownState()) {
+ return InitError(Untranslated("Initializing wait-for-shutdown state failed."));
+ }
if (!SetupNetworking()) {
return InitError(Untranslated("Initializing networking failed."));
diff --git a/src/net.cpp b/src/net.cpp
index 7a04a215eb..b3c521116b 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -16,6 +16,7 @@
#include <net_permissions.h>
#include <netbase.h>
#include <node/ui_interface.h>
+#include <optional.h>
#include <protocol.h>
#include <random.h>
#include <scheduler.h>
@@ -844,21 +845,6 @@ size_t CConnman::SocketSendData(CNode *pnode) const EXCLUSIVE_LOCKS_REQUIRED(pno
return nSentSize;
}
-struct NodeEvictionCandidate
-{
- NodeId id;
- int64_t nTimeConnected;
- int64_t nMinPingUsecTime;
- int64_t nLastBlockTime;
- int64_t nLastTXTime;
- bool fRelevantServices;
- bool fRelayTxes;
- bool fBloomFilter;
- uint64_t nKeyedNetGroup;
- bool prefer_evict;
- bool m_is_local;
-};
-
static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
{
return a.nMinPingUsecTime > b.nMinPingUsecTime;
@@ -914,43 +900,8 @@ static void EraseLastKElements(std::vector<T> &elements, Comparator comparator,
elements.erase(elements.end() - eraseSize, elements.end());
}
-/** Try to find a connection to evict when the node is full.
- * Extreme care must be taken to avoid opening the node to attacker
- * triggered network partitioning.
- * The strategy used here is to protect a small number of peers
- * for each of several distinct characteristics which are difficult
- * to forge. In order to partition a node the attacker must be
- * simultaneously better at all of them than honest peers.
- */
-bool CConnman::AttemptToEvictConnection()
+[[nodiscard]] Optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates)
{
- std::vector<NodeEvictionCandidate> vEvictionCandidates;
- {
- LOCK(cs_vNodes);
-
- for (const CNode* node : vNodes) {
- if (node->HasPermission(PF_NOBAN))
- continue;
- if (!node->IsInboundConn())
- continue;
- if (node->fDisconnect)
- continue;
- bool peer_relay_txes = false;
- bool peer_filter_not_null = false;
- if (node->m_tx_relay != nullptr) {
- LOCK(node->m_tx_relay->cs_filter);
- peer_relay_txes = node->m_tx_relay->fRelayTxes;
- peer_filter_not_null = node->m_tx_relay->pfilter != nullptr;
- }
- NodeEvictionCandidate candidate = {node->GetId(), node->nTimeConnected, node->nMinPingUsecTime,
- node->nLastBlockTime, node->nLastTXTime,
- HasAllDesirableServiceFlags(node->nServices),
- peer_relay_txes, peer_filter_not_null, node->nKeyedNetGroup,
- node->m_prefer_evict, node->addr.IsLocal()};
- vEvictionCandidates.push_back(candidate);
- }
- }
-
// Protect connections with certain characteristics
// Deterministically select 4 peers to protect by netgroup.
@@ -988,7 +939,7 @@ bool CConnman::AttemptToEvictConnection()
total_protect_size -= initial_size - vEvictionCandidates.size();
EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size);
- if (vEvictionCandidates.empty()) return false;
+ if (vEvictionCandidates.empty()) return nullopt;
// If any remaining peers are preferred for eviction consider only them.
// This happens after the other preferences since if a peer is really the best by other criteria (esp relaying blocks)
@@ -1020,10 +971,52 @@ bool CConnman::AttemptToEvictConnection()
vEvictionCandidates = std::move(mapNetGroupNodes[naMostConnections]);
// Disconnect from the network group with the most connections
- NodeId evicted = vEvictionCandidates.front().id;
+ return vEvictionCandidates.front().id;
+}
+
+/** Try to find a connection to evict when the node is full.
+ * Extreme care must be taken to avoid opening the node to attacker
+ * triggered network partitioning.
+ * The strategy used here is to protect a small number of peers
+ * for each of several distinct characteristics which are difficult
+ * to forge. In order to partition a node the attacker must be
+ * simultaneously better at all of them than honest peers.
+ */
+bool CConnman::AttemptToEvictConnection()
+{
+ std::vector<NodeEvictionCandidate> vEvictionCandidates;
+ {
+
+ LOCK(cs_vNodes);
+ for (const CNode* node : vNodes) {
+ if (node->HasPermission(PF_NOBAN))
+ continue;
+ if (!node->IsInboundConn())
+ continue;
+ if (node->fDisconnect)
+ continue;
+ bool peer_relay_txes = false;
+ bool peer_filter_not_null = false;
+ if (node->m_tx_relay != nullptr) {
+ LOCK(node->m_tx_relay->cs_filter);
+ peer_relay_txes = node->m_tx_relay->fRelayTxes;
+ peer_filter_not_null = node->m_tx_relay->pfilter != nullptr;
+ }
+ NodeEvictionCandidate candidate = {node->GetId(), node->nTimeConnected, node->nMinPingUsecTime,
+ node->nLastBlockTime, node->nLastTXTime,
+ HasAllDesirableServiceFlags(node->nServices),
+ peer_relay_txes, peer_filter_not_null, node->nKeyedNetGroup,
+ node->m_prefer_evict, node->addr.IsLocal()};
+ vEvictionCandidates.push_back(candidate);
+ }
+ }
+ const Optional<NodeId> node_id_to_evict = SelectNodeToEvict(std::move(vEvictionCandidates));
+ if (!node_id_to_evict) {
+ return false;
+ }
LOCK(cs_vNodes);
for (CNode* pnode : vNodes) {
- if (pnode->GetId() == evicted) {
+ if (pnode->GetId() == *node_id_to_evict) {
pnode->fDisconnect = true;
return true;
}
@@ -1235,7 +1228,7 @@ void CConnman::InactivityCheck(CNode *pnode)
LogPrintf("socket sending timeout: %is\n", nTime - pnode->nLastSend);
pnode->fDisconnect = true;
}
- else if (nTime - pnode->nLastRecv > (pnode->GetCommonVersion() > BIP0031_VERSION ? TIMEOUT_INTERVAL : 90*60))
+ else if (nTime - pnode->nLastRecv > TIMEOUT_INTERVAL)
{
LogPrintf("socket receive timeout: %is\n", nTime - pnode->nLastRecv);
pnode->fDisconnect = true;
diff --git a/src/net.h b/src/net.h
index 20e356562b..b7c45abb09 100644
--- a/src/net.h
+++ b/src/net.h
@@ -33,6 +33,7 @@
#include <map>
#include <memory>
#include <thread>
+#include <vector>
class CScheduler;
class CNode;
@@ -1176,18 +1177,23 @@ public:
m_addr_known->insert(_addr.GetKey());
}
- void PushAddress(const CAddress& _addr, FastRandomContext &insecure_rand)
+ /**
+ * Whether the peer supports the address. For example, a peer that does not
+ * implement BIP155 cannot receive Tor v3 addresses because it requires
+ * ADDRv2 (BIP155) encoding.
+ */
+ bool IsAddrCompatible(const CAddress& addr) const
{
- // Whether the peer supports the address in `_addr`. For example,
- // nodes that do not implement BIP155 cannot receive Tor v3 addresses
- // because they require ADDRv2 (BIP155) encoding.
- const bool addr_format_supported = m_wants_addrv2 || _addr.IsAddrV1Compatible();
+ return m_wants_addrv2 || addr.IsAddrV1Compatible();
+ }
+ void PushAddress(const CAddress& _addr, FastRandomContext &insecure_rand)
+ {
// Known checking here is only to save space from duplicates.
// SendMessages will filter it again for knowns that were added
// after addresses were pushed.
assert(m_addr_known);
- if (_addr.IsValid() && !m_addr_known->contains(_addr.GetKey()) && addr_format_supported) {
+ if (_addr.IsValid() && !m_addr_known->contains(_addr.GetKey()) && IsAddrCompatible(_addr)) {
if (vAddrToSend.size() >= MAX_ADDR_TO_SEND) {
vAddrToSend[insecure_rand.randrange(vAddrToSend.size())] = _addr;
} else {
@@ -1239,4 +1245,21 @@ inline std::chrono::microseconds PoissonNextSend(std::chrono::microseconds now,
return std::chrono::microseconds{PoissonNextSend(now.count(), average_interval.count())};
}
+struct NodeEvictionCandidate
+{
+ NodeId id;
+ int64_t nTimeConnected;
+ int64_t nMinPingUsecTime;
+ int64_t nLastBlockTime;
+ int64_t nLastTXTime;
+ bool fRelevantServices;
+ bool fRelayTxes;
+ bool fBloomFilter;
+ uint64_t nKeyedNetGroup;
+ bool prefer_evict;
+ bool m_is_local;
+};
+
+[[nodiscard]] Optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates);
+
#endif // BITCOIN_NET_H
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 05e5681df3..17aa889ab0 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -1443,8 +1443,8 @@ static void RelayAddress(const CNode& originator,
std::array<std::pair<uint64_t, CNode*>,2> best{{{0, nullptr}, {0, nullptr}}};
assert(nRelayNodes <= best.size());
- auto sortfunc = [&best, &hasher, nRelayNodes, &originator](CNode* pnode) {
- if (pnode->RelayAddrsWithConn() && pnode != &originator) {
+ auto sortfunc = [&best, &hasher, nRelayNodes, &originator, &addr](CNode* pnode) {
+ if (pnode->RelayAddrsWithConn() && pnode != &originator && pnode->IsAddrCompatible(addr)) {
uint64_t hashKey = CSipHasher(hasher).Write(pnode->GetId()).Finalize();
for (unsigned int i = 0; i < nRelayNodes; i++) {
if (hashKey > best[i].first) {
@@ -2394,8 +2394,8 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat
// empty and no one will know who we are, so these mechanisms are
// important to help us connect to the network.
//
- // We skip this for BLOCK_RELAY peers to avoid potentially leaking
- // information about our BLOCK_RELAY connections via address relay.
+ // We skip this for block-relay-only peers to avoid potentially leaking
+ // information about our block-relay-only connections via address relay.
if (fListen && !::ChainstateActive().IsInitialBlockDownload())
{
CAddress addr = GetLocalAddress(&pfrom.addr, pfrom.GetLocalServices());
@@ -3963,10 +3963,10 @@ void PeerManager::EvictExtraOutboundPeers(int64_t time_in_seconds)
});
}
- // Check whether we have too many OUTBOUND_FULL_RELAY peers
+ // Check whether we have too many outbound-full-relay peers
if (m_connman.GetExtraFullOutboundCount() > 0) {
- // If we have more OUTBOUND_FULL_RELAY peers than we target, disconnect one.
- // Pick the OUTBOUND_FULL_RELAY peer that least recently announced
+ // If we have more outbound-full-relay peers than we target, disconnect one.
+ // Pick the outbound-full-relay peer that least recently announced
// us a new block, with ties broken by choosing the more recent
// connection (higher node id)
NodeId worst_peer = -1;
@@ -3975,7 +3975,7 @@ void PeerManager::EvictExtraOutboundPeers(int64_t time_in_seconds)
m_connman.ForEachNode([&](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
- // Only consider OUTBOUND_FULL_RELAY peers that are not already
+ // Only consider outbound-full-relay peers that are not already
// marked for disconnection
if (!pnode->IsFullOutboundConn() || pnode->fDisconnect) return;
CNodeState *state = State(pnode->GetId());
diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp
index 4e33fd6cb5..8e367d31d0 100644
--- a/src/policy/policy.cpp
+++ b/src/policy/policy.cpp
@@ -75,7 +75,7 @@ bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType)
bool IsStandardTx(const CTransaction& tx, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason)
{
- if (tx.nVersion > CTransaction::MAX_STANDARD_VERSION || tx.nVersion < 1) {
+ if (tx.nVersion > TX_MAX_STANDARD_VERSION || tx.nVersion < 1) {
reason = "version";
return false;
}
diff --git a/src/policy/policy.h b/src/policy/policy.h
index 726a14a27e..fc163e958b 100644
--- a/src/policy/policy.h
+++ b/src/policy/policy.h
@@ -90,25 +90,32 @@ CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee);
bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee);
bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType);
- /**
- * Check for standard transaction types
- * @return True if all outputs (scriptPubKeys) use only standard transaction forms
- */
+
+
+// Changing the default transaction version requires a two step process: first
+// adapting relay policy by bumping TX_MAX_STANDARD_VERSION, and then later
+// allowing the new transaction version in the wallet/RPC.
+static constexpr decltype(CTransaction::nVersion) TX_MAX_STANDARD_VERSION{2};
+
+/**
+* Check for standard transaction types
+* @return True if all outputs (scriptPubKeys) use only standard transaction forms
+*/
bool IsStandardTx(const CTransaction& tx, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason);
- /**
- * Check for standard transaction types
- * @param[in] mapInputs Map of previous transactions that have outputs we're spending
- * @param[in] taproot_active Whether or taproot consensus rules are active (used to decide whether spends of them are permitted)
- * @return True if all inputs (scriptSigs) use only standard transaction forms
- */
+/**
+* Check for standard transaction types
+* @param[in] mapInputs Map of previous transactions that have outputs we're spending
+* @param[in] taproot_active Whether or taproot consensus rules are active (used to decide whether spends of them are permitted)
+* @return True if all inputs (scriptSigs) use only standard transaction forms
+*/
bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, bool taproot_active);
- /**
- * Check if the transaction is over standard P2WSH resources limit:
- * 3600bytes witnessScript size, 80bytes per witness stack element, 100 witness stack elements
- * These limits are adequate for multisignatures up to n-of-100 using OP_CHECKSIG, OP_ADD, and OP_EQUAL.
- *
- * Also enforce a maximum stack item size limit and no annexes for tapscript spends.
- */
+/**
+* Check if the transaction is over standard P2WSH resources limit:
+* 3600bytes witnessScript size, 80bytes per witness stack element, 100 witness stack elements
+* These limits are adequate for multisignatures up to n-of-100 using OP_CHECKSIG, OP_ADD, and OP_EQUAL.
+*
+* Also enforce a maximum stack item size limit and no annexes for tapscript spends.
+*/
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs);
/** Compute the virtual transaction size (weight reinterpreted as bytes). */
diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h
index c1e9f0af21..ec09668e7a 100644
--- a/src/primitives/transaction.h
+++ b/src/primitives/transaction.h
@@ -262,12 +262,6 @@ public:
// Default transaction version.
static const int32_t CURRENT_VERSION=2;
- // Changing the default transaction version requires a two step process: first
- // adapting relay policy by bumping MAX_STANDARD_VERSION, and then later date
- // bumping the default CURRENT_VERSION at which point both CURRENT_VERSION and
- // MAX_STANDARD_VERSION will be equal.
- static const int32_t MAX_STANDARD_VERSION=2;
-
// The local variables are made const to prevent unintended modification
// without updating the cached hash value. However, CTransaction is not
// actually immutable; deserialization and assignment are implemented,
diff --git a/src/qt/bitcoinunits.cpp b/src/qt/bitcoinunits.cpp
index fd55c547fc..5402ed371d 100644
--- a/src/qt/bitcoinunits.cpp
+++ b/src/qt/bitcoinunits.cpp
@@ -8,6 +8,8 @@
#include <cassert>
+static constexpr auto MAX_DIGITS_BTC = 16;
+
BitcoinUnits::BitcoinUnits(QObject *parent):
QAbstractListModel(parent),
unitlist(availableUnits())
@@ -108,7 +110,9 @@ QString BitcoinUnits::format(int unit, const CAmount& nIn, bool fPlus, Separator
qint64 n_abs = (n > 0 ? n : -n);
qint64 quotient = n_abs / coin;
QString quotient_str = QString::number(quotient);
- if (justify) quotient_str = quotient_str.rightJustified(16 - num_decimals, ' ');
+ if (justify) {
+ quotient_str = quotient_str.rightJustified(MAX_DIGITS_BTC - num_decimals, ' ');
+ }
// Use SI-style thin space separators as these are locale independent and can't be
// confused with the decimal marker.
diff --git a/src/shutdown.cpp b/src/shutdown.cpp
index dec497d8ec..a3321a6106 100644
--- a/src/shutdown.cpp
+++ b/src/shutdown.cpp
@@ -5,19 +5,108 @@
#include <shutdown.h>
+#include <config/bitcoin-config.h>
+
+#include <assert.h>
#include <atomic>
+#ifdef WIN32
+#include <condition_variable>
+#else
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#endif
static std::atomic<bool> fRequestShutdown(false);
+#ifdef WIN32
+/** On windows it is possible to simply use a condition variable. */
+std::mutex g_shutdown_mutex;
+std::condition_variable g_shutdown_cv;
+#else
+/** On UNIX-like operating systems use the self-pipe trick.
+ * Index 0 will be the read end of the pipe, index 1 the write end.
+ */
+static int g_shutdown_pipe[2] = {-1, -1};
+#endif
+
+bool InitShutdownState()
+{
+#ifndef WIN32
+#if HAVE_O_CLOEXEC
+ // If we can, make sure that the file descriptors are closed on exec()
+ // to prevent interference.
+ if (pipe2(g_shutdown_pipe, O_CLOEXEC) != 0) {
+ return false;
+ }
+#else
+ if (pipe(g_shutdown_pipe) != 0) {
+ return false;
+ }
+#endif
+#endif
+ return true;
+}
void StartShutdown()
{
+#ifdef WIN32
+ std::unique_lock<std::mutex> lk(g_shutdown_mutex);
fRequestShutdown = true;
+ g_shutdown_cv.notify_one();
+#else
+ // This must be reentrant and safe for calling in a signal handler, so using a condition variable is not safe.
+ // Make sure that the token is only written once even if multiple threads call this concurrently or in
+ // case of a reentrant signal.
+ if (!fRequestShutdown.exchange(true)) {
+ // Write an arbitrary byte to the write end of the shutdown pipe.
+ const char token = 'x';
+ while (true) {
+ int result = write(g_shutdown_pipe[1], &token, 1);
+ if (result < 0) {
+ // Failure. It's possible that the write was interrupted by another signal.
+ // Other errors are unexpected here.
+ assert(errno == EINTR);
+ } else {
+ assert(result == 1);
+ break;
+ }
+ }
+ }
+#endif
}
+
void AbortShutdown()
{
+ if (fRequestShutdown) {
+ // Cancel existing shutdown by waiting for it, this will reset condition flags and remove
+ // the shutdown token from the pipe.
+ WaitForShutdown();
+ }
fRequestShutdown = false;
}
+
bool ShutdownRequested()
{
return fRequestShutdown;
}
+
+void WaitForShutdown()
+{
+#ifdef WIN32
+ std::unique_lock<std::mutex> lk(g_shutdown_mutex);
+ g_shutdown_cv.wait(lk, [] { return fRequestShutdown.load(); });
+#else
+ char token;
+ while (true) {
+ int result = read(g_shutdown_pipe[0], &token, 1);
+ if (result < 0) {
+ // Failure. Check if the read was interrupted by a signal.
+ // Other errors are unexpected here.
+ assert(errno == EINTR);
+ } else {
+ assert(result == 1);
+ break;
+ }
+ }
+#endif
+}
diff --git a/src/shutdown.h b/src/shutdown.h
index 3ed851c789..23f84179e9 100644
--- a/src/shutdown.h
+++ b/src/shutdown.h
@@ -6,8 +6,25 @@
#ifndef BITCOIN_SHUTDOWN_H
#define BITCOIN_SHUTDOWN_H
+/** Initialize shutdown state. This must be called before using either StartShutdown(),
+ * AbortShutdown() or WaitForShutdown(). Calling ShutdownRequested() is always safe.
+ */
+bool InitShutdownState();
+
+/** Request shutdown of the application. */
void StartShutdown();
+
+/** Clear shutdown flag. Only use this during init (before calling WaitForShutdown in any
+ * thread), or in the unit tests. Calling it in other circumstances will cause a race condition.
+ */
void AbortShutdown();
+
+/** Returns true if a shutdown is requested, false otherwise. */
bool ShutdownRequested();
+/** Wait for StartShutdown to be called in any thread. This can only be used
+ * from a single thread.
+ */
+void WaitForShutdown();
+
#endif
diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp
index 3362b8d17c..cb66d5164e 100644
--- a/src/test/key_tests.cpp
+++ b/src/test/key_tests.cpp
@@ -172,20 +172,30 @@ BOOST_AUTO_TEST_CASE(key_signature_tests)
}
BOOST_CHECK(found);
- // When entropy is not specified, we should always see low R signatures that are less than 70 bytes in 256 tries
+ // When entropy is not specified, we should always see low R signatures that are less than or equal to 70 bytes in 256 tries
+ // The low R signatures should always have the value of their "length of R" byte less than or equal to 32
// We should see at least one signature that is less than 70 bytes.
- found = true;
bool found_small = false;
+ bool found_big = false;
+ bool bad_sign = false;
for (int i = 0; i < 256; ++i) {
sig.clear();
std::string msg = "A message to be signed" + ToString(i);
msg_hash = Hash(msg);
- BOOST_CHECK(key.Sign(msg_hash, sig));
- found = sig[3] == 0x20;
- BOOST_CHECK(sig.size() <= 70);
+ if (!key.Sign(msg_hash, sig)) {
+ bad_sign = true;
+ break;
+ }
+ // sig.size() > 70 implies sig[3] > 32, because S is always low.
+ // But check both conditions anyway, just in case this implication is broken for some reason
+ if (sig[3] > 32 || sig.size() > 70) {
+ found_big = true;
+ break;
+ }
found_small |= sig.size() < 70;
}
- BOOST_CHECK(found);
+ BOOST_CHECK(!bad_sign);
+ BOOST_CHECK(!found_big);
BOOST_CHECK(found_small);
}
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index c86974ba5b..beac65942e 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -9,6 +9,7 @@
#include <cstdint>
#include <net.h>
#include <netbase.h>
+#include <optional.h>
#include <serialize.h>
#include <span.h>
#include <streams.h>
@@ -21,6 +22,7 @@
#include <boost/test/unit_test.hpp>
+#include <algorithm>
#include <ios>
#include <memory>
#include <string>
@@ -781,4 +783,147 @@ BOOST_AUTO_TEST_CASE(PoissonNextSend)
g_mock_deterministic_tests = false;
}
+std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(const int n_candidates, FastRandomContext& random_context)
+{
+ std::vector<NodeEvictionCandidate> candidates;
+ for (int id = 0; id < n_candidates; ++id) {
+ candidates.push_back({
+ /* id */ id,
+ /* nTimeConnected */ static_cast<int64_t>(random_context.randrange(100)),
+ /* nMinPingUsecTime */ static_cast<int64_t>(random_context.randrange(100)),
+ /* nLastBlockTime */ static_cast<int64_t>(random_context.randrange(100)),
+ /* nLastTXTime */ static_cast<int64_t>(random_context.randrange(100)),
+ /* fRelevantServices */ random_context.randbool(),
+ /* fRelayTxes */ random_context.randbool(),
+ /* fBloomFilter */ random_context.randbool(),
+ /* nKeyedNetGroup */ random_context.randrange(100),
+ /* prefer_evict */ random_context.randbool(),
+ /* m_is_local */ random_context.randbool(),
+ });
+ }
+ return candidates;
+}
+
+// Returns true if any of the node ids in node_ids are selected for eviction.
+bool IsEvicted(std::vector<NodeEvictionCandidate> candidates, const std::vector<NodeId>& node_ids, FastRandomContext& random_context)
+{
+ Shuffle(candidates.begin(), candidates.end(), random_context);
+ const Optional<NodeId> evicted_node_id = SelectNodeToEvict(std::move(candidates));
+ if (!evicted_node_id) {
+ return false;
+ }
+ return std::find(node_ids.begin(), node_ids.end(), *evicted_node_id) != node_ids.end();
+}
+
+// Create number_of_nodes random nodes, apply setup function candidate_setup_fn,
+// apply eviction logic and then return true if any of the node ids in node_ids
+// are selected for eviction.
+bool IsEvicted(const int number_of_nodes, std::function<void(NodeEvictionCandidate&)> candidate_setup_fn, const std::vector<NodeId>& node_ids, FastRandomContext& random_context)
+{
+ std::vector<NodeEvictionCandidate> candidates = GetRandomNodeEvictionCandidates(number_of_nodes, random_context);
+ for (NodeEvictionCandidate& candidate : candidates) {
+ candidate_setup_fn(candidate);
+ }
+ return IsEvicted(candidates, node_ids, random_context);
+}
+
+namespace {
+constexpr int NODE_EVICTION_TEST_ROUNDS{10};
+constexpr int NODE_EVICTION_TEST_UP_TO_N_NODES{200};
+} // namespace
+
+BOOST_AUTO_TEST_CASE(node_eviction_test)
+{
+ FastRandomContext random_context{true};
+
+ for (int i = 0; i < NODE_EVICTION_TEST_ROUNDS; ++i) {
+ for (int number_of_nodes = 0; number_of_nodes < NODE_EVICTION_TEST_UP_TO_N_NODES; ++number_of_nodes) {
+ // Four nodes with the highest keyed netgroup values should be
+ // protected from eviction.
+ BOOST_CHECK(!IsEvicted(
+ number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
+ candidate.nKeyedNetGroup = number_of_nodes - candidate.id;
+ },
+ {0, 1, 2, 3}, random_context));
+
+ // Eight nodes with the lowest minimum ping time should be protected
+ // from eviction.
+ BOOST_CHECK(!IsEvicted(
+ number_of_nodes, [](NodeEvictionCandidate& candidate) {
+ candidate.nMinPingUsecTime = candidate.id;
+ },
+ {0, 1, 2, 3, 4, 5, 6, 7}, random_context));
+
+ // Four nodes that most recently sent us novel transactions accepted
+ // into our mempool should be protected from eviction.
+ BOOST_CHECK(!IsEvicted(
+ number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
+ candidate.nLastTXTime = number_of_nodes - candidate.id;
+ },
+ {0, 1, 2, 3}, random_context));
+
+ // Up to eight non-tx-relay peers that most recently sent us novel
+ // blocks should be protected from eviction.
+ BOOST_CHECK(!IsEvicted(
+ number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
+ candidate.nLastBlockTime = number_of_nodes - candidate.id;
+ if (candidate.id <= 7) {
+ candidate.fRelayTxes = false;
+ candidate.fRelevantServices = true;
+ }
+ },
+ {0, 1, 2, 3, 4, 5, 6, 7}, random_context));
+
+ // Four peers that most recently sent us novel blocks should be
+ // protected from eviction.
+ BOOST_CHECK(!IsEvicted(
+ number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
+ candidate.nLastBlockTime = number_of_nodes - candidate.id;
+ },
+ {0, 1, 2, 3}, random_context));
+
+ // Combination of the previous two tests.
+ BOOST_CHECK(!IsEvicted(
+ number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
+ candidate.nLastBlockTime = number_of_nodes - candidate.id;
+ if (candidate.id <= 7) {
+ candidate.fRelayTxes = false;
+ candidate.fRelevantServices = true;
+ }
+ },
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context));
+
+ // Combination of all tests above.
+ BOOST_CHECK(!IsEvicted(
+ number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
+ candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected
+ candidate.nMinPingUsecTime = candidate.id; // 8 protected
+ candidate.nLastTXTime = number_of_nodes - candidate.id; // 4 protected
+ candidate.nLastBlockTime = number_of_nodes - candidate.id; // 4 protected
+ },
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context));
+
+ // An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most
+ // four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay
+ // peers by last novel block time, and four more peers by last novel block time.
+ if (number_of_nodes >= 29) {
+ BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
+ }
+
+ // No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least
+ // four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last
+ // novel block time.
+ if (number_of_nodes <= 20) {
+ BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
+ }
+
+ // Cases left to test:
+ // * "Protect the half of the remaining nodes which have been connected the longest. [...]"
+ // * "Pick out up to 1/4 peers that are localhost, sorted by longest uptime. [...]"
+ // * "If any remaining peers are preferred for eviction consider only them. [...]"
+ // * "Identify the network group with the most connections and youngest member. [...]"
+ }
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/validation.h b/src/validation.h
index 6d8c6d431a..d10b260d8a 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -562,7 +562,7 @@ public:
//! @returns whether or not the CoinsViews object has been fully initialized and we can
//! safely flush this object to disk.
- bool CanFlushToDisk() EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
+ bool CanFlushToDisk() const EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
return m_coins_views && m_coins_views->m_cacheview;
}
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index 4127cd45f8..019161415c 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -64,7 +64,8 @@ static void add_coin(CWallet& wallet, const CAmount& nValue, int nAge = 6*24, bo
if (spendable) {
CTxDestination dest;
std::string error;
- assert(wallet.GetNewDestination(OutputType::BECH32, "", dest, error));
+ const bool destination_ok = wallet.GetNewDestination(OutputType::BECH32, "", dest, error);
+ assert(destination_ok);
tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest);
}
if (fIsFromMe) {
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index dad1232e10..fda9025588 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -21,30 +21,27 @@ static void WalletToolReleaseWallet(CWallet* wallet)
delete wallet;
}
-static void WalletCreate(CWallet* wallet_instance)
+static void WalletCreate(CWallet* wallet_instance, uint64_t wallet_creation_flags)
{
LOCK(wallet_instance->cs_wallet);
wallet_instance->SetMinVersion(FEATURE_HD_SPLIT);
+ wallet_instance->AddWalletFlags(wallet_creation_flags);
- // generate a new HD seed
- auto spk_man = wallet_instance->GetOrCreateLegacyScriptPubKeyMan();
- CPubKey seed = spk_man->GenerateNewSeed();
- spk_man->SetHDSeed(seed);
+ if (!wallet_instance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ auto spk_man = wallet_instance->GetOrCreateLegacyScriptPubKeyMan();
+ spk_man->SetupGeneration(false);
+ } else {
+ wallet_instance->SetupDescriptorScriptPubKeyMans();
+ }
tfm::format(std::cout, "Topping up keypool...\n");
wallet_instance->TopUpKeyPool();
}
-static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::path& path, bool create)
+static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::path& path, DatabaseOptions options)
{
- DatabaseOptions options;
DatabaseStatus status;
- if (create) {
- options.require_create = true;
- } else {
- options.require_existing = true;
- }
bilingual_str error;
std::unique_ptr<WalletDatabase> database = MakeDatabase(path, options, status, error);
if (!database) {
@@ -85,7 +82,7 @@ static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::pa
}
}
- if (create) WalletCreate(wallet_instance.get());
+ if (options.require_create) WalletCreate(wallet_instance.get(), options.create_flags);
return wallet_instance;
}
@@ -110,14 +107,23 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
fs::path path = fs::absolute(name, GetWalletDir());
if (command == "create") {
- std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, /* create= */ true);
+ DatabaseOptions options;
+ options.require_create = true;
+ if (gArgs.GetBoolArg("-descriptors", false)) {
+ options.create_flags |= WALLET_FLAG_DESCRIPTORS;
+ options.require_format = DatabaseFormat::SQLITE;
+ }
+
+ std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
if (wallet_instance) {
WalletShowInfo(wallet_instance.get());
wallet_instance->Close();
}
} else if (command == "info" || command == "salvage") {
if (command == "info") {
- std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, /* create= */ false);
+ DatabaseOptions options;
+ options.require_existing = true;
+ std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
if (!wallet_instance) return false;
WalletShowInfo(wallet_instance.get());
wallet_instance->Close();