Skip to content

Commit

Permalink
Merge #6431: test: improve mine rotation quorums for IS in functional…
Browse files Browse the repository at this point in the history
… tests

f8cfda6 refactor: simplify and optimize creation of rotation IS quorum in functional tests (Konstantin Akimov)

Pull request description:

  ## Issue being fixed or feature implemented

  Mining rotation quorum currently is not trivial. All over a codebase used this template to get a rotating quorum:
  ```
          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)
  ```
  The performance of this code is not great also because generating 3 or 4 batches of blocks requires time to sync nodes after each batch.

  ## What was done?
  This PR move instantly to 3-4 DKG sessions forward and starts from cycling quorum.
  It's only one cycling quorum (instant-send), so, extra params are also removed.
  To get it just call:
  ```
  self.mine_cycle_quorum() # first time, which is usually used to get IS feature working in functional tests
  self.mine_cycle_quorum(is_first=False) # if you don't need preparing 3 DKG sessions in advance
  ```

  ## How Has This Been Tested?
  Run unit and functional tests
  By my benchmark it gives roughly 10 seconds for each functional test that uses rotation quorum.

  ## Breaking Changes
  N/A

  ## Checklist:
  - [x] I have performed a self-review of my own code
  - [x] I have commented my code, particularly in hard-to-understand areas
  - [x] I have added or updated relevant unit/integration/functional/e2e tests
  - [x] I have made corresponding changes to the documentation
  - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)

ACKs for top commit:
  PastaPastaPasta:
    utACK f8cfda6; I wish that is_first could be automatically detected instead of manually set.
  UdjinM6:
    utACK f8cfda6

Tree-SHA512: 993f42b54c0c11ba146e164cc6db8b5c3f302dd78488aa2572b3ed199d25c266d8d1ff1a027a4e450eab2349b691c19fcd8f5859e2c423ccf2357db29ee3d536
  • Loading branch information
PastaPastaPasta committed Dec 4, 2024
2 parents eb88589 + f8cfda6 commit 678d28d
Show file tree
Hide file tree
Showing 11 changed files with 49 additions and 113 deletions.
9 changes: 1 addition & 8 deletions test/functional/feature_governance_cl.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,7 @@ def run_test(self):
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)
self.mine_cycle_quorum()

self.sync_blocks()
self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash())
Expand Down
8 changes: 1 addition & 7 deletions test/functional/feature_llmq_chainlocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,7 @@ def run_test(self):
self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0)
self.wait_for_sporks_same()

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)
self.mine_cycle_quorum()
self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash())

self.log.info("Mine single block, ensure it includes latest chainlock")
Expand Down
12 changes: 3 additions & 9 deletions test/functional/feature_llmq_connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,7 @@ def run_test(self):

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)
self.mine_cycle_quorum()

# Since we IS quorums are mined only using dip24 (rotation) we need to enable rotation, and continue tests on llmq_test_dip0024 for connections.

Expand All @@ -93,7 +87,7 @@ def run_test(self):
try:
with mn.node.assert_debug_log(['removing masternodes quorum connections']):
with mn.node.assert_debug_log(['keeping mn quorum connections']):
self.mine_cycle_quorum(llmq_type_name='llmq_test_dip0024', llmq_type=103)
self.mine_cycle_quorum(is_first=False)
mn.node.mockscheduler(60) # we check for old connections via the scheduler every 60 seconds
removed = True
except:
Expand All @@ -108,7 +102,7 @@ def run_test(self):
if len(mn.node.quorum("memberof", mn.proTxHash)) > 0:
try:
with mn.node.assert_debug_log(['adding mn inter-quorum connections']):
self.mine_cycle_quorum(llmq_type_name='llmq_test_dip0024', llmq_type=103)
self.mine_cycle_quorum(is_first=False)
added = True
except:
pass # it's ok to not add connections sometimes
Expand Down
9 changes: 1 addition & 8 deletions test/functional/feature_llmq_is_cl_conflicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,7 @@ def run_test(self):

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)
self.mine_cycle_quorum()

# mine single block, wait for chainlock
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
Expand Down
10 changes: 1 addition & 9 deletions test/functional/feature_llmq_is_retroactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,7 @@ def run_test(self):
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)
self.mine_cycle_quorum()

# 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)
Expand Down
20 changes: 17 additions & 3 deletions test/functional/feature_llmq_rotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def run_test(self):
self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash())


(quorum_info_0_0, quorum_info_0_1) = self.mine_cycle_quorum(llmq_type_name=llmq_type_name, llmq_type=llmq_type)
(quorum_info_0_0, quorum_info_0_1) = self.mine_cycle_quorum(is_first=False)
assert(self.test_quorum_listextended(quorum_info_0_0, llmq_type_name))
assert(self.test_quorum_listextended(quorum_info_0_1, llmq_type_name))
quorum_members_0_0 = extract_quorum_members(quorum_info_0_0)
Expand All @@ -176,7 +176,7 @@ def run_test(self):
self.log.info("Wait for chainlock")
self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash())

(quorum_info_1_0, quorum_info_1_1) = self.mine_cycle_quorum(llmq_type_name=llmq_type_name, llmq_type=llmq_type)
(quorum_info_1_0, quorum_info_1_1) = self.mine_cycle_quorum(is_first=False)
assert(self.test_quorum_listextended(quorum_info_1_0, llmq_type_name))
assert(self.test_quorum_listextended(quorum_info_1_1, llmq_type_name))
quorum_members_1_0 = extract_quorum_members(quorum_info_1_0)
Expand Down Expand Up @@ -210,7 +210,7 @@ def run_test(self):
self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash())

self.log.info("Mine a quorum to invalidate")
(quorum_info_3_0, quorum_info_3_1) = self.mine_cycle_quorum(llmq_type_name=llmq_type_name, llmq_type=llmq_type)
(quorum_info_3_0, quorum_info_3_1) = self.mine_cycle_quorum(is_first=False)

new_quorum_list = self.nodes[0].quorum("list", llmq_type)
assert_equal(len(new_quorum_list[llmq_type_name]), len(quorum_list[llmq_type_name]) + 2)
Expand Down Expand Up @@ -383,6 +383,20 @@ def test_quorum_listextended(self, quorum_info, llmq_type_name):
return True
return False

def move_to_next_cycle(self):
cycle_length = 24
mninfos_online = self.mninfo.copy()
nodes = [self.nodes[0]] + [mn.node for mn in mninfos_online]
cur_block = self.nodes[0].getblockcount()

# move forward to next DKG
skip_count = cycle_length - (cur_block % cycle_length)
if skip_count != 0:
self.bump_mocktime(1, nodes=nodes)
self.generate(self.nodes[0], skip_count, sync_fun=self.no_op)
self.sync_blocks(nodes)
self.log.info('Moved from block %d to %d' % (cur_block, self.nodes[0].getblockcount()))


if __name__ == '__main__':
LLMQQuorumRotationTest().main()
9 changes: 1 addition & 8 deletions test/functional/feature_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,7 @@ def run_test(self):
self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0)
self.nodes[0].sporkupdate("SPORK_19_CHAINLOCKS_ENABLED", 4070908800)
self.wait_for_sporks_same()
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()))

(quorum_info_i_0, quorum_info_i_1) = self.mine_cycle_quorum(llmq_type_name='llmq_test_dip0024', llmq_type=103)
(quorum_info_i_0, quorum_info_i_1) = self.mine_cycle_quorum()
self.nodes[0].sporkupdate("SPORK_19_CHAINLOCKS_ENABLED", 0)
self.wait_for_sporks_same()

Expand Down
15 changes: 2 additions & 13 deletions test/functional/interface_zmq_dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,8 @@ def run_test(self):
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)
self.mine_cycle_quorum()

self.sync_blocks()
self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash())
Expand All @@ -162,12 +156,7 @@ def run_test(self):
self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash())
self.test_instantsend_publishers()
# At this point, we need to move forward 3 cycles (3 x 24 blocks) so the first 3 quarters can be created (without DKG sessions)
self.move_to_next_cycle()
self.test_instantsend_publishers()
self.move_to_next_cycle()
self.test_instantsend_publishers()
self.move_to_next_cycle()
self.test_instantsend_publishers()
self.generate(self.nodes[0], 24)
self.mine_cycle_quorum()
self.test_instantsend_publishers()
finally:
Expand Down
8 changes: 1 addition & 7 deletions test/functional/p2p_instantsend.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,7 @@ def run_test(self):
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()))
(quorum_info_i_0, quorum_info_i_1) = self.mine_cycle_quorum(llmq_type_name='llmq_test_dip0024', llmq_type=103)
(quorum_info_i_0, quorum_info_i_1) = self.mine_cycle_quorum()

self.test_mempool_doublespend()
self.test_block_doublespend()
Expand Down
13 changes: 3 additions & 10 deletions test/functional/rpc_verifyislock.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,7 @@ def run_test(self):

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)
self.mine_cycle_quorum()
self.bump_mocktime(1)
self.generate(self.nodes[0], 8, sync_fun=self.sync_blocks())

Expand All @@ -64,7 +57,7 @@ def run_test(self):
assert node.verifyislock(request_id, txid, rec_sig, node.getblockcount() + 100)

# Mine one more cycle of rotated quorums
self.mine_cycle_quorum(llmq_type_name='llmq_test_dip0024', llmq_type=103)
self.mine_cycle_quorum(is_first=False)
# Create an ISLOCK using an active quorum which will be replaced when a new cycle happens
request_id = None
utxos = node.listunspent()
Expand All @@ -87,7 +80,7 @@ def run_test(self):
# Create the ISDLOCK, then mine a cycle quorum to move renew active set
isdlock = self.create_isdlock(rawtx)
# Mine one block to trigger the "signHeight + dkgInterval" verification for the ISDLOCK
self.mine_cycle_quorum(llmq_type_name='llmq_test_dip0024', llmq_type=103)
self.mine_cycle_quorum(is_first=False)
# Verify the ISLOCK for a transaction that is not yet known by the node
rawtx_txid = node.decoderawtransaction(rawtx)["txid"]
assert_raises_rpc_error(-5, "No such mempool or blockchain transaction", node.getrawtransaction, rawtx_txid)
Expand Down
49 changes: 18 additions & 31 deletions test/functional/test_framework/test_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -1895,31 +1895,32 @@ def mine_quorum(self, llmq_type_name="llmq_test", llmq_type=100, expected_connec

return new_quorum

def mine_cycle_quorum(self, llmq_type_name="llmq_test_dip0024", llmq_type=103, expected_connections=None, expected_members=None, expected_contributions=None, expected_complaints=0, expected_justifications=0, expected_commitments=None, mninfos_online=None):
def mine_cycle_quorum(self, is_first=True):
spork21_active = self.nodes[0].spork('show')['SPORK_21_QUORUM_ALL_CONNECTED'] <= 1
spork23_active = self.nodes[0].spork('show')['SPORK_23_QUORUM_POSE'] <= 1

if expected_connections is None:
expected_connections = (self.llmq_size_dip0024 - 1) if spork21_active else 2
if expected_members is None:
expected_members = self.llmq_size_dip0024
if expected_contributions is None:
expected_contributions = self.llmq_size_dip0024
if expected_commitments is None:
expected_commitments = self.llmq_size_dip0024
if mninfos_online is None:
mninfos_online = self.mninfo.copy()
llmq_type_name="llmq_test_dip0024"
llmq_type=103
expected_connections = (self.llmq_size_dip0024 - 1) if spork21_active else 2
expected_members = self.llmq_size_dip0024
expected_contributions = self.llmq_size_dip0024
expected_commitments = self.llmq_size_dip0024
mninfos_online = self.mninfo.copy()
expected_complaints=0
expected_justifications=0

self.log.info("Mining quorum: expected_members=%d, expected_connections=%d, expected_contributions=%d, expected_complaints=%d, expected_justifications=%d, "
"expected_commitments=%d" % (expected_members, expected_connections, expected_contributions, expected_complaints,
expected_justifications, expected_commitments))
self.log.info(f"Mining quorum: expected_members={expected_members}, expected_connections={expected_connections}, expected_contributions={expected_contributions}, expected_commitments={expected_commitments}, no complains and justfications expected")

nodes = [self.nodes[0]] + [mn.node for mn in mninfos_online]

# move forward to next DKG
skip_count = 24 - (self.nodes[0].getblockcount() % 24)
cycle_length = 24
cur_block = self.nodes[0].getblockcount()

self.move_blocks(nodes, skip_count)
skip_count = cycle_length - (cur_block % cycle_length)
# move forward to next 3 DKG rounds for the first quorum
extra_blocks = 24 * 3 if is_first else 0
self.move_blocks(nodes, extra_blocks + skip_count)
self.log.info('Moved from block %d to %d' % (cur_block, self.nodes[0].getblockcount()))

q_0 = self.nodes[0].getbestblockhash()
self.log.info("Expected quorum_0 at:" + str(self.nodes[0].getblockcount()))
Expand Down Expand Up @@ -2018,20 +2019,6 @@ def mine_cycle_quorum(self, llmq_type_name="llmq_test_dip0024", llmq_type=103,

return (quorum_info_0, quorum_info_1)

def move_to_next_cycle(self):
cycle_length = 24
mninfos_online = self.mninfo.copy()
nodes = [self.nodes[0]] + [mn.node for mn in mninfos_online]
cur_block = self.nodes[0].getblockcount()

# move forward to next DKG
skip_count = cycle_length - (cur_block % cycle_length)
if skip_count != 0:
self.bump_mocktime(1, nodes=nodes)
self.generate(self.nodes[0], skip_count, sync_fun=self.no_op)
self.sync_blocks(nodes)
self.log.info('Moved from block %d to %d' % (cur_block, self.nodes[0].getblockcount()))

def wait_for_recovered_sig(self, rec_sig_id, rec_sig_msg_hash, llmq_type=100, timeout=10):
def check_recovered_sig():
self.bump_mocktime(1)
Expand Down

0 comments on commit 678d28d

Please sign in to comment.