@@ -81,6 +81,79 @@ def run_test(self):
81
81
# When comparing genesis and best block, we shouldn't see the previously added and then deleted MN
82
82
mnList = self .test_getmnlistdiff (null_hash , self .nodes [0 ].getbestblockhash (), {}, [], expectedUpdated2 )
83
83
84
+ #############################
85
+ # Now start testing quorum commitment merkle roots
86
+
87
+ height = self .nodes [0 ].getblockcount ()
88
+ # Test DIP8 activation once with a pre-existing quorum and once without (we don't know in which order it will activate on mainnet)
89
+ self .test_dip8_quorum_merkle_root_activation (True )
90
+ for n in self .nodes :
91
+ n .invalidateblock (n .getblockhash (height + 1 ))
92
+ first_quorum = self .test_dip8_quorum_merkle_root_activation (False )
93
+
94
+ self .nodes [0 ].spork ("SPORK_17_QUORUM_DKG_ENABLED" , 0 )
95
+ self .wait_for_sporks_same ()
96
+
97
+ # Verify that the first quorum appears in MNLISTDIFF
98
+ expectedDeleted = []
99
+ expectedNew = [QuorumId (100 , int (first_quorum , 16 ))]
100
+ quorumList = self .test_getmnlistdiff_quorums (null_hash , self .nodes [0 ].getbestblockhash (), {}, expectedDeleted , expectedNew )
101
+ baseBlockHash = self .nodes [0 ].getbestblockhash ()
102
+
103
+ second_quorum = self .mine_quorum ()
104
+
105
+ # Verify that the second quorum appears in MNLISTDIFF
106
+ expectedDeleted = []
107
+ expectedNew = [QuorumId (100 , int (second_quorum , 16 ))]
108
+ quorums_before_third = self .test_getmnlistdiff_quorums (baseBlockHash , self .nodes [0 ].getbestblockhash (), quorumList , expectedDeleted , expectedNew )
109
+ block_before_third = self .nodes [0 ].getbestblockhash ()
110
+
111
+ third_quorum = self .mine_quorum ()
112
+
113
+ # Verify that the first quorum is deleted and the third quorum is added in MNLISTDIFF (the first got inactive)
114
+ expectedDeleted = [QuorumId (100 , int (first_quorum , 16 ))]
115
+ expectedNew = [QuorumId (100 , int (third_quorum , 16 ))]
116
+ self .test_getmnlistdiff_quorums (block_before_third , self .nodes [0 ].getbestblockhash (), quorums_before_third , expectedDeleted , expectedNew )
117
+
118
+ # Verify that the diff between genesis and best block is the current active set (second and third quorum)
119
+ expectedDeleted = []
120
+ expectedNew = [QuorumId (100 , int (second_quorum , 16 )), QuorumId (100 , int (third_quorum , 16 ))]
121
+ self .test_getmnlistdiff_quorums (null_hash , self .nodes [0 ].getbestblockhash (), {}, expectedDeleted , expectedNew )
122
+
123
+ # Now verify that diffs are correct around the block that mined the third quorum.
124
+ # This tests the logic in CalcCbTxMerkleRootQuorums, which has to manually add the commitment from the current
125
+ # block
126
+ mined_in_block = self .nodes [0 ].quorum ("info" , 100 , third_quorum )["minedBlock" ]
127
+ prev_block = self .nodes [0 ].getblock (mined_in_block )["previousblockhash" ]
128
+ prev_block2 = self .nodes [0 ].getblock (prev_block )["previousblockhash" ]
129
+ next_block = self .nodes [0 ].getblock (mined_in_block )["nextblockhash" ]
130
+ next_block2 = self .nodes [0 ].getblock (mined_in_block )["nextblockhash" ]
131
+ # The 2 block before the quorum was mined should both give an empty diff
132
+ expectedDeleted = []
133
+ expectedNew = []
134
+ self .test_getmnlistdiff_quorums (block_before_third , prev_block2 , quorums_before_third , expectedDeleted , expectedNew )
135
+ self .test_getmnlistdiff_quorums (block_before_third , prev_block , quorums_before_third , expectedDeleted , expectedNew )
136
+ # The block in which the quorum was mined and the 2 after that should all give the same diff
137
+ expectedDeleted = [QuorumId (100 , int (first_quorum , 16 ))]
138
+ expectedNew = [QuorumId (100 , int (third_quorum , 16 ))]
139
+ quorums_with_third = self .test_getmnlistdiff_quorums (block_before_third , mined_in_block , quorums_before_third , expectedDeleted , expectedNew )
140
+ self .test_getmnlistdiff_quorums (block_before_third , next_block , quorums_before_third , expectedDeleted , expectedNew )
141
+ self .test_getmnlistdiff_quorums (block_before_third , next_block2 , quorums_before_third , expectedDeleted , expectedNew )
142
+ # A diff between the two block that happened after the quorum was mined should give an empty diff
143
+ expectedDeleted = []
144
+ expectedNew = []
145
+ self .test_getmnlistdiff_quorums (mined_in_block , next_block , quorums_with_third , expectedDeleted , expectedNew )
146
+ self .test_getmnlistdiff_quorums (mined_in_block , next_block2 , quorums_with_third , expectedDeleted , expectedNew )
147
+ self .test_getmnlistdiff_quorums (next_block , next_block2 , quorums_with_third , expectedDeleted , expectedNew )
148
+
149
+ # Using the same block for baseBlockHash and blockHash should give empty diffs
150
+ self .test_getmnlistdiff_quorums (prev_block , prev_block , quorums_before_third , expectedDeleted , expectedNew )
151
+ self .test_getmnlistdiff_quorums (prev_block2 , prev_block2 , quorums_before_third , expectedDeleted , expectedNew )
152
+ self .test_getmnlistdiff_quorums (mined_in_block , mined_in_block , quorums_with_third , expectedDeleted , expectedNew )
153
+ self .test_getmnlistdiff_quorums (next_block , next_block , quorums_with_third , expectedDeleted , expectedNew )
154
+ self .test_getmnlistdiff_quorums (next_block2 , next_block2 , quorums_with_third , expectedDeleted , expectedNew )
155
+
156
+
84
157
def test_getmnlistdiff (self , baseBlockHash , blockHash , baseMNList , expectedDeleted , expectedUpdated ):
85
158
d = self .test_getmnlistdiff_base (baseBlockHash , blockHash )
86
159
@@ -107,6 +180,34 @@ def test_getmnlistdiff(self, baseBlockHash, blockHash, baseMNList, expectedDelet
107
180
108
181
return newMNList
109
182
183
+ def test_getmnlistdiff_quorums (self , baseBlockHash , blockHash , baseQuorumList , expectedDeleted , expectedNew ):
184
+ d = self .test_getmnlistdiff_base (baseBlockHash , blockHash )
185
+
186
+ assert_equal (set (d .deletedQuorums ), set (expectedDeleted ))
187
+ assert_equal (set ([QuorumId (e .llmqType , e .quorumHash ) for e in d .newQuorums ]), set (expectedNew ))
188
+
189
+ newQuorumList = baseQuorumList .copy ()
190
+
191
+ for e in d .deletedQuorums :
192
+ newQuorumList .pop (e )
193
+
194
+ for e in d .newQuorums :
195
+ newQuorumList [QuorumId (e .llmqType , e .quorumHash )] = e
196
+
197
+ cbtx = CCbTx ()
198
+ cbtx .deserialize (BytesIO (d .cbTx .vExtraPayload ))
199
+
200
+ if cbtx .version >= 2 :
201
+ hashes = []
202
+ for qc in newQuorumList .values ():
203
+ hashes .append (hash256 (qc .serialize ()))
204
+ hashes .sort ()
205
+ merkleRoot = CBlock .get_merkle_root (hashes )
206
+ assert_equal (merkleRoot , cbtx .merkleRootQuorums )
207
+
208
+ return newQuorumList
209
+
210
+
110
211
def test_getmnlistdiff_base (self , baseBlockHash , blockHash ):
111
212
hexstr = self .nodes [0 ].getblockheader (blockHash , False )
112
213
header = FromHex (CBlockHeader (), hexstr )
@@ -128,9 +229,55 @@ def test_getmnlistdiff_base(self, baseBlockHash, blockHash):
128
229
assert_equal (d2 ["cbTx" ], d .cbTx .serialize ().hex ())
129
230
assert_equal (set ([int (e , 16 ) for e in d2 ["deletedMNs" ]]), set (d .deletedMNs ))
130
231
assert_equal (set ([int (e ["proRegTxHash" ], 16 ) for e in d2 ["mnList" ]]), set ([e .proRegTxHash for e in d .mnList ]))
232
+ assert_equal (set ([QuorumId (e ["llmqType" ], int (e ["quorumHash" ], 16 )) for e in d2 ["deletedQuorums" ]]), set (d .deletedQuorums ))
233
+ assert_equal (set ([QuorumId (e ["llmqType" ], int (e ["quorumHash" ], 16 )) for e in d2 ["newQuorums" ]]), set ([QuorumId (e .llmqType , e .quorumHash ) for e in d .newQuorums ]))
131
234
132
235
return d
133
236
237
+ def test_dip8_quorum_merkle_root_activation (self , with_initial_quorum ):
238
+ if with_initial_quorum :
239
+ self .nodes [0 ].spork ("SPORK_17_QUORUM_DKG_ENABLED" , 0 )
240
+ self .wait_for_sporks_same ()
241
+
242
+ # Mine one quorum before dip8 is activated
243
+ self .mine_quorum ()
244
+
245
+ self .nodes [0 ].spork ("SPORK_17_QUORUM_DKG_ENABLED" , 4070908800 )
246
+ self .wait_for_sporks_same ()
247
+
248
+ cbtx = self .nodes [0 ].getblock (self .nodes [0 ].getbestblockhash (), 2 )["tx" ][0 ]
249
+ assert (cbtx ["cbTx" ]["version" ] == 1 )
250
+
251
+ assert (self .nodes [0 ].getblockchaininfo ()["bip9_softforks" ]["dip0008" ]["status" ] != "active" )
252
+
253
+ while self .nodes [0 ].getblockchaininfo ()["bip9_softforks" ]["dip0008" ]["status" ] != "active" :
254
+ self .nodes [0 ].generate (4 )
255
+ self .nodes [0 ].generate (1 )
256
+ sync_blocks (self .nodes )
257
+
258
+ # Assert that merkleRootQuorums is present and 0 (we have no quorums yet)
259
+ cbtx = self .nodes [0 ].getblock (self .nodes [0 ].getbestblockhash (), 2 )["tx" ][0 ]
260
+ assert_equal (cbtx ["cbTx" ]["version" ], 2 )
261
+ assert ("merkleRootQuorums" in cbtx ["cbTx" ])
262
+ merkleRootQuorums = int (cbtx ["cbTx" ]["merkleRootQuorums" ], 16 )
263
+
264
+ if with_initial_quorum :
265
+ assert (merkleRootQuorums != 0 )
266
+ else :
267
+ assert_equal (merkleRootQuorums , 0 )
268
+
269
+ set_mocktime (get_mocktime () + 1 )
270
+ set_node_times (self .nodes , get_mocktime ())
271
+ self .nodes [0 ].spork ("SPORK_17_QUORUM_DKG_ENABLED" , 0 )
272
+ self .wait_for_sporks_same ()
273
+
274
+ # Mine quorum and verify that merkleRootQuorums has changed
275
+ quorum = self .mine_quorum ()
276
+ cbtx = self .nodes [0 ].getblock (self .nodes [0 ].getbestblockhash (), 2 )["tx" ][0 ]
277
+ assert (int (cbtx ["cbTx" ]["merkleRootQuorums" ], 16 ) != merkleRootQuorums )
278
+
279
+ return quorum
280
+
134
281
def confirm_mns (self ):
135
282
while True :
136
283
diff = self .nodes [0 ].protx ("diff" , 1 , self .nodes [0 ].getblockcount ())
0 commit comments