diff --git a/configure.ac b/configure.ac index f5f75eafd4775..91031f8156848 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ AC_PREREQ([2.69]) define(_CLIENT_VERSION_MAJOR, 19) define(_CLIENT_VERSION_MINOR, 0) define(_CLIENT_VERSION_BUILD, 0) -define(_CLIENT_VERSION_RC, 8) +define(_CLIENT_VERSION_RC, 9) define(_CLIENT_VERSION_IS_RELEASE, false) define(_COPYRIGHT_YEAR, 2023) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/doc/release-notes-5273.md b/doc/release-notes-5273.md new file mode 100644 index 0000000000000..16bd0c88dcef0 --- /dev/null +++ b/doc/release-notes-5273.md @@ -0,0 +1,5 @@ +Added RPCs +-------- + +- `cleardiscouraged` clears all the already discouraged peers. + diff --git a/src/banman.cpp b/src/banman.cpp index 48973e9c4a97d..807ad19faa0ac 100644 --- a/src/banman.cpp +++ b/src/banman.cpp @@ -68,6 +68,16 @@ void BanMan::ClearBanned() if (m_client_interface) m_client_interface->BannedListChanged(); } +void BanMan::ClearDiscouraged() +{ + { + LOCK(m_cs_banned); + m_discouraged.reset(); + m_is_dirty = true; + } + if (m_client_interface) m_client_interface->BannedListChanged(); +} + bool BanMan::IsDiscouraged(const CNetAddr& net_addr) { LOCK(m_cs_banned); diff --git a/src/banman.h b/src/banman.h index 6b972c84e28e2..ca46677e3ee41 100644 --- a/src/banman.h +++ b/src/banman.h @@ -63,6 +63,7 @@ class BanMan void Ban(const CSubNet& sub_net, int64_t ban_time_offset = 0, bool since_unix_epoch = false); void Discourage(const CNetAddr& net_addr); void ClearBanned(); + void ClearDiscouraged(); //! Return whether net_addr is banned bool IsBanned(const CNetAddr& net_addr); diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 9905d351b5428..fe266149ad9c0 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -218,15 +218,35 @@ std::vector CDeterministicMNList::GetProjectedMNPayees(int if (nCount < 0 ) { return {}; } - nCount = std::min(nCount, int(GetValidMNsCount())); + nCount = std::min(nCount, int(GetValidWeightedMNsCount())); std::vector result; result.reserve(nCount); + auto remaining_hpmn_payments = 0; + CDeterministicMNCPtr hpmn_to_be_skipped = nullptr; ForEachMNShared(true, [&](const CDeterministicMNCPtr& dmn) { - result.emplace_back(dmn); + if (dmn->pdmnState->nLastPaidHeight == nHeight) { + // We found the last MN Payee. + // If the last payee is a HPMN, we need to check its consecutive payments and pay him again if needed + if (dmn->nType == MnType::HighPerformance && dmn->pdmnState->nConsecutivePayments < dmn_types::HighPerformance.voting_weight) { + remaining_hpmn_payments = dmn_types::HighPerformance.voting_weight - dmn->pdmnState->nConsecutivePayments; + for ([[maybe_unused]] auto _ : irange::range(remaining_hpmn_payments)) { + result.emplace_back(dmn); + hpmn_to_be_skipped = dmn; + } + } + } + return; + }); + + ForEachMNShared(true, [&](const CDeterministicMNCPtr& dmn) { + if (dmn == hpmn_to_be_skipped) return; + for ([[maybe_unused]] auto _ : irange::range(GetMnType(dmn->nType).voting_weight)) { + result.emplace_back(dmn); + } }); - std::sort(result.begin(), result.end(), [&](const CDeterministicMNCPtr& a, const CDeterministicMNCPtr& b) { + std::sort(result.begin() + remaining_hpmn_payments, result.end(), [&](const CDeterministicMNCPtr& a, const CDeterministicMNCPtr& b) { return CompareByLastPaid(a.get(), b.get()); }); diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index bd7f74d15fa7b..108cbbc75f7d9 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -19,6 +19,7 @@ #include +#include #include #include @@ -241,6 +242,14 @@ class CDeterministicMNList return ranges::count_if(mnMap, [](const auto& p) { return p.second->nType == MnType::HighPerformance && IsMNValid(*p.second); }); } + [[nodiscard]] size_t GetValidWeightedMNsCount() const + { + return std::accumulate(mnMap.begin(), mnMap.end(), 0, [](auto res, const auto& p) { + if (!IsMNValid(*p.second)) return res; + return res + GetMnType(p.second->nType).voting_weight; + }); + } + /** * Execute a callback on all masternodes in the mnList. This will pass a reference * of each masternode to the callback function. This should be preferred over ForEachMNShared. diff --git a/src/evo/mnauth.cpp b/src/evo/mnauth.cpp index 252b1390c8cd0..c0ef483c2155f 100644 --- a/src/evo/mnauth.cpp +++ b/src/evo/mnauth.cpp @@ -91,6 +91,7 @@ void CMNAuth::ProcessMessage(CNode& peer, CConnman& connman, std::string_view ms if (!mnauth.sig.IsValid()) { LOCK(cs_main); Misbehaving(peer.GetId(), 100, "invalid mnauth signature"); + LogPrint(BCLog::NET_NETCONN, "CMNAuth::ProcessMessage -- invalid mnauth for protx=%s with sig=%s\n", mnauth.proRegTxHash.ToString(), mnauth.sig.ToString()); return; } diff --git a/src/llmq/quorums.cpp b/src/llmq/quorums.cpp index 3d91777e0e156..cb4190863a81e 100644 --- a/src/llmq/quorums.cpp +++ b/src/llmq/quorums.cpp @@ -283,7 +283,7 @@ void CQuorumManager::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fInitial LOCK(cs_data_requests); auto it = mapQuorumDataRequests.begin(); while (it != mapQuorumDataRequests.end()) { - if (it->second.IsExpired()) { + if (it->second.IsExpired(/*add_bias=*/true)) { it = mapQuorumDataRequests.erase(it); } else { ++it; @@ -474,10 +474,12 @@ bool CQuorumManager::RequestQuorumData(CNode* pfrom, Consensus::LLMQType llmqTyp key.quorumHash = pQuorumBaseBlockIndex->GetBlockHash(); key.llmqType = llmqType; auto it = mapQuorumDataRequests.emplace(key, CQuorumDataRequest(llmqType, pQuorumBaseBlockIndex->GetBlockHash(), nDataMask, proTxHash)); - if (!it.second && !it.first->second.IsExpired()) { + if (!it.second && !it.first->second.IsExpired(/*add_bias=*/true)) { LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Already requested\n", __func__); return false; } + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- sending QGETDATA quorumHash[%s] llmqType[%d] proRegTx[%s]\n", __func__, key.quorumHash.ToString(), + ToUnderlying(key.llmqType), key.proRegTx.ToString()); CNetMsgMaker msgMaker(pfrom->GetSendVersion()); connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::QGETDATA, it.first->second)); @@ -630,13 +632,29 @@ void CQuorumManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, C CQuorumDataRequest request; vRecv >> request; - auto sendQDATA = [&](CQuorumDataRequest::Errors nError = CQuorumDataRequest::Errors::UNDEFINED, + auto sendQDATA = [&](CQuorumDataRequest::Errors nError, + bool request_limit_exceeded, const CDataStream& body = CDataStream(SER_NETWORK, PROTOCOL_VERSION)) { + switch (nError) { + case (CQuorumDataRequest::Errors::NONE): + case (CQuorumDataRequest::Errors::QUORUM_TYPE_INVALID): + case (CQuorumDataRequest::Errors::QUORUM_BLOCK_NOT_FOUND): + case (CQuorumDataRequest::Errors::QUORUM_NOT_FOUND): + case (CQuorumDataRequest::Errors::MASTERNODE_IS_NO_MEMBER): + case (CQuorumDataRequest::Errors::UNDEFINED): + if (request_limit_exceeded) errorHandler("Request limit exceeded", 25); + break; + case (CQuorumDataRequest::Errors::QUORUM_VERIFICATION_VECTOR_MISSING): + case (CQuorumDataRequest::Errors::ENCRYPTED_CONTRIBUTIONS_MISSING): + // Do not punish limit exceed if we don't have the requested data + break; + } request.SetError(nError); CDataStream ssResponse(SER_NETWORK, pfrom.GetSendVersion(), request, body); connman.PushMessage(&pfrom, CNetMsgMaker(pfrom.GetSendVersion()).Make(NetMsgType::QDATA, ssResponse)); }; + bool request_limit_exceeded(false); { LOCK2(cs_main, cs_data_requests); CQuorumDataRequestKey key; @@ -647,27 +665,27 @@ void CQuorumManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, C auto it = mapQuorumDataRequests.find(key); if (it == mapQuorumDataRequests.end()) { it = mapQuorumDataRequests.emplace(key, request).first; - } else if (it->second.IsExpired()) { + } else if (it->second.IsExpired(/*add_bias=*/false)) { it->second = request; } else { - errorHandler("Request limit exceeded", 25); + request_limit_exceeded = true; } } if (!GetLLMQParams(request.GetLLMQType()).has_value()) { - sendQDATA(CQuorumDataRequest::Errors::QUORUM_TYPE_INVALID); + sendQDATA(CQuorumDataRequest::Errors::QUORUM_TYPE_INVALID, request_limit_exceeded); return; } const CBlockIndex* pQuorumBaseBlockIndex = WITH_LOCK(cs_main, return LookupBlockIndex(request.GetQuorumHash())); if (pQuorumBaseBlockIndex == nullptr) { - sendQDATA(CQuorumDataRequest::Errors::QUORUM_BLOCK_NOT_FOUND); + sendQDATA(CQuorumDataRequest::Errors::QUORUM_BLOCK_NOT_FOUND, request_limit_exceeded); return; } const CQuorumCPtr pQuorum = GetQuorum(request.GetLLMQType(), pQuorumBaseBlockIndex); if (pQuorum == nullptr) { - sendQDATA(CQuorumDataRequest::Errors::QUORUM_NOT_FOUND); + sendQDATA(CQuorumDataRequest::Errors::QUORUM_NOT_FOUND, request_limit_exceeded); return; } @@ -676,7 +694,7 @@ void CQuorumManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, C // Check if request wants QUORUM_VERIFICATION_VECTOR data if (request.GetDataMask() & CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR) { if (!pQuorum->HasVerificationVector()) { - sendQDATA(CQuorumDataRequest::Errors::QUORUM_VERIFICATION_VECTOR_MISSING); + sendQDATA(CQuorumDataRequest::Errors::QUORUM_VERIFICATION_VECTOR_MISSING, request_limit_exceeded); return; } @@ -688,20 +706,20 @@ void CQuorumManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, C int memberIdx = pQuorum->GetMemberIndex(request.GetProTxHash()); if (memberIdx == -1) { - sendQDATA(CQuorumDataRequest::Errors::MASTERNODE_IS_NO_MEMBER); + sendQDATA(CQuorumDataRequest::Errors::MASTERNODE_IS_NO_MEMBER, request_limit_exceeded); return; } std::vector> vecEncrypted; if (!dkgManager.GetEncryptedContributions(request.GetLLMQType(), pQuorumBaseBlockIndex, pQuorum->qc->validMembers, request.GetProTxHash(), vecEncrypted)) { - sendQDATA(CQuorumDataRequest::Errors::ENCRYPTED_CONTRIBUTIONS_MISSING); + sendQDATA(CQuorumDataRequest::Errors::ENCRYPTED_CONTRIBUTIONS_MISSING, request_limit_exceeded); return; } ssResponseData << vecEncrypted; } - sendQDATA(CQuorumDataRequest::Errors::NONE, ssResponseData); + sendQDATA(CQuorumDataRequest::Errors::NONE, request_limit_exceeded, ssResponseData); return; } @@ -902,7 +920,7 @@ void CQuorumManager::StartQuorumDataRecoveryThread(const CQuorumCPtr pQuorum, co key.quorumHash = pQuorum->qc->quorumHash; key.llmqType = pQuorum->qc->llmqType; auto it = mapQuorumDataRequests.find(key); - if (it != mapQuorumDataRequests.end() && !it->second.IsExpired()) { + if (it != mapQuorumDataRequests.end() && !it->second.IsExpired(/*add_bias=*/true)) { printLog("Already asked"); continue; } diff --git a/src/llmq/quorums.h b/src/llmq/quorums.h index 5e6d12dd8c06d..6e24d369d1fd8 100644 --- a/src/llmq/quorums.h +++ b/src/llmq/quorums.h @@ -83,6 +83,7 @@ class CQuorumDataRequest bool fProcessed{false}; static constexpr int64_t EXPIRATION_TIMEOUT{300}; + static constexpr int64_t EXPIRATION_BIAS{60}; public: @@ -119,7 +120,7 @@ class CQuorumDataRequest Errors GetError() const { return nError; } std::string GetErrorString() const; - bool IsExpired() const { return (GetTime() - nTime) >= EXPIRATION_TIMEOUT; } + bool IsExpired(bool add_bias) const { return (GetTime() - nTime) >= (EXPIRATION_TIMEOUT + (add_bias ? EXPIRATION_BIAS : 0)); } bool IsProcessed() const { return fProcessed; } void SetProcessed() { fProcessed = true; } diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index eca3d2179c6ff..8db4721c1922b 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -736,6 +736,27 @@ static UniValue clearbanned(const JSONRPCRequest& request) return NullUniValue; } +static UniValue cleardiscouraged(const JSONRPCRequest& request) +{ + RPCHelpMan{"cleardiscouraged", + "\nClear all discouraged nodes.\n", + {}, + RPCResult{RPCResult::Type::NONE, "", ""}, + RPCExamples{ + HelpExampleCli("cleardiscouraged", "") + + HelpExampleRpc("cleardiscouraged", "") + }, + }.Check(request); + NodeContext& node = EnsureNodeContext(request.context); + if (!node.banman) { + throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); + } + + node.banman->ClearDiscouraged(); + + return NullUniValue; +} + static UniValue setnetworkactive(const JSONRPCRequest& request) { RPCHelpMan{"setnetworkactive", @@ -825,6 +846,7 @@ static const CRPCCommand commands[] = { "network", "setban", &setban, {"subnet", "command", "bantime", "absolute"} }, { "network", "listbanned", &listbanned, {} }, { "network", "clearbanned", &clearbanned, {} }, + { "network", "cleardiscouraged", &cleardiscouraged, {} }, { "network", "setnetworkactive", &setnetworkactive, {"state"} }, { "network", "getnodeaddresses", &getnodeaddresses, {"count"} }, }; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 5118f7401f4c0..3e1388da25d9b 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2482,7 +2482,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) {RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB"}, {RPCResult::Type::STR_HEX, "hdchainid", "the ID of the HD chain"}, {RPCResult::Type::STR, "hdaccountcount", "how many accounts of the HD chain are in this wallet"}, - {RPCResult::Type::ARR, "", "", + {RPCResult::Type::ARR, "hdaccounts", "", { {RPCResult::Type::OBJ, "", "", { diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index dcdb12f5e143a..04a6c34b6b477 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -980,8 +980,8 @@ def set_dash_test_params(self, num_nodes, masterodes_count, extra_args=None, fas # This is nRequestTimeout in dash-q-recovery thread self.quorum_data_thread_request_timeout_seconds = 10 - # This is EXPIRATION_TIMEOUT in CQuorumDataRequest - self.quorum_data_request_expiration_timeout = 300 + # This is EXPIRATION_TIMEOUT + EXPIRATION_BIAS in CQuorumDataRequest + self.quorum_data_request_expiration_timeout = 360 def set_dash_dip8_activation(self, activate_after_block): self.dip8_activation_height = activate_after_block