|
4 | 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. |
5 | 5 |
|
6 | 6 | 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 |
8 | 8 |
|
9 | 9 | ''' |
10 | 10 | p2p_instantsend.py |
@@ -36,6 +36,7 @@ def run_test(self): |
36 | 36 |
|
37 | 37 | self.test_mempool_doublespend() |
38 | 38 | self.test_block_doublespend() |
| 39 | + self.test_instantsend_after_restart() |
39 | 40 |
|
40 | 41 | def test_block_doublespend(self): |
41 | 42 | sender = self.nodes[self.sender_idx] |
@@ -143,5 +144,85 @@ def test_mempool_doublespend(self): |
143 | 144 | # mine more blocks |
144 | 145 | self.generate(self.nodes[0], 2) |
145 | 146 |
|
| 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 only. |
| 178 | + # Quorum connections between MNs must be re-established automatically |
| 179 | + # via InitializeCurrentBlockTip → EnsureQuorumConnections, NOT via |
| 180 | + # manual connect_nodes between MN pairs. |
| 181 | + for i in range(1, num_simple_nodes): |
| 182 | + self.connect_nodes(i, 0) |
| 183 | + for mn_info in self.mninfo: |
| 184 | + self.connect_nodes(mn_info.nodeIdx, 0) |
| 185 | + for i in range(num_simple_nodes): |
| 186 | + force_finish_mnsync(self.nodes[i]) |
| 187 | + |
| 188 | + # bump past WAIT_FOR_ISLOCK_TIMEOUT so txFirstSeenTime loss doesn't |
| 189 | + # block chainlock signing for TXs mined before restart |
| 190 | + self.bump_mocktime(10 * 60 + 1) |
| 191 | + self.sync_blocks() |
| 192 | + |
| 193 | + # Verify that MNs formed quorum connections to other MNs after restart. |
| 194 | + # InitializeCurrentBlockTip → EnsureQuorumConnections must populate |
| 195 | + # masternodeQuorumNodes so ThreadOpenMasternodeConnections establishes |
| 196 | + # MN-to-MN links beyond the manual connections to node 0. |
| 197 | + self.log.info("Verifying MN-to-MN quorum connections formed after restart") |
| 198 | + for mn_info in self.mninfo: |
| 199 | + mn_node = self.nodes[mn_info.nodeIdx] |
| 200 | + |
| 201 | + def check_mn_peers(node=mn_node, my_hash=mn_info.proTxHash): |
| 202 | + peers = node.getpeerinfo() |
| 203 | + mn_peers = set(p['verified_proregtx_hash'] for p in peers |
| 204 | + if p.get('verified_proregtx_hash', '') != '') |
| 205 | + other_mn_peers = mn_peers - {my_hash} |
| 206 | + return len(other_mn_peers) > 0 |
| 207 | + self.wait_until(check_mn_peers, timeout=30) |
| 208 | + |
| 209 | + # re-grab references after restart |
| 210 | + sender = self.nodes[self.sender_idx] |
| 211 | + receiver = self.nodes[self.receiver_idx] |
| 212 | + |
| 213 | + # send a TX — needs IS lock from all restarted MNs, no new blocks mined |
| 214 | + is_id = sender.sendtoaddress(receiver_addr, 0.5) |
| 215 | + self.bump_mocktime(30) |
| 216 | + self.sync_mempools() |
| 217 | + for node in self.nodes: |
| 218 | + self.wait_for_instantlock(is_id, node) |
| 219 | + self.log.info("InstantSend lock succeeded after full restart") |
| 220 | + |
| 221 | + # clean up |
| 222 | + receiver.sendtoaddress(self.nodes[0].getnewaddress(), 0.5, "", "", True) |
| 223 | + self.bump_mocktime(30) |
| 224 | + self.sync_mempools() |
| 225 | + self.generate(self.nodes[0], 2) |
| 226 | + |
146 | 227 | if __name__ == '__main__': |
147 | 228 | InstantSendTest().main() |
0 commit comments