def test_token_info_not_utf8(self): token_name = 'TestCoin' token_symbol = 'TST' # Token version 1; Name length; Name; Symbol length; Symbol bytes1 = (bytes([0x01]) + int_to_bytes(len(token_name), 1) + token_name.encode('utf-8') + int_to_bytes(len(token_symbol), 1) + token_symbol.encode('utf-8')) name, symbol, _ = TokenCreationTransaction.deserialize_token_info( bytes1) self.assertEqual(name, token_name) self.assertEqual(symbol, token_symbol) encoded_name = token_name.encode('utf-16') bytes2 = (bytes([0x01]) + int_to_bytes(len(encoded_name), 1) + encoded_name + int_to_bytes(len(token_symbol), 1) + token_symbol.encode('utf-8')) with self.assertRaises(StructError): TokenCreationTransaction.deserialize_token_info(bytes2) encoded_symbol = token_symbol.encode('utf-16') bytes3 = (bytes([0x01]) + int_to_bytes(len(token_name), 1) + token_name.encode('utf-8') + int_to_bytes(len(encoded_symbol), 1) + encoded_symbol) with self.assertRaises(StructError): TokenCreationTransaction.deserialize_token_info(bytes3)
def test_token_info_serialization(self): tx = create_tokens(self.manager, self.address_b58, mint_amount=500) info = tx.serialize_token_info() # try with version 2 info2 = bytes([0x02]) + info[1:] with self.assertRaises(ValueError): TokenCreationTransaction.deserialize_token_info(info2)
def tx_or_block_from_proto( tx_proto: protos.BaseTransaction, storage: Optional['TransactionStorage'] = None) -> 'BaseTransaction': from hathor.transaction.block import Block from hathor.transaction.merge_mined_block import MergeMinedBlock from hathor.transaction.token_creation_tx import TokenCreationTransaction from hathor.transaction.transaction import Transaction if tx_proto.HasField('transaction'): return Transaction.create_from_proto(tx_proto, storage=storage) elif tx_proto.HasField('block'): if tx_proto.block.HasField('aux_pow'): return MergeMinedBlock.create_from_proto(tx_proto, storage=storage) else: return Block.create_from_proto(tx_proto, storage=storage) elif tx_proto.HasField('tokenCreationTransaction'): return TokenCreationTransaction.create_from_proto(tx_proto, storage=storage) else: raise ValueError('invalid base_transaction_oneof')
def create_tokens(manager: 'HathorManager', address_b58: str = None, mint_amount: int = 300, token_name: str = 'TestCoin', token_symbol: str = 'TTC', propagate: bool = True): """Creates a new token and propagates a tx with the following UTXOs: 0. some tokens (already mint some tokens so they can be transferred); 1. mint authority; 2. melt authority; 3. deposit change; :param manager: hathor manager :type manager: :class:`hathor.manager.HathorManager` :param address_b58: address where tokens will be transferred to :type address_b58: string :param token_name: the token name for the new token :type token_name: str :param token_symbol: the token symbol for the new token :type token_symbol: str :return: the propagated transaction so others can spend their outputs """ genesis = manager.tx_storage.get_all_genesis() genesis_blocks = [tx for tx in genesis if tx.is_block] genesis_txs = [tx for tx in genesis if not tx.is_block] genesis_block = genesis_blocks[0] genesis_private_key = get_genesis_key() wallet = manager.wallet outputs = [] if address_b58 is None: address_b58 = wallet.get_unused_address(mark_as_used=True) address = decode_address(address_b58) parents = [tx.hash for tx in genesis_txs] script = P2PKH.create_output_script(address) # deposit input deposit_amount = get_deposit_amount(mint_amount) deposit_input = TxInput(genesis_block.hash, 0, b'') # mint output if mint_amount > 0: outputs.append(TxOutput(mint_amount, script, 0b00000001)) # authority outputs outputs.append(TxOutput(TxOutput.TOKEN_MINT_MASK, script, 0b10000001)) outputs.append(TxOutput(TxOutput.TOKEN_MELT_MASK, script, 0b10000001)) # deposit output outputs.append(TxOutput(genesis_block.outputs[0].value - deposit_amount, script, 0)) tx = TokenCreationTransaction( weight=1, parents=parents, storage=manager.tx_storage, inputs=[deposit_input], outputs=outputs, token_name=token_name, token_symbol=token_symbol, timestamp=int(manager.reactor.seconds()) ) data_to_sign = tx.get_sighash_all(clear_input_data=True) public_bytes, signature = wallet.get_input_aux_data(data_to_sign, genesis_private_key) tx.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) tx.resolve() if propagate: tx.verify() manager.propagate_tx(tx, fails_silently=False) manager.reactor.advance(8) return tx
def create_tokens(manager: 'HathorManager', address_b58: Optional[str] = None, mint_amount: int = 300, token_name: str = 'TestCoin', token_symbol: str = 'TTC', propagate: bool = True, use_genesis: bool = True, nft_data: Optional[str] = None) -> TokenCreationTransaction: """Creates a new token and propagates a tx with the following UTXOs: 0. some tokens (already mint some tokens so they can be transferred); 1. mint authority; 2. melt authority; 3. deposit change; :param manager: hathor manager :type manager: :class:`hathor.manager.HathorManager` :param address_b58: address where tokens will be transferred to :type address_b58: string :param token_name: the token name for the new token :type token_name: str :param token_symbol: the token symbol for the new token :type token_symbol: str :param use_genesis: If True will use genesis outputs to create token, otherwise will use manager wallet :type token_symbol: bool :param nft_data: If not None we create a first output as the NFT data script :type nft_data: str :return: the propagated transaction so others can spend their outputs """ wallet = manager.wallet assert wallet is not None if address_b58 is None: address_b58 = wallet.get_unused_address(mark_as_used=True) address = decode_address(address_b58) script = P2PKH.create_output_script(address) deposit_amount = get_deposit_amount(mint_amount) if nft_data: # NFT creation needs 0.01 HTR of fee deposit_amount += 1 genesis = manager.tx_storage.get_all_genesis() genesis_blocks = [tx for tx in genesis if tx.is_block] genesis_txs = [tx for tx in genesis if not tx.is_block] genesis_block = genesis_blocks[0] genesis_private_key = get_genesis_key() change_output: Optional[TxOutput] parents: List[bytes] if use_genesis: genesis_hash = genesis_block.hash assert genesis_hash is not None deposit_input = [TxInput(genesis_hash, 0, b'')] change_output = TxOutput(genesis_block.outputs[0].value - deposit_amount, script, 0) parents = [cast(bytes, tx.hash) for tx in genesis_txs] timestamp = int(manager.reactor.seconds()) else: total_reward = 0 deposit_input = [] while total_reward < deposit_amount: block = add_new_block(manager, advance_clock=1, address=address) deposit_input.append(TxInput(block.hash, 0, b'')) total_reward += block.outputs[0].value if total_reward > deposit_amount: change_output = TxOutput(total_reward - deposit_amount, script, 0) else: change_output = None add_blocks_unlock_reward(manager) timestamp = int(manager.reactor.seconds()) parents = manager.get_new_tx_parents(timestamp) outputs = [] if nft_data: script_data = DataScript.create_output_script(nft_data) output_data = TxOutput(1, script_data, 0) outputs.append(output_data) # mint output if mint_amount > 0: outputs.append(TxOutput(mint_amount, script, 0b00000001)) # authority outputs outputs.append(TxOutput(TxOutput.TOKEN_MINT_MASK, script, 0b10000001)) outputs.append(TxOutput(TxOutput.TOKEN_MELT_MASK, script, 0b10000001)) # deposit output if change_output: outputs.append(change_output) tx = TokenCreationTransaction( weight=1, parents=parents, storage=manager.tx_storage, inputs=deposit_input, outputs=outputs, token_name=token_name, token_symbol=token_symbol, timestamp=timestamp ) data_to_sign = tx.get_sighash_all() if use_genesis: public_bytes, signature = wallet.get_input_aux_data(data_to_sign, genesis_private_key) else: private_key = wallet.get_private_key(address_b58) public_bytes, signature = wallet.get_input_aux_data(data_to_sign, private_key) for input_ in tx.inputs: input_.data = P2PKH.create_input_data(public_bytes, signature) tx.resolve() if propagate: tx.verify() manager.propagate_tx(tx, fails_silently=False) manager.reactor.advance(8) return tx
def test_token_struct(self): tx = create_tokens(self.manager, self.address_b58, mint_amount=500) tx2 = TokenCreationTransaction.create_from_struct(tx.get_struct()) self.assertEqual(tx.hash, tx2.hash)
def test_token_info(self): def update_tx(tx): """ sighash_all data changes with token name or symbol, so we have to compute signature again """ data_to_sign = tx.get_sighash_all() public_bytes, signature = self.manager.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) tx.inputs[0].data = P2PKH.create_input_data( public_bytes, signature) tx.resolve() # test token name and symbol tx = create_tokens(self.manager, self.address_b58) # max token name length tx.token_name = 'a' * settings.MAX_LENGTH_TOKEN_NAME update_tx(tx) tx.verify() # max token symbol length tx.token_symbol = 'a' * settings.MAX_LENGTH_TOKEN_SYMBOL update_tx(tx) tx.verify() # long token name tx.token_name = 'a' * (settings.MAX_LENGTH_TOKEN_NAME + 1) update_tx(tx) with self.assertRaises(TransactionDataError): tx.verify() # long token symbol tx.token_name = 'ValidName' tx.token_symbol = 'a' * (settings.MAX_LENGTH_TOKEN_SYMBOL + 1) update_tx(tx) with self.assertRaises(TransactionDataError): tx.verify() # Hathor token name tx.token_name = settings.HATHOR_TOKEN_NAME tx.token_symbol = 'TST' update_tx(tx) with self.assertRaises(TransactionDataError): tx.verify() # Hathor token symbol tx.token_name = 'Test' tx.token_symbol = settings.HATHOR_TOKEN_SYMBOL update_tx(tx) with self.assertRaises(TransactionDataError): tx.verify() # Token name unicode tx.token_name = 'Test ∞' tx.token_symbol = 'TST' token_info = tx.serialize_token_info() TokenCreationTransaction.deserialize_token_info(token_info) update_tx(tx) tx.verify() # Token symbol unicode tx.token_name = 'Test Token' tx.token_symbol = 'TST∞' token_info = tx.serialize_token_info() TokenCreationTransaction.deserialize_token_info(token_info) update_tx(tx) tx.verify()
def test_get_one_known_tx_with_authority(self): # Tx tesnet 00005f234469407614bf0abedec8f722bb5e534949ad37650f6077c899741ed7 # We had a bug with this endpoint in this tx because the token_data from inputs # was not considering authority mask # First add needed data on storage tx_hex = ( '0001010202000023b318c91dcfd4b967b205dc938f9f5e2fd5114256caacfb8f6dd13db330000023b318c91dcfd4b967b20' '5dc938f9f5e2fd5114256caacfb8f6dd13db33000006946304402200f7de9e866fbc2d600d6a46eb620fa2d72c9bf032250' 'f1bb4d241b988182ecfe022002e3010a01ecc539f1f095759642549ca3c626d5603b8efa9499acba0ea13c3621038f962b5' '6731fdb26740e04830b63ae5a39e392fd821beef0e99f6d9ae401f201000023b318c91dcfd4b967b205dc938f9f5e2fd511' '4256caacfb8f6dd13db33002006946304402200f7de9e866fbc2d600d6a46eb620fa2d72c9bf032250f1bb4d241b988182e' 'cfe022002e3010a01ecc539f1f095759642549ca3c626d5603b8efa9499acba0ea13c3621038f962b56731fdb26740e0483' '0b63ae5a39e392fd821beef0e99f6d9ae401f2010000000100001976a914ee216186a0fad459df6f067f9bfa51ce913e1b0' '588ac0000000281001976a914ee216186a0fad459df6f067f9bfa51ce913e1b0588ac4030c398b4620e3161087c07020000' '7851af043c11e19f28675b010e8cf4d8da3278f126d2429490a804a7fb2c000023b318c91dcfd4b967b205dc938f9f5e2fd' '5114256caacfb8f6dd13db33000020393') tx = Transaction.create_from_struct(bytes.fromhex(tx_hex), self.manager.tx_storage) self.manager.tx_storage.save_transaction(tx) tx_parent1_hex = ( '0001010203000023b318c91dcfd4b967b205dc938f9f5e2fd5114256caacfb8f6dd13db330000023b318c91dcfd' '4b967b205dc938f9f5e2fd5114256caacfb8f6dd13db33003006a473045022100b1a0293277469636ae5af69703' '5c2cba5f15f625814f27938e29ffab8d609ce2022047bce945a30dd498e429b8e73cbce51ef6413c4f4cba5de83' '7559dafb754ed45210234490c2447ce61a54cd242b8c24e76fb7e1c6f5313792b33ef75bbc85b3f4302000023b3' '18c91dcfd4b967b205dc938f9f5e2fd5114256caacfb8f6dd13db330010069463044022056588e67e0971ab42d6' '432d0b758e28247393e0dcfddec6bdb07805655d9948f0220141cc506e7e0c95d672e476498cd0eacd7f62737fe' '16f475791eaaa372094e9e21038f962b56731fdb26740e04830b63ae5a39e392fd821beef0e99f6d9ae401f2010' '000006401001976a914d937d2c33f04ee680c996ebbc80af79330c4071288ac0000000181001976a914d937d2c3' '3f04ee680c996ebbc80af79330c4071288ac0000000800001976a914ed9c36b495444302885969447f0fae5e256' '08ef288ac40311513e4fef9d161087be202000023b318c91dcfd4b967b205dc938f9f5e2fd5114256caacfb8f6d' 'd13db3300038c3d3b69ce90bb88c0c4d6a87b9f0c349e5b10c9b7ce6714f996e512ac16400021261' ) tx_parent1 = Transaction.create_from_struct( bytes.fromhex(tx_parent1_hex), self.manager.tx_storage) self.manager.tx_storage.save_transaction(tx_parent1) tx_parent2_hex = ( '000201040000476810205cb3625d62897fcdad620e01d66649869329640f5504d77e960d01006a473045022100c' 'e2ce57330c77b5599e2d044686338a1d55faca50d3436359a60be81654db2d00220574e78eebf7c97f57cde9468' '323aacc2f0abeadc84f69ca6fa2485b99eac3ac62102b4efd2d336030d430b37c3b287ae6de6c2bd5aced0d5e03' '653de6e33c18e4ebe0000006401001976a91481ec322ae3282046d833013529da8d5dcbfb30bf88ac0000000181' '001976a91481ec322ae3282046d833013529da8d5dcbfb30bf88ac0000000281001976a91481ec322ae3282046d' '833013529da8d5dcbfb30bf88ac0000000900001976a914fe96dc8cd6ed2e8fda3ce6fe12e4714195c215b888ac' '010757617420776174035741544030e34594da5bdd6108740d020038c3d3b69ce90bb88c0c4d6a87b9f0c349e5b' '10c9b7ce6714f996e512ac1640000476810205cb3625d62897fcdad620e01d66649869329640f5504d77e960d00' '00d810') tx_parent2_bytes = bytes.fromhex(tx_parent2_hex) tx_parent2 = TokenCreationTransaction.create_from_struct( tx_parent2_bytes, self.manager.tx_storage) self.manager.tx_storage.save_transaction(tx_parent2) # Both inputs are the same as the last parent, so no need to manually add them # XXX: this is completely dependant on MemoryTokensIndex implementation token_bytes1 = bytes.fromhex( '000023b318c91dcfd4b967b205dc938f9f5e2fd5114256caacfb8f6dd13db330') status = self.manager.tx_storage.indexes.tokens._tokens[token_bytes1] status.name = 'Wat wat' status.symbol = 'WAT' response = yield self.web.get( "transaction", { b'id': b'00005f234469407614bf0abedec8f722bb5e534949ad37650f6077c899741ed7' }) data = response.json_value() self.assertEqual(len(data['tx']['inputs']), 2) self.assertEqual(len(data['tx']['outputs']), 2) self.assertEqual(len(data['tx']['tokens']), 1) # Inputs token data self.assertEqual(data['tx']['inputs'][0]['token_data'], 1) self.assertEqual(data['tx']['inputs'][0]['decoded']['token_data'], 1) self.assertEqual(data['tx']['inputs'][1]['token_data'], 129) self.assertEqual(data['tx']['inputs'][1]['decoded']['token_data'], 129) # Outputs token data self.assertEqual(data['tx']['outputs'][0]['token_data'], 0) self.assertEqual(data['tx']['outputs'][0]['decoded']['token_data'], 0) self.assertEqual(data['tx']['outputs'][1]['token_data'], 129) self.assertEqual(data['tx']['outputs'][1]['decoded']['token_data'], 129)