Esempio n. 1
0
    def test_output_value(self):
        from hathor.transaction.base_transaction import bytes_to_output_value
        # first test using a small output value with 8 bytes. It should fail
        parents = [tx.hash for tx in self.genesis_txs]
        outputs = [TxOutput(1, b'')]
        tx = Transaction(outputs=outputs, parents=parents)
        original_struct = tx.get_struct()
        struct_bytes = tx.get_funds_struct()

        # we'll get the struct without the last output bytes and add it ourselves
        struct_bytes = struct_bytes[:-7]
        # add small value using 8 bytes and expect failure when trying to deserialize
        struct_bytes += (-1).to_bytes(8, byteorder='big', signed=True)
        struct_bytes += int_to_bytes(0, 1)
        struct_bytes += int_to_bytes(0, 2)
        struct_bytes += tx.get_graph_struct()
        struct_bytes += int_to_bytes(tx.nonce, tx.SERIALIZATION_NONCE_SIZE)

        len_difference = len(struct_bytes) - len(original_struct)
        assert len_difference == 4, 'new struct is incorrect, len difference={}'.format(len_difference)

        with self.assertRaises(ValueError):
            Transaction.create_from_struct(struct_bytes)

        # now use 8 bytes and make sure it's working
        outputs = [TxOutput(MAX_OUTPUT_VALUE, b'')]
        tx = Transaction(outputs=outputs, parents=parents)
        tx.update_hash()
        original_struct = tx.get_struct()
        tx2 = Transaction.create_from_struct(original_struct)
        tx2.update_hash()
        assert tx == tx2

        # Validating that all output values must be positive
        value = 1
        address = decode_address('WUDtnw3GYjvUnZmiHAmus6hhs9GoSUSJMG')
        script = P2PKH.create_output_script(address)
        output = TxOutput(value, script)
        output.value = -1
        tx = Transaction(inputs=[], outputs=[output], parents=parents, storage=self.tx_storage)
        with self.assertRaises(InvalidOutputValue):
            tx.resolve()

        # 'Manually resolving', to validate verify method
        tx.hash = bytes.fromhex('012cba011be3c29f1c406f9015e42698b97169dbc6652d1f5e4d5c5e83138858')
        with self.assertRaises(InvalidOutputValue):
            tx.verify()

        # Invalid output value
        invalid_output = bytes.fromhex('ffffffff')
        with self.assertRaises(InvalidOutputValue):
            bytes_to_output_value(invalid_output)

        # Can't instantiate an output with negative value
        with self.assertRaises(AssertionError):
            TxOutput(-1, script)
Esempio n. 2
0
    def render_POST(self, request):
        """ Creates a nano contract tx and returns it in hexadecimal format.

        Post data should be a json with the following items:
        values: List[{'address', 'value'}], with bet address and value
        fallback_address: if none of the addresses above is the winner, this address
                          can execute the contract
        oracle_pubkey_hash: oracle's public key hashed
        oracle_data_id: oracle's id about this nano contract
        total_value: nano contract total value
        input_value: amount this wallet should stake in the nano contract

        :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'POST')

        try:
            data = json.loads(request.content.read().decode('utf-8'))
        except json.JSONDecodeError:
            return json.dumps({'success': False, 'message': 'Invalid format for post data'}).encode('utf-8')

        for param in PARAMS_POST:
            if param not in data:
                return get_missing_params_msg(param)

        try:
            decoded_params = self.decode_post_params(data)
        except ValueError as e:
            return json.dumps({'success': False, 'message': e.message}).encode('utf-8')

        nano_contract = NanoContractMatchValues(
            decoded_params.oracle_pubkey_hash, decoded_params.min_timestamp, decoded_params.oracle_data_id,
            decoded_params.value_dict, decoded_params.fallback_address
        )

        tx_outputs = []
        tx_outputs.append(TxOutput(decoded_params.total_value, nano_contract.create_output_script()))

        inputs, total_inputs_amount = self.manager.wallet.get_inputs_from_amount(
            decoded_params.input_value,
            self.manager.tx_storage
        )
        change_tx = self.manager.wallet.handle_change_tx(total_inputs_amount, decoded_params.input_value)
        if change_tx:
            tx_outputs.append(TxOutput(change_tx.value, P2PKH.create_output_script(change_tx.address)))
        tx_inputs = [TxInput(txin.tx_id, txin.index, b'') for txin in inputs]

        tx = Transaction(inputs=tx_inputs, outputs=tx_outputs)

        ret = {'success': True, 'hex_tx': tx.get_struct().hex()}
        return json.dumps(ret).encode('utf-8')
Esempio n. 3
0
    def test_get(self):
        # Mining new block
        response_mining = yield self.web_mining.get('mining')
        data_mining = response_mining.json_value()
        block_bytes = resolve_block_bytes(
            block_bytes=data_mining['block_bytes'])
        yield self.web_mining.post(
            'mining',
            {'block_bytes': base64.b64encode(block_bytes).decode('utf-8')})

        # Unlocking wallet
        self.manager.wallet.unlock(b'MYPASS')

        # Creating a valid transaction to be pushed to the network
        blocks = add_new_blocks(self.manager, 3, advance_clock=2)
        add_blocks_unlock_reward(self.manager)
        tx_id = blocks[0].hash
        output = blocks[0].outputs[0]
        script_type_out = parse_address_script(output.script)
        address = script_type_out.address
        private_key = self.manager.wallet.get_private_key(address)

        output_address = decode_address(self.get_address(0))
        value = self.manager.get_tokens_issued_per_block(1)
        o = TxOutput(value, create_output_script(output_address, None))
        i = TxInput(tx_id, 0, b'')
        tx = Transaction(inputs=[i], outputs=[o])

        data_to_sign = tx.get_sighash_all()
        public_key_bytes, signature_bytes = self.manager.wallet.get_input_aux_data(
            data_to_sign, private_key)
        i.data = P2PKH.create_input_data(public_key_bytes, signature_bytes)
        tx.inputs = [i]
        tx.timestamp = int(self.clock.seconds())
        tx.weight = self.manager.minimum_tx_weight(tx)
        tx.parents = self.manager.get_new_tx_parents(tx.timestamp)
        tx.resolve()

        response = yield self.web.get(
            'push_tx', {b'hex_tx': bytes(tx.get_struct().hex(), 'utf-8')})
        data = response.json_value()
        self.assertTrue(data['success'])

        # Sending token to random address without input
        data_json = {
            'outputs': [{
                'address': self.get_address(0),
                'value': 5
            }],
            'inputs': []
        }
        yield self.web_tokens.post('wallet/send_tokens', {'data': data_json})

        # modify tx so it will be a double spending, then rejected
        tx.weight += 0.1
        tx.resolve()
        response_success = yield self.web.get(
            'push_tx', {b'hex_tx': bytes(tx.get_struct().hex(), 'utf-8')})
        data_success = response_success.json_value()
        self.assertFalse(data_success['success'])

        # Invalid tx (don't have inputs)
        genesis_tx = get_genesis_transactions(self.manager.tx_storage)[1]
        response_genesis = yield self.web.get(
            'push_tx',
            {b'hex_tx': bytes(genesis_tx.get_struct().hex(), 'utf-8')})
        data_genesis = response_genesis.json_value()
        self.assertFalse(data_genesis['success'])

        # Invalid hex
        response_error1 = yield self.web.get('push_tx', {b'hex_tx': b'XXXX'})
        data_error1 = response_error1.json_value()

        self.assertFalse(data_error1['success'])

        # Invalid tx hex
        response_error2 = yield self.web.get('push_tx', {b'hex_tx': b'a12c'})
        data_error2 = response_error2.json_value()

        self.assertFalse(data_error2['success'])

        # Token creation tx
        tx2 = create_tokens(self.manager,
                            address,
                            mint_amount=100,
                            propagate=False)
        response = yield self.web.get(
            'push_tx', {b'hex_tx': bytes(tx2.get_struct().hex(), 'utf-8')})
        data = response.json_value()
        self.assertTrue(data['success'])
Esempio n. 4
0
    def _run_push_tx_test(self, is_post: bool) -> Generator:
        # Mining new block
        response_mining = yield self.web_mining.get('mining')
        data_mining = response_mining.json_value()
        block_bytes = resolve_block_bytes(block_bytes=data_mining['block_bytes'])
        yield self.web_mining.post('mining', {'block_bytes': base64.b64encode(block_bytes).decode('utf-8')})

        # Unlocking wallet
        self.manager.wallet.unlock(b'MYPASS')

        # Creating a valid transaction to be pushed to the network
        blocks = add_new_blocks(self.manager, 3, advance_clock=2)
        add_blocks_unlock_reward(self.manager)
        tx_id = blocks[0].hash
        output = blocks[0].outputs[0]
        script_type_out = parse_address_script(output.script)
        assert script_type_out is not None
        address = script_type_out.address
        private_key = self.manager.wallet.get_private_key(address)

        script_out_addr = self.get_address(0)
        assert script_out_addr is not None
        output_address = decode_address(script_out_addr)
        value = self.manager.get_tokens_issued_per_block(1)
        o = TxOutput(value, create_output_script(output_address, None))
        i = TxInput(tx_id, 0, b'')
        tx = Transaction(inputs=[i], outputs=[o])

        data_to_sign = tx.get_sighash_all()
        public_key_bytes, signature_bytes = self.manager.wallet.get_input_aux_data(data_to_sign, private_key)
        i.data = P2PKH.create_input_data(public_key_bytes, signature_bytes)
        tx.inputs = [i]
        tx.timestamp = int(self.clock.seconds())
        tx.weight = self.manager.minimum_tx_weight(tx)
        tx.parents = self.manager.get_new_tx_parents(tx.timestamp)
        tx.resolve()

        push_tx_fn = self.web.post if is_post else self.web.get
        hex_param = 'hex_tx' if is_post else b'hex_tx'
        force_param = 'force' if is_post else b'force'

        tx_hex = tx.get_struct().hex()
        hex_data = tx_hex if is_post else bytes(tx_hex, 'utf-8')

        response = yield push_tx_fn('push_tx', {hex_param: hex_data})
        data = response.json_value()
        self.assertTrue(data['success'])

        # Sending token to random address without input
        data_json = {'outputs': [{'address': self.get_address(0), 'value': 5}], 'inputs': []}
        yield self.web_tokens.post('wallet/send_tokens', {'data': data_json})

        # modify tx so it will be a double spending, then rejected
        tx.weight += 0.1
        tx.resolve()

        tx_hex = tx.get_struct().hex()
        hex_data = tx_hex if is_post else bytes(tx_hex, 'utf-8')
        response_success = yield push_tx_fn('push_tx', {hex_param: hex_data})
        data_success = response_success.json_value()
        self.assertFalse(data_success['success'])

        # invalid transaction, without forcing
        tx.timestamp = 5
        tx.inputs = [TxInput(blocks[1].hash, 0, b'')]
        script_type_out = parse_address_script(blocks[1].outputs[0].script)
        assert script_type_out is not None
        private_key = self.manager.wallet.get_private_key(script_type_out.address)
        data_to_sign = tx.get_sighash_all()
        public_key_bytes, signature_bytes = self.manager.wallet.get_input_aux_data(data_to_sign, private_key)
        tx.inputs[0].data = P2PKH.create_input_data(public_key_bytes, signature_bytes)

        tx_hex = tx.get_struct().hex()
        hex_data = tx_hex if is_post else bytes(tx_hex, 'utf-8')
        response = yield push_tx_fn('push_tx', {hex_param: hex_data})
        data = response.json_value()
        self.assertFalse(data['success'])

        # force
        tx_hex = tx.get_struct().hex()
        hex_data = tx_hex if is_post else bytes(tx_hex, 'utf-8')
        response = yield push_tx_fn('push_tx', {hex_param: hex_data, force_param: True if is_post else b'true'})
        data = response.json_value()
        self.assertFalse(data['success'])

        # Invalid tx (don't have inputs)
        genesis_tx = next(x for x in self.manager.tx_storage.get_all_genesis() if x.is_transaction)
        genesis_hex = genesis_tx.get_struct().hex()
        hex_data = genesis_hex if is_post else bytes(genesis_hex, 'utf-8')
        response_genesis = yield push_tx_fn('push_tx', {hex_param: hex_data})
        data_genesis = response_genesis.json_value()
        self.assertFalse(data_genesis['success'])

        # Invalid tx hex
        invalid_hex_data = 'a12c' if is_post else b'a12c'
        response_error2 = yield push_tx_fn('push_tx', {hex_param: invalid_hex_data})
        data_error2 = response_error2.json_value()
        self.assertFalse(data_error2['success'])

        # Token creation tx
        tx2 = create_tokens(self.manager, address, mint_amount=100, propagate=False)
        tx2_hex = tx2.get_struct().hex()
        hex_data = tx2_hex if is_post else bytes(tx2_hex, 'utf-8')
        response = yield push_tx_fn('push_tx', {hex_param: hex_data})
        data = response.json_value()
        self.assertTrue(data['success'])
Esempio n. 5
0
    def render_POST(self, request):
        """ Creates and propagates a tx to spend a nano contract output.

        Post data should be a json with the following items:
        spent_tx_id: tx id being spent
        spent_tx_index: tx index being spent
        oracle_data: the data provided by the oracle
        oracle_signature: signature of the oracle data
        oracle_pubkey: oracle's public key
        address: the winning address
        value: nano contract total value

        :rtype: string (json)
        """
        request.setHeader(b'content-type', b'application/json; charset=utf-8')
        set_cors(request, 'POST')

        content = request.content.read()
        if not content:
            return json.dumps({
                'success': False,
                'message': 'No post data received'
            }).encode('utf-8')

        try:
            data = json.loads(content.decode('utf-8'))
        except json.JSONDecodeError:
            return json.dumps({
                'success': False,
                'message': 'Invalid format for post data'
            }).encode('utf-8')

        for param in PARAMS:
            if param not in data:
                return get_missing_params_msg(param)

        try:
            decoded_data = self.decode_params(data)
        except ValueError as e:
            return json.dumps({
                'success': False,
                'message': e.message
            }).encode('utf-8')

        tx_outputs = []
        tx_outputs.append(
            TxOutput(decoded_data.value,
                     P2PKH.create_output_script(decoded_data.address)))

        input_data = NanoContractMatchValues.create_input_data(
            decoded_data.oracle_data, decoded_data.oracle_signature,
            decoded_data.oracle_pubkey)
        tx_input = TxInput(decoded_data.spent_tx_id,
                           decoded_data.spent_tx_index, input_data)
        tx = Transaction(inputs=[tx_input], outputs=tx_outputs)
        tx.storage = self.manager.tx_storage

        tx.parents = self.manager.get_new_tx_parents()
        tx.update_timestamp(int(self.manager.reactor.seconds()))
        tx.weight = self.manager.minimum_tx_weight(tx)
        tx.resolve()
        success = self.manager.propagate_tx(tx)

        ret = {'success': success, 'hex_tx': tx.get_struct().hex()}
        return json.dumps(ret).encode('utf-8')
Esempio n. 6
0
    def test_post(self):
        # Unlocking wallet
        self.manager.wallet.unlock(b'MYPASS')

        blocks = add_new_blocks(self.manager, 3, advance_clock=1)
        add_blocks_unlock_reward(self.manager)
        blocks_tokens = [
            sum(txout.value for txout in blk.outputs) for blk in blocks
        ]

        self.assertEqual(
            self.manager.wallet.balance[settings.HATHOR_TOKEN_UID].available,
            sum(blocks_tokens))

        # Options
        yield self.web.options('thin_wallet/send_tokens')

        tx_id = blocks[0].hash
        output = blocks[0].outputs[0]
        script_type_out = parse_address_script(output.script)
        address = script_type_out.address
        private_key = self.manager.wallet.get_private_key(address)

        output_address = decode_address(self.get_address(0))
        value = blocks_tokens[0]
        o = TxOutput(value, create_output_script(output_address, None))
        o_invalid_amount = TxOutput(value - 1,
                                    create_output_script(output_address, None))
        i = TxInput(tx_id, 0, b'')

        # wrong weight
        tx = Transaction(inputs=[i], outputs=[o])

        data_to_sign = tx.get_sighash_all()
        public_key_bytes, signature_bytes = self.manager.wallet.get_input_aux_data(
            data_to_sign, private_key)

        i.data = P2PKH.create_input_data(public_key_bytes, signature_bytes)
        tx.inputs = [i]
        tx.timestamp = int(self.clock.seconds())
        tx.weight = 0

        response = yield self.web.post('thin_wallet/send_tokens',
                                       {'tx_hex': tx.get_struct().hex()})
        data = response.json_value()
        self.assertFalse(data['success'])

        # Error wrong amount
        tx2 = Transaction(inputs=[i], outputs=[o_invalid_amount])

        data_to_sign = tx2.get_sighash_all()
        public_key_bytes, signature_bytes = self.manager.wallet.get_input_aux_data(
            data_to_sign, private_key)

        i.data = P2PKH.create_input_data(public_key_bytes, signature_bytes)
        tx2.inputs = [i]
        tx2.timestamp = int(self.clock.seconds())
        tx2.weight = self.manager.minimum_tx_weight(tx2)

        response_wrong_amount = yield self.web.post(
            'thin_wallet/send_tokens', {'tx_hex': tx2.get_struct().hex()})
        data_wrong_amount = response_wrong_amount.json_value()
        self.assertFalse(data_wrong_amount['success'])

        # successful tx
        tx3 = Transaction(inputs=[i], outputs=[o])

        data_to_sign = tx3.get_sighash_all()
        public_key_bytes, signature_bytes = self.manager.wallet.get_input_aux_data(
            data_to_sign, private_key)

        i.data = P2PKH.create_input_data(public_key_bytes, signature_bytes)
        tx3.inputs = [i]
        tx3.timestamp = int(self.clock.seconds())
        tx3.weight = self.manager.minimum_tx_weight(tx3)

        # Then send tokens
        response = yield self.web.post('thin_wallet/send_tokens',
                                       {'tx_hex': tx3.get_struct().hex()})
        data = response.json_value()
        self.assertTrue(data['success'])

        # Trying to send a double spending will not have success
        self.clock.advance(5)
        tx3.timestamp = int(self.clock.seconds())
        response = yield self.web.post('thin_wallet/send_tokens',
                                       {'tx_hex': tx3.get_struct().hex()})
        data_error = response.json_value()
        self.assertFalse(data_error['success'])
        self.clock.advance(5)

        # Check if tokens were really sent
        self.assertEqual(
            self.manager.wallet.balance[settings.HATHOR_TOKEN_UID].available,
            sum(blocks_tokens[:-1]))

        response_history = yield self.web_address_history.get(
            'thin_wallet/address_history', {
                b'addresses[]': address.encode(),
            })

        response_data = response_history.json_value()['history']
        self.assertIn(data['tx']['hash'], [x['tx_id'] for x in response_data])

        # Create token tx
        tx4 = create_tokens(self.manager,
                            address,
                            mint_amount=100,
                            propagate=False)
        tx4.nonce = 0
        tx4.timestamp = int(self.clock.seconds())
        response = yield self.web.post('thin_wallet/send_tokens',
                                       {'tx_hex': tx4.get_struct().hex()})
        data = response.json_value()
        self.assertTrue(data['success'])
Esempio n. 7
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())