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 = bytes_to_hex_str(self.receive(ZMQPublisher.hash_recovered_sig).read(32)) 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)) 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']) assert_equal(uint256_to_string(zmq_recovered_sig_raw.msgHash), rpc_recovered_sig['msgHash']) assert_equal(bytes_to_hex_str(zmq_recovered_sig_raw.sig), rpc_recovered_sig['sig'])
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 = bytes_to_hex_str( self.receive(ZMQPublisher.hash_recovered_sig).read(32)) 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)) 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']) assert_equal(uint256_to_string(zmq_recovered_sig_raw.msgHash), rpc_recovered_sig['msgHash']) assert_equal(bytes_to_hex_str(zmq_recovered_sig_raw.sig), rpc_recovered_sig['sig']) recovered_sig_publishers = [ ZMQPublisher.hash_recovered_sig, ZMQPublisher.raw_recovered_sig ] self.log.info("Testing %d recovered signature publishers" % len(recovered_sig_publishers)) # Subscribe to recovered signature messages self.subscribe(recovered_sig_publishers) # Generate a ChainLock and make sure this leads to valid recovered sig ZMQ messages rpc_last_block_hash = self.nodes[0].generate(1)[0] self.wait_for_chainlocked_block_all_nodes(rpc_last_block_hash) height = self.nodes[0].getblockcount() rpc_request_id = hash256( ser_string(b"clsig") + struct.pack("<I", height))[::-1].hex() validate_recovered_sig(rpc_request_id, rpc_last_block_hash) # Sign an arbitrary and make sure this leads to valid recovered sig ZMQ messages sign_id = uint256_to_string(random.getrandbits(256)) sign_msg_hash = uint256_to_string(random.getrandbits(256)) for mn in self.get_quorum_masternodes(self.quorum_hash): mn.node.quorum("sign", self.quorum_type, sign_id, sign_msg_hash) validate_recovered_sig(sign_id, sign_msg_hash) # Unsubscribe from recovered signature messages self.unsubscribe(recovered_sig_publishers)
def test_chainlock_publishers(self): chain_lock_publishers = [ ZMQPublisher.hash_chain_lock, ZMQPublisher.raw_chain_lock, ZMQPublisher.raw_chain_lock_sig ] self.log.info("Testing %d ChainLock publishers" % len(chain_lock_publishers)) # Subscribe to ChainLock messages self.subscribe(chain_lock_publishers) # Generate ChainLock generated_hash = self.nodes[0].generate(1)[0] self.wait_for_chainlocked_block_all_nodes(generated_hash) rpc_best_chain_lock = self.nodes[0].getbestchainlock() rpc_best_chain_lock_hash = rpc_best_chain_lock["blockhash"] rpc_best_chain_lock_sig = rpc_best_chain_lock["signature"] assert_equal(generated_hash, rpc_best_chain_lock_hash) rpc_chain_locked_block = self.nodes[0].getblock( rpc_best_chain_lock_hash) rpc_chain_lock_height = rpc_chain_locked_block["height"] rpc_chain_lock_hash = rpc_chain_locked_block["hash"] assert_equal(generated_hash, rpc_chain_lock_hash) # Validate hashchainlock zmq_chain_lock_hash = bytes_to_hex_str( self.receive(ZMQPublisher.hash_chain_lock).read(32)) 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)) 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_locked_block = CBlock() zmq_chain_locked_block.deserialize(zmq_chain_lock_sig_stream) assert (zmq_chain_locked_block.is_valid()) zmq_chain_lock = msg_clsig() zmq_chain_lock.deserialize(zmq_chain_lock_sig_stream) assert_equal(zmq_chain_lock.height, rpc_chain_lock_height) assert_equal(uint256_to_string(zmq_chain_lock.blockHash), rpc_chain_lock_hash) assert_equal(zmq_chain_locked_block.hash, rpc_chain_lock_hash) assert_equal(bytes_to_hex_str(zmq_chain_lock.sig), rpc_best_chain_lock_sig) # Unsubscribe from ChainLock messages self.unsubscribe(chain_lock_publishers)
def mine_block(self, node, vtx=[], miner_address=None, mn_payee=None, mn_amount=None, use_mnmerkleroot_from_tip=False, expected_error=None): bt = node.getblocktemplate() height = bt['height'] tip_hash = bt['previousblockhash'] tip_block = node.getblock(tip_hash) coinbasevalue = bt['coinbasevalue'] if miner_address is None: miner_address = self.nodes[0].getnewaddress() if mn_payee is None: if isinstance(bt['masternode'], list): mn_payee = bt['masternode'][0]['payee'] else: mn_payee = bt['masternode']['payee'] # we can't take the masternode payee amount from the template here as we might have additional fees in vtx # calculate fees that the block template included (we'll have to remove it from the coinbase as we won't # include the template's transactions bt_fees = 0 for tx in bt['transactions']: bt_fees += tx['fee'] new_fees = 0 for tx in vtx: in_value = 0 out_value = 0 for txin in tx.vin: txout = node.gettxout(uint256_to_string(txin.prevout.hash), txin.prevout.n, False) in_value += int(txout['value'] * COIN) for txout in tx.vout: out_value += txout.nValue new_fees += in_value - out_value # fix fees coinbasevalue -= bt_fees coinbasevalue += new_fees if mn_amount is None: realloc_info = get_bip9_status(self.nodes[0], 'realloc') realloc_height = 99999999 if realloc_info['status'] == 'active': realloc_height = realloc_info['since'] mn_amount = get_masternode_payment(height, coinbasevalue, realloc_height) miner_amount = coinbasevalue - mn_amount outputs = {miner_address: str(Decimal(miner_amount) / COIN)} if mn_amount > 0: outputs[mn_payee] = str(Decimal(mn_amount) / COIN) coinbase = FromHex(CTransaction(), node.createrawtransaction([], outputs)) coinbase.vin = create_coinbase(height).vin # We can't really use this one as it would result in invalid merkle roots for masternode lists if len(bt['coinbase_payload']) != 0: cbtx = FromHex(CCbTx(version=1), bt['coinbase_payload']) if use_mnmerkleroot_from_tip: if 'cbTx' in tip_block: cbtx.merkleRootMNList = int( tip_block['cbTx']['merkleRootMNList'], 16) else: cbtx.merkleRootMNList = 0 coinbase.nVersion = 3 coinbase.nType = 5 # CbTx coinbase.vExtraPayload = cbtx.serialize() coinbase.calc_sha256() block = create_block(int(tip_hash, 16), coinbase) block.vtx += vtx # Add quorum commitments from template for tx in bt['transactions']: tx2 = FromHex(CTransaction(), tx['data']) if tx2.nType == 6: block.vtx.append(tx2) block.hashMerkleRoot = block.calc_merkle_root() block.solve() result = node.submitblock(ToHex(block)) if expected_error is not None and result != expected_error: raise AssertionError( 'mining the block should have failed with error %s, but submitblock returned %s' % (expected_error, result)) elif expected_error is None and result is not None: raise AssertionError('submitblock returned %s' % (result))
def test_governance_publishers(self): governance_publishers = [ ZMQPublisher.hash_governance_object, ZMQPublisher.raw_governance_object, ZMQPublisher.hash_governance_vote, ZMQPublisher.raw_governance_vote ] self.log.info("Testing %d governance publishers" % len(governance_publishers)) # Subscribe to governance messages self.subscribe(governance_publishers) # Create a proposal and submit it to the network proposal_rev = 1 proposal_time = int(time.time()) proposal_data = { "type": 1, # GOVERNANCE_OBJECT_PROPOSAL "name": "Test", "start_epoch": proposal_time, "end_epoch": proposal_time + 60, "payment_amount": 5, "payment_address": self.nodes[0].getnewaddress(), "url": "https://xazab.org" } proposal_hex = ''.join( format(x, '02x') for x in json.dumps(proposal_data).encode()) collateral = self.nodes[0].gobject("prepare", "0", proposal_rev, proposal_time, proposal_hex) self.wait_for_instantlock(collateral, self.nodes[0]) self.nodes[0].generate(6) 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 = bytes_to_hex_str( self.receive(ZMQPublisher.hash_governance_object).read(32)) assert_equal(zmq_governance_object_hash, rpc_proposal_hash) zmq_governance_object_raw = CGovernanceObject() zmq_governance_object_raw.deserialize( self.receive(ZMQPublisher.raw_governance_object)) 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) assert_equal(json.loads(zmq_governance_object_raw.vchData.decode()), proposal_data) assert_equal(zmq_governance_object_raw.nObjectType, proposal_data["type"]) assert_equal(zmq_governance_object_raw.masternodeOutpoint.hash, COutPoint().hash) assert_equal(zmq_governance_object_raw.masternodeOutpoint.n, COutPoint().n) # Vote for the proposal and validate the governance vote message map_vote_outcomes = {0: "none", 1: "yes", 2: "no", 3: "abstain"} map_vote_signals = { 0: "none", 1: "funding", 2: "valid", 3: "delete", 4: "endorsed" } 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 = bytes_to_hex_str( self.receive(ZMQPublisher.hash_governance_vote).read(32)) 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)) 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('-') assert_equal( uint256_to_string(zmq_governance_vote_raw.masternodeOutpoint.hash), rpc_outpoint_parts[0]) assert_equal(zmq_governance_vote_raw.masternodeOutpoint.n, int(rpc_outpoint_parts[1])) assert_equal(zmq_governance_vote_raw.nTime, int(rpc_vote_parts[1])) assert_equal(map_vote_outcomes[zmq_governance_vote_raw.nVoteOutcome], rpc_vote_parts[2]) assert_equal(map_vote_signals[zmq_governance_vote_raw.nVoteSignal], rpc_vote_parts[3]) # Unsubscribe from governance messages self.unsubscribe(governance_publishers)
def test_instantsend_publishers(self): instantsend_publishers = [ ZMQPublisher.hash_tx_lock, ZMQPublisher.raw_tx_lock, ZMQPublisher.raw_tx_lock_sig, ZMQPublisher.hash_instantsend_doublespend, ZMQPublisher.raw_instantsend_doublespend ] self.log.info("Testing %d InstantSend publishers" % len(instantsend_publishers)) # Subscribe to InstantSend messages self.subscribe(instantsend_publishers) # Initialize test node self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn()) network_thread_start() self.nodes[0].p2p.wait_for_verack() # Make sure all nodes agree self.wait_for_chainlocked_block_all_nodes( self.nodes[0].getbestblockhash()) # Create two raw TXs, they will conflict with each other rpc_raw_tx_1 = self.create_raw_tx(self.nodes[0], self.nodes[0], 1, 1, 100) rpc_raw_tx_2 = self.create_raw_tx(self.nodes[0], self.nodes[0], 1, 1, 100) # Send the first transaction and wait for the InstantLock 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 = bytes_to_hex_str( self.receive(ZMQPublisher.hash_tx_lock).read(32)) 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)) 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_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_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 = bytes_to_hex_str( self.receive(ZMQPublisher.hash_instantsend_doublespend).read(32)) zmq_double_spend_hash1 = bytes_to_hex_str( self.receive(ZMQPublisher.hash_instantsend_doublespend).read(32)) 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)) 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)) 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) # Validate NO hashtxlock time.sleep(1) try: self.receive(ZMQPublisher.hash_tx_lock, 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.wait_for_instantlock(rpc_raw_tx_3['txid'], self.nodes[0]) # Validate hashtxlock zmq_tx_lock_hash = bytes_to_hex_str( self.receive(ZMQPublisher.hash_tx_lock).read(32)) assert_equal(zmq_tx_lock_hash, rpc_raw_tx_3['txid']) # Drop test node connection self.nodes[0].disconnect_p2ps() # Unsubscribe from InstantSend messages self.unsubscribe(instantsend_publishers)