def test_tx_inputs_conflict(self): # the new tx inputs will try to spend the same output 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) # We can't only duplicate the value because genesis is using the max value possible outputs = [TxOutput(value, script), TxOutput(value, script)] _input = TxInput(genesis_block.hash, 0, b'') tx = Transaction(weight=1, inputs=[_input, _input], outputs=outputs, 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() with self.assertRaises(ConflictingInputs): tx.verify()
def test_token_transfer(self): wallet = self.manager.wallet tx = create_tokens(self.manager, self.address_b58) token_uid = tx.tokens[0] utxo = tx.outputs[0] parents = self.manager.get_new_tx_parents() _input1 = TxInput(tx.hash, 0, b'') script = P2PKH.create_output_script(self.address) # regular transfer token_output = TxOutput(utxo.value, script, 1) tx2 = 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 = tx2.get_sighash_all(clear_input_data=True) public_bytes, signature = wallet.get_input_aux_data(data_to_sign, wallet.get_private_key(self.address_b58)) tx2.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) tx2.resolve() tx2.verify() # missing tokens token_output = TxOutput(utxo.value - 1, script, 1) tx3 = 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 = tx3.get_sighash_all(clear_input_data=True) public_bytes, signature = wallet.get_input_aux_data(data_to_sign, wallet.get_private_key(self.address_b58)) tx3.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) tx3.resolve() with self.assertRaises(InputOutputMismatch): tx3.verify()
def test_regular_tx(self): # this should succeed 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) _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() tx.verify()
def test_tx_duplicated_parents(self): # the new tx will confirm the same tx twice parents = [self.genesis_txs[0].hash, self.genesis_txs[0].hash] 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) _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() with self.assertRaises(DuplicatedParents): tx.verify()
def test_unknown_authority(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) # try an unknown authority input1 = TxInput(tx.hash, 1, b'') input2 = TxInput(tx.hash, 2, b'') output = TxOutput((TxOutput.ALL_AUTHORITIES << 1), script, 0b10000001) tx2 = Transaction(weight=1, inputs=[input1, input2], outputs=[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() with self.assertRaises(InvalidToken): tx2.verify()
def test_token_transfer_authority(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) # input with mint and output with melt _input1 = TxInput(tx.hash, 1, b'') token_output = TxOutput(TxOutput.TOKEN_MELT_MASK, script, 0b10000001) tx2 = 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 = tx2.get_sighash_all(clear_input_data=True) public_bytes, signature = wallet.get_input_aux_data(data_to_sign, wallet.get_private_key(self.address_b58)) tx2.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) tx2.resolve() with self.assertRaises(InvalidToken): tx2.verify() # input with melt and output with mint _input1 = TxInput(tx.hash, 2, b'') token_output = TxOutput(TxOutput.TOKEN_MINT_MASK, script, 0b10000001) tx3 = 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 = tx3.get_sighash_all(clear_input_data=True) public_bytes, signature = wallet.get_input_aux_data(data_to_sign, wallet.get_private_key(self.address_b58)) tx3.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) tx3.resolve() with self.assertRaises(InvalidToken): tx3.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 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 test_tokens_balance(self): # create tokens and check balances # initial tokens address_b58 = self.manager.wallet.get_unused_address() address = decode_address(address_b58) tx = create_tokens(self.manager, address_b58) token_id = tx.tokens[0] amount = tx.outputs[0].value # initial token balance self.assertEqual(self.manager.wallet.balance[token_id], WalletBalance(0, amount)) # initial hathor balance # we don't consider HTR balance 0 because we transfer genesis tokens to this # wallet during token creation hathor_balance = self.manager.wallet.balance[settings.HATHOR_TOKEN_UID] # transfer token to another wallet and check balance again parents = self.manager.get_new_tx_parents() _input1 = TxInput(tx.hash, 0, b'') script = P2PKH.create_output_script(address) token_output1 = TxOutput(30, b'', 0b00000001) token_output2 = TxOutput(amount - 30, script, 0b00000001) tx2 = Transaction(weight=1, inputs=[_input1], outputs=[token_output1, token_output2], parents=parents, tokens=[token_id], storage=self.manager.tx_storage, timestamp=int(self.manager.reactor.seconds())) data_to_sign = tx2.get_sighash_all(clear_input_data=True) public_bytes, signature = self.manager.wallet.get_input_aux_data( data_to_sign, self.manager.wallet.get_private_key(address_b58)) tx2.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) tx2.resolve() tx2.verify() self.manager.propagate_tx(tx2) self.run_to_completion() # verify balance self.assertEqual(self.manager.wallet.balance[token_id], WalletBalance(0, amount - 30)) # hathor balance remains the same self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], hathor_balance)
def _spend_reward_tx(self, manager, reward_block): value = reward_block.outputs[0].value address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) input_ = TxInput(reward_block.hash, 0, b'') output = TxOutput(value, script) tx = Transaction( weight=1, timestamp=int(manager.reactor.seconds()) + 1, inputs=[input_], outputs=[output], parents=manager.get_new_tx_parents(), storage=manager.tx_storage, ) data_to_sign = tx.get_sighash_all(clear_input_data=True) 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() return tx
def test_tx_token_outputs(self): genesis_block = self.genesis_blocks[0] _input = TxInput(genesis_block.hash, 0, b'') value = genesis_block.outputs[0].value script = P2PKH.create_output_script(self.address) output = TxOutput(value, script, 1) parents = [tx.hash for tx in self.genesis_txs] tx = Transaction(weight=1, inputs=[_input], outputs=[output], parents=parents, storage=self.manager.tx_storage) # no token uids in list data_to_sign = tx.get_sighash_all() public_bytes, signature = self.manager.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) tx.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) tx.resolve() with self.assertRaises(InvalidToken): tx.verify() # with 1 token uid in list tx.tokens = [ bytes.fromhex( '0023be91834c973d6a6ddd1a0ae411807b7c8ef2a015afb5177ee64b666ce602' ) ] output.token_data = 2 data_to_sign = tx.get_sighash_all() public_bytes, signature = self.manager.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) tx.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) tx.resolve() with self.assertRaises(InvalidToken): tx.verify() # try hathor authority UTXO output = TxOutput(value, script, 0b10000000) tx.outputs = [output] data_to_sign = tx.get_sighash_all() public_bytes, signature = self.manager.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) tx.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) tx.resolve() with self.assertRaises(InvalidToken): tx.verify()
def test_tx_inputs_out_of_range(self): # we'll try to spend output 3 from genesis transaction, which does not exist 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) _input = TxInput(genesis_block.hash, len(genesis_block.outputs) + 1, b'') tx = Transaction(weight=1, inputs=[_input], outputs=[output], parents=parents, storage=self.tx_storage) data_to_sign = tx.get_sighash_all() public_bytes, signature = self.wallet.get_input_aux_data( data_to_sign, self.genesis_private_key) data = P2PKH.create_input_data(public_bytes, signature) tx.inputs[0].data = data # test with an inexistent index tx.resolve() with self.assertRaises(InexistentInput): tx.verify() # now with index equals of len of outputs _input = [ TxInput(genesis_block.hash, len(genesis_block.outputs), data) ] tx.inputs = _input # test with an inexistent index tx.resolve() with self.assertRaises(InexistentInput): tx.verify() # now with inexistent tx hash random_bytes = bytes.fromhex( '0000184e64683b966b4268f387c269915cc61f6af5329823a93e3696cb0fe902') _input = [TxInput(random_bytes, 3, data)] tx.inputs = _input tx.resolve() with self.assertRaises(InexistentInput): tx.verify()
def test_tx_number_parents(self): genesis_block = self.genesis_blocks[0] _input = TxInput(genesis_block.hash, 0, b'') 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) parents = [self.genesis_txs[0].hash] 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) tx.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) # in first test, only with 1 parent tx.resolve() with self.assertRaises(IncorrectParents): tx.verify() # test with 3 parents parents = [tx.hash for tx in self.genesis] tx.parents = parents tx.resolve() with self.assertRaises(IncorrectParents): tx.verify() # 2 parents, 1 tx and 1 block parents = [self.genesis_txs[0].hash, self.genesis_blocks[0].hash] tx.parents = parents tx.resolve() with self.assertRaises(IncorrectParents): tx.verify()
class _TransactionStorageTest(unittest.TestCase): def setUp(self, tx_storage, reactor=None): if not reactor: self.reactor = Clock() else: self.reactor = reactor self.reactor.advance(time.time()) self.tx_storage = tx_storage 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] from hathor.manager import HathorManager self.tmpdir = tempfile.mkdtemp(dir='/tmp/') wallet = Wallet(directory=self.tmpdir) wallet.unlock(b'teste') self.manager = HathorManager(self.reactor, tx_storage=self.tx_storage, wallet=wallet) self.tx_storage.wallet_index = WalletIndex(self.manager.pubsub) self.tx_storage.tokens_index = TokensIndex() block_parents = [ tx.hash for tx in chain(self.genesis_blocks, self.genesis_txs) ] output = TxOutput( 200, bytes.fromhex('1e393a5ce2ff1c98d4ff6892f2175100f2dad049')) self.block = Block(timestamp=MIN_TIMESTAMP, weight=12, outputs=[output], parents=block_parents, nonce=100781, storage=tx_storage) self.block.resolve() self.block.verify() 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() # Disable weakref to test the internal methods. Otherwise, most methods return objects from weakref. self.tx_storage._disable_weakref() def tearDown(self): shutil.rmtree(self.tmpdir) def test_genesis(self): self.assertEqual(1, len(self.genesis_blocks)) self.assertEqual(2, len(self.genesis_txs)) for tx in self.genesis: tx.verify() for tx in self.genesis: tx2 = self.tx_storage.get_transaction(tx.hash) self.assertEqual(tx, tx2) self.assertTrue(self.tx_storage.transaction_exists(tx.hash)) def test_storage_basic(self): self.assertEqual(1, self.tx_storage.get_block_count()) self.assertEqual(2, self.tx_storage.get_tx_count()) self.assertEqual(3, self.tx_storage.get_count_tx_blocks()) block_parents_hash = [ x.data for x in self.tx_storage.get_block_tips() ] self.assertEqual(1, len(block_parents_hash)) self.assertEqual(block_parents_hash, [self.genesis_blocks[0].hash]) tx_parents_hash = [x.data for x in self.tx_storage.get_tx_tips()] self.assertEqual(2, len(tx_parents_hash)) self.assertEqual( set(tx_parents_hash), {self.genesis_txs[0].hash, self.genesis_txs[1].hash}) def validate_save(self, obj): self.tx_storage.save_transaction(obj) loaded_obj1 = self.tx_storage.get_transaction(obj.hash) self.assertTrue(self.tx_storage.transaction_exists(obj.hash)) self.assertEqual(obj, loaded_obj1) self.assertEqual(len(obj.get_funds_struct()), len(loaded_obj1.get_funds_struct())) self.assertEqual(bytes(obj), bytes(loaded_obj1)) self.assertEqual(obj.to_json(), loaded_obj1.to_json()) self.assertEqual(obj.is_block, loaded_obj1.is_block) # Testing add and remove from cache if self.tx_storage.with_index: if obj.is_block: self.assertTrue(obj.hash in self.tx_storage.block_index. tips_index.tx_last_interval) else: self.assertTrue(obj.hash in self.tx_storage.tx_index. tips_index.tx_last_interval) self.tx_storage._del_from_cache(obj) if self.tx_storage.with_index: if obj.is_block: self.assertFalse(obj.hash in self.tx_storage.block_index. tips_index.tx_last_interval) else: self.assertFalse(obj.hash in self.tx_storage.tx_index. tips_index.tx_last_interval) self.tx_storage._add_to_cache(obj) if self.tx_storage.with_index: if obj.is_block: self.assertTrue(obj.hash in self.tx_storage.block_index. tips_index.tx_last_interval) else: self.assertTrue(obj.hash in self.tx_storage.tx_index. tips_index.tx_last_interval) def test_save_block(self): self.validate_save(self.block) def test_save_tx(self): self.validate_save(self.tx) def test_save_token_creation_tx(self): tx = create_tokens(self.manager, propagate=False) self.validate_save(tx) def _validate_not_in_index(self, tx, index): tips = index.tips_index[self.tx.timestamp] self.assertNotIn(self.tx.hash, [x.data for x in tips]) self.assertNotIn(self.tx.hash, index.tips_index.tx_last_interval) self.assertIsNone(index.txs_index.find_tx_index(tx)) def _test_remove_tx_or_block(self, tx): self.validate_save(tx) self.tx_storage.remove_transaction(tx) with self.assertRaises(TransactionDoesNotExist): self.tx_storage.get_transaction(tx.hash) if hasattr(self.tx_storage, 'all_index'): self._validate_not_in_index(tx, self.tx_storage.all_index) if tx.is_block: if hasattr(self.tx_storage, 'block_index'): self._validate_not_in_index(tx, self.tx_storage.block_index) else: if hasattr(self.tx_storage, 'tx_index'): self._validate_not_in_index(tx, self.tx_storage.tx_index) # Check wallet index. wallet_index = self.tx_storage.wallet_index addresses = wallet_index._get_addresses(tx) for address in addresses: self.assertNotIn(tx.hash, wallet_index.index[address]) # TODO Check self.tx_storage.tokens_index # Try to remove twice. It is supposed to do nothing. self.tx_storage.remove_transaction(tx) def test_remove_tx(self): self._test_remove_tx_or_block(self.tx) def test_remove_block(self): self._test_remove_tx_or_block(self.block) def test_shared_memory(self): # Enable weakref to this test only. self.tx_storage._enable_weakref() self.validate_save(self.block) self.validate_save(self.tx) for tx in [self.tx, self.block]: # just making sure, if it is genesis the test is wrong self.assertFalse(tx.is_genesis) # load transactions twice tx1 = self.tx_storage.get_transaction(tx.hash) tx2 = self.tx_storage.get_transaction(tx.hash) # naturally they should be equal, but this time so do the objects self.assertTrue(tx1 == tx2) self.assertTrue(tx1 is tx2) meta1 = tx1.get_metadata() meta2 = tx2.get_metadata() # and naturally the metadata too self.assertTrue(meta1 == meta2) self.assertTrue(meta1 is meta2) def test_get_wrong_tx(self): hex_error = bytes.fromhex( '00001c5c0b69d13b05534c94a69b2c8272294e6b0c536660a3ac264820677024' ) with self.assertRaises(TransactionDoesNotExist): self.tx_storage.get_transaction(hex_error) def test_save_metadata(self): # Saving genesis metadata self.tx_storage.save_transaction(self.genesis_txs[0], only_metadata=True) tx = self.block # First we save to the storage self.tx_storage.save_transaction(tx) metadata = tx.get_metadata() metadata.spent_outputs[1].append(self.genesis_blocks[0].hash) random_tx = bytes.fromhex( '0000222e64683b966b4268f387c269915cc61f6af5329823a93e3696cb0f2222' ) metadata.children.append(random_tx) self.tx_storage.save_transaction(tx, only_metadata=True) tx2 = self.tx_storage.get_transaction(tx.hash) metadata2 = tx2.get_metadata() self.assertEqual(metadata, metadata2) total = 0 for tx in self.tx_storage.get_all_transactions(): total += 1 self.assertEqual(total, 4) def test_storage_new_blocks(self): tip_blocks = [x.data for x in self.tx_storage.get_block_tips()] self.assertEqual(tip_blocks, [self.genesis_blocks[0].hash]) block1 = self._add_new_block() tip_blocks = [x.data for x in self.tx_storage.get_block_tips()] self.assertEqual(tip_blocks, [block1.hash]) block2 = self._add_new_block() tip_blocks = [x.data for x in self.tx_storage.get_block_tips()] self.assertEqual(tip_blocks, [block2.hash]) # Block3 has the same parents as block2. block3 = self._add_new_block(parents=block2.parents) tip_blocks = [x.data for x in self.tx_storage.get_block_tips()] self.assertEqual(set(tip_blocks), {block2.hash, block3.hash}) # Re-generate caches to test topological sort. self.tx_storage._manually_initialize() tip_blocks = [x.data for x in self.tx_storage.get_block_tips()] self.assertEqual(set(tip_blocks), {block2.hash, block3.hash}) def test_token_list(self): tx = self.tx self.validate_save(tx) # 2 token uids tx.tokens.append( bytes.fromhex( '00001c5c0b69d13b05534c94a69b2c8272294e6b0c536660a3ac264820677024' )) tx.resolve() self.validate_save(tx) # no tokens tx.tokens = [] tx.resolve() self.validate_save(tx) def _add_new_block(self, parents=None): block = self.manager.generate_mining_block() block.data = b'Testing, testing, 1, 2, 3... testing, testing...' if parents is not None: block.parents = parents block.weight = 10 self.assertTrue(block.resolve()) block.verify() self.manager.tx_storage.save_transaction(block) self.reactor.advance(5) return block def test_topological_sort(self): self.manager.test_mode = TestMode.TEST_ALL_WEIGHT _total = 0 blocks = add_new_blocks(self.manager, 1, advance_clock=1) _total += len(blocks) blocks = add_blocks_unlock_reward(self.manager) _total += len(blocks) add_new_transactions(self.manager, 1, advance_clock=1)[0] total = 0 for tx in self.tx_storage._topological_sort(): total += 1 # added blocks + genesis txs + added tx self.assertEqual(total, _total + 3 + 1) def test_get_best_block_weight(self): block = self._add_new_block() weight = self.tx_storage.get_weight_best_block() self.assertEqual(block.weight, weight)
class BaseTransactionStorageTest(unittest.TestCase): __test__ = False 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 tearDown(self): shutil.rmtree(self.tmpdir) def test_genesis_ref(self): # Enable weakref to this test only. self.tx_storage._enable_weakref() genesis_set = set(self.tx_storage.get_all_genesis()) for tx in genesis_set: tx2 = self.tx_storage.get_transaction(tx.hash) self.assertTrue(tx is tx2) from hathor.transaction.genesis import _get_genesis_transactions_unsafe genesis_from_settings = _get_genesis_transactions_unsafe(None) for tx in genesis_from_settings: tx2 = self.tx_storage.get_transaction(tx.hash) self.assertTrue(tx is not tx2) for tx3 in genesis_set: self.assertTrue(tx is not tx3) if tx2 == tx3: self.assertTrue(tx2 is tx3) def test_genesis(self): self.assertEqual(1, len(self.genesis_blocks)) self.assertEqual(2, len(self.genesis_txs)) for tx in self.genesis: tx.verify() for tx in self.genesis: tx2 = self.tx_storage.get_transaction(tx.hash) self.assertEqual(tx, tx2) self.assertTrue(self.tx_storage.transaction_exists(tx.hash)) def test_get_empty_merklee_tree(self): # We use `first_timestamp - 1` to ensure that the merkle tree will be empty. self.tx_storage.get_merkle_tree(self.tx_storage.first_timestamp - 1) def test_first_timestamp(self): self.assertEqual(self.tx_storage.first_timestamp, min(x.timestamp for x in self.genesis)) def test_storage_basic(self): self.assertEqual(1, self.tx_storage.get_block_count()) self.assertEqual(2, self.tx_storage.get_tx_count()) self.assertEqual(3, self.tx_storage.get_count_tx_blocks()) block_parents_hash = [x.data for x in self.tx_storage.get_block_tips()] self.assertEqual(1, len(block_parents_hash)) self.assertEqual(block_parents_hash, [self.genesis_blocks[0].hash]) tx_parents_hash = [x.data for x in self.tx_storage.get_tx_tips()] self.assertEqual(2, len(tx_parents_hash)) self.assertEqual(set(tx_parents_hash), {self.genesis_txs[0].hash, self.genesis_txs[1].hash}) def test_storage_basic_v2(self): self.assertEqual(1, self.tx_storage.get_block_count()) self.assertEqual(2, self.tx_storage.get_tx_count()) self.assertEqual(3, self.tx_storage.get_count_tx_blocks()) block_parents_hash = self.tx_storage.get_best_block_tips() self.assertEqual(1, len(block_parents_hash)) self.assertEqual(block_parents_hash, [self.genesis_blocks[0].hash]) tx_parents_hash = self.manager.get_new_tx_parents() self.assertEqual(2, len(tx_parents_hash)) self.assertEqual(set(tx_parents_hash), {self.genesis_txs[0].hash, self.genesis_txs[1].hash}) def validate_save(self, obj): self.tx_storage.save_transaction(obj, add_to_indexes=True) loaded_obj1 = self.tx_storage.get_transaction(obj.hash) self.assertTrue(self.tx_storage.transaction_exists(obj.hash)) self.assertEqual(obj, loaded_obj1) self.assertEqual(len(obj.get_funds_struct()), len(loaded_obj1.get_funds_struct())) self.assertEqual(bytes(obj), bytes(loaded_obj1)) self.assertEqual(obj.to_json(), loaded_obj1.to_json()) self.assertEqual(obj.is_block, loaded_obj1.is_block) # Testing add and remove from cache if self.tx_storage.with_index: if obj.is_block: self.assertTrue(obj.hash in self.tx_storage.indexes.block_tips. tx_last_interval) else: self.assertTrue(obj.hash in self.tx_storage.indexes.tx_tips. tx_last_interval) self.tx_storage.del_from_indexes(obj) if self.tx_storage.with_index: if obj.is_block: self.assertFalse(obj.hash in self.tx_storage.indexes. block_tips.tx_last_interval) else: self.assertFalse(obj.hash in self.tx_storage.indexes.tx_tips. tx_last_interval) self.tx_storage.add_to_indexes(obj) if self.tx_storage.with_index: if obj.is_block: self.assertTrue(obj.hash in self.tx_storage.indexes.block_tips. tx_last_interval) else: self.assertTrue(obj.hash in self.tx_storage.indexes.tx_tips. tx_last_interval) def test_save_block(self): self.validate_save(self.block) def test_save_tx(self): self.validate_save(self.tx) def test_save_token_creation_tx(self): tx = create_tokens(self.manager, propagate=False) tx.get_metadata().validation = ValidationState.FULL self.validate_save(tx) def _validate_not_in_index(self, tx, index): tips = index.tips_index[self.tx.timestamp] self.assertNotIn(self.tx.hash, [x.data for x in tips]) self.assertNotIn(self.tx.hash, index.tips_index.tx_last_interval) self.assertIsNone(index.txs_index.find_tx_index(tx)) def _test_remove_tx_or_block(self, tx): self.validate_save(tx) self.tx_storage.remove_transaction(tx) with self.assertRaises(TransactionDoesNotExist): self.tx_storage.get_transaction(tx.hash) if hasattr(self.tx_storage, 'all_index'): self._validate_not_in_index(tx, self.tx_storage.all_index) if tx.is_block: if hasattr(self.tx_storage, 'block_index'): self._validate_not_in_index(tx, self.tx_storage.block_index) else: if hasattr(self.tx_storage, 'tx_index'): self._validate_not_in_index(tx, self.tx_storage.tx_index) # Check wallet index. addresses_index = self.tx_storage.indexes.addresses addresses = tx.get_related_addresses() for address in addresses: self.assertNotIn(tx.hash, addresses_index.get_from_address(address)) # TODO Check self.tx_storage.tokens_index # Try to remove twice. It is supposed to do nothing. self.tx_storage.remove_transaction(tx) def test_remove_tx(self): self._test_remove_tx_or_block(self.tx) def test_remove_block(self): self._test_remove_tx_or_block(self.block) def test_shared_memory(self): # Enable weakref to this test only. self.tx_storage._enable_weakref() self.validate_save(self.block) self.validate_save(self.tx) for tx in [self.tx, self.block]: # just making sure, if it is genesis the test is wrong self.assertFalse(tx.is_genesis) # load transactions twice tx1 = self.tx_storage.get_transaction(tx.hash) tx2 = self.tx_storage.get_transaction(tx.hash) # naturally they should be equal, but this time so do the objects self.assertTrue(tx1 == tx2) self.assertTrue(tx1 is tx2) meta1 = tx1.get_metadata() meta2 = tx2.get_metadata() # and naturally the metadata too self.assertTrue(meta1 == meta2) self.assertTrue(meta1 is meta2) def test_get_wrong_tx(self): hex_error = bytes.fromhex( '00001c5c0b69d13b05534c94a69b2c8272294e6b0c536660a3ac264820677024') with self.assertRaises(TransactionDoesNotExist): self.tx_storage.get_transaction(hex_error) def test_save_metadata(self): # Saving genesis metadata self.tx_storage.save_transaction(self.genesis_txs[0], only_metadata=True) tx = self.block # First we save to the storage self.tx_storage.save_transaction(tx) metadata = tx.get_metadata() metadata.spent_outputs[1].append(self.genesis_blocks[0].hash) random_tx = bytes.fromhex( '0000222e64683b966b4268f387c269915cc61f6af5329823a93e3696cb0f2222') metadata.children.append(random_tx) self.tx_storage.save_transaction(tx, only_metadata=True) tx2 = self.tx_storage.get_transaction(tx.hash) metadata2 = tx2.get_metadata() self.assertEqual(metadata, metadata2) total = 0 for tx in self.tx_storage.get_all_transactions(): total += 1 self.assertEqual(total, 4) def test_storage_new_blocks(self): tip_blocks = [x.data for x in self.tx_storage.get_block_tips()] self.assertEqual(tip_blocks, [self.genesis_blocks[0].hash]) block1 = self._add_new_block() tip_blocks = [x.data for x in self.tx_storage.get_block_tips()] self.assertEqual(tip_blocks, [block1.hash]) block2 = self._add_new_block() tip_blocks = [x.data for x in self.tx_storage.get_block_tips()] self.assertEqual(tip_blocks, [block2.hash]) # Block3 has the same parents as block2. block3 = self._add_new_block(parents=block2.parents) tip_blocks = [x.data for x in self.tx_storage.get_block_tips()] self.assertEqual(set(tip_blocks), {block2.hash, block3.hash}) # Re-generate caches to test topological sort. self.tx_storage._manually_initialize() tip_blocks = [x.data for x in self.tx_storage.get_block_tips()] self.assertEqual(set(tip_blocks), {block2.hash, block3.hash}) def test_token_list(self): tx = self.tx self.validate_save(tx) # 2 token uids tx.tokens.append( bytes.fromhex( '00001c5c0b69d13b05534c94a69b2c8272294e6b0c536660a3ac264820677024' )) tx.resolve() self.validate_save(tx) # no tokens tx.tokens = [] tx.resolve() self.validate_save(tx) def _add_new_block(self, parents=None): block = self.manager.generate_mining_block() block.data = b'Testing, testing, 1, 2, 3... testing, testing...' if parents is not None: block.parents = parents block.weight = 10 self.assertTrue(block.resolve()) block.verify() self.manager.propagate_tx(block, fails_silently=False) self.reactor.advance(5) return block def test_topological_sort(self): _set_test_mode(TestMode.TEST_ALL_WEIGHT) _total = 0 blocks = add_new_blocks(self.manager, 1, advance_clock=1) _total += len(blocks) blocks = add_blocks_unlock_reward(self.manager) _total += len(blocks) add_new_transactions(self.manager, 1, advance_clock=1) total = 0 for tx in self.tx_storage._topological_sort(): total += 1 # added blocks + genesis txs + added tx self.assertEqual(total, _total + 3 + 1) def test_get_best_block_weight(self): block = self._add_new_block() weight = self.tx_storage.get_weight_best_block() self.assertEqual(block.weight, weight) @inlineCallbacks def test_concurrent_access(self): self.tx_storage.save_transaction(self.tx) self.tx_storage._enable_weakref() def handle_error(err): self.fail( 'Error resolving concurrent access deferred. {}'.format(err)) deferreds = [] for i in range(5): d = deferToThread(self.tx_storage.get_transaction, self.tx.hash) d.addErrback(handle_error) deferreds.append(d) self.reactor.advance(3) yield gatherResults(deferreds) self.tx_storage._disable_weakref() def test_full_verification_attribute(self): self.assertFalse(self.tx_storage.is_running_full_verification()) self.tx_storage.start_full_verification() self.assertTrue(self.tx_storage.is_running_full_verification()) self.tx_storage.finish_full_verification() self.assertFalse(self.tx_storage.is_running_full_verification()) def test_key_value_attribute(self): attr = 'test' val = 'a' # Try to get a key that does not exist self.assertIsNone(self.tx_storage.get_value(attr)) # Try to remove this key that does not exist self.tx_storage.remove_value(attr) # Add the key/value self.tx_storage.add_value(attr, val) # Get correct value self.assertEqual(self.tx_storage.get_value(attr), val) # Remove the key self.tx_storage.remove_value(attr) # Key should not exist again self.assertIsNone(self.tx_storage.get_value(attr))
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 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_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 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 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 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_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']))