diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 785267e10b231..e778859131262 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -838,6 +839,12 @@ bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, gsl::no newState->platformP2PPort = opt_proTx->platformP2PPort; newState->platformHTTPPort = opt_proTx->platformHTTPPort; } + if (auto meta_info = m_mn_metaman.GetMetaInfo(opt_proTx->proTxHash, false); + !meta_info || !meta_info->SetPlatformBan(false, nHeight)) { + LogPrintf("CDeterministicMNManager::%s -- MN %s is not Platform revived at height %d\n", __func__, + opt_proTx->proTxHash.ToString(), nHeight); + } + if (newState->IsBanned()) { // only revive when all keys are set if (newState->pubKeyOperator != CBLSLazyPublicKey() && !newState->keyIDVoting.IsNull() && diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 76375e2d0695b..00efe5c767f77 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -30,6 +30,7 @@ class CBlock; class CBlockIndex; class CCoinsViewCache; class CEvoDB; +class CMasternodeMetaMan; class TxValidationState; extern RecursiveMutex cs_main; @@ -563,6 +564,7 @@ class CDeterministicMNManager std::atomic to_cleanup {0}; CEvoDB& m_evoDb; + CMasternodeMetaMan& m_mn_metaman; std::unordered_map mnListsCache GUARDED_BY(cs); std::unordered_map mnListDiffsCache GUARDED_BY(cs); @@ -570,8 +572,9 @@ class CDeterministicMNManager const CBlockIndex* m_initial_snapshot_index GUARDED_BY(cs) {nullptr}; public: - explicit CDeterministicMNManager(CEvoDB& evoDb) : - m_evoDb(evoDb) + explicit CDeterministicMNManager(CEvoDB& evoDb, CMasternodeMetaMan& mn_metaman) : + m_evoDb(evoDb), + m_mn_metaman(mn_metaman) { } ~CDeterministicMNManager() = default; diff --git a/src/llmq/dkgsession.cpp b/src/llmq/dkgsession.cpp index e7e806f2f2db5..919bb43ba429b 100644 --- a/src/llmq/dkgsession.cpp +++ b/src/llmq/dkgsession.cpp @@ -495,11 +495,15 @@ void CDKGSession::VerifyConnectionAndMinProtoVersions(CConnman& connman) const m->badConnection = true; logger.Batch("%s does not have min proto version %d (has %d)", m->dmn->proTxHash.ToString(), MIN_MASTERNODE_PROTO_VERSION, it->second); } - - if (m_mn_metaman.GetMetaInfo(m->dmn->proTxHash)->OutboundFailedTooManyTimes()) { + const auto meta_info = m_mn_metaman.GetMetaInfo(m->dmn->proTxHash); + if (meta_info->OutboundFailedTooManyTimes()) { m->badConnection = true; logger.Batch("%s failed to connect to it too many times", m->dmn->proTxHash.ToString()); } + if (meta_info->IsPlatformBanned()) { + m->badConnection = true; + logger.Batch("%s is Platform PoSe banned", m->dmn->proTxHash.ToString()); + } } } diff --git a/src/masternode/meta.cpp b/src/masternode/meta.cpp index 3cc3ffbb5932e..cb8e584bd0d11 100644 --- a/src/masternode/meta.cpp +++ b/src/masternode/meta.cpp @@ -43,6 +43,11 @@ UniValue CMasternodeMetaInfo::ToJson() const ret.pushKV("lastOutboundAttemptElapsed", now - lastOutboundAttempt); ret.pushKV("lastOutboundSuccess", lastOutboundSuccess); ret.pushKV("lastOutboundSuccessElapsed", now - lastOutboundSuccess); + { + LOCK(cs); + ret.pushKV("platform_ban", m_platform_ban); + ret.pushKV("platform_ban_updated", m_platform_ban_height); + } return ret; } @@ -127,8 +132,31 @@ std::vector CMasternodeMetaMan::GetAndClearDirtyGovernanceObjectHashes( return vecTmp; } +bool CMasternodeMetaMan::AlreadyHavePlatformBan(const uint256& inv_hash) const +{ + LOCK(cs); + return m_seen_platform_bans.contains(inv_hash); +} + +std::optional CMasternodeMetaMan::GetPlatformBan(const uint256& inv_hash) const +{ + LOCK(cs); + auto it = m_seen_platform_bans.find(inv_hash); + if (it == m_seen_platform_bans.end()) return std::nullopt; + + return it->second; +} + +void CMasternodeMetaMan::RememberPlatformBan(const uint256& inv_hash, PlatformBanMessage& msg) +{ + LOCK(cs); + m_seen_platform_bans.insert({inv_hash, msg}); +} + std::string MasternodeMetaStore::ToString() const { LOCK(cs); return strprintf("Masternodes: meta infos object count: %d, nDsqCount: %d", metaInfos.size(), nDsqCount); } + +uint256 PlatformBanMessage::GetHash() const { return ::SerializeHash(*this); } diff --git a/src/masternode/meta.h b/src/masternode/meta.h index 72eb844bb159a..96fc29fcfff7d 100644 --- a/src/masternode/meta.h +++ b/src/masternode/meta.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_MASTERNODE_META_H #define BITCOIN_MASTERNODE_META_H +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include class CConnman; @@ -46,6 +48,9 @@ class CMasternodeMetaInfo std::atomic lastOutboundAttempt{0}; std::atomic lastOutboundSuccess{0}; + bool m_platform_ban GUARDED_BY(cs){false}; + int m_platform_ban_height GUARDED_BY(cs){0}; + public: CMasternodeMetaInfo() = default; explicit CMasternodeMetaInfo(const uint256& _proTxHash) : proTxHash(_proTxHash) {} @@ -55,22 +60,18 @@ class CMasternodeMetaInfo nMixingTxCount(ref.nMixingTxCount.load()), mapGovernanceObjectsVotedOn(ref.mapGovernanceObjectsVotedOn), lastOutboundAttempt(ref.lastOutboundAttempt.load()), - lastOutboundSuccess(ref.lastOutboundSuccess.load()) + lastOutboundSuccess(ref.lastOutboundSuccess.load()), + m_platform_ban(ref.m_platform_ban), + m_platform_ban_height(ref.m_platform_ban_height) { } SERIALIZE_METHODS(CMasternodeMetaInfo, obj) { LOCK(obj.cs); - READWRITE( - obj.proTxHash, - obj.nLastDsq, - obj.nMixingTxCount, - obj.mapGovernanceObjectsVotedOn, - obj.outboundAttemptCount, - obj.lastOutboundAttempt, - obj.lastOutboundSuccess - ); + READWRITE(obj.proTxHash, obj.nLastDsq, obj.nMixingTxCount, obj.mapGovernanceObjectsVotedOn, + obj.outboundAttemptCount, obj.lastOutboundAttempt, obj.lastOutboundSuccess, obj.m_platform_ban, + obj.m_platform_ban_height); } UniValue ToJson() const; @@ -96,6 +97,24 @@ class CMasternodeMetaInfo int64_t GetLastOutboundAttempt() const { return lastOutboundAttempt; } void SetLastOutboundSuccess(int64_t t) { lastOutboundSuccess = t; outboundAttemptCount = 0; } int64_t GetLastOutboundSuccess() const { return lastOutboundSuccess; } + bool SetPlatformBan(bool is_banned, int height) + { + LOCK(cs); + if (height < m_platform_ban_height) { + return false; + } + if (height == m_platform_ban_height && !is_banned) { + return false; + } + m_platform_ban = is_banned; + m_platform_ban_height = height; + return true; + } + bool IsPlatformBanned() const + { + LOCK(cs); + return m_platform_ban; + } }; using CMasternodeMetaInfoPtr = std::shared_ptr; @@ -150,6 +169,40 @@ class MasternodeMetaStore std::string ToString() const; }; +/** + * Platform PoSe Ban are result in the node voting against the targeted evonode in all future DKG sessions until that targeted + *evonode has been successfully banned. Platform will initiate this ban process by passing relevant information to Core using RPC. See DIP-0031 + * + * We use 2 main classes to manage Platform PoSe Ban + * + * PlatformBanMessage + * PlatformBanManager - a higher-level construct which store information about ban status + **/ + +/** + * PlatformBanMessage - low-level constructs which contain the m_protx_hash, m_requested_height, m_quorum_hash and m_signature + */ +class PlatformBanMessage +{ +public: + uint256 m_protx_hash; + int32_t m_requested_height{0}; + uint256 m_quorum_hash; + CBLSSignature m_signature; + + PlatformBanMessage() = default; + + SERIALIZE_METHODS(PlatformBanMessage, obj) + { + READWRITE(obj.m_protx_hash, obj.m_requested_height, obj.m_quorum_hash); + if (!(s.GetType() & SER_GETHASH)) { + READWRITE(CBLSSignatureVersionWrapper(const_cast(obj.m_signature), false)); + } + } + + uint256 GetHash() const; +}; + class CMasternodeMetaMan : public MasternodeMetaStore { private: @@ -160,6 +213,7 @@ class CMasternodeMetaMan : public MasternodeMetaStore bool is_valid{false}; std::vector vecDirtyGovernanceObjectHashes GUARDED_BY(cs); + std::map m_seen_platform_bans GUARDED_BY(cs); public: explicit CMasternodeMetaMan(); @@ -181,6 +235,10 @@ class CMasternodeMetaMan : public MasternodeMetaStore void RemoveGovernanceObject(const uint256& nGovernanceObjectHash); std::vector GetAndClearDirtyGovernanceObjectHashes(); + + bool AlreadyHavePlatformBan(const uint256& inv_hash) const; + std::optional GetPlatformBan(const uint256& inv_hash) const; + void RememberPlatformBan(const uint256& inv_hash, PlatformBanMessage& msg); }; #endif // BITCOIN_MASTERNODE_META_H diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 43feb7c261db9..dd54d666cb06d 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -712,6 +713,9 @@ class PeerManagerImpl final : public PeerManager const std::vector& headers, bool via_compact_block) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex); + PeerMsgRet ProcessPlatformBanMessage(CNode& peer, std::string_view msg_type, CDataStream& vRecv) + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex); + /** Various helpers for headers processing, invoked by ProcessHeadersMessage() */ /** Deal with state tracking and headers sync for peers that send the * occasional non-connecting header (this can happen due to BIP 130 headers @@ -2256,6 +2260,8 @@ bool PeerManagerImpl::AlreadyHave(const CInv& inv) #else return m_cj_ctx->server->HasQueue(inv.hash); #endif + case MSG_PLATFORM_BAN: + return m_mn_metaman.AlreadyHavePlatformBan(inv.hash); } @@ -2878,6 +2884,13 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic push = true; } } + if (!push && inv.type == MSG_PLATFORM_BAN) { + auto opt_platform_ban = m_mn_metaman.GetPlatformBan(inv.hash); + if (opt_platform_ban.has_value()) { + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::PLATFORMBAN, *opt_platform_ban)); + push = true; + } + } if (!push) { vNotFound.push_back(inv); @@ -3506,6 +3519,74 @@ void PeerManagerImpl::PostProcessMessage(MessageProcessingResult&& result, NodeI } } +PeerMsgRet PeerManagerImpl::ProcessPlatformBanMessage(CNode& pfrom, std::string_view msg_type, CDataStream& vRecv) +{ + if (msg_type != NetMsgType::PLATFORMBAN) return {}; + + // Do nothing if node is out of sync + if (!m_mn_sync.IsBlockchainSynced()) { + return {}; + } + + PlatformBanMessage ban_msg; + vRecv >> ban_msg; + + const uint256 hash = ban_msg.GetHash(); + + LogPrintf("PLATFORMBAN -- hash: %s protx_hash: %s height: %d peer=%d\n", hash.ToString(), ban_msg.m_protx_hash.ToString(), ban_msg.m_requested_height, pfrom.GetId()); + + const auto list = Assert(m_dmnman)->GetListAtChainTip(); + auto dmn = list.GetMN(ban_msg.m_protx_hash); + if (!dmn) { + // small P2P penalty (1), as the evonode may have very recently been removed + return tl::unexpected{1}; + } + if (dmn->nType != MnType::Evo) { + // Ban node, P2P penalty (100) if protx_hash is associated with a regular node not an evonode + LogPrintf("PLATFORMBAN -- hash: %s protx_hash: %s unexpected type of node\n", hash.ToString(), ban_msg.m_protx_hash.ToString()); + return tl::unexpected{100}; + } + const int day_of_blocks = 576; + int tipHeight = WITH_LOCK(cs_main, return m_chainman.ActiveChainstate().m_chain.Height()); + if (tipHeight < ban_msg.m_requested_height || tipHeight - day_of_blocks > ban_msg.m_requested_height) { + // m_requested_height is inside the range [TipHeight - 576 - 5, TipHeight + 5] + LogPrintf("PLATFORMBAN -- hash: %s protx_hash: %s unexpected height: %d tip: %d\n", hash.ToString(), ban_msg.m_protx_hash.ToString(), ban_msg.m_requested_height, tipHeight); + if (tipHeight + 5 < ban_msg.m_requested_height || tipHeight - day_of_blocks - 5 > ban_msg.m_requested_height) { + // m_requested_height is outside the range [TipHeight - 576 - 5, TipHeight + 5] + return tl::unexpected{10}; + } + return tl::unexpected{1}; + } + + Consensus::LLMQType llmq_type = Params().GetConsensus().llmqTypePlatform; + auto quorum = m_llmq_ctx->qman->GetQuorum(llmq_type, ban_msg.m_quorum_hash); + if (!quorum) { + LogPrintf("PLATFORMBAN -- hash: %s protx_hash: %s missing quorum_hash: %s llmq_type: %d\n", hash.ToString(), ban_msg.m_protx_hash.ToString(), ban_msg.m_quorum_hash.ToString(), ToUnderlying(llmq_type)); + return tl::unexpected{100}; + } + + const std::string PLATFORM_BAN_REQUESTID_PREFIX = "PlatformPoSeBan"; + const auto data = std::make_pair(ban_msg.m_protx_hash, ban_msg.m_requested_height); + const uint256 request_id = ::SerializeHash(std::make_pair(PLATFORM_BAN_REQUESTID_PREFIX, data)); + const uint256 msg_hash = ::SerializeHash(data); + + auto ret = llmq::VerifyRecoveredSig(llmq_type, m_chainman.ActiveChainstate().m_chain, *m_llmq_ctx->qman, ban_msg.m_requested_height, request_id, msg_hash, ban_msg.m_signature); + if (ret != llmq::VerifyRecSigStatus::Valid) { + LogPrintf("PLATFORMBAN -- hash: %s protx_hash: %s request_id: %s msg_hash: %s sig validation failed: %d\n", hash.ToString(), ban_msg.m_protx_hash.ToString(), request_id.ToString(), msg_hash.ToString(), ToUnderlying(ret)); + return tl::unexpected{100}; + } + + // At this point, the outgoing message serialization version can't change. + const auto meta_info = m_mn_metaman.GetMetaInfo(ban_msg.m_protx_hash); + if (meta_info->SetPlatformBan(true, ban_msg.m_requested_height)) { + LogPrintf("PLATFORMBAN -- forward message to other nodes\n"); + m_mn_metaman.RememberPlatformBan(hash, ban_msg); + CInv platform_ban_inv{MSG_PLATFORM_BAN, hash}; + RelayInv(platform_ban_inv); + } + return {}; +} + void PeerManagerImpl::ProcessMessage( CNode& pfrom, const std::string& msg_type, @@ -5219,7 +5300,6 @@ void PeerManagerImpl::ProcessMessage( Misbehaving(pfrom.GetId(), 100, strprintf("received not-requested quorumrotationinfo. peer=%d", pfrom.GetId())); return; } - if (msg_type == NetMsgType::NOTFOUND) { // Remove the NOTFOUND transactions from the peer LOCK(cs_main); @@ -5273,6 +5353,7 @@ void PeerManagerImpl::ProcessMessage( ProcessPeerMsgRet(m_llmq_ctx->qman->ProcessMessage(pfrom, m_connman, msg_type, vRecv), pfrom); m_llmq_ctx->shareman->ProcessMessage(pfrom, *this, m_sporkman, msg_type, vRecv); ProcessPeerMsgRet(m_llmq_ctx->sigman->ProcessMessage(pfrom, *this, msg_type, vRecv), pfrom); + ProcessPeerMsgRet(ProcessPlatformBanMessage(pfrom, msg_type, vRecv), pfrom); if (msg_type == NetMsgType::CLSIG) { if (llmq::AreChainLocksEnabled(m_sporkman)) { diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index fbfad427d749e..bc87b4c1fcd3f 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -213,7 +213,7 @@ void DashChainstateSetup(ChainstateManager& chainman, { // Same logic as pblocktree dmnman.reset(); - dmnman = std::make_unique(*evodb); + dmnman = std::make_unique(*evodb, mn_metaman); cpoolman.reset(); cpoolman = std::make_unique(*evodb); diff --git a/src/protocol.cpp b/src/protocol.cpp index 53c1f6b8a16e9..c5683a16dc9dc 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -90,6 +90,7 @@ MAKE_MSG(SENDHEADERS2, "sendheaders2"); MAKE_MSG(HEADERS2, "headers2"); MAKE_MSG(GETQUORUMROTATIONINFO, "getqrinfo"); MAKE_MSG(QUORUMROTATIONINFO, "qrinfo"); +MAKE_MSG(PLATFORMBAN, "platformban"); }; // namespace NetMsgType /** All known message types. Keep this in the same order as the list of @@ -170,7 +171,8 @@ const static std::string allNetMessageTypes[] = { NetMsgType::SENDHEADERS2, NetMsgType::HEADERS2, NetMsgType::GETQUORUMROTATIONINFO, - NetMsgType::QUORUMROTATIONINFO + NetMsgType::QUORUMROTATIONINFO, + NetMsgType::PLATFORMBAN, }; const static std::vector allNetMessageTypesVec(std::begin(allNetMessageTypes), std::end(allNetMessageTypes)); @@ -190,6 +192,7 @@ const static std::string netMessageTypesViolateBlocksOnly[] = { NetMsgType::DSTX, NetMsgType::DSVIN, NetMsgType::GETQUORUMROTATIONINFO, + NetMsgType::PLATFORMBAN, NetMsgType::QBSIGSHARES, NetMsgType::QCOMPLAINT, NetMsgType::QCONTRIB, @@ -296,6 +299,7 @@ const char* CInv::GetCommandInternal() const case MSG_CLSIG: return NetMsgType::CLSIG; case MSG_ISDLOCK: return NetMsgType::ISDLOCK; case MSG_DSQ: return NetMsgType::DSQUEUE; + case MSG_PLATFORM_BAN: return NetMsgType::PLATFORMBAN; default: return nullptr; } diff --git a/src/protocol.h b/src/protocol.h index 0c5161888a3fc..8cceaed608ebd 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -298,6 +298,7 @@ extern const char* SENDHEADERS2; extern const char* HEADERS2; extern const char* GETQUORUMROTATIONINFO; extern const char* QUORUMROTATIONINFO; +extern const char* PLATFORMBAN; }; /* Get a vector of all valid message types (see above) */ @@ -524,6 +525,7 @@ enum GetDataMsg : uint32_t { /* MSG_ISLOCK = 30, */ // Non-deterministic InstantSend and not used anymore MSG_ISDLOCK = 31, MSG_DSQ = 32, + MSG_PLATFORM_BAN = 33, // Platform service ban (DIP-0031) }; /** inv message data */ diff --git a/src/version.h b/src/version.h index 025c3ea7beb69..4364d77b6400b 100644 --- a/src/version.h +++ b/src/version.h @@ -11,7 +11,7 @@ */ -static const int PROTOCOL_VERSION = 70237; +static const int PROTOCOL_VERSION = 70238; //! initial proto version, to be increased after version/verack negotiation static const int INIT_PROTO_VERSION = 209; @@ -20,7 +20,7 @@ static const int INIT_PROTO_VERSION = 209; static const int MIN_PEER_PROTO_VERSION = 70216; //! minimum proto version of masternode to accept in DKGs -static const int MIN_MASTERNODE_PROTO_VERSION = 70237; +static const int MIN_MASTERNODE_PROTO_VERSION = 70238; //! protocol version is included in MNAUTH starting with this version static const int MNAUTH_NODE_VER_VERSION = 70218; @@ -64,9 +64,13 @@ static const int INCREASE_MAX_HEADERS2_VERSION = 70235; //! Behavior of QRINFO is changed in this protocol version static const int EFFICIENT_QRINFO_VERSION = 70236; + //! cycleHash in isdlock message switched to using quorum's base block in this version static const int ISDLOCK_CYCLEHASH_UPDATE_VERSION = 70237; +//! Introduced new p2p message platform pose BAN +static const int PLATFORM_BAN_VERSION = 70238; + // Make sure that none of the values above collide with `ADDRV2_FORMAT`. #endif // BITCOIN_VERSION_H diff --git a/test/functional/p2p_platform_ban.py b/test/functional/p2p_platform_ban.py new file mode 100755 index 0000000000000..b1eceea40cdaa --- /dev/null +++ b/test/functional/p2p_platform_ban.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# Copyright (c) 2025 The Dash Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.messages import msg_platformban, hash256, ser_string, ser_uint256 +from test_framework.p2p import ( + p2p_lock, + P2PInterface, +) +from test_framework.test_framework import DashTestFramework +from test_framework.util import wait_until_helper + +import struct + +class PlatformBanInterface(P2PInterface): + def __init__(self): + super().__init__() + + +class PlatformBanMessagesTest(DashTestFramework): + def set_test_params(self): + self.set_dash_test_params(1, 0, [[]], evo_count=3) + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def check_banned(self, mn): + info = self.nodes[0].protx('info', mn.proTxHash) + return info['state']['PoSeBanHeight'] != -1 + + def run_test(self): + node = self.nodes[0] + + node.sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0) + node.sporkupdate("SPORK_23_QUORUM_POSE", 0) + self.wait_for_sporks_same() + + evo_info_0 = self.dynamically_add_masternode(evo=True) + for _ in range(2): + self.dynamically_add_masternode(evo=True) + + self.mempool_size = 0 + + self.mine_quorum(llmq_type_name='llmq_test_platform', llmq_type=106) + + self.log.info("Create and sign platform-ban message for mn-0") + msg = msg_platformban() + msg.protx_hash = int(self.mninfo[0].proTxHash, 16) + msg.requested_height = node.getblockcount() + + request_id_buf = ser_string(b"PlatformPoSeBan") + ser_uint256(msg.protx_hash) + struct.pack(" 0, timeout=10, lock=p2p_lock) + p2p_node2.message_count["platformban"] = 0 + + + assert not self.check_banned(self.mninfo[0]) + + mninfos_valid = self.mninfo.copy()[1:] + self.mine_quorum(llmq_type_name='llmq_test_platform', expected_members=2, expected_connections=1, expected_contributions=2, expected_commitments=2, llmq_type=106, mninfos_valid=mninfos_valid) + + p2p_node = node.add_p2p_connection(PlatformBanInterface()) + p2p_node.send_message(msg) + self.mine_quorum(llmq_type_name='llmq_test_platform', expected_members=2, expected_connections=1, expected_contributions=2, expected_commitments=2, llmq_type=106, mninfos_valid=mninfos_valid) + assert self.check_banned(self.mninfo[0]) + + self.dynamically_evo_update_service(evo_info_0) + assert not self.check_banned(self.mninfo[0]) + +if __name__ == '__main__': + PlatformBanMessagesTest().main() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 95023f840c5b0..e2dd07d096b3c 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -2345,6 +2345,41 @@ def __repr__(self): (self.nVersion, repr(self.inputs), self.txid, self.cycleHash) +class msg_platformban: + __slots__ = ("protx_hash", "requested_height", "quorum_hash", "sig") + msgtype = b"platformban" + + def __init__(self, protx_hash=0, requested_height=0, quorum_hash=0, sig=b'\x00' * 96): + self.protx_hash = protx_hash + self.requested_height = requested_height + self.quorum_hash = quorum_hash + self.sig = sig + + def deserialize(self, f): + self.protx_hash = deser_uint256(f) + self.requested_height= struct.unpack("