tests: various fixes/cleanups (#4797)

* tests: move `move_to_next_cycle` to `DashTestFramework`

* tests: set correct defaults for `mine_cycle_quorum`

* tests: use correct quorum type in `create_islock`

* tests: fix `rpc_verifyislock.py`

* tests: fix `feature_llmq_is_cl_conflicts.py`

* tests: isolate zmq subscribers in `interface_zmq_dash.py`

this lets us call `test_*_publishers()` in any order and any number of times

* tests: check zmq for both deterministic and non-deterministic islocks
This commit is contained in:
UdjinM6 2022-04-25 22:12:04 +03:00 committed by GitHub
parent a2e1f46cf4
commit 7fc9ced96d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 104 additions and 81 deletions

View File

@ -51,8 +51,8 @@ class TestP2PConn(P2PInterface):
class LLMQ_IS_CL_Conflicts(DashTestFramework):
def set_test_params(self):
self.set_dash_test_params(4, 3, fast_dip3_enforcement=True)
#disable_mocktime()
self.set_dash_test_params(5, 4, fast_dip3_enforcement=True)
self.set_dash_llmq_test_params(4, 4)
def run_test(self):
self.activate_dip8()
@ -72,6 +72,14 @@ class LLMQ_IS_CL_Conflicts(DashTestFramework):
self.test_chainlock_overrides_islock(True, False)
self.test_chainlock_overrides_islock(True, True)
self.test_chainlock_overrides_islock_overrides_nonchainlock(False)
self.activate_dip0024()
self.log.info("Activated DIP0024 at height:" + str(self.nodes[0].getblockcount()))
self.test_chainlock_overrides_islock_overrides_nonchainlock(False)
# 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.move_to_next_cycle()
self.move_to_next_cycle()
self.mine_cycle_quorum()
self.test_chainlock_overrides_islock_overrides_nonchainlock(True)
def test_chainlock_overrides_islock(self, test_block_conflict, mine_confllicting=False):
@ -320,7 +328,7 @@ class LLMQ_IS_CL_Conflicts(DashTestFramework):
coinbase.calc_sha256()
block = create_block(int(tip_hash, 16), coinbase, ntime=bt['curtime'])
block = create_block(int(tip_hash, 16), coinbase, ntime=bt['curtime'], version=bt['version'])
block.vtx += vtx
# Add quorum commitments from template

View File

@ -6,7 +6,7 @@ import time
from test_framework.messages import CTransaction, FromHex, hash256, ser_compact_size, ser_string
from test_framework.test_framework import DashTestFramework
from test_framework.util import wait_until, connect_nodes, sync_blocks
from test_framework.util import wait_until, connect_nodes
'''
feature_llmq_is_migration.py
@ -91,7 +91,7 @@ class LLMQISMigrationTest(DashTestFramework):
self.move_to_next_cycle()
self.log.info("Cycle H+2C height:" + str(self.nodes[0].getblockcount()))
(quorum_info_0_0, quorum_info_0_1) = self.mine_cycle_quorum("llmq_test_dip0024", 103)
(quorum_info_0_0, quorum_info_0_1) = self.mine_cycle_quorum()
q_list = self.nodes[0].quorum("list")
self.log.info(q_list)
@ -118,20 +118,5 @@ class LLMQISMigrationTest(DashTestFramework):
assert not n.quorum("hasrecsig", 104, request_id2, txid2)
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.nodes[0].generate(skip_count)
sync_blocks(nodes)
time.sleep(1)
self.log.info('Moved from block %d to %d' % (cur_block, self.nodes[0].getblockcount()))
if __name__ == '__main__':
LLMQISMigrationTest().main()

View File

@ -9,7 +9,6 @@ feature_llmq_rotation.py
Checks LLMQs Quorum Rotation
'''
import time
from test_framework.test_framework import DashTestFramework
from test_framework.util import (
assert_equal,
@ -128,21 +127,6 @@ class LLMQQuorumRotationTest(DashTestFramework):
wait_until(lambda: self.nodes[0].getbestblockhash() == new_quorum_blockhash, sleep=1)
assert_equal(self.nodes[0].quorum("list", llmq_type), new_quorum_list)
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.nodes[0].generate(skip_count)
sync_blocks(nodes)
time.sleep(1)
self.log.info('Moved from block %d to %d' % (cur_block, self.nodes[0].getblockcount()))
if __name__ == '__main__':
LLMQQuorumRotationTest().main()

View File

@ -28,6 +28,7 @@ from test_framework.messages import (
msg_clsig,
msg_inv,
msg_isdlock,
msg_islock,
msg_tx,
ser_string,
uint256_from_str,
@ -52,24 +53,39 @@ class ZMQPublisher(Enum):
raw_recovered_sig = "rawrecoveredsig"
class ZMQSubscriber:
def __init__(self, socket, topic):
self.socket = socket
self.topic = topic
import zmq
self.socket.setsockopt(zmq.SUBSCRIBE, self.topic)
def receive(self, flags=0):
topic, body, seq = self.socket.recv_multipart(flags)
# Topic should match the subscriber topic.
assert_equal(topic, self.topic)
return io.BytesIO(body)
class TestP2PConn(P2PInterface):
def __init__(self):
super().__init__()
self.islocks = {}
self.txes = {}
def send_islock(self, islock):
def send_islock(self, islock, deterministic):
hash = uint256_from_str(hash256(islock.serialize()))
self.islocks[hash] = islock
inv = msg_inv([CInv(30, hash)])
inv = msg_inv([CInv(31 if deterministic else 30, hash)])
self.send_message(inv)
def send_tx(self, tx):
def send_tx(self, tx, deterministic):
hash = uint256_from_str(hash256(tx.serialize()))
self.txes[hash] = tx
inv = msg_inv([CInv(30, hash)])
inv = msg_inv([CInv(31 if deterministic else 30, hash)])
self.send_message(inv)
def on_getdata(self, message):
@ -89,7 +105,10 @@ class DashZMQTest (DashTestFramework):
node0_extra_args.append("-whitelist=127.0.0.1")
node0_extra_args.append("-watchquorums") # have to watch quorums to receive recsigs and trigger zmq
self.set_dash_test_params(4, 3, fast_dip3_enforcement=True, extra_args=[node0_extra_args, [], [], []])
extra_args = [[]] * 5
extra_args[0] = node0_extra_args
self.set_dash_test_params(5, 4, fast_dip3_enforcement=True, extra_args=extra_args)
self.set_dash_llmq_test_params(4, 4)
def skip_test_if_missing_module(self):
self.skip_if_no_py3_zmq()
@ -97,16 +116,15 @@ class DashZMQTest (DashTestFramework):
self.skip_if_no_wallet()
def run_test(self):
self.subscribers = {}
# Check that dashd has been built with ZMQ enabled.
config = configparser.ConfigParser()
config.read_file(open(self.options.configfile))
import zmq
try:
# Setup the ZMQ subscriber socket
# Setup the ZMQ subscriber context
self.zmq_context = zmq.Context()
self.socket = self.zmq_context.socket(zmq.SUB)
self.socket.connect(self.address)
# Initialize the network
self.activate_dip8()
self.nodes[0].spork("SPORK_17_QUORUM_DKG_ENABLED", 0)
@ -122,30 +140,41 @@ class DashZMQTest (DashTestFramework):
# Test all dash related ZMQ publisher
self.test_recovered_signature_publishers()
self.test_chainlock_publishers()
self.test_instantsend_publishers()
self.test_governance_publishers()
self.test_getzmqnotifications()
self.test_instantsend_publishers(False)
self.activate_dip0024()
self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash())
self.log.info("Activated DIP0024 at height:" + str(self.nodes[0].getblockcount()))
self.test_instantsend_publishers(False)
# 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(False)
self.move_to_next_cycle()
self.test_instantsend_publishers(False)
self.move_to_next_cycle()
self.test_instantsend_publishers(False)
self.mine_cycle_quorum()
self.test_instantsend_publishers(True)
finally:
# Destroy the ZMQ context.
self.log.debug("Destroying ZMQ context")
self.zmq_context.destroy(linger=None)
def subscribe(self, publishers):
import zmq
# Setup the ZMQ subscriber socket
socket = self.zmq_context.socket(zmq.SUB)
socket.set(zmq.RCVTIMEO, 60000)
socket.connect(self.address)
# Subscribe to a list of ZMQPublishers
for pub in publishers:
self.socket.subscribe(pub.value)
self.subscribers[pub] = ZMQSubscriber(socket, pub.value.encode())
def unsubscribe(self, publishers):
# Unsubscribe from a list of ZMQPublishers
for pub in publishers:
self.socket.unsubscribe(pub.value)
def receive(self, publisher, flags=0):
# Receive a ZMQ message and validate it's sent from the correct ZMQPublisher
topic, body, seq = self.socket.recv_multipart(flags)
# Topic should match the publisher value
assert_equal(topic.decode(), publisher.value)
return io.BytesIO(body)
del self.subscribers[pub]
def test_recovered_signature_publishers(self):
@ -153,11 +182,11 @@ class DashZMQTest (DashTestFramework):
# Make sure the recovered sig exists by RPC
rpc_recovered_sig = self.get_recovered_sig(request_id, msg_hash)
# Validate hashrecoveredsig
zmq_recovered_sig_hash = self.receive(ZMQPublisher.hash_recovered_sig).read(32).hex()
zmq_recovered_sig_hash = self.subscribers[ZMQPublisher.hash_recovered_sig].receive().read(32).hex()
assert_equal(zmq_recovered_sig_hash, msg_hash)
# Validate rawrecoveredsig
zmq_recovered_sig_raw = CRecoveredSig()
zmq_recovered_sig_raw.deserialize(self.receive(ZMQPublisher.raw_recovered_sig))
zmq_recovered_sig_raw.deserialize(self.subscribers[ZMQPublisher.raw_recovered_sig].receive())
assert_equal(zmq_recovered_sig_raw.llmqType, rpc_recovered_sig['llmqType'])
assert_equal(uint256_to_string(zmq_recovered_sig_raw.quorumHash), rpc_recovered_sig['quorumHash'])
assert_equal(uint256_to_string(zmq_recovered_sig_raw.id), rpc_recovered_sig['id'])
@ -207,15 +236,15 @@ class DashZMQTest (DashTestFramework):
rpc_chain_lock_hash = rpc_chain_locked_block["hash"]
assert_equal(generated_hash, rpc_chain_lock_hash)
# Validate hashchainlock
zmq_chain_lock_hash = self.receive(ZMQPublisher.hash_chain_lock).read(32).hex()
zmq_chain_lock_hash = self.subscribers[ZMQPublisher.hash_chain_lock].receive().read(32).hex()
assert_equal(zmq_chain_lock_hash, rpc_best_chain_lock_hash)
# Validate rawchainlock
zmq_chain_locked_block = CBlock()
zmq_chain_locked_block.deserialize(self.receive(ZMQPublisher.raw_chain_lock))
zmq_chain_locked_block.deserialize(self.subscribers[ZMQPublisher.raw_chain_lock].receive())
assert zmq_chain_locked_block.is_valid()
assert_equal(zmq_chain_locked_block.hash, rpc_chain_lock_hash)
# Validate rawchainlocksig
zmq_chain_lock_sig_stream = self.receive(ZMQPublisher.raw_chain_lock_sig)
zmq_chain_lock_sig_stream = self.subscribers[ZMQPublisher.raw_chain_lock_sig].receive()
zmq_chain_locked_block = CBlock()
zmq_chain_locked_block.deserialize(zmq_chain_lock_sig_stream)
assert zmq_chain_locked_block.is_valid()
@ -228,7 +257,7 @@ class DashZMQTest (DashTestFramework):
# Unsubscribe from ChainLock messages
self.unsubscribe(chain_lock_publishers)
def test_instantsend_publishers(self):
def test_instantsend_publishers(self, deterministic):
import zmq
instantsend_publishers = [
ZMQPublisher.hash_tx_lock,
@ -251,57 +280,57 @@ class DashZMQTest (DashTestFramework):
rpc_raw_tx_1_hash = self.nodes[0].sendrawtransaction(rpc_raw_tx_1['hex'])
self.wait_for_instantlock(rpc_raw_tx_1_hash, self.nodes[0])
# Validate hashtxlock
zmq_tx_lock_hash = self.receive(ZMQPublisher.hash_tx_lock).read(32).hex()
zmq_tx_lock_hash = self.subscribers[ZMQPublisher.hash_tx_lock].receive().read(32).hex()
assert_equal(zmq_tx_lock_hash, rpc_raw_tx_1['txid'])
# Validate rawtxlock
zmq_tx_lock_raw = CTransaction()
zmq_tx_lock_raw.deserialize(self.receive(ZMQPublisher.raw_tx_lock))
zmq_tx_lock_raw.deserialize(self.subscribers[ZMQPublisher.raw_tx_lock].receive())
assert zmq_tx_lock_raw.is_valid()
assert_equal(zmq_tx_lock_raw.hash, rpc_raw_tx_1['txid'])
# Validate rawtxlocksig
zmq_tx_lock_sig_stream = self.receive(ZMQPublisher.raw_tx_lock_sig)
zmq_tx_lock_sig_stream = self.subscribers[ZMQPublisher.raw_tx_lock_sig].receive()
zmq_tx_lock_tx = CTransaction()
zmq_tx_lock_tx.deserialize(zmq_tx_lock_sig_stream)
assert zmq_tx_lock_tx.is_valid()
assert_equal(zmq_tx_lock_tx.hash, rpc_raw_tx_1['txid'])
zmq_tx_lock = msg_isdlock()
zmq_tx_lock = msg_isdlock() if deterministic else msg_islock()
zmq_tx_lock.deserialize(zmq_tx_lock_sig_stream)
assert_equal(uint256_to_string(zmq_tx_lock.txid), rpc_raw_tx_1['txid'])
# Try to send the second transaction. This must throw an RPC error because it conflicts with rpc_raw_tx_1
# which already got the InstantSend lock.
assert_raises_rpc_error(-26, "tx-txlock-conflict", self.nodes[0].sendrawtransaction, rpc_raw_tx_2['hex'])
# Validate hashinstantsenddoublespend
zmq_double_spend_hash2 = self.receive(ZMQPublisher.hash_instantsend_doublespend).read(32).hex()
zmq_double_spend_hash1 = self.receive(ZMQPublisher.hash_instantsend_doublespend).read(32).hex()
zmq_double_spend_hash2 = self.subscribers[ZMQPublisher.hash_instantsend_doublespend].receive().read(32).hex()
zmq_double_spend_hash1 = self.subscribers[ZMQPublisher.hash_instantsend_doublespend].receive().read(32).hex()
assert_equal(zmq_double_spend_hash2, rpc_raw_tx_2['txid'])
assert_equal(zmq_double_spend_hash1, rpc_raw_tx_1['txid'])
# Validate rawinstantsenddoublespend
zmq_double_spend_tx_2 = CTransaction()
zmq_double_spend_tx_2.deserialize(self.receive(ZMQPublisher.raw_instantsend_doublespend))
zmq_double_spend_tx_2.deserialize(self.subscribers[ZMQPublisher.raw_instantsend_doublespend].receive())
assert zmq_double_spend_tx_2.is_valid()
assert_equal(zmq_double_spend_tx_2.hash, rpc_raw_tx_2['txid'])
zmq_double_spend_tx_1 = CTransaction()
zmq_double_spend_tx_1.deserialize(self.receive(ZMQPublisher.raw_instantsend_doublespend))
zmq_double_spend_tx_1.deserialize(self.subscribers[ZMQPublisher.raw_instantsend_doublespend].receive())
assert zmq_double_spend_tx_1.is_valid()
assert_equal(zmq_double_spend_tx_1.hash, rpc_raw_tx_1['txid'])
# No islock notifications when tx is not received yet
self.nodes[0].generate(1)
rpc_raw_tx_3 = self.create_raw_tx(self.nodes[0], self.nodes[0], 1, 1, 100)
islock = self.create_islock(rpc_raw_tx_3['hex'])
self.test_node.send_islock(islock)
islock = self.create_islock(rpc_raw_tx_3['hex'], deterministic)
self.test_node.send_islock(islock, deterministic)
# Validate NO hashtxlock
time.sleep(1)
try:
self.receive(ZMQPublisher.hash_tx_lock, zmq.NOBLOCK)
self.subscribers[ZMQPublisher.hash_tx_lock].receive(zmq.NOBLOCK)
assert False
except zmq.ZMQError:
# this is expected
pass
# Now send the tx itself
self.test_node.send_tx(FromHex(msg_tx(), rpc_raw_tx_3['hex']))
self.test_node.send_tx(FromHex(msg_tx(), rpc_raw_tx_3['hex']), deterministic)
self.wait_for_instantlock(rpc_raw_tx_3['txid'], self.nodes[0])
# Validate hashtxlock
zmq_tx_lock_hash = self.receive(ZMQPublisher.hash_tx_lock).read(32).hex()
zmq_tx_lock_hash = self.subscribers[ZMQPublisher.hash_tx_lock].receive().read(32).hex()
assert_equal(zmq_tx_lock_hash, rpc_raw_tx_3['txid'])
# Drop test node connection
self.nodes[0].disconnect_p2ps()
@ -337,10 +366,11 @@ class DashZMQTest (DashTestFramework):
self.sync_blocks()
rpc_proposal_hash = self.nodes[0].gobject("submit", "0", proposal_rev, proposal_time, proposal_hex, collateral)
# Validate hashgovernanceobject
zmq_governance_object_hash = self.receive(ZMQPublisher.hash_governance_object).read(32).hex()
zmq_governance_object_hash = self.subscribers[ZMQPublisher.hash_governance_object].receive().read(32).hex()
assert_equal(zmq_governance_object_hash, rpc_proposal_hash)
# Validate rawgovernanceobject
zmq_governance_object_raw = CGovernanceObject()
zmq_governance_object_raw.deserialize(self.receive(ZMQPublisher.raw_governance_object))
zmq_governance_object_raw.deserialize(self.subscribers[ZMQPublisher.raw_governance_object].receive())
assert_equal(zmq_governance_object_raw.nHashParent, 0)
assert_equal(zmq_governance_object_raw.nRevision, proposal_rev)
assert_equal(zmq_governance_object_raw.nTime, proposal_time)
@ -365,11 +395,11 @@ class DashZMQTest (DashTestFramework):
self.nodes[0].gobject("vote-many", rpc_proposal_hash, map_vote_signals[1], map_vote_outcomes[1])
rpc_proposal_votes = self.nodes[0].gobject('getcurrentvotes', rpc_proposal_hash)
# Validate hashgovernancevote
zmq_governance_vote_hash = self.receive(ZMQPublisher.hash_governance_vote).read(32).hex()
zmq_governance_vote_hash = self.subscribers[ZMQPublisher.hash_governance_vote].receive().read(32).hex()
assert zmq_governance_vote_hash in rpc_proposal_votes
# Validate rawgovernancevote
zmq_governance_vote_raw = CGovernanceVote()
zmq_governance_vote_raw.deserialize(self.receive(ZMQPublisher.raw_governance_vote))
zmq_governance_vote_raw.deserialize(self.subscribers[ZMQPublisher.raw_governance_vote].receive())
assert_equal(uint256_to_string(zmq_governance_vote_raw.nParentHash), rpc_proposal_hash)
rpc_vote_parts = rpc_proposal_votes[zmq_governance_vote_hash].split(':')
rpc_outpoint_parts = rpc_vote_parts[0].split('-')

View File

@ -82,7 +82,7 @@ class RPCVerifyISLockTest(DashTestFramework):
break
assert selected_hash == oldest_quorum_hash
# Create the ISLOCK, then mine a quorum to move the signing quorum out of the active set
islock = self.create_islock(rawtx, True)
islock = self.create_islock(rawtx, False)
# Mine one block to trigger the "signHeight + dkgInterval" verification for the ISLOCK
self.mine_quorum()
# Verify the ISLOCK for a transaction that is not yet known by the node

View File

@ -1038,13 +1038,14 @@ class DashTestFramework(BitcoinTestFramework):
request_id = hash256(request_id_buf)[::-1].hex()
message_hash = tx.hash
llmq_type = 103 if deterministic else 104
quorum_member = None
for mn in self.mninfo:
res = mn.node.quorum('sign', 104, request_id, message_hash)
res = mn.node.quorum('sign', llmq_type, request_id, message_hash)
if (res and quorum_member is None):
quorum_member = mn
rec_sig = self.get_recovered_sig(request_id, message_hash, node=quorum_member.node, llmq_type=104)
rec_sig = self.get_recovered_sig(request_id, message_hash, node=quorum_member.node, llmq_type=llmq_type)
if deterministic:
block_count = quorum_member.node.getblockcount()
@ -1330,7 +1331,7 @@ class DashTestFramework(BitcoinTestFramework):
return new_quorum
def mine_cycle_quorum(self, llmq_type_name="llmq_test", llmq_type=100, expected_connections=None, expected_members=None, expected_contributions=None, expected_complaints=0, expected_justifications=0, expected_commitments=None, mninfos_online=None, mninfos_valid=None):
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, mninfos_valid=None):
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
@ -1467,6 +1468,21 @@ class DashTestFramework(BitcoinTestFramework):
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.nodes[0].generate(skip_count)
sync_blocks(nodes)
time.sleep(1)
self.log.info('Moved from block %d to %d' % (cur_block, self.nodes[0].getblockcount()))
def get_recovered_sig(self, rec_sig_id, rec_sig_msg_hash, llmq_type=100, node=None):
# Note: recsigs aren't relayed to regular nodes by default,
# make sure to pick a mn as a node to query for recsigs.