diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 015408b9306a..f9e3efd7ec18 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -76,6 +76,7 @@ #include #include #include +#include using node::ReadBlockFromDisk; using node::fImporting; @@ -389,6 +390,10 @@ struct Peer { /** Whether this peer has already sent us a getaddr message. */ bool m_getaddr_recvd GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false}; + /** Whether this peer has already requested sporks. */ + bool m_getsporks_recvd GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false}; + /** Hashes of active sporks sent in the last getsporks response. */ + std::vector m_getsporks_last_response GUARDED_BY(NetEventsInterface::g_msgproc_mutex){}; /** Number of addresses that can be processed from this peer. Start at 1 to * permit self-announcement. */ double m_addr_token_bucket GUARDED_BY(NetEventsInterface::g_msgproc_mutex){1.0}; @@ -5449,6 +5454,24 @@ void PeerManagerImpl::ProcessMessage( if (msg_type == NetMsgType::GETSPORKS) { // For 'getsporks', active sporks is sent to the requesting peer. auto active_sporks = m_sporkman.ActiveSporks(); + std::vector active_spork_hashes; + for (const auto& pair : active_sporks) { + for (const auto& spork_pair : pair.second) { + active_spork_hashes.push_back(spork_pair.second.GetHash()); + } + } + std::sort(active_spork_hashes.begin(), active_spork_hashes.end()); + + // Ignore repeated requests only while the active spork set is unchanged. + // Functional tests and some peers request sporks again after a spork + // update; those requests must receive the newer active set. + if (peer->m_getsporks_recvd && peer->m_getsporks_last_response == active_spork_hashes) { + LogPrint(BCLog::NET, "Ignoring repeated \"getsporks\". peer=%d\n", pfrom.GetId()); + return; + } + peer->m_getsporks_recvd = true; + peer->m_getsporks_last_response = active_spork_hashes; + for (const auto& pair : active_sporks) { for (const auto& signerSporkPair : pair.second) { m_connman.PushMessage(&pfrom, CNetMsgMaker(pfrom.GetCommonVersion()).Make(NetMsgType::SPORK, signerSporkPair.second)); diff --git a/test/functional/feature_sporks.py b/test/functional/feature_sporks.py index b6b5fdf7b817..7940c1aa9b86 100755 --- a/test/functional/feature_sporks.py +++ b/test/functional/feature_sporks.py @@ -3,8 +3,48 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. +from test_framework.p2p import MESSAGEMAP, P2PInterface from test_framework.test_framework import BitcoinTestFramework + +class msg_getsporks: + __slots__ = () + msgtype = b"getsporks" + + def deserialize(self, f): + pass + + def serialize(self): + return b"" + + def __repr__(self): + return "msg_getsporks()" + + +class msg_spork_raw: + __slots__ = ("raw",) + msgtype = b"spork" + + def __init__(self): + self.raw = b"" + + def deserialize(self, f): + self.raw = f.read() + + def serialize(self): + return self.raw + + def __repr__(self): + return f"msg_spork_raw(len={len(self.raw)})" + + +class SporkP2PInterface(P2PInterface): + def on_inv(self, message): + pass + + def on_spork(self, message): + pass + ''' ''' @@ -44,6 +84,22 @@ def run_test(self): self.set_test_spork_state(self.nodes[0], spork_new_state) self.wait_until(lambda: self.get_test_spork_state(self.nodes[1]), timeout=10) + # GETSPORKS should not resend an unchanged active spork set, but must + # answer again after the active spork set changes on the same connection. + MESSAGEMAP[b"spork"] = msg_spork_raw + peer = self.nodes[0].add_p2p_connection(SporkP2PInterface()) + peer.send_message(msg_getsporks()) + peer.wait_until(lambda: peer.message_count["spork"] > 0) + peer.sync_with_ping() + spork_responses = peer.message_count["spork"] + peer.send_message(msg_getsporks()) + peer.sync_with_ping() + assert peer.message_count["spork"] == spork_responses + + self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0) + peer.send_message(msg_getsporks()) + peer.wait_until(lambda: peer.message_count["spork"] > spork_responses) + # restart nodes to check spork persistence self.stop_node(0) self.stop_node(1)