forked from dashpay/dash
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfeature_llmq_is_retroactive.py
executable file
·208 lines (184 loc) · 10.2 KB
/
feature_llmq_is_retroactive.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#!/usr/bin/env python3
# Copyright (c) 2015-2023 The Dash Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
'''
feature_llmq_is_retroactive.py
Tests retroactive signing
We have 5 nodes where node 0 is the control node, nodes 1-4 are masternodes.
Mempool inconsistencies are simulated via disconnecting/reconnecting node 3
and by having a higher relay fee on node 4.
'''
import time
from test_framework.test_framework import DashTestFramework
from test_framework.util import set_node_times
class LLMQ_IS_RetroactiveSigning(DashTestFramework):
def set_test_params(self):
# -whitelist is needed to avoid the trickling logic on node0
self.set_dash_test_params(5, 4, [["-whitelist=127.0.0.1"], [], [], [], ["-minrelaytxfee=0.001"]], fast_dip3_enforcement=True)
def run_test(self):
self.activate_dip8()
self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0)
# Turn mempool IS signing off
self.nodes[0].sporkupdate("SPORK_2_INSTANTSEND_ENABLED", 1)
self.wait_for_sporks_same()
self.activate_v19(expected_activation_height=900)
self.log.info("Activated v19 at height:" + str(self.nodes[0].getblockcount()))
self.move_to_next_cycle()
self.log.info("Cycle H height:" + str(self.nodes[0].getblockcount()))
self.move_to_next_cycle()
self.log.info("Cycle H+C height:" + str(self.nodes[0].getblockcount()))
self.move_to_next_cycle()
self.log.info("Cycle H+2C height:" + str(self.nodes[0].getblockcount()))
self.mine_cycle_quorum(llmq_type_name='llmq_test_dip0024', llmq_type=103)
# Make sure that all nodes are chainlocked at the same height before starting actual tests
self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash(), timeout=30)
self.log.info("trying normal IS lock w/ signing spork off. Shouldn't be islocked before block is created.")
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
# 3 nodes should be enough to create an IS lock even if nodes 4 and 5 (which have no tx itself)
# are the only "neighbours" in intra-quorum connections for one of them.
self.wait_for_instantlock(txid, self.nodes[0], False, 5)
# Have to disable ChainLocks to avoid signing a block with a "safe" tx too early
self.nodes[0].sporkupdate("SPORK_19_CHAINLOCKS_ENABLED", 4000000000)
self.wait_for_sporks_same()
# We have to wait in order to include tx in block
self.bump_mocktime(10 * 60 + 1)
block = self.nodes[0].generate(1)[0]
self.wait_for_instantlock(txid, self.nodes[0])
self.nodes[0].sporkupdate("SPORK_19_CHAINLOCKS_ENABLED", 0)
self.wait_for_sporks_same()
self.wait_for_chainlocked_block_all_nodes(block)
self.log.info("Enable mempool IS signing")
self.nodes[0].sporkupdate("SPORK_2_INSTANTSEND_ENABLED", 0)
self.wait_for_sporks_same()
self.log.info("trying normal IS lock")
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
# 3 nodes should be enough to create an IS lock even if nodes 4 and 5 (which have no tx itself)
# are the only "neighbours" in intra-quorum connections for one of them.
self.wait_for_instantlock(txid, self.nodes[0])
self.bump_mocktime(1)
block = self.nodes[0].generate(1)[0]
self.wait_for_chainlocked_block_all_nodes(block)
self.log.info("testing normal signing with partially known TX")
self.isolate_node(3)
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
# Make sure nodes 1 and 2 received the TX before we continue,
# otherwise it might announce the TX to node 3 when reconnecting
self.wait_for_tx(txid, self.nodes[1])
self.wait_for_tx(txid, self.nodes[2])
self.reconnect_isolated_node(3, 0)
# Make sure nodes actually try re-connecting quorum connections
self.bump_mocktime(30)
self.wait_for_mnauth(self.nodes[3], 2)
# node 3 fully reconnected but the TX wasn't relayed to it, so there should be no IS lock
self.wait_for_instantlock(txid, self.nodes[0], False, 5)
# push the tx directly via rpc
self.nodes[3].sendrawtransaction(self.nodes[0].getrawtransaction(txid))
# node 3 should vote on a tx now since it became aware of it via sendrawtransaction
# and this should be enough to complete an IS lock
self.wait_for_instantlock(txid, self.nodes[0])
self.log.info("testing retroactive signing with unknown TX")
self.isolate_node(3)
rawtx = self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1})
rawtx = self.nodes[0].fundrawtransaction(rawtx)['hex']
rawtx = self.nodes[0].signrawtransactionwithwallet(rawtx)['hex']
txid = self.nodes[3].sendrawtransaction(rawtx)
# Make node 3 consider the TX as safe
self.bump_mocktime(10 * 60 + 1)
block = self.nodes[3].generatetoaddress(1, self.nodes[0].getnewaddress())[0]
self.reconnect_isolated_node(3, 0)
self.wait_for_chainlocked_block_all_nodes(block)
self.nodes[0].setmocktime(self.mocktime)
self.log.info("testing retroactive signing with partially known TX")
self.isolate_node(3)
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
# Make sure nodes 1 and 2 received the TX before we continue,
# otherwise it might announce the TX to node 3 when reconnecting
self.wait_for_tx(txid, self.nodes[1])
self.wait_for_tx(txid, self.nodes[2])
self.reconnect_isolated_node(3, 0)
# Make sure nodes actually try re-connecting quorum connections
self.bump_mocktime(30)
self.wait_for_mnauth(self.nodes[3], 2)
# node 3 fully reconnected but the TX wasn't relayed to it, so there should be no IS lock
self.wait_for_instantlock(txid, self.nodes[0], False, 5)
# Make node0 consider the TX as safe
self.bump_mocktime(10 * 60 + 1)
block = self.nodes[0].generate(1)[0]
assert txid in self.nodes[0].getblock(block, 1)['tx']
self.wait_for_chainlocked_block_all_nodes(block)
self.log.info("testing retroactive signing with partially known TX and all nodes session timeout")
self.test_all_nodes_session_timeout(False)
self.log.info("repeating test, but with cycled LLMQs")
self.test_all_nodes_session_timeout(True)
self.log.info("testing retroactive signing with partially known TX and single node session timeout")
self.test_single_node_session_timeout(False)
self.log.info("repeating test, but with cycled LLMQs")
self.test_single_node_session_timeout(True)
def cycle_llmqs(self):
self.mine_quorum()
self.mine_quorum()
self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash(), timeout=30)
def test_all_nodes_session_timeout(self, do_cycle_llmqs):
set_node_times(self.nodes, self.mocktime)
self.isolate_node(3)
rawtx = self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1})
rawtx = self.nodes[0].fundrawtransaction(rawtx)['hex']
rawtx = self.nodes[0].signrawtransactionwithwallet(rawtx)['hex']
txid = self.nodes[0].sendrawtransaction(rawtx)
txid = self.nodes[3].sendrawtransaction(rawtx)
# Make sure nodes 1 and 2 received the TX before we continue
self.wait_for_tx(txid, self.nodes[1])
self.wait_for_tx(txid, self.nodes[2])
# Make sure signing is done on nodes 1 and 2 (it's async)
time.sleep(5)
# Make the signing session for the IS lock timeout on nodes 1-3
self.bump_mocktime(61)
time.sleep(2) # make sure Cleanup() is called
self.reconnect_isolated_node(3, 0)
# Make sure nodes actually try re-connecting quorum connections
self.bump_mocktime(30)
self.wait_for_mnauth(self.nodes[3], 2)
# node 3 fully reconnected but the signing session is already timed out on all nodes, so no IS lock
self.wait_for_instantlock(txid, self.nodes[0], False, 5)
if do_cycle_llmqs:
self.cycle_llmqs()
self.wait_for_instantlock(txid, self.nodes[0], False, 5)
# Make node 0 consider the TX as safe
self.bump_mocktime(10 * 60 + 1)
block = self.nodes[0].generate(1)[0]
assert txid in self.nodes[0].getblock(block, 1)['tx']
self.wait_for_chainlocked_block_all_nodes(block)
def test_single_node_session_timeout(self, do_cycle_llmqs):
set_node_times(self.nodes, self.mocktime)
self.isolate_node(3)
rawtx = self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1})
rawtx = self.nodes[0].fundrawtransaction(rawtx)['hex']
rawtx = self.nodes[0].signrawtransactionwithwallet(rawtx)['hex']
txid = self.nodes[3].sendrawtransaction(rawtx)
time.sleep(2) # make sure signing is done on node 2 (it's async)
# Make the signing session for the IS lock timeout on node 3
self.bump_mocktime(61)
time.sleep(2) # make sure Cleanup() is called
self.reconnect_isolated_node(3, 0)
# Make sure nodes actually try re-connecting quorum connections
self.bump_mocktime(30)
self.wait_for_mnauth(self.nodes[3], 2)
self.nodes[0].sendrawtransaction(rawtx)
# Make sure nodes 1 and 2 received the TX
self.wait_for_tx(txid, self.nodes[1])
self.wait_for_tx(txid, self.nodes[2])
# Make sure signing is done on nodes 1 and 2 (it's async)
time.sleep(5)
# node 3 fully reconnected but the signing session is already timed out on it, so no IS lock
self.wait_for_instantlock(txid, self.nodes[0], False, 1)
if do_cycle_llmqs:
self.cycle_llmqs()
self.wait_for_instantlock(txid, self.nodes[0], False, 5)
# Make node 0 consider the TX as safe
self.bump_mocktime(10 * 60 + 1)
block = self.nodes[0].generate(1)[0]
assert txid in self.nodes[0].getblock(block, 1)['tx']
self.wait_for_chainlocked_block_all_nodes(block)
if __name__ == '__main__':
LLMQ_IS_RetroactiveSigning().main()