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_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_weight_inf(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(inputs=[_input], outputs=[output], parents=parents, storage=self.tx_storage) tx.weight = float('inf') 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.update_hash() self.assertTrue(isinf(tx.weight)) with self.assertRaises(WeightError): 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_script(self): genesis_block = self.genesis_blocks[0] # random keys to be used random_priv = 'MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgMnAHVIyj7Hym2yI' \ 'w+JcKEfdCHByIp+FHfPoIkcnjqGyhRANCAATX76SGshGeoacUcZDhXEzERt' \ 'AHbd30CVpUg8RRnAIhaFcuMY3G+YFr/mReAPRuiLKCnolWz3kCltTtNj36rJyd' private_key_random = get_private_key_from_bytes( base64.b64decode(random_priv)) # create input data with incorrect private key _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) tx = Transaction(inputs=[_input], outputs=[output], 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, private_key_random) data_wrong = P2PKH.create_input_data(public_bytes, signature) _input.data = data_wrong with self.assertRaises(InvalidInputData): tx.verify_inputs()
def test_reward_lock_timestamp(self): from hathor.transaction.exceptions import RewardLocked # add block with a reward we can spend reward_block = self.manager.generate_mining_block( address=get_address_from_public_key(self.genesis_public_key)) reward_block.resolve() self.assertTrue(self.manager.propagate_tx(reward_block)) # we add enough blocks that this output could be spent based on block height blocks = add_blocks_unlock_reward(self.manager) # tx timestamp is equal to the block that unlock the spent rewards. It should # be greater, so it'll fail tx = self._spend_reward_tx(self.manager, reward_block) tx.timestamp = blocks[-1].timestamp tx.resolve() with self.assertRaises(RewardLocked): tx.verify() # we can fix it be incrementing the timestamp tx._height_cache = None tx.timestamp = blocks[-1].timestamp + 1 tx.resolve() tx.verify()
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 get_genesis_output(): # use this if to calculate the genesis output. We have to do it if: # - we change genesis priv/pub keys # - there's some change to the way we calculate hathor addresses from hathor.transaction.scripts import P2PKH from hathor.crypto.util import get_address_from_public_key # read genesis keys genesis_private_key = get_genesis_key() address = get_address_from_public_key(genesis_private_key.public_key()) return P2PKH.create_output_script(address).hex()
def test_output_sum_ignore_authority(self): # sum of tx outputs should ignore authority outputs address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output1 = TxOutput(5, script) # regular utxo output2 = TxOutput(30, script, 0b10000001) # authority utxo output3 = TxOutput(3, script) # regular utxo tx = Transaction(outputs=[output1, output2, output3], storage=self.tx_storage) self.assertEqual(8, tx.sum_outputs)
def test_sighash_data_cache(self): from unittest import mock address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(5, script) tx = Transaction(outputs=[output], storage=self.tx_storage) with mock.patch('hathor.transaction.transaction.hashlib') as mocked: for _ in range(10): tx.get_sighash_all_data() mocked.sha256.assert_called_once()
def test_sigops_input_single_below_limit(self) -> None: genesis_block = self.genesis_blocks[0] value = genesis_block.outputs[0].value - 1 address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) _output = TxOutput(value, script) hscript = create_script_with_sigops(settings.MAX_TX_SIGOPS_INPUT - 1) input3 = TxInput(genesis_block.hash, 0, hscript) tx = Transaction(inputs=[input3], outputs=[_output], storage=self.tx_storage) tx.update_hash() tx.verify_sigops_input()
def test_sigops_input_single_above_limit(self) -> None: genesis_block = self.genesis_blocks[0] value = genesis_block.outputs[0].value - 1 address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) _output = TxOutput(value, script) hscript = create_script_with_sigops(settings.MAX_TX_SIGOPS_INPUT + 1) input1 = TxInput(genesis_block.hash, 0, hscript) tx = Transaction(inputs=[input1], outputs=[_output], storage=self.tx_storage) tx.update_hash() with self.assertRaises(TooManySigOps): tx.verify()
def test_reward_lock(self): from hathor.transaction.exceptions import RewardLocked # add block with a reward we can spend reward_block = self.manager.generate_mining_block(address=get_address_from_public_key(self.genesis_public_key)) reward_block.resolve() self.assertTrue(self.manager.propagate_tx(reward_block)) # reward cannot be spent while not enough blocks are added for _ in range(settings.REWARD_SPEND_MIN_BLOCKS): tx = self._spend_reward_tx(self.manager, reward_block) with self.assertRaises(RewardLocked): tx.verify() add_new_blocks(self.manager, 1, advance_clock=1) # now it should be spendable tx = self._spend_reward_tx(self.manager, reward_block) self.assertTrue(self.manager.propagate_tx(tx, fails_silently=False))
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, TxOutput, TxInput 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 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_block_number_parents(self): address = get_address_from_public_key(self.genesis_public_key) output_script = P2PKH.create_output_script(address) tx_outputs = [TxOutput(100, output_script)] parents = [tx.hash for tx in self.genesis_txs] 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(IncorrectParents): block.verify()
def test_input_output_match(self): genesis_block = self.genesis_blocks[0] _input = TxInput(genesis_block.hash, 0, b'') # spend less than what was generated value = genesis_block.outputs[0].value - 1 address = get_address_from_public_key(self.genesis_public_key) script = P2PKH.create_output_script(address) output = TxOutput(value, script) tx = Transaction(inputs=[_input], outputs=[output], storage=self.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) with self.assertRaises(InputOutputMismatch): tx.verify_sum()
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 _gen_tx_spending_genesis_block(self): parents = [tx.hash for tx in self.genesis_txs] 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) tx = Transaction(nonce=100, inputs=[_input], outputs=[output], parents=parents, storage=self.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) tx.inputs[0].data = P2PKH.create_input_data(public_bytes, signature) tx.update_hash() return tx
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 _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_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()
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_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_address(self): address = get_address_from_public_key(self.public_key) address_b58 = get_address_b58_from_public_key(self.public_key) self.assertEqual(address, decode_address(address_b58))