def test_tokens_in_block(self): # a block with token index > 1 should be invalid parents = [tx.hash for tx in self.genesis] output_script = P2PKH.create_output_script(self.address) tx_outputs = [TxOutput(100, output_script, 1)] block = Block( nonce=100, outputs=tx_outputs, parents=parents, weight=1, # low weight so we don't waste time with PoW storage=self.manager.tx_storage) block.resolve() with self.assertRaises(BlockWithTokensError): block.verify()
def test_tx_propagate_multiple_inputs(self): self.manager.test_mode = 0 # disable test_mode so the weight is not 1 output_address = 'HNXsVtRUmwDCtpcCJUrH4QiHo9kUKx199A' resp = (yield self.web.post('create_tx', { 'inputs': [ { 'tx_id': self.unspent_tx.hash_hex, 'index': 1, }, { 'tx_id': self.unspent_tx2.hash_hex, 'index': 1, }, { 'tx_id': self.unspent_tx3.hash_hex, 'index': 1, }, ], 'outputs': [ { 'address': output_address, 'value': 600, }, ] })).json_value() self.assertEqual(resp['success'], True) data = resp['data'] hex_data = resp['hex_data'] struct_bytes = bytes.fromhex(hex_data) orig_tx = Transaction.create_from_struct(struct_bytes) tx = orig_tx.clone() tx_data = tx.to_json() del tx_data['hash'] del tx_data['nonce'] self.assertEqual(data, tx_data) data_to_sign = tx.get_sighash_all() private_key = self.manager.wallet.get_private_key(self.unspent_address) public_key_bytes, signature_bytes = self.manager.wallet.get_input_aux_data(data_to_sign, private_key) input_data = P2PKH.create_input_data(public_key_bytes, signature_bytes) tx.inputs[0].data = input_data tx.inputs[1].data = input_data tx.inputs[2].data = input_data # XXX: tx.resolve is a bit CPU intensive, but not so much as to make this test disabled by default tx.resolve(False) self.assertTrue(self.manager.propagate_tx(tx))
def test_block_unknown_parent(self): address = get_address_from_public_key(self.genesis_public_key) output_script = P2PKH.create_output_script(address) tx_outputs = [TxOutput(100, output_script)] # Random unknown parent parents = [hashlib.sha256().digest()] block = Block( nonce=100, outputs=tx_outputs, parents=parents, weight=1, # low weight so we don't waste time with PoW storage=self.tx_storage) block.resolve() with self.assertRaises(ParentDoesNotExist): block.verify()
def test_block_outputs(self): from hathor.transaction import MAX_NUM_OUTPUTS from hathor.transaction.exceptions import TooManyOutputs # a block should have no more than MAX_NUM_OUTPUTS outputs parents = [tx.hash for tx in self.genesis] address = get_address_from_public_key(self.genesis_public_key) output_script = P2PKH.create_output_script(address) tx_outputs = [TxOutput(100, output_script)] * (MAX_NUM_OUTPUTS + 1) block = Block( nonce=100, outputs=tx_outputs, parents=parents, weight=1, # low weight so we don't waste time with PoW storage=self.tx_storage) with self.assertRaises(TooManyOutputs): block.verify_outputs()
def test_get_sigops_count(self): multisig_script = MultiSig.create_output_script(BURN_ADDRESS) p2pkh_script = P2PKH.create_output_script(BURN_ADDRESS) redeem_script = HathorScript() redeem_script.addOpcode(Opcode.OP_9) redeem_script.addOpcode(Opcode.OP_CHECKMULTISIG) input_script = HathorScript() input_script.pushData(BURN_ADDRESS) input_script.addOpcode(Opcode.OP_CHECKSIG) input_script.pushData(redeem_script.data) # include redeem_script if output is MultiSig self.assertEqual(get_sigops_count(input_script.data, multisig_script), 10) # if output is not MultiSig, count only input self.assertEqual(get_sigops_count(input_script.data, p2pkh_script), 1) # if no output_script, count only input self.assertEqual(get_sigops_count(input_script.data), 1)
def sign_transaction(self, tx: Transaction, tx_storage: 'TransactionStorage') -> None: """Signs a transaction. Iterates over all inputs and signs the ones belonging to this wallet. :param tx: transaction to sign :type tx: py:class:`hathor.transaction.Transaction` :param tx_storage: storage to search for output tx :type tx_storage: TransactionStorage :return: there's no return. This function modifies the tx given to it :rtype: None """ data_to_sign = tx.get_sighash_all(clear_input_data=True) for _input, address58 in self.match_inputs(tx.inputs, tx_storage): if address58: public_key_bytes, signature = self.get_input_aux_data( data_to_sign, self.get_private_key(address58)) _input.data = P2PKH.create_input_data(public_key_bytes, signature)
def test_update_timestamp(self): parents = [tx for tx in self.genesis_txs] genesis_block = self.genesis_blocks[0] value = genesis_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) # update based on input _input = TxInput(genesis_block.hash, 0, b'') tx = Transaction(weight=1, inputs=[_input], outputs=[output], parents=[p.hash for p in parents], storage=self.tx_storage) input_timestamp = genesis_block.timestamp max_ts = max(input_timestamp, parents[0].timestamp, parents[1].timestamp) tx.update_timestamp(0) self.assertEquals(tx.timestamp, max_ts + 1) ts = max_ts + 20 tx.update_timestamp(ts) self.assertEquals(tx.timestamp, ts)
def test_block_inputs(self): # a block with inputs should be invalid parents = [tx.hash for tx in self.genesis] genesis_block = self.genesis_blocks[0] tx_inputs = [TxInput(genesis_block.hash, 0, b'')] address = get_address_from_public_key(self.genesis_public_key) output_script = P2PKH.create_output_script(address) tx_outputs = [TxOutput(100, output_script)] block = Block( nonce=100, outputs=tx_outputs, parents=parents, weight=1, # low weight so we don't waste time with PoW storage=self.tx_storage) block.inputs = tx_inputs block.resolve() with self.assertRaises(BlockWithInputs): block.verify()
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 random_bytes = bytes.fromhex( '0000184e64683b966b4268f387c269915cc61f6af5329823a93e3696cb0fe902') _input = TxInput(random_bytes, 0, random_bytes) tx = Transaction(inputs=[_input], 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 test_token_melt(self): wallet = self.manager.wallet tx = create_tokens(self.manager, self.address_b58) token_uid = tx.tokens[0] parents = self.manager.get_new_tx_parents() script = P2PKH.create_output_script(self.address) # melt tokens and transfer melt authority melt_amount = 100 new_amount = tx.outputs[0].value - melt_amount withdraw_amount = get_withdraw_amount(melt_amount) _input1 = TxInput(tx.hash, 0, b'') _input2 = TxInput(tx.hash, 2, b'') token_output1 = TxOutput(new_amount, script, 1) token_output2 = TxOutput(TxOutput.TOKEN_MELT_MASK, script, 0b10000001) withdraw_output = TxOutput(withdraw_amount, script, 0) tx2 = Transaction( weight=1, inputs=[_input1, _input2], outputs=[token_output1, token_output2, withdraw_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(tx.hash, 1), mint) self.assertIn(TokenUtxoInfo(tx2.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)) # check total amount of tokens self.assertEqual(new_amount, tokens_index.get_total()) # melt tokens and withdraw more than what's allowed melt_amount = 100 withdraw_amount = get_withdraw_amount(melt_amount) _input1 = TxInput(tx.hash, 0, b'') _input2 = TxInput(tx.hash, 2, b'') token_output1 = TxOutput(tx.outputs[0].value - melt_amount, script, 1) token_output2 = TxOutput(TxOutput.TOKEN_MELT_MASK, script, 0b10000001) withdraw_output = TxOutput(withdraw_amount + 1, script, 0) tx3 = Transaction( weight=1, inputs=[_input1, _input2], outputs=[token_output1, token_output2, withdraw_output], 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.inputs[1].data = data tx3.resolve() with self.assertRaises(InputOutputMismatch): tx3.verify() # try to melt using mint authority UTXO _input1 = TxInput(tx.hash, 0, b'') _input2 = TxInput(tx.hash, 1, b'') token_output = TxOutput(tx.outputs[0].value - 1, script, 1) tx4 = Transaction(weight=1, inputs=[_input1, _input2], outputs=[token_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()
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 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 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_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.tokens_index.tokens[token_uid] self.assertIn((tx2.hash, 1), tokens_index.mint) self.assertIn((tx.hash, 2), tokens_index.melt) # there should only be one element on the indexes for the token self.assertEqual(1, len(tokens_index.mint)) self.assertEqual(1, len(tokens_index.melt)) # check total amount of tokens self.assertEqual(500 + mint_amount, tokens_index.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()
def test_find_p2pkh(self): with self.assertRaises(MissingStackItems): op_find_p2pkh([], log=[], extras=None) addr1 = '15d14K5jMqsN2uwUEFqiPG5SoD7Vr1BfnH' addr2 = '1K35zJQeYrVzQAW7X3s7vbPKmngj5JXTBc' addr3 = '1MnHN3D41yaMN5WLLKPARRdF77USvPLDfy' import base58 out1 = P2PKH.create_output_script(base58.b58decode(addr1)) out2 = P2PKH.create_output_script(base58.b58decode(addr2)) out3 = P2PKH.create_output_script(base58.b58decode(addr3)) # read genesis keys genesis_address = get_address_from_public_key(self.genesis_public_key) out_genesis = P2PKH.create_output_script(genesis_address) from hathor.transaction import Transaction, TxInput, TxOutput spent_tx = Transaction(outputs=[TxOutput(1, b'nano_contract_code')]) txin = TxInput(b'dont_care', 0, b'data') # try with just 1 output stack = [genesis_address] tx = Transaction(outputs=[TxOutput(1, out_genesis)]) extras = ScriptExtras(tx=tx, txin=txin, spent_tx=spent_tx) op_find_p2pkh(stack, log=[], extras=extras) self.assertEqual(stack.pop(), 1) # several outputs and correct output among them stack = [genesis_address] tx = Transaction(outputs=[ TxOutput(1, out1), TxOutput(1, out2), TxOutput(1, out_genesis), TxOutput(1, out3) ]) extras = ScriptExtras(tx=tx, txin=txin, spent_tx=spent_tx) op_find_p2pkh(stack, log=[], extras=extras) self.assertEqual(stack.pop(), 1) # several outputs without correct amount output stack = [genesis_address] tx = Transaction(outputs=[ TxOutput(1, out1), TxOutput(1, out2), TxOutput(2, out_genesis), TxOutput(1, out3) ]) extras = ScriptExtras(tx=tx, txin=txin, spent_tx=spent_tx) with self.assertRaises(VerifyFailed): op_find_p2pkh(stack, log=[], extras=extras) # several outputs without correct address output stack = [genesis_address] tx = Transaction( outputs=[TxOutput(1, out1), TxOutput(1, out2), TxOutput(1, out3)]) extras = ScriptExtras(tx=tx, txin=txin, spent_tx=spent_tx) with self.assertRaises(VerifyFailed): op_find_p2pkh(stack, log=[], extras=extras)
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 setUp(self, tx_storage, reactor=None): from hathor.manager import HathorManager if not reactor: self.reactor = Clock() else: self.reactor = reactor self.reactor.advance(time.time()) self.tx_storage = tx_storage assert tx_storage.first_timestamp > 0 tx_storage._manually_initialize() self.genesis = self.tx_storage.get_all_genesis() self.genesis_blocks = [tx for tx in self.genesis if tx.is_block] self.genesis_txs = [tx for tx in self.genesis if not tx.is_block] self.tmpdir = tempfile.mkdtemp() wallet = Wallet(directory=self.tmpdir) wallet.unlock(b'teste') self.manager = HathorManager(self.reactor, tx_storage=self.tx_storage, wallet=wallet) self.tx_storage.indexes.enable_address_index(self.manager.pubsub) self.tx_storage.indexes.enable_tokens_index() block_parents = [ tx.hash for tx in chain(self.genesis_blocks, self.genesis_txs) ] output = TxOutput(200, P2PKH.create_output_script(BURN_ADDRESS)) self.block = Block(timestamp=MIN_TIMESTAMP, weight=12, outputs=[output], parents=block_parents, nonce=100781, storage=tx_storage) self.block.resolve() self.block.verify() self.block.get_metadata().validation = ValidationState.FULL tx_parents = [tx.hash for tx in self.genesis_txs] tx_input = TxInput( tx_id=self.genesis_blocks[0].hash, index=0, data=bytes.fromhex( '46304402203470cb9818c9eb842b0c433b7e2b8aded0a51f5903e971649e870763d0266a' 'd2022049b48e09e718c4b66a0f3178ef92e4d60ee333d2d0e25af8868acf5acbb35aaa583' '056301006072a8648ce3d020106052b8104000a034200042ce7b94cba00b654d4308f8840' '7345cacb1f1032fb5ac80407b74d56ed82fb36467cb7048f79b90b1cf721de57e942c5748' '620e78362cf2d908e9057ac235a63')) self.tx = Transaction( timestamp=MIN_TIMESTAMP + 2, weight=10, nonce=932049, inputs=[tx_input], outputs=[output], tokens=[ bytes.fromhex( '0023be91834c973d6a6ddd1a0ae411807b7c8ef2a015afb5177ee64b666ce602' ) ], parents=tx_parents, storage=tx_storage) self.tx.resolve() self.tx.get_metadata().validation = ValidationState.FULL # Disable weakref to test the internal methods. Otherwise, most methods return objects from weakref. self.tx_storage._disable_weakref() self.tx_storage.enable_lock()
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_history(self): self.manager.wallet.unlock(b'MYPASS') resource = StubSite(TokenHistoryResource(self.manager)) add_new_blocks(self.manager, 1, advance_clock=1) add_blocks_unlock_reward(self.manager) tx = create_tokens(self.manager, mint_amount=100, token_name='Teste', token_symbol='TST') token_uid = tx.tokens[0] response = yield resource.get('thin_wallet/token_history', { b'id': token_uid.hex().encode(), b'count': 3 }) data = response.json_value() # Success returning the token creation tx self.assertTrue(data['success']) self.assertFalse(data['has_more']) self.assertEqual(1, len(data['transactions'])) self.assertEqual(tx.hash.hex(), data['transactions'][0]['tx_id']) response = yield resource.get('thin_wallet/token_history', { b'id': b'123', b'count': 3 }) data = response.json_value() # Fail because token is unknown self.assertFalse(data['success']) # Create a tx with this token, so we can have more tx in the history output = tx.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)) o = TxOutput(100, create_output_script(output_address, None), 1) i = TxInput(tx.hash, 0, b'') tx2 = Transaction(inputs=[i], outputs=[o], tokens=[token_uid]) 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) tx2.parents = self.manager.get_new_tx_parents() tx2.resolve() self.manager.propagate_tx(tx2) # Now we have 2 txs with this token response = yield resource.get('thin_wallet/token_history', { b'id': token_uid.hex().encode(), b'count': 3 }) data = response.json_value() # Success returning the token creation tx and newly created tx self.assertTrue(data['success']) self.assertFalse(data['has_more']) self.assertEqual(2, len(data['transactions'])) self.assertEqual(tx2.hash.hex(), data['transactions'][0]['tx_id']) self.assertEqual(tx.hash.hex(), data['transactions'][1]['tx_id']) response = yield resource.get('thin_wallet/token_history', { b'id': token_uid.hex().encode(), b'count': 1 }) data = response.json_value() # Testing has_more self.assertTrue(data['success']) self.assertTrue(data['has_more']) self.assertEqual(1, len(data['transactions'])) response = yield resource.get( 'thin_wallet/token_history', { b'id': token_uid.hex().encode(), b'count': 10, b'page': b'next', b'hash': tx2.hash.hex().encode(), b'timestamp': str(tx2.timestamp).encode(), }) data = response.json_value() # Testing next self.assertTrue(data['success']) self.assertFalse(data['has_more']) self.assertEqual(1, len(data['transactions'])) self.assertEqual(tx.hash.hex(), data['transactions'][0]['tx_id']) response = yield resource.get( 'thin_wallet/token_history', { b'id': token_uid.hex().encode(), b'count': 10, b'page': b'previous', b'hash': tx.hash.hex().encode(), b'timestamp': str(tx.timestamp).encode(), }) data = response.json_value() # Testing previous self.assertTrue(data['success']) self.assertFalse(data['has_more']) self.assertEqual(1, len(data['transactions'])) self.assertEqual(tx2.hash.hex(), data['transactions'][0]['tx_id']) response = yield resource.get( 'thin_wallet/token_history', { b'id': token_uid.hex().encode(), b'count': 10, b'page': b'previous', b'hash': tx2.hash.hex().encode(), b'timestamp': str(tx2.timestamp).encode(), }) data = response.json_value() # Testing previous from first self.assertTrue(data['success']) self.assertFalse(data['has_more']) self.assertEqual(0, len(data['transactions']))
def test_push_tx(self) -> Generator: self.manager.wallet.unlock(b'MYPASS') blocks = add_new_blocks(self.manager, 5, advance_clock=15) add_blocks_unlock_reward(self.manager) tx = self.get_tx() tx_hex = tx.get_struct().hex() response = yield self.push_tx({'hex_tx': tx_hex}) 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() response_success = yield self.push_tx({'hex_tx': tx_hex}) 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() response = yield self.push_tx({'hex_tx': tx_hex}) data = response.json_value() self.assertFalse(data['success']) # force tx_hex = tx.get_struct().hex() response = yield self.push_tx({'hex_tx': tx_hex, 'force': 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() response_genesis = yield self.push_tx({'tx_hex': genesis_hex}) data_genesis = response_genesis.json_value() self.assertFalse(data_genesis['success']) # Token creation tx script_type_out = parse_address_script(blocks[0].outputs[0].script) assert script_type_out is not None address = script_type_out.address tx2 = create_tokens(self.manager, address, mint_amount=100, propagate=False) tx2_hex = tx2.get_struct().hex() response = yield self.push_tx({'hex_tx': tx2_hex}) data = response.json_value() self.assertTrue(data['success'])
def test_wallet_index(self): # First transaction: send tokens to output with address=address_b58 parents = [tx.hash for tx in self.genesis_txs] genesis_block = self.genesis_blocks[0] value = genesis_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) address_b58 = parse_address_script(script).address # Get how many transactions wallet index already has for this address wallet_index_count = len( self.tx_storage.wallet_index.index[address_b58]) _input = TxInput(genesis_block.hash, 0, b'') tx = Transaction(weight=1, inputs=[_input], outputs=[output], parents=parents, storage=self.tx_storage, timestamp=self.last_block.timestamp + 1) data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) _input.data = P2PKH.create_input_data(public_bytes, signature) tx.resolve() self.manager.propagate_tx(tx) # This transaction has an output to address_b58, so we need one more element on the index self.assertEqual(len(self.tx_storage.wallet_index.index[address_b58]), wallet_index_count + 1) # Second transaction: spend tokens from output with address=address_b58 and # send tokens to 2 outputs, one with address=address_b58 and another one # with address=new_address_b58, which is an address of a random wallet new_address_b58 = self.get_address(0) new_address = decode_address(new_address_b58) output1 = TxOutput(value - 100, script) script2 = P2PKH.create_output_script(new_address) output2 = TxOutput(100, script2) input1 = TxInput(tx.hash, 0, b'') tx2 = Transaction(weight=1, inputs=[input1], outputs=[output1, output2], parents=parents, storage=self.tx_storage, timestamp=self.last_block.timestamp + 2) data_to_sign = tx2.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) input1.data = P2PKH.create_input_data(public_bytes, signature) tx2.resolve() self.manager.propagate_tx(tx2) # tx2 has two outputs, for address_b58 and new_address_b58 # So we must have one more element on address_b58 index and only one on new_address_b58 self.assertEqual(len(self.tx_storage.wallet_index.index[address_b58]), wallet_index_count + 2) self.assertEqual( len(self.tx_storage.wallet_index.index[new_address_b58]), 1) # Third transaction: spend tokens from output with address=address_b58 and send # tokens to a new address = output3_address_b58, which is from a random wallet output3_address_b58 = self.get_address(1) output3_address = decode_address(output3_address_b58) script3 = P2PKH.create_output_script(output3_address) output3 = TxOutput(value - 100, script3) input2 = TxInput(tx2.hash, 0, b'') tx3 = Transaction(weight=1, inputs=[input2], outputs=[output3], parents=parents, storage=self.tx_storage, timestamp=self.last_block.timestamp + 3) data_to_sign = tx3.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) input2.data = P2PKH.create_input_data(public_bytes, signature) tx3.resolve() self.manager.propagate_tx(tx3) # tx3 has one output, for another new address (output3_address_b58) and it's spending an output of address_b58 # So address_b58 index must have one more element and output3_address_b58 should have one element also # new_address_b58 was not spent neither received tokens, so didn't change self.assertEqual(len(self.tx_storage.wallet_index.index[address_b58]), wallet_index_count + 3) self.assertEqual( len(self.tx_storage.wallet_index.index[output3_address_b58]), 1) self.assertEqual( len(self.tx_storage.wallet_index.index[new_address_b58]), 1)
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_PUT(self, request): """ Updates a nano contract tx and returns it in hexadecimal format. Post data should be a json with the following items: hex_tx: tx being updated, in hex value new_values: List[{'address', 'value'}], with bet address and 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, 'PUT') 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_PUT: if param not in data: return get_missing_params_msg(param) try: decoded_params = self.decode_put_params(data) except ValueError as e: return json.dumps({'success': False, 'message': e.message}).encode('utf-8') try: tx = Transaction.create_from_struct(decoded_params.tx_bytes) except struct.error: return json.dumps({'success': False, 'message': 'Could not decode hex transaction'}).encode('utf-8') tx_outputs = [] nano_contract = None for _output in tx.outputs: _nano_contract = NanoContractMatchValues.parse_script(_output.script) if _nano_contract: total_value = _output.value nano_contract = _nano_contract else: tx_outputs.append(_output) if not nano_contract: return json.dumps({'success': False, 'message': 'Nano contract not found'}).encode('utf-8') for address, value in decoded_params.new_value_dict.items(): nano_contract.value_dict[address] = value tx.outputs = tx_outputs 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.outputs.insert(0, TxOutput(total_value, nano_contract.create_output_script())) for txin in inputs: tx.inputs.append(TxInput(txin.tx_id, txin.index, b'')) ret = {'success': True, 'hex_tx': tx.get_struct().hex()} return json.dumps(ret).encode('utf-8')
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())
def test_spend_multisig(self): # Adding funds to the wallet blocks = add_new_blocks(self.manager, 2, advance_clock=15) add_blocks_unlock_reward(self.manager) self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, sum(blk.outputs[0].value for blk in blocks))) first_block_amount = blocks[0].outputs[0].value # First we send tokens to a multisig address outputs = [ WalletOutputInfo(address=self.multisig_address, value=first_block_amount, timelock=int(self.clock.seconds()) + 15) ] tx1 = self.manager.wallet.prepare_transaction_compute_inputs( Transaction, outputs) tx1.weight = 10 tx1.parents = self.manager.get_new_tx_parents() tx1.timestamp = int(self.clock.seconds()) tx1.resolve() self.manager.propagate_tx(tx1) self.clock.advance(10) self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, first_block_amount)) # Then we create a new tx that spends this tokens from multisig wallet tx = Transaction.create_from_struct(tx1.get_struct()) tx.weight = 10 tx.parents = self.manager.get_new_tx_parents() tx.timestamp = int(self.clock.seconds()) multisig_script = create_output_script(self.multisig_address) multisig_output = TxOutput(200, multisig_script) wallet_output = TxOutput(300, create_output_script(self.address)) outside_output = TxOutput(first_block_amount - 200 - 300, create_output_script(self.outside_address)) tx.outputs = [multisig_output, wallet_output, outside_output] tx_input = TxInput(tx1.hash, 0, b'') tx.inputs = [tx_input] signatures = [] for private_key_hex in self.private_keys: signature = generate_signature(tx, bytes.fromhex(private_key_hex), password=b'1234') signatures.append(signature) input_data = MultiSig.create_input_data(self.redeem_script, signatures) tx.inputs[0].data = input_data tx.resolve() # Transaction is still locked self.assertFalse(self.manager.propagate_tx(tx)) self.clock.advance(6) tx.timestamp = int(self.clock.seconds()) tx.resolve() # First we try to propagate with a P2PKH input private_key_obj = get_private_key_from_bytes(bytes.fromhex( self.private_keys[0]), password=b'1234') pubkey_obj = private_key_obj.public_key() public_key_compressed = get_public_key_bytes_compressed(pubkey_obj) p2pkh_input_data = P2PKH.create_input_data(public_key_compressed, signatures[0]) tx2 = Transaction.create_from_struct(tx.get_struct()) tx2.inputs[0].data = p2pkh_input_data tx2.resolve() self.assertFalse(self.manager.propagate_tx(tx2)) # Now we propagate the correct self.assertTrue(self.manager.propagate_tx(tx)) self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, first_block_amount + 300)) # Testing the MultiSig class methods cls_script = parse_address_script(multisig_script) self.assertTrue(isinstance(cls_script, MultiSig)) self.assertEqual(cls_script.address, self.multisig_address_b58) expected_dict = { 'type': 'MultiSig', 'address': self.multisig_address_b58, 'timelock': None } self.assertEqual(cls_script.to_human_readable(), expected_dict) script_eval(tx, tx_input, tx1) # Script error with self.assertRaises(ScriptError): create_output_script( base58.b58decode('55d14K5jMqsN2uwUEFqiPG5SoD7Vr1BfnH'))
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) value_dict = {} for item in data['values']: value_dict[base58.b58decode(item['address'])] = item['value'] fallback_address = base58.b58decode( data['fallback_address']) if data['fallback_address'] else b'\x00' min_timestamp = data['min_timestamp'] if data.get( 'min_timestamp') else int(self.manager.reactor.seconds()) nano_contract = NanoContractMatchValues( base64.b64decode(data['oracle_pubkey_hash']), min_timestamp, data['oracle_data_id'].encode('utf-8'), value_dict, fallback_address) tx_outputs = [] tx_outputs.append( TxOutput(data['total_value'], nano_contract.create_output_script())) inputs, total_inputs_amount = self.manager.wallet.get_inputs_from_amount( data['input_value']) change_tx = self.manager.wallet.handle_change_tx( total_inputs_amount, data['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')