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_checkdatasig_raise_on_uncompressed_pubkey(self): block = self.genesis_blocks[0] data = b'some_random_data' from hathor.transaction import Transaction, TxInput, TxOutput txin = TxInput(tx_id=block.hash, index=0, data=b'') txout = TxOutput(value=block.outputs[0].value, script=b'') tx = Transaction(inputs=[txin], outputs=[txout]) import hashlib data_to_sign = tx.get_sighash_all() hashed_data = hashlib.sha256(data_to_sign).digest() signature = self.genesis_private_key.sign(hashed_data, ec.ECDSA(hashes.SHA256())) from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat pubkey_uncompressed = self.genesis_public_key.public_bytes( Encoding.X962, PublicFormat.UncompressedPoint) # ScriptError if pubkey is not a valid compressed public key # with wrong signature stack = [data, b'123', pubkey_uncompressed] with self.assertRaises(ScriptError): op_checkdatasig(stack, log=[], extras=None) # or with rigth one # this will make sure the signature is not made when parameters are wrong stack = [data, signature, pubkey_uncompressed] with self.assertRaises(ScriptError): op_checkdatasig(stack, log=[], extras=None)
def test_match_values(self): pubkey_hash = '6o6ul2c+sqAariBVW+CwNaSJb9w=' pubkey = 'Awmloohhey8WhajdDURgvbk1z3JHX2vxDSBjz9uG9wEp' # ./hathor-cli oracle-encode-data str:some_id int:1543974403 int:100 oracle_data = 'B3NvbWVfaWQEXAcuAwFk' oracle_signature = 'MEYCIQC5cyg1tOY4oyPZ5KY7ugWJGRShrsSPxr8AxxyuvO5PYwIhAOxHBDMid7aRXe' \ '+85rIaDPI2ussIcw54avaFWfT9svSp' address = base58.b58decode(self.get_address(0)) # they should be the same nc = NanoContractMatchValues(base64.b64decode(pubkey_hash), 1543970403, 'some_id'.encode('utf-8'), {address: 100}) script = nc.create_output_script() nc2 = NanoContractMatchValues.parse_script(script) self.assertIsNotNone(nc2) self.assertEqual(json.dumps(nc.to_human_readable()), json.dumps(nc2.to_human_readable())) # if we add some more bytes, parsing should not match script2 = script + b'00' nc3 = NanoContractMatchValues.parse_script(script2) self.assertIsNone(nc3) # test script eval is true input_data = NanoContractMatchValues.create_input_data( base64.b64decode(oracle_data), base64.b64decode(oracle_signature), base64.b64decode(pubkey)) txin = TxInput(b'aa', 0, input_data) spent_tx = Transaction(outputs=[TxOutput(20, script)]) tx = Transaction( outputs=[TxOutput(20, P2PKH.create_output_script(address))]) script_eval(tx, txin, spent_tx)
def test_checksig(self): with self.assertRaises(MissingStackItems): op_checksig([1], log=[], extras=None) block = self.genesis_blocks[0] from hathor.transaction import Transaction, TxInput, TxOutput txin = TxInput(tx_id=block.hash, index=0, data=b'') txout = TxOutput(value=block.outputs[0].value, script=b'') tx = Transaction(inputs=[txin], outputs=[txout]) import hashlib data_to_sign = tx.get_sighash_all() hashed_data = hashlib.sha256(data_to_sign).digest() signature = self.genesis_private_key.sign(hashed_data, ec.ECDSA(hashes.SHA256())) pubkey_bytes = get_public_key_bytes_compressed(self.genesis_public_key) extras = ScriptExtras(tx=tx, txin=None, spent_tx=None) # wrong signature puts False (0) on stack stack = [b'aaaaaaaaa', pubkey_bytes] op_checksig(stack, log=[], extras=extras) self.assertEqual(0, stack.pop()) stack = [signature, pubkey_bytes] op_checksig(stack, log=[], extras=extras) self.assertEqual(1, stack.pop())
def update_voided_info(self, tx: Transaction) -> None: """ This method should be called only once when the transactions is added to the DAG. """ assert tx.hash is not None assert tx.storage is not None voided_by: Set[bytes] = set() # Union of voided_by of parents for parent in tx.get_parents(): parent_meta = parent.get_metadata() if parent_meta.voided_by: voided_by.update(parent_meta.voided_by) # Union of voided_by of inputs for txin in tx.inputs: spent_tx = tx.storage.get_transaction(txin.tx_id) spent_meta = spent_tx.get_metadata() if spent_meta.voided_by: voided_by.update(spent_meta.voided_by) # Update accumulated weight of the transactions voiding us. assert tx.hash not in voided_by for h in voided_by: tx2 = tx.storage.get_transaction(h) tx2_meta = tx2.get_metadata() tx2_meta.accumulated_weight = sum_weights( tx2_meta.accumulated_weight, tx.weight) assert tx2.storage is not None tx2.storage.save_transaction(tx2, only_metadata=True) # Then, we add ourselves. meta = tx.get_metadata() assert not meta.voided_by or meta.voided_by == {tx.hash} assert meta.accumulated_weight == tx.weight if meta.conflict_with: voided_by.add(tx.hash) if voided_by: meta.voided_by = voided_by.copy() tx.storage.save_transaction(tx, only_metadata=True) tx.storage._del_from_cache(tx) # XXX: accessing private method # Check conflicts of the transactions voiding us. for h in voided_by: if h == tx.hash: continue conflict_tx = tx.storage.get_transaction(h) if not conflict_tx.is_block: assert isinstance(conflict_tx, Transaction) self.check_conflicts(conflict_tx) # Finally, check our conflicts. meta = tx.get_metadata() if meta.voided_by == {tx.hash}: self.check_conflicts(tx)
def test_too_many_outputs(self): random_bytes = bytes.fromhex('0000184e64683b966b4268f387c269915cc61f6af5329823a93e3696cb0fe902') output = TxOutput(1, random_bytes) outputs = [output] * (MAX_NUM_OUTPUTS + 1) tx = Transaction(outputs=outputs, storage=self.tx_storage) with self.assertRaises(TooManyOutputs): tx.verify_number_of_outputs()
def test_too_many_inputs(self): random_bytes = bytes.fromhex('0000184e64683b966b4268f387c269915cc61f6af5329823a93e3696cb0fe902') _input = TxInput(random_bytes, 0, random_bytes) inputs = [_input] * (MAX_NUM_INPUTS + 1) tx = Transaction(inputs=inputs, storage=self.tx_storage) with self.assertRaises(TooManyInputs): tx.verify_number_of_inputs()
def _test_txout_script_limit(self, offset): genesis_block = self.genesis_blocks[0] _input = TxInput(genesis_block.hash, 0, b'') value = genesis_block.outputs[0].value script = b'*' * (settings.MAX_OUTPUT_SCRIPT_SIZE + offset) _output = TxOutput(value, script) tx = Transaction(inputs=[_input], outputs=[_output], storage=self.tx_storage) tx.verify_outputs()
def render_POST(self, request): """ Creates a nano contract tx and returns it in hexadecimal format. Post data should be a json with the following items: values: List[{'address', 'value'}], with bet address and value fallback_address: if none of the addresses above is the winner, this address can execute the contract oracle_pubkey_hash: oracle's public key hashed oracle_data_id: oracle's id about this nano contract total_value: nano contract total value input_value: amount this wallet should stake in the nano contract :rtype: string (json) """ request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'POST') try: data = json.loads(request.content.read().decode('utf-8')) except json.JSONDecodeError: return json.dumps({'success': False, 'message': 'Invalid format for post data'}).encode('utf-8') for param in PARAMS_POST: if param not in data: return get_missing_params_msg(param) try: decoded_params = self.decode_post_params(data) except ValueError as e: return json.dumps({'success': False, 'message': e.message}).encode('utf-8') nano_contract = NanoContractMatchValues( decoded_params.oracle_pubkey_hash, decoded_params.min_timestamp, decoded_params.oracle_data_id, decoded_params.value_dict, decoded_params.fallback_address ) tx_outputs = [] tx_outputs.append(TxOutput(decoded_params.total_value, nano_contract.create_output_script())) inputs, total_inputs_amount = self.manager.wallet.get_inputs_from_amount( decoded_params.input_value, self.manager.tx_storage ) change_tx = self.manager.wallet.handle_change_tx(total_inputs_amount, decoded_params.input_value) if change_tx: tx_outputs.append(TxOutput(change_tx.value, P2PKH.create_output_script(change_tx.address))) tx_inputs = [TxInput(txin.tx_id, txin.index, b'') for txin in inputs] tx = Transaction(inputs=tx_inputs, outputs=tx_outputs) ret = {'success': True, 'hex_tx': tx.get_struct().hex()} return json.dumps(ret).encode('utf-8')
def _test_txin_data_limit(self, offset): genesis_block = self.genesis_blocks[0] data = b'*' * (settings.MAX_INPUT_DATA_SIZE + offset) _input = TxInput(genesis_block.hash, 0, data) value = genesis_block.outputs[0].value _output = TxOutput(value, b'') tx = Transaction(timestamp=int(self.manager.reactor.seconds()) + 1, inputs=[_input], outputs=[_output], storage=self.tx_storage) tx.verify_inputs(skip_script=True)
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 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 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] from hathor.manager import HathorManager 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.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() self.tx_storage.enable_lock()
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_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_twin(self): # Normal twin params = ['--raw_tx', self.tx.get_struct().hex()] args = self.parser.parse_args(params) f = StringIO() with capture_logs(): with redirect_stdout(f): execute(args) # Transforming prints str in array output = f.getvalue().strip().splitlines() twin_tx = Transaction.create_from_struct(bytes.fromhex(output[0])) # Parents are the same but in different order self.assertEqual(twin_tx.parents[0], self.tx.parents[1]) self.assertEqual(twin_tx.parents[1], self.tx.parents[0]) # Testing metadata creation from json meta_before_conflict = self.tx.get_metadata() meta_before_conflict_json = meta_before_conflict.to_json() del meta_before_conflict_json['conflict_with'] del meta_before_conflict_json['voided_by'] del meta_before_conflict_json['twins'] new_meta = TransactionMetadata.create_from_json(meta_before_conflict_json) self.assertEqual(meta_before_conflict, new_meta) self.manager.propagate_tx(twin_tx) # Validate they are twins meta = self.tx.get_metadata(force_reload=True) self.assertEqual(meta.twins, [twin_tx.hash]) meta2 = twin_tx.get_metadata() self.assertFalse(meta == meta2)
def test_balance_update3(self): # Tx2 is twin with tx1 with higher acc weight, so tx1 will get voided # Start balance self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.initial_balance)) # Change of parents only, so it's a twin. # With higher weight, so the balance will continue because tx2 will be the winner tx2 = Transaction.create_from_struct(self.tx1.get_struct()) tx2.parents = [self.tx1.parents[1], self.tx1.parents[0]] tx2.weight = 13 tx2.resolve() # Propagate a conflicting twin transaction self.manager.propagate_tx(tx2) self.run_to_completion() meta1 = self.tx1.get_metadata(force_reload=True) self.assertEqual(meta1.twins, [tx2.hash]) self.assertEqual(meta1.voided_by, {self.tx1.hash}) meta2 = tx2.get_metadata() self.assertEqual(meta2.voided_by, None) # Balance is the same self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.initial_balance))
def test_balance_update2(self): # Tx2 is twin with tx1 with equal acc weight, so both will get voided # Start balance self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.initial_balance)) # Change of parents only, so it's a twin. # Same weight, so both will be voided then the balance increases tx2 = Transaction.create_from_struct(self.tx1.get_struct()) tx2.parents = [self.tx1.parents[1], self.tx1.parents[0]] tx2.resolve() # Propagate a conflicting twin transaction self.manager.propagate_tx(tx2) self.run_to_completion() meta1 = self.tx1.get_metadata(force_reload=True) self.assertEqual(meta1.twins, [tx2.hash]) self.assertEqual(meta1.voided_by, {self.tx1.hash}) meta2 = tx2.get_metadata() self.assertEqual(meta2.voided_by, {tx2.hash}) # Balance changed self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, sum(self.blocks_tokens[:3])))
def test_spend_tx_by_script(self): src_tx = self.unspent_tx address = 'HNXsVtRUmwDCtpcCJUrH4QiHo9kUKx199A' script = create_base_script(address).get_script() script_str = base64.b64encode(script).decode('utf-8') resp = (yield self.web.post( 'create_tx', { 'inputs': [{ 'tx_id': src_tx.hash_hex, 'index': 1, }], 'outputs': [{ 'script': script_str, 'value': 100, }] })).json_value() self.assertEqual(resp['success'], True) data = resp['data'] hex_data = resp['hex_data'] struct_bytes = bytes.fromhex(hex_data) tx = Transaction.create_from_struct(struct_bytes) tx_data = tx.to_json() del tx_data['hash'] del tx_data['nonce'] self.assertEqual(data, tx_data) self.assertEqual(len(tx.inputs), 1) self.assertEqual(tx.inputs[0].tx_id, src_tx.hash) self.assertEqual(tx.inputs[0].index, 1) self.assertEqual(tx.inputs[0].data, b'') self.assertEqual(len(tx.outputs), 1) self.assertEqual(tx.outputs[0].value, 100) self.assertEqual(tx.outputs[0].token_data, 0) self.assertEqual(tx.outputs[0].script, script)
def setUp(self): super().setUp() self.network = 'testnet' self.manager = self.create_peer(self.network, unlock_wallet=True) self.tx_storage = self.manager.tx_storage data = b'This is a test block.' self.blocks = add_new_blocks(self.manager, 3, advance_clock=15, block_data=data) address = self.get_address(0) value = 100 outputs = [ WalletOutputInfo(address=decode_address(address), value=int(value), timelock=None) ] self.tx1 = self.manager.wallet.prepare_transaction_compute_inputs(Transaction, outputs) self.tx1.weight = 10 self.tx1.parents = self.manager.get_new_tx_parents() self.tx1.timestamp = int(self.clock.seconds()) self.tx1.resolve() self.manager.propagate_tx(self.tx1) # Change of parents only, so it's a twin. # With less weight, so the balance will continue because tx1 will be the winner self.tx2 = Transaction.create_from_struct(self.tx1.get_struct()) self.tx2.parents = [self.tx1.parents[1], self.tx1.parents[0]] self.tx2.weight = 9 self.tx2.resolve() # Propagate a conflicting twin transaction self.manager.propagate_tx(self.tx2)
def test_tx_propagate(self): _set_test_mode( TestMode.DISABLED) # disable test_mode so the weight is not 1 src_tx = self.unspent_tx output_address = 'HNXsVtRUmwDCtpcCJUrH4QiHo9kUKx199A' resp = (yield self.web.post( 'create_tx', { 'inputs': [{ 'tx_id': src_tx.hash_hex, 'index': 1, }], 'outputs': [{ 'address': output_address, 'value': 100, }] })).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 # 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_spend_block(self): block = self.unspent_blocks[0] address = 'HNXsVtRUmwDCtpcCJUrH4QiHo9kUKx199A' script = create_base_script(address).get_script() resp = (yield self.web.post('create_tx', { 'inputs': [ { 'tx_id': block.hash_hex, 'index': 0, } ], 'outputs': [ { 'address': address, 'value': 6400, } ] })).json_value() self.assertEqual(resp['success'], True) data = resp['data'] hex_data = resp['hex_data'] struct_bytes = bytes.fromhex(hex_data) tx = Transaction.create_from_struct(struct_bytes) tx_data = tx.to_json() del tx_data['hash'] del tx_data['nonce'] self.assertEqual(data, tx_data) self.assertEqual(len(tx.inputs), 1) self.assertEqual(tx.inputs[0].tx_id, block.hash) self.assertEqual(tx.inputs[0].index, 0) self.assertEqual(tx.inputs[0].data, b'') self.assertEqual(len(tx.outputs), 1) self.assertEqual(tx.outputs[0].value, 6400) self.assertEqual(tx.outputs[0].token_data, 0) self.assertEqual(tx.outputs[0].script, script)
def _render_POST(self, tx: Transaction, request: Request) -> None: """ Resolves the request without stratum The transaction is completed and then sent to be mined in a thread """ if tx.inputs: max_ts_spent_tx = max(tx.get_spent_tx(txin).timestamp for txin in tx.inputs) # Set tx timestamp as max between tx and inputs tx.timestamp = max(max_ts_spent_tx + 1, tx.timestamp) # Set parents tx.parents = self.manager.get_new_tx_parents(tx.timestamp) deferred = threads.deferToThreadPool(reactor, self.manager.pow_thread_pool, self._render_POST_thread, tx, request) deferred.addCallback(self._cb_tx_resolve, request) deferred.addErrback(self._err_tx_resolve, request)
def test_balance_update1(self): # Tx2 is twin with tx1 but less acc weight, so it will get voided # Start balance self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.initial_balance)) # Change of parents only, so it's a twin. # With less weight, so the balance will continue because tx1 will be the winner tx2 = Transaction.create_from_struct(self.tx1.get_struct()) tx2.parents = [self.tx1.parents[1], self.tx1.parents[0]] tx2.weight = 9 tx2.resolve() # Propagate a conflicting twin transaction self.manager.propagate_tx(tx2) self.run_to_completion() meta1 = self.tx1.get_metadata(force_reload=True) self.assertEqual(meta1.twins, [tx2.hash]) meta2 = tx2.get_metadata(force_reload=True) self.assertEqual(meta2.voided_by, {tx2.hash}) # Balance is the same self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.initial_balance)) # Voided wallet history index_voided = 0 output_voided = tx2.outputs[index_voided] address = output_voided.to_human_readable()['address'] voided_unspent = UnspentTx(tx2.hash, index_voided, output_voided.value, tx2.timestamp, address, output_voided.token_data, voided=True) self.assertEqual(len(self.manager.wallet.voided_unspent), 1) voided_utxo = self.manager.wallet.voided_unspent.get( (voided_unspent.tx_id, index_voided)) self.assertIsNotNone(voided_utxo) self.assertEqual(voided_utxo.to_dict(), voided_unspent.to_dict()) input_voided = tx2.inputs[0] key = (input_voided.tx_id, input_voided.index) voided_spent = SpentTx(tx2.hash, input_voided.tx_id, input_voided.index, self.blocks_tokens[0], tx2.timestamp, voided=True) self.assertEqual(len(self.manager.wallet.voided_spent), 1) self.assertEqual(len(self.manager.wallet.voided_spent[key]), 1) self.assertEqual(self.manager.wallet.voided_spent[key][0].to_dict(), voided_spent.to_dict())
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_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_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 execute(args: Namespace, priv_key_password: str) -> None: from hathor.transaction import Transaction from hathor.wallet.util import generate_signature tx = Transaction.create_from_struct(bytes.fromhex(args.partial_tx)) assert isinstance(tx, Transaction) signature = generate_signature(tx, bytes.fromhex(args.private_key), password=priv_key_password.encode()) print('Signature: ', signature.hex())
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_tips_twin(self): add_new_blocks(self.manager, 6, advance_clock=1) add_blocks_unlock_reward(self.manager) self.assertEqual( len(self.manager.tx_storage.indexes.mempool_tips.get()), 0) tx1 = add_new_transactions(self.manager, 1, advance_clock=1)[0] tx2 = add_new_transactions(self.manager, 1, advance_clock=1)[0] tx3 = add_new_transactions(self.manager, 1, advance_clock=1)[0] # 3 txs and the last one is still a tip self.assertCountEqual( self.manager.tx_storage.indexes.mempool_tips.get(), set([tx3.hash])) # A new tx with custom parents, so tx3 and tx4 will become two tips tx4 = add_new_transactions(self.manager, 1, advance_clock=1, propagate=False)[0] tx4.parents = [tx1.hash, tx2.hash] tx4.resolve() self.manager.propagate_tx(tx4, fails_silently=False) self.manager.reactor.advance(10) self.assertCountEqual( self.manager.tx_storage.indexes.mempool_tips.get(), set([tx4.hash, tx3.hash])) # A twin tx with tx4, that will be voided initially, then won't change the tips tx5 = Transaction.create_from_struct(tx4.get_struct()) tx5.parents = [tx2.hash, tx3.hash] tx5.resolve() self.manager.propagate_tx(tx5) self.manager.reactor.advance(10) # tx4 and tx5 are twins, so both are voided self.assertIsNotNone(tx4.get_metadata(force_reload=True).voided_by) self.assertIsNotNone(tx5.get_metadata(force_reload=True).voided_by) self.assertCountEqual( self.manager.tx_storage.indexes.mempool_tips.get(), set([tx3.hash])) # add new tx confirming tx5, which will become valid and tx4 becomes voided tx6 = add_new_transactions(self.manager, 1, advance_clock=1, propagate=False)[0] tx6.parents = [tx5.hash, tx2.hash] tx6.resolve() self.manager.propagate_tx(tx6, fails_silently=False) self.manager.reactor.advance(10) self.assertIsNotNone(tx4.get_metadata(force_reload=True).voided_by) self.assertIsNone(tx5.get_metadata(force_reload=True).voided_by) # tx6 is the only one left self.assertCountEqual( self.manager.tx_storage.indexes.mempool_tips.get(), set([tx6.hash]))