def test_revert_slave_txn(self): tx = SlaveTransaction.create(**self.params) tx.sign(self.alice) addresses_state = dict(self.addresses_state) addresses_state[self.alice.address].pbdata.balance = 100 state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) tx.apply(self.state, state_container) tx.revert(self.state, state_container) self.assertEqual(addresses_state[self.alice.address].balance, 100) storage_key = state_container.paginated_tx_hash.generate_key( self.alice.address, 1) self.assertIn(storage_key, state_container.paginated_tx_hash.key_value) self.assertEqual( [], state_container.paginated_tx_hash.key_value[storage_key]) self.assertIn((tx.addr_from, tx.slave_pks[0]), state_container.slaves.data) data = state_container.slaves.data[(tx.addr_from, tx.slave_pks[0])] self.assertIsInstance(data, SlaveMetadata) self.assertEqual(tx.access_types[0], data.access_type) self.assertEqual(tx.txhash, data.tx_hash)
def create_block(self, prev_hash, mining_address=None): if not mining_address: mining_address = self.alice_xmss.address transactions = [] block_prev = self.qrlnode.get_block_from_hash(prev_hash) block_idx = block_prev.block_number + 1 if block_idx == 1: slave_tx = SlaveTransaction.create(slave_pks=[self.bob_xmss.pk], access_types=[0], fee=0, xmss_pk=self.alice_xmss.pk) slave_tx.sign(self.alice_xmss) slave_tx._data.nonce = 1 transactions = [slave_tx] time_offset = 60 if block_idx % 2 == 0: time_offset += 2 self.time_mock.return_value = self.time_mock.return_value + time_offset self.ntp_mock.return_value = self.ntp_mock.return_value + time_offset block_new = Block.create(block_number=block_idx, prev_headerhash=block_prev.headerhash, prev_timestamp=block_prev.timestamp, transactions=transactions, miner_address=mining_address) while not self.qrlnode._chain_manager.validate_mining_nonce(blockheader=block_new.blockheader): block_new.set_nonces(block_new.mining_nonce + 1, 0) return block_new
def slave_tx_generate(ctx, src, master, number_of_slaves, access_type, fee, pk, ots_key_index): """ Generates Slave Transaction for the wallet """ try: _, src_xmss = _select_wallet(ctx, src) ots_key_index = validate_ots_index(ots_key_index, src_xmss) src_xmss.set_ots_index(ots_key_index) if src_xmss: address_src_pk = src_xmss.pk else: address_src_pk = pk.encode() master_addr = None if master: master_addr = parse_qaddress(master) fee_shor = _quanta_to_shor(fee) except Exception as e: click.echo("Error validating arguments: {}".format(e)) quit(1) slave_xmss = [] slave_pks = [] access_types = [] slave_xmss_seed = [] if number_of_slaves > 100: click.echo("Error: Max Limit for the number of slaves is 100") quit(1) for i in range(number_of_slaves): print("Generating Slave #" + str(i + 1)) xmss = XMSS.from_height(config.dev.xmss_tree_height) slave_xmss.append(xmss) slave_xmss_seed.append(xmss.extended_seed) slave_pks.append(xmss.pk) access_types.append(access_type) print("Successfully Generated Slave %s/%s" % (str(i + 1), number_of_slaves)) try: tx = SlaveTransaction.create(slave_pks=slave_pks, access_types=access_types, fee=fee_shor, xmss_pk=address_src_pk, master_addr=master_addr) tx.sign(src_xmss) with open('slaves.json', 'w') as f: json.dump( [bin2hstr(src_xmss.address), slave_xmss_seed, tx.to_json()], f) click.echo('Successfully created slaves.json') click.echo( 'Move slaves.json file from current directory to the mining node inside ~/.qrl/' ) except Exception as e: click.echo("Unhandled error: {}".format(str(e))) quit(1)
def create_slave_tx(self, slave_pks: list, access_types: list, fee: int, xmss_pk: bytes, master_addr: bytes) -> SlaveTransaction: return SlaveTransaction.create(slave_pks=slave_pks, access_types=access_types, fee=fee, xmss_pk=xmss_pk, master_addr=master_addr)
def generate_slave_tx(self, signer_pk: bytes, slave_pk_list: list, master_addr=None): return SlaveTransaction.create(slave_pks=slave_pk_list, access_types=[0] * len(slave_pk_list), fee=0, xmss_pk=signer_pk, master_addr=master_addr)
def test_broadcast_tx(self, m_reactor, m_logger): # broadcast_tx() should handle all Transaction Types self.factory.broadcast_tx(MessageTransaction()) self.factory.broadcast_tx(TransferTransaction()) self.factory.broadcast_tx(TokenTransaction()) self.factory.broadcast_tx(TransferTokenTransaction()) self.factory.broadcast_tx(SlaveTransaction()) with self.assertRaises(ValueError): m_tx = Mock(autospec=TransferTransaction, txhash=bhstr2bin('deadbeef')) self.factory.broadcast_tx(m_tx)
def test_validate_custom(self, m_logger): """ SlaveTransaction._validate_custom() checks for the following things: 1. if you specify more than 100 slave_pks at once 2. if len(slave_pks) != len(access_types) 3. access_types can only be 0, 1 """ # Unequal length slave_pks and access_types params = self.params.copy() params["slave_pks"] = [self.slave.pk] params["access_types"] = [0, 1] with self.assertRaises(ValueError): SlaveTransaction.create(**params) # access_type is a weird, undefined number params = self.params.copy() params["access_types"] = [5] with self.assertRaises(ValueError): SlaveTransaction.create(**params)
def test_revert_state_changes_empty_addresses_state( self, m_logger, m_apply_state_PK, m_revert_state_PK): tx = SlaveTransaction.create(**self.params) tx.sign(self.alice) addresses_state = {} tx.revert_state_changes(addresses_state, self.unused_chain_manager_mock) self.assertEqual({}, addresses_state) m_revert_state_PK.assert_called_once()
def test_validate_tx(self, m_logger): tx = SlaveTransaction.create(**self.params) tx.sign(self.alice) self.assertTrue(tx.validate_or_raise()) tx._data.transaction_hash = b'abc' # Should fail, as we have modified with invalid transaction_hash with self.assertRaises(ValueError): tx.validate_or_raise()
def test_validate_custom(self, m_logger): """ SlaveTransaction._validate_custom() checks for the following things: 1. if you specify more than 100 slave_pks at once 2. if len(slave_pks) != len(access_types) 3. access_types can only be 0, 1 """ # We're going to need all the XMSS trees we can get here bob = get_bob_xmss() # Too many slave_pks with patch('qrl.core.txs.SlaveTransaction.config', autospec=True) as m_config: m_config.dev.transaction_multi_output_limit = 2 params = self.params.copy() params["slave_pks"] = [self.alice.pk, bob.pk, self.slave.pk] params["access_types"] = [0, 0, 0] with self.assertRaises(ValueError): SlaveTransaction.create(**params) # Unequal length slave_pks and access_types params = self.params.copy() params["slave_pks"] = [self.slave.pk] params["access_types"] = [0, 1] with self.assertRaises(ValueError): SlaveTransaction.create(**params) # access_type is a weird, undefined number params = self.params.copy() params["access_types"] = [5] with self.assertRaises(ValueError): SlaveTransaction.create(**params)
def relay_slave_txn(self, slave_pks: list, access_types: list, fee: int, master_qaddress, signer_address: str, ots_index: int): self.authenticate() index, xmss = self._get_wallet_index_xmss(signer_address, ots_index) tx = SlaveTransaction.create( slave_pks=slave_pks, access_types=access_types, fee=fee, xmss_pk=xmss.pk, master_addr=self.qaddress_to_address(master_qaddress)) self._push_transaction(tx, xmss) self._wallet.set_ots_index(index, xmss.ots_index) return self.to_plain_transaction(tx.pbdata)
def test_validate_extended(self, m_validate_slave, m_logger): """ SlaveTransaction.validate_extended checks for: 1. valid master/slave 2. negative fee, 3. addr_from has enough funds for the fee 4. addr_from ots_key reuse """ m_addr_from_state = Mock(autospec=AddressState, name='addr_from State', balance=100) m_addr_from_pk_state = Mock(autospec=AddressState, name='addr_from_pk State') m_addr_from_pk_state.ots_key_reuse.return_value = False tx = SlaveTransaction.create(**self.params) tx.sign(self.alice) result = tx.validate_extended(m_addr_from_state, m_addr_from_pk_state) self.assertTrue(result) # Invalid master XMSS/slave XMSS relationship m_validate_slave.return_value = False result = tx.validate_extended(m_addr_from_state, m_addr_from_pk_state) self.assertFalse(result) m_validate_slave.return_value = True # fee = -1 with patch('qrl.core.txs.SlaveTransaction.SlaveTransaction.fee', new_callable=PropertyMock) as m_fee: m_fee.return_value = -1 result = tx.validate_extended(m_addr_from_state, m_addr_from_pk_state) self.assertFalse(result) # balance = 0, cannot pay the Transaction fee m_addr_from_state.balance = 0 result = tx.validate_extended(m_addr_from_state, m_addr_from_pk_state) self.assertFalse(result) m_addr_from_state.balance = 100 # addr_from_pk has used this OTS key before m_addr_from_pk_state.ots_key_reuse.return_value = True result = tx.validate_extended(m_addr_from_state, m_addr_from_pk_state) self.assertFalse(result)
def get_slaves(alice_ots_index, txn_nonce): # [master_address: bytes, slave_seeds: list, slave_txn: json] slave_xmss = get_slave_xmss() alice_xmss = get_alice_xmss() alice_xmss.set_ots_index(alice_ots_index) slave_txn = SlaveTransaction.create([slave_xmss.pk], [1], 0, alice_xmss.pk) slave_txn._data.nonce = txn_nonce slave_txn.sign(alice_xmss) slave_data = json.loads( json.dumps([ bin2hstr(alice_xmss.address), [slave_xmss.extended_seed], slave_txn.to_json() ])) slave_data[0] = bytes(hstr2bin(slave_data[0])) return slave_data
def relay_slave_txn_by_slave(self, slave_pks: list, access_types: list, fee: int, master_qaddress): self.authenticate() index, group_index, slave_index, slave_xmss = self.get_slave_xmss( master_qaddress) if slave_index == -1: raise Exception("No Slave Found") tx = SlaveTransaction.create( slave_pks=slave_pks, access_types=access_types, fee=fee, xmss_pk=slave_xmss.pk, master_addr=self.qaddress_to_address(master_qaddress)) self.sign_and_push_transaction(tx, slave_xmss, index, group_index, slave_index) return self.to_plain_transaction(tx.pbdata)
def test_apply_state_changes(self, m_logger, m_apply_state_PK, m_revert_state_PK): tx = SlaveTransaction.create(**self.params) tx.sign(self.alice) addresses_state = self.generate_addresses_state(tx) tx.apply_state_changes(addresses_state) self.assertEqual(addresses_state[self.alice.address].balance, 99) self.assertEqual( [tx.txhash], addresses_state[self.alice.address].transaction_hashes) self.assertEqual( [], addresses_state[self.slave.address].transaction_hashes) addresses_state[ self.alice.address].add_slave_pks_access_type.assert_called_once() addresses_state[ self.slave.address].add_slave_pks_access_type.assert_not_called() m_apply_state_PK.assert_called_once()
def test_bad_tx(self, mock_logger): source = Mock() source.factory = Mock() source.factory.master_mr = Mock() source.factory.master_mr.isRequested = Mock() source.factory.add_unprocessed_txn = Mock() channel = Observable(source) self.tx_manager = P2PTxManagement() self.tx_manager.new_channel(channel) tx = SlaveTransaction.create(slave_pks=[], access_types=[], fee=1, xmss_pk=bytes(67)) event = qrllegacy_pb2.LegacyMessage( func_name=qrllegacy_pb2.LegacyMessage.TX, txData=tx.pbdata) channel.notify(event, force_delivery=True) source.factory.master_mr.isRequested.assert_not_called() source.factory.add_unprocessed_txn.assert_not_called()
def test_revert_state_changes(self, m_logger, m_apply_state_PK, m_revert_state_PK): tx = SlaveTransaction.create(**self.params) tx.sign(self.alice) addresses_state = self.generate_addresses_state(tx) addresses_state[self.alice.address].balance = 99 addresses_state[self.alice.address].transaction_hashes = [tx.txhash] tx.revert_state_changes(addresses_state, self.unused_chain_manager_mock) self.assertEqual(addresses_state[self.alice.address].balance, 100) self.assertEqual( [], addresses_state[self.alice.address].transaction_hashes) self.assertEqual( [], addresses_state[self.slave.address].transaction_hashes) addresses_state[ self.alice. address].remove_slave_pks_access_type.assert_called_once() addresses_state[ self.slave.address].remove_slave_pks_access_type.assert_not_called( ) m_revert_state_PK.assert_called_once()
def test_create_validate(self, m_logger): """Default self.params should result in a valid SlaveTransaction""" tx = SlaveTransaction.create(**self.params) tx.sign(self.alice) result = tx.validate_or_raise() self.assertTrue(result)
def test_add_block(self, mock_difficulty_tracker_get): """ Add block_1 on genesis block (that registers Bob as Alice's slave) Add a competing fork_block on genesis block (without the SlaveTransaction) Add block_2 on fork_block (without the SlaveTransaction) Bob should be free from slavery now. """ mock_difficulty_tracker_get.return_value = ask_difficulty_tracker('2') self.chain_manager.load(self.genesis_block) # Add block_1 on genesis block. slave_tx = SlaveTransaction.create(slave_pks=[bob.pk], access_types=[0], fee=0, xmss_pk=alice.pk) slave_tx.sign(alice) slave_tx._data.nonce = 1 self.assertTrue(slave_tx.validate()) with patch('qrl.core.misc.ntp.getTime') as time_mock: time_mock.return_value = 1615270948 # Very high to get an easy difficulty block_1 = Block.create( block_number=1, prev_headerhash=self.genesis_block.headerhash, prev_timestamp=self.genesis_block.timestamp, transactions=[slave_tx], miner_address=alice.address) block_1.set_nonces(2, 0) # Uncomment only to determine the correct mining_nonce of above blocks # from qrl.core.PoWValidator import PoWValidator # while not PoWValidator().validate_mining_nonce(self.state, block_1.blockheader, False): # block_1.set_nonces(block_1.mining_nonce + 1) # print(block_1.mining_nonce) self.assertTrue(block_1.validate(self.chain_manager, {})) result = self.chain_manager.add_block(block_1) self.assertTrue(result) self.assertEqual(self.chain_manager.last_block, block_1) # Yes, Bob is Alice's slave. alice_state = self.chain_manager.get_address_state(alice.address) self.assertEqual(len(alice_state.slave_pks_access_type), 1) self.assertTrue(str(bob.pk) in alice_state.slave_pks_access_type) # Add fork block on genesis block with patch('qrl.core.misc.ntp.getTime') as time_mock: time_mock.return_value = 1715270948 # Very high to get an easy difficulty fork_block = Block.create( block_number=1, prev_headerhash=self.genesis_block.headerhash, prev_timestamp=self.genesis_block.timestamp, transactions=[], miner_address=bob.address) fork_block.set_nonces(4, 0) # Uncomment only to determine the correct mining_nonce of above blocks # from qrl.core.PoWValidator import PoWValidator # while not PoWValidator().validate_mining_nonce(self.state, fork_block.blockheader, False): # fork_block.set_nonces(fork_block.mining_nonce + 1) # print(fork_block.mining_nonce) self.assertTrue(fork_block.validate(self.chain_manager, {})) result = self.chain_manager.add_block(fork_block) self.assertTrue(result) self.assertEqual(self.chain_manager.last_block, block_1) fork_block = self.state.get_block(fork_block.headerhash) self.assertIsNotNone(fork_block) # Add block_2 on fork_block. with patch('qrl.core.misc.ntp.getTime') as time_mock: time_mock.return_value = 1815270948 # Very high to get an easy difficulty block_2 = fork_block.create(block_number=2, prev_headerhash=fork_block.headerhash, prev_timestamp=fork_block.timestamp, transactions=[], miner_address=bob.address) block_2.set_nonces(1, 0) # Uncomment only to determine the correct mining_nonce of above blocks # from qrl.core.PoWValidator import PoWValidator # while not PoWValidator().validate_mining_nonce(state, block_2.blockheader, False): # block_2.set_nonces(block_2.mining_nonce + 1, 0) # print(block_2.mining_nonce) self.assertTrue(block_2.validate(self.chain_manager, {})) result = self.chain_manager.add_block(block_2) self.assertTrue(result) self.assertEqual(self.chain_manager.last_block.block_number, block_2.block_number) self.assertEqual(self.chain_manager.last_block.serialize(), block_2.serialize()) # Now we are on the forked chain, Bob is no longer Alice's slave. alice_state = self.chain_manager.get_address_state(alice.address) self.assertFalse(str(bob.pk) in alice_state.slave_pks_access_type)
def test_validate_extended(self, m_validate_slave, m_logger): """ SlaveTransaction._validate_extended checks for: 1. valid master/slave 2. negative fee, 3. addr_from has enough funds for the fee 4. addr_from ots_key reuse """ alice_address_state = OptimizedAddressState.get_default( self.alice.address) alice_address_state.pbdata.balance = 100 addresses_state = {alice_address_state.address: alice_address_state} tx = SlaveTransaction.create(**self.params) tx.sign(self.alice) state_container = StateContainer(addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer( b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=1, total_coin_supply=100, current_dev_config=config.dev, write_access=True, my_db=self.state._db, batch=None) result = tx._validate_extended(state_container) self.assertTrue(result) # Invalid master XMSS/slave XMSS relationship m_validate_slave.return_value = False result = tx.validate_all(state_container) self.assertFalse(result) m_validate_slave.return_value = True # fee = -1 with patch('qrl.core.txs.SlaveTransaction.SlaveTransaction.fee', new_callable=PropertyMock) as m_fee: m_fee.return_value = -1 result = tx._validate_custom() self.assertFalse(result) # balance = 0, cannot pay the Transaction fee alice_address_state.pbdata.balance = 0 result = tx._validate_extended(state_container) self.assertFalse(result) alice_address_state.pbdata.balance = 100 addresses_state = {self.alice.address: alice_address_state} # addr_from_pk has used this OTS key before state_container.paginated_bitfield.set_ots_key( addresses_state, alice_address_state.address, tx.ots_key) result = tx.validate_all(state_container) self.assertFalse(result) bob = get_bob_xmss() # Too many slave_pks with patch('qrl.core.config', autospec=True) as m_config: m_config.dev = config.dev.create(config.dev.prev_state_key, config.dev.current_state_key, b'', 10, True, True) m_config.dev.pbdata.transaction.multi_output_limit = 2 state_container.current_dev_config = m_config.dev params = self.params.copy() params["slave_pks"] = [self.alice.pk, bob.pk, self.slave.pk] params["access_types"] = [0, 0, 0] tx = SlaveTransaction.create(**params) self.assertFalse(tx._validate_extended(state_container))