diff --git a/test/functional/p2p_instantsend.py b/test/functional/p2p_instantsend.py index 8dd14eb35cd7..7f4066f3e40d 100755 --- a/test/functional/p2p_instantsend.py +++ b/test/functional/p2p_instantsend.py @@ -3,6 +3,8 @@ # 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_qsendrecsigs +from test_framework.p2p import P2PInterface from test_framework.test_framework import DashTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error, force_finish_mnsync @@ -12,6 +14,24 @@ Tests InstantSend functionality (prevent doublespend for unconfirmed transactions) ''' +class RecSigsObserver(P2PInterface): + """Non-MN peer that opts in to recsigs and records every ISDLOCK inv it sees.""" + + def __init__(self): + super().__init__() + self.isdlock_inv_seen = False + + def send_qsendrecsigs(self, wants_recsigs=True): + self.send_message(msg_qsendrecsigs(wants_recsigs)) + + def on_inv(self, message): + for inv in message.inv: + # MSG_ISDLOCK inv type, see src/protocol.h + if inv.type == 31: + self.isdlock_inv_seen = True + super().on_inv(message) + + class InstantSendTest(DashTestFramework): def add_options(self, parser): self.add_wallet_options(parser) @@ -36,6 +56,7 @@ def run_test(self): self.test_mempool_doublespend() self.test_block_doublespend() + self.test_isdlock_relayed_to_recsigs_observer() self.test_instantsend_after_restart() def test_block_doublespend(self): @@ -131,6 +152,27 @@ def test_mempool_doublespend(self): # mine more blocks self.generate(self.nodes[0], 2) + def test_isdlock_relayed_to_recsigs_observer(self): + self.log.info("Non-MN peer started with -watchquorums must still get ISDLOCK invs") + observers = [] + for mn in self.mninfo: + node = mn.get_node(self) + obs = node.add_p2p_connection(RecSigsObserver()) + obs.send_qsendrecsigs(True) + obs.sync_with_ping() + observers.append((node, obs)) + + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) + self.wait_for_instantlock(txid) + for _, obs in observers: + obs.sync_with_ping() + + assert any(obs.isdlock_inv_seen for _, obs in observers), \ + "non-MN peer with QSENDRECSIGS got no MSG_ISDLOCK inv" + + for node, _ in observers: + node.disconnect_p2ps() + def test_instantsend_after_restart(self): self.log.info("Testing InstantSend works after full restart without new blocks") diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 1bddb79f12f2..cbc792c9248e 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -2447,6 +2447,23 @@ def __repr__(self): return "msg_qsigshare(sigShares=%d)" % (len(self.sig_shares)) +class msg_qsendrecsigs: + __slots__ = ("wants_recsigs",) + msgtype = b"qsendrecsigs" + + def __init__(self, wants_recsigs=True): + self.wants_recsigs = wants_recsigs + + def deserialize(self, f): + self.wants_recsigs = bool(struct.unpack("