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
14 changes: 11 additions & 3 deletions test/functional/feature_llmq_is_cl_conflicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def on_getdata(self, message):

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()
Expand All @@ -72,6 +72,14 @@ def run_test(self):
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):
Expand Down Expand Up @@ -320,7 +328,7 @@ def create_block(self, node, vtx=[]):

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
Expand Down
19 changes: 2 additions & 17 deletions test/functional/feature_llmq_is_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

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
Expand Down Expand Up @@ -91,7 +91,7 @@ def run_test(self):
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)
Expand All @@ -118,20 +118,5 @@ def run_test(self):
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()
16 changes: 0 additions & 16 deletions test/functional/feature_llmq_rotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
Checks LLMQs Quorum Rotation

'''
import time
from test_framework.test_framework import DashTestFramework
from test_framework.util import (
assert_equal,
Expand Down Expand Up @@ -128,21 +127,6 @@ def run_test(self):
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()
112 changes: 71 additions & 41 deletions test/functional/interface_zmq_dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
msg_clsig,
msg_inv,
msg_isdlock,
msg_islock,
msg_tx,
ser_string,
uint256_from_str,
Expand All @@ -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):
Expand All @@ -89,24 +105,26 @@ def set_test_params(self):
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()
self.skip_if_no_bitcoind_zmq()
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)
Expand All @@ -122,42 +140,53 @@ def run_test(self):
# 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):

def validate_recovered_sig(request_id, msg_hash):
# 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'])
Expand Down Expand Up @@ -207,15 +236,15 @@ def test_chainlock_publishers(self):
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()
Expand All @@ -228,7 +257,7 @@ def test_chainlock_publishers(self):
# 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,
Expand All @@ -251,57 +280,57 @@ def test_instantsend_publishers(self):
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()
Expand Down Expand Up @@ -337,10 +366,11 @@ def test_governance_publishers(self):
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)
Expand All @@ -365,11 +395,11 @@ def test_governance_publishers(self):
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('-')
Expand Down
2 changes: 1 addition & 1 deletion test/functional/rpc_verifyislock.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def run_test(self):
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
Expand Down
Loading