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)
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')
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'])
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'])
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')
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'])
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())