Skip to content

Commit c25c61a

Browse files
UdjinM6claude
andcommitted
test: add InstantSend after full restart test
Tests that InstantSend works after a full cluster restart without mining any new blocks. This exercises the quorum connection re-establishment path added in the previous commit. The test: - Funds a sender, chainlocks the tip - Restarts all nodes (simple nodes and masternodes) - Reconnects all nodes to node 0 - Bumps mocktime past WAIT_FOR_ISLOCK_TIMEOUT - Sends a transaction and verifies it gets an IS lock Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5e79dbe commit c25c61a

1 file changed

Lines changed: 63 additions & 1 deletion

File tree

test/functional/p2p_instantsend.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

66
from test_framework.test_framework import DashTestFramework
7-
from test_framework.util import assert_equal, assert_raises_rpc_error
7+
from test_framework.util import assert_equal, assert_raises_rpc_error, force_finish_mnsync
88

99
'''
1010
p2p_instantsend.py
@@ -36,6 +36,7 @@ def run_test(self):
3636

3737
self.test_mempool_doublespend()
3838
self.test_block_doublespend()
39+
self.test_instantsend_after_restart()
3940

4041
def test_block_doublespend(self):
4142
sender = self.nodes[self.sender_idx]
@@ -143,5 +144,66 @@ def test_mempool_doublespend(self):
143144
# mine more blocks
144145
self.generate(self.nodes[0], 2)
145146

147+
def test_instantsend_after_restart(self):
148+
self.log.info("Testing InstantSend works after full restart without new blocks")
149+
150+
# fund sender with confirmed coins
151+
sender = self.nodes[self.sender_idx]
152+
receiver = self.nodes[self.receiver_idx]
153+
sender_addr = sender.getnewaddress()
154+
fund_id = self.nodes[0].sendtoaddress(sender_addr, 1)
155+
self.bump_mocktime(30)
156+
self.sync_mempools()
157+
for node in self.nodes:
158+
self.wait_for_instantlock(fund_id, node)
159+
tip = self.generate(self.nodes[0], 2)[-1]
160+
self.bump_mocktime(30)
161+
self.wait_for_chainlocked_block_all_nodes(tip)
162+
self.sync_blocks()
163+
assert sender.getbalance() >= 0.5
164+
165+
receiver_addr = receiver.getnewaddress()
166+
167+
# restart all nodes without mining new blocks
168+
self.log.info("Restarting all nodes")
169+
num_simple_nodes = self.num_nodes - self.mn_count
170+
self.stop_nodes()
171+
172+
for i in range(num_simple_nodes):
173+
self.start_node(i)
174+
for mn_info in self.mninfo:
175+
self.start_masternode(mn_info)
176+
177+
# reconnect: simple nodes to node 0, MNs to node 0
178+
# quorum connections are re-established automatically via InitializeCurrentBlockTip
179+
for i in range(1, num_simple_nodes):
180+
self.connect_nodes(i, 0)
181+
for mn_info in self.mninfo:
182+
self.connect_nodes(mn_info.nodeIdx, 0)
183+
for i in range(num_simple_nodes):
184+
force_finish_mnsync(self.nodes[i])
185+
186+
# bump past WAIT_FOR_ISLOCK_TIMEOUT so txFirstSeenTime loss doesn't
187+
# block chainlock signing for TXs mined before restart
188+
self.bump_mocktime(10 * 60 + 1)
189+
self.sync_blocks()
190+
191+
# re-grab references after restart
192+
sender = self.nodes[self.sender_idx]
193+
receiver = self.nodes[self.receiver_idx]
194+
195+
# send a TX — needs IS lock from all restarted MNs, no new blocks mined
196+
is_id = sender.sendtoaddress(receiver_addr, 0.5)
197+
self.bump_mocktime(30)
198+
self.sync_mempools()
199+
self.wait_for_instantlock(is_id, sender)
200+
self.log.info("InstantSend lock succeeded after full restart")
201+
202+
# clean up
203+
receiver.sendtoaddress(self.nodes[0].getnewaddress(), 0.5, "", "", True)
204+
self.bump_mocktime(30)
205+
self.sync_mempools()
206+
self.generate(self.nodes[0], 2)
207+
146208
if __name__ == '__main__':
147209
InstantSendTest().main()

0 commit comments

Comments
 (0)