Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion test/functional/feature_governance_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def add_options(self, parser):
self.add_wallet_options(parser)

def set_test_params(self):
self.set_dash_test_params(2, 1)
self.set_dash_test_params(1, 0)

def prepare_object(self, object_type, parent_hash, creation_time, revision, name, amount):
payment_address = self.nodes[0].getnewaddress()
Expand Down
25 changes: 4 additions & 21 deletions test/functional/feature_llmq_singlenode.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@

'''

import time

from test_framework.authproxy import JSONRPCException
from test_framework.test_framework import (
DashTestFramework,
Expand Down Expand Up @@ -40,21 +38,6 @@ def set_test_params(self):
evo_count=2)
self.set_dash_llmq_test_params(1, 1)

def mine_single_node_quorum(self):
node = self.nodes[0]
quorums = node.quorum('list')['llmq_test']

skip_count = 24 - (self.nodes[0].getblockcount() % 24)
if skip_count != 0:
self.bump_mocktime(1)
self.generate(self.nodes[0], skip_count)
time.sleep(1)
self.generate(self.nodes[0], 30)
new_quorums_list = node.quorum('list')['llmq_test']

self.log.info(f"Test Quorums at height={node.getblockcount()} : {new_quorums_list}")
assert new_quorums_list != quorums

def check_sigs(self, hasrecsigs, isconflicting1, isconflicting2):
has_sig = False
conflicting_1 = False
Expand Down Expand Up @@ -97,7 +80,7 @@ def run_test(self):
self.dynamically_add_masternode(evo=True)
self.connect_nodes(1, 2)

self.mine_single_node_quorum()
self.mine_quorum_single_member()

self.log_connections()
assert_greater_than(len(self.nodes[0].quorum('list')['llmq_test']), 0)
Expand Down Expand Up @@ -158,13 +141,13 @@ def run_test(self):
assert_raises_rpc_error(-8, "quorum not found", node.quorum, "verify", q_type, id, msgHash, recsig["sig"], hash_bad)

self.log.info("Mine one more quorum, so that we have 2 active ones, nothing should change")
self.mine_single_node_quorum()
self.mine_quorum_single_member()
self.assert_sigs_nochange(True, False, True, 3)


self.log.info("Mine 2 more quorums, so that the one used for the the recovered sig should become inactive, nothing should change")
self.mine_single_node_quorum()
self.mine_single_node_quorum()
self.mine_quorum_single_member()
self.mine_quorum_single_member()
self.assert_sigs_nochange(True, False, True, 3)

self.log_connections()
Expand Down
8 changes: 6 additions & 2 deletions test/functional/rpc_quorum.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ def add_options(self, parser):
self.add_wallet_options(parser)

def set_test_params(self):
self.set_dash_test_params(4, 3)
# A single-member quorum exercises the same per-member `quorum info`
# output the test asserts, so one masternode is sufficient.
self.set_dash_test_params(2, 1)
self.set_dash_llmq_test_params(1, 1)

def run_test(self):
self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0)
self.wait_for_sporks_same()
quorum_hash = self.mine_quorum()
self.mine_until_mns_confirmed_for_next_dkg()
quorum_hash = self.mine_quorum_single_member()

quorum_info = self.nodes[0].quorum("info", 100, quorum_hash)
for idx in range(0, self.mn_count):
Expand Down
11 changes: 7 additions & 4 deletions test/functional/rpc_verifychainlock.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ def add_options(self, parser):
self.add_wallet_options(parser)

def set_test_params(self):
# -whitelist is needed to avoid the trickling logic on node0
self.set_dash_test_params(5, 3, [["-whitelist=127.0.0.1"], [], [], [], []])
self.set_dash_llmq_test_params(3, 2)
# -whitelist is needed to avoid the trickling logic on node0.
# A single-member quorum still produces real ChainLocks; node1 stays a
# non-masternode so it can be isolated to test divergent ChainLock state.
self.set_dash_test_params(3, 1, [["-whitelist=127.0.0.1"], [], []])
self.set_dash_llmq_test_params(1, 1)

def cl_helper(self, height, chainlock, mempool):
return {'height': height, 'chainlock': chainlock, 'mempool': mempool}
Expand All @@ -33,7 +35,8 @@ def run_test(self):
node1 = self.nodes[1]
self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0)
self.wait_for_sporks_same()
self.mine_quorum()
self.mine_until_mns_confirmed_for_next_dkg()
self.mine_quorum_single_member()
self.wait_for_chainlocked_block(node0, self.generate(node0, 1, sync_fun=self.no_op)[0])
chainlock = node0.getbestchainlock()
block_hash = chainlock["blockhash"]
Expand Down
60 changes: 60 additions & 0 deletions test/functional/test_framework/test_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -2229,6 +2229,66 @@ def mine_quorum(self, llmq_type_name="llmq_test", llmq_type=100, expected_connec

return new_quorum

def mine_until_mns_confirmed_for_next_dkg(self):
"""Advance the chain until the next non-rotating DKG cycle can select members.

Members are picked from the masternode list of the work block, which sits
WORK_DIFF_DEPTH (8) blocks below the cycle's base block, and only masternodes
that already carry a confirmedHash there are eligible. Masternodes registered
shortly before a cycle are still unconfirmed at its work block, so that cycle
would select nobody and produce no quorum at all. Skip such cycles by mining
to the next base block, one whole cycle at a time.

A work block above the tip is checked at the tip instead: masternodes are
registered before this runs and a confirmedHash is never unset again, so
whatever is confirmed at the tip stays confirmed at any later work block.
"""
node = self.nodes[0]
dkg_interval = 24
work_diff_depth = 8
expected_mns = {mn.proTxHash for mn in self.mninfo}
# Confirmation takes nMasternodeMinimumConfirmations (1 on regtest) blocks after
# registration, so at most two cycles of headroom are needed: registration can
# fall just below a base block, leaving the cycle after it still unconfirmed.
for attempt in range(3):
height = node.getblockcount()
next_base_height = height + dkg_interval - (height % dkg_interval)
work_height = min(height, next_base_height - work_diff_depth)
# A masternode missing from the work block's list is as unselectable as an
# unconfirmed one, so require every masternode to be present and confirmed.
confirmed_mns = {mn['proRegTxHash'] for mn in node.protx('diff', 1, work_height)['mnList']
if int(mn['confirmedHash'], 16) != 0}
if expected_mns.issubset(confirmed_mns):
return
assert attempt < 2, f"masternodes are still unconfirmed at work block {work_height}"
self.log.info(f"Skipping DKG cycle with base height={next_base_height}: not all "
f"masternodes are confirmed at its work block {work_height}")
self.bump_mocktime(1)
self.generate(node, next_base_height - height)

def mine_quorum_single_member(self):
"""Mine a single-member (llmq_size == 1) quorum and return its hash.

mine_quorum can't be reused for these: a one-member DKG has no
inter-member connections or probes, so its phase and connection waits
never complete.
"""
node = self.nodes[0]
quorums = node.quorum('list')['llmq_test']

skip_count = 24 - (node.getblockcount() % 24)
if skip_count != 0:
self.bump_mocktime(1)
self.generate(node, skip_count)
time.sleep(1)
self.generate(node, 30)
new_quorums_list = node.quorum('list')['llmq_test']

self.log.info(f"Test Quorums at height={node.getblockcount()} : {new_quorums_list}")
assert new_quorums_list != quorums

return (set(new_quorums_list) - set(quorums)).pop()

def mine_cycle_quorum(self):
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
Expand Down
Loading