Example #1
0
    def check_authorities_and_deposit(
            self, token_dict: Dict[bytes, TokenInfo]) -> None:
        """Verify that the sum of outputs is equal of the sum of inputs, for each token. If sum of inputs
        and outputs is not 0, make sure inputs have mint/melt authority.

        token_dict sums up all tokens present in the tx and their properties (amount, can_mint, can_melt)
        amount = outputs - inputs, thus:
        - amount < 0 when melting
        - amount > 0 when minting

        :raises InputOutputMismatch: if sum of inputs is not equal to outputs and there's no mint/melt
        """
        withdraw = 0
        deposit = 0
        for token_uid, token_info in token_dict.items():
            if token_uid == settings.HATHOR_TOKEN_UID:
                continue

            if token_info.amount == 0:
                # that's the usual behavior, nothing to do
                pass
            elif token_info.amount < 0:
                # tokens have been melted
                if not token_info.can_melt:
                    raise InputOutputMismatch(
                        '{} {} tokens melted, but there is no melt authority input'
                        .format(token_info.amount, token_uid.hex()))
                withdraw += get_withdraw_amount(token_info.amount)
            else:
                # tokens have been minted
                if not token_info.can_mint:
                    raise InputOutputMismatch(
                        '{} {} tokens minted, but there is no mint authority input'
                        .format((-1) * token_info.amount, token_uid.hex()))
                deposit += get_deposit_amount(token_info.amount)

        # check whether the deposit/withdraw amount is correct
        htr_expected_amount = withdraw - deposit
        htr_info = token_dict[settings.HATHOR_TOKEN_UID]
        if htr_info.amount != htr_expected_amount:
            raise InputOutputMismatch(
                'HTR balance is different than expected. (amount={}, expected={})'
                .format(
                    htr_info.amount,
                    htr_expected_amount,
                ))
Example #2
0
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
Example #3
0
    def test_token_index_with_conflict(self, mint_amount=0):
        # create a new token and have a mint operation done. The tx that mints the
        # tokens has the following outputs:
        # 0. minted tokens
        # 1. mint authority;
        # 2. melt authority
        # 3. HTR deposit change
        tx = create_tokens(self.manager, self.address_b58, mint_amount=100)
        token_uid = tx.tokens[0]
        tokens_index = self.manager.tx_storage.indexes.tokens.get_token_info(
            tx.tokens[0])
        mint = list(tokens_index.iter_mint_utxos())
        melt = list(tokens_index.iter_melt_utxos())
        self.assertIn(TokenUtxoInfo(tx.hash, 1), mint)
        self.assertIn(TokenUtxoInfo(tx.hash, 2), melt)
        # there should only be one element on the indexes for the token
        self.assertEqual(1, len(mint))
        self.assertEqual(1, len(melt))
        # check total amount of tokens
        self.assertEqual(100, tokens_index.get_total())

        # new tx minting tokens
        mint_amount = 300
        deposit_amount = get_deposit_amount(mint_amount)
        script = P2PKH.create_output_script(self.address)
        # inputs
        mint_input = TxInput(tx.hash, 1, b'')
        melt_input = TxInput(tx.hash, 2, b'')
        deposit_input = TxInput(tx.hash, 3, b'')
        # outputs
        mint_output = TxOutput(mint_amount, script, 1)
        authority_output1 = TxOutput(TxOutput.TOKEN_MINT_MASK, script,
                                     0b10000001)
        authority_output2 = TxOutput(TxOutput.TOKEN_MELT_MASK, script,
                                     0b10000001)
        deposit_output = TxOutput(tx.outputs[3].value - deposit_amount, script,
                                  0)
        tx2 = Transaction(weight=1,
                          inputs=[mint_input, melt_input, deposit_input],
                          outputs=[
                              authority_output1, authority_output2,
                              mint_output, deposit_output
                          ],
                          parents=self.manager.get_new_tx_parents(),
                          tokens=[token_uid],
                          storage=self.manager.tx_storage,
                          timestamp=int(self.clock.seconds()))
        # sign inputs
        wallet = self.manager.wallet
        data_to_sign = tx2.get_sighash_all()
        public_bytes, signature = wallet.get_input_aux_data(
            data_to_sign, wallet.get_private_key(self.address_b58))
        data = P2PKH.create_input_data(public_bytes, signature)
        tx2.inputs[0].data = data
        tx2.inputs[1].data = data
        tx2.inputs[2].data = data
        tx2.resolve()
        tx2.verify()
        self.manager.propagate_tx(tx2)
        self.run_to_completion()

        # there should only be one element on the indexes for the token
        tokens_index = self.manager.tx_storage.indexes.tokens.get_token_info(
            tx.tokens[0])
        mint = list(tokens_index.iter_mint_utxos())
        melt = list(tokens_index.iter_melt_utxos())
        self.assertEqual(1, len(mint))
        self.assertEqual(1, len(melt))
        self.assertIn(TokenUtxoInfo(tx2.hash, 0), mint)
        self.assertIn(TokenUtxoInfo(tx2.hash, 1), melt)
        # check total amount of tokens has been updated
        self.assertEqual(400, tokens_index.get_total())

        # create conflicting tx by changing parents
        tx3 = Transaction.create_from_struct(tx2.get_struct())
        tx3.parents = [tx.parents[1], tx.parents[0]]
        tx3.weight = 3
        tx3.resolve()
        self.assertNotEqual(tx3.hash, tx2.hash)
        self.assertTrue(tx3.weight > tx2.weight)
        self.manager.propagate_tx(tx3)
        self.run_to_completion()

        # new tx should be on tokens index. Old tx should not be present
        tokens_index = self.manager.tx_storage.indexes.tokens.get_token_info(
            tx.tokens[0])
        mint = list(tokens_index.iter_mint_utxos())
        melt = list(tokens_index.iter_melt_utxos())
        self.assertIn(TokenUtxoInfo(tx3.hash, 0), mint)
        self.assertIn(TokenUtxoInfo(tx3.hash, 1), melt)
        # there should only be one element on the indexes for the token
        self.assertEqual(1, len(mint))
        self.assertEqual(1, len(melt))
        # should have same amount of tokens
        self.assertEqual(400, tokens_index.get_total())
Example #4
0
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
Example #5
0
    def test_token_mint(self):
        wallet = self.manager.wallet
        tx = create_tokens(self.manager, self.address_b58, mint_amount=500)
        token_uid = tx.tokens[0]
        parents = self.manager.get_new_tx_parents()
        script = P2PKH.create_output_script(self.address)

        # mint tokens and transfer mint authority
        mint_amount = 10000000
        deposit_amount = get_deposit_amount(mint_amount)
        _input1 = TxInput(tx.hash, 1, b'')
        _input2 = TxInput(tx.hash, 3, b'')
        token_output1 = TxOutput(mint_amount, script, 1)
        token_output2 = TxOutput(TxOutput.TOKEN_MINT_MASK, script, 0b10000001)
        deposit_output = TxOutput(tx.outputs[3].value - deposit_amount, script,
                                  0)
        tx2 = Transaction(
            weight=1,
            inputs=[_input1, _input2],
            outputs=[token_output1, token_output2, deposit_output],
            parents=parents,
            tokens=[token_uid],
            storage=self.manager.tx_storage,
            timestamp=int(self.clock.seconds()))
        data_to_sign = tx2.get_sighash_all()
        public_bytes, signature = wallet.get_input_aux_data(
            data_to_sign, wallet.get_private_key(self.address_b58))
        data = P2PKH.create_input_data(public_bytes, signature)
        tx2.inputs[0].data = data
        tx2.inputs[1].data = data
        tx2.resolve()
        tx2.verify()
        self.manager.propagate_tx(tx2)
        self.run_to_completion()

        # check tokens index
        tokens_index = self.manager.tx_storage.indexes.tokens.get_token_info(
            token_uid)
        mint = list(tokens_index.iter_mint_utxos())
        melt = list(tokens_index.iter_melt_utxos())
        self.assertIn(TokenUtxoInfo(tx2.hash, 1), mint)
        self.assertIn(TokenUtxoInfo(tx.hash, 2), melt)
        # there should only be one element on the indexes for the token
        self.assertEqual(1, len(mint))
        self.assertEqual(1, len(melt))
        # check total amount of tokens
        self.assertEqual(500 + mint_amount, tokens_index.get_total())

        # try to mint 1 token unit without deposit
        mint_amount = 1
        _input1 = TxInput(tx.hash, 1, b'')
        token_output1 = TxOutput(mint_amount, script, 1)
        token_output2 = TxOutput(TxOutput.TOKEN_MINT_MASK, script, 0b10000001)
        tx3 = Transaction(weight=1,
                          inputs=[_input1],
                          outputs=[token_output1, token_output2],
                          parents=parents,
                          tokens=[token_uid],
                          storage=self.manager.tx_storage,
                          timestamp=int(self.clock.seconds()))
        data_to_sign = tx3.get_sighash_all()
        public_bytes, signature = wallet.get_input_aux_data(
            data_to_sign, wallet.get_private_key(self.address_b58))
        data = P2PKH.create_input_data(public_bytes, signature)
        tx3.inputs[0].data = data
        tx3.resolve()
        with self.assertRaises(InputOutputMismatch):
            tx3.verify()

        # try to mint and deposit less tokens than necessary
        mint_amount = 10000000
        deposit_amount = get_deposit_amount(mint_amount) - 1
        _input1 = TxInput(tx.hash, 1, b'')
        _input2 = TxInput(tx.hash, 3, b'')
        token_output1 = TxOutput(mint_amount, script, 1)
        token_output2 = TxOutput(TxOutput.TOKEN_MINT_MASK, script, 0b10000001)
        deposit_output = TxOutput(tx.outputs[3].value - deposit_amount, script,
                                  0)
        tx4 = Transaction(
            weight=1,
            inputs=[_input1, _input2],
            outputs=[token_output1, token_output2, deposit_output],
            parents=parents,
            tokens=[token_uid],
            storage=self.manager.tx_storage,
            timestamp=int(self.clock.seconds()))
        data_to_sign = tx4.get_sighash_all()
        public_bytes, signature = wallet.get_input_aux_data(
            data_to_sign, wallet.get_private_key(self.address_b58))
        data = P2PKH.create_input_data(public_bytes, signature)
        tx4.inputs[0].data = data
        tx4.inputs[1].data = data
        tx4.resolve()
        with self.assertRaises(InputOutputMismatch):
            tx4.verify()

        # try to mint using melt authority UTXO
        _input1 = TxInput(tx.hash, 2, b'')
        token_output = TxOutput(10000000, script, 1)
        tx5 = Transaction(weight=1,
                          inputs=[_input1],
                          outputs=[token_output],
                          parents=parents,
                          tokens=[token_uid],
                          storage=self.manager.tx_storage,
                          timestamp=int(self.clock.seconds()))
        data_to_sign = tx5.get_sighash_all()
        public_bytes, signature = wallet.get_input_aux_data(
            data_to_sign, wallet.get_private_key(self.address_b58))
        tx5.inputs[0].data = P2PKH.create_input_data(public_bytes, signature)
        tx5.resolve()
        with self.assertRaises(InputOutputMismatch):
            tx5.verify()