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_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 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_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 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_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 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_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_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_tx_methods(self): blocks = add_new_blocks(self.manager, 2, advance_clock=1) add_blocks_unlock_reward(self.manager) txs = add_new_transactions(self.manager, 2, advance_clock=1) # Validate __str__, __bytes__, __eq__ tx = txs[0] tx2 = txs[1] str_tx = str(tx) self.assertTrue(isinstance(str_tx, str)) self.assertEqual(bytes(tx), tx.get_struct()) tx_equal = Transaction.create_from_struct(tx.get_struct()) self.assertTrue(tx == tx_equal) self.assertFalse(tx == tx2) tx2_hash = tx2.hash tx2.hash = None self.assertFalse(tx == tx2) tx2.hash = tx2_hash # Validate is_genesis without storage tx_equal.storage = None self.assertFalse(tx_equal.is_genesis) # Pow error tx2.verify_pow() tx2.weight = 100 with self.assertRaises(PowError): tx2.verify_pow() # Get sighashall is different with and without data self.assertNotEqual(tx.get_sighash_all(), tx.get_sighash_all(clear_input_data=False)) # Verify parent timestamps tx2.verify_parents() tx2_timestamp = tx2.timestamp tx2.timestamp = 2 with self.assertRaises(TimestampError): tx2.verify_parents() tx2.timestamp = tx2_timestamp # Verify inputs timestamps tx2.verify_inputs() tx2.timestamp = 2 with self.assertRaises(TimestampError): tx2.verify_inputs() tx2.timestamp = tx2_timestamp # Validate maximum distance between blocks block = blocks[0] block2 = blocks[1] block2.timestamp = block.timestamp + settings.MAX_DISTANCE_BETWEEN_BLOCKS block2.verify_parents() block2.timestamp += 1 with self.assertRaises(TimestampError): block2.verify_parents()
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]))
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_balance_update4(self): # Tx2 spends Tx1 output # Tx3 is twin of Tx2 with same acc weight, so both will get voided self.manager.reactor.advance(1) # Start balance self.assertEqual(self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.initial_balance)) address = self.manager.wallet.get_unused_address_bytes() value = self.blocks_tokens[0] - 100 inputs = [WalletInputInfo(tx_id=self.tx1.hash, index=0, private_key=None)] outputs = [WalletOutputInfo(address=address, value=int(value), timelock=None)] tx2 = self.manager.wallet.prepare_transaction_incomplete_inputs(Transaction, inputs, outputs, self.manager.tx_storage) tx2.weight = 10 tx2.parents = [self.tx1.hash, self.tx1.parents[0]] tx2.timestamp = int(self.clock.seconds()) tx2.resolve() self.manager.propagate_tx(tx2) self.run_to_completion() # Test create same tx with allow double spending with self.assertRaises(PrivateKeyNotFound): self.manager.wallet.prepare_transaction_incomplete_inputs( Transaction, inputs=inputs, outputs=outputs, tx_storage=self.manager.tx_storage ) self.manager.wallet.prepare_transaction_incomplete_inputs(Transaction, inputs=inputs, outputs=outputs, force=True, tx_storage=self.manager.tx_storage) # Change of parents only, so it's a twin. tx3 = Transaction.create_from_struct(tx2.get_struct()) tx3.parents = [tx2.parents[1], tx2.parents[0]] tx3.resolve() # Propagate a conflicting twin transaction self.manager.propagate_tx(tx3) self.run_to_completion() meta2 = tx2.get_metadata(force_reload=True) self.assertEqual(meta2.twins, [tx3.hash]) self.assertEqual(meta2.voided_by, {tx2.hash}) meta3 = tx3.get_metadata() self.assertEqual(meta3.voided_by, {tx3.hash}) # Balance is the same self.assertEqual(self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.initial_balance))
def render_GET(self, request): """ Get request /wallet/nano-contract/decode/ that returns the tx decoded, if success Expects 'hex_tx' as GET parameter :rtype: string (json) """ request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'GET') if b'hex_tx' in request.args: requested_decode = request.args[b'hex_tx'][0].decode('utf-8') else: return get_missing_params_msg('hex_tx') pattern = r'[a-fA-F\d]+' if re.match(pattern, requested_decode) and len(requested_decode) % 2 == 0: tx_bytes = bytes.fromhex(requested_decode) try: tx = Transaction.create_from_struct(tx_bytes) except struct.error: data = {'success': False, 'message': 'Invalid transaction'} return json.dumps(data).encode('utf-8') outputs = [] nano_contract = None for _output in tx.outputs: _nano_contract = NanoContractMatchValues.parse_script( _output.script) if _nano_contract: nano_contract = _nano_contract.to_human_readable() nano_contract['value'] = _output.value continue else: outputs.append(_output.to_human_readable()) my_inputs, other_inputs = self.manager.wallet.separate_inputs( tx.inputs, self.manager.tx_storage) my_inputs = [_in.to_human_readable() for _in in my_inputs] other_inputs = [_in.to_human_readable() for _in in other_inputs] data = { 'success': True, 'nano_contract': nano_contract, 'outputs': outputs, 'my_inputs': my_inputs, 'other_inputs': other_inputs } else: data = {'success': False, 'message': 'Invalid transaction'} return json.dumps(data).encode('utf-8')
def execute(args: Namespace) -> None: from hathor.transaction import Transaction from hathor.transaction.scripts import MultiSig tx = Transaction.create_from_struct(bytes.fromhex(args.partial_tx)) signatures = [bytes.fromhex(signature) for signature in args.signatures.split(',')] input_data = MultiSig.create_input_data(bytes.fromhex(args.redeem_script), signatures) tx.inputs[0].data = input_data tx.resolve() print('Transaction after POW: ', tx.get_struct().hex())
def test_twin_different(self): server = run_server() # Unlock wallet to start mining request_server('wallet/unlock', 'POST', data={'passphrase': '123'}) # Mining execute_mining(count=2) # Generating txs execute_tx_gen(count=4) response = request_server('transaction', 'GET', data={ b'count': 4, b'type': 'tx' }) tx = response['transactions'][-1] response = request_server('transaction', 'GET', data={b'id': tx['tx_id']}) tx = response['tx'] # Twin different weight and parents host = 'http://localhost:8085/{}/'.format(settings.API_VERSION_PREFIX) params = [ '--url', host, '--hash', tx['hash'], '--parents', '--weight', '14' ] args = self.parser.parse_args(params) f = StringIO() with redirect_stdout(f): execute(args) # Transforming prints str in array output = f.getvalue().split('\n') # Last element is always empty string output.pop() twin_tx = Transaction.create_from_struct(bytes.fromhex(output[0])) # Parents are differents self.assertNotEqual(twin_tx.parents[0], tx['parents'][0]) self.assertNotEqual(twin_tx.parents[0], tx['parents'][1]) self.assertNotEqual(twin_tx.parents[1], tx['parents'][0]) self.assertNotEqual(twin_tx.parents[1], tx['parents'][1]) self.assertNotEqual(twin_tx.weight, tx['weight']) self.assertEqual(twin_tx.weight, 14.0) server.terminate()
def test_tips_winner(self): add_new_block(self.manager, 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] # tx1 will be the tip self.assertCountEqual( self.manager.tx_storage.indexes.mempool_tips.get(), set([tx1.hash])) tx2 = add_new_transactions(self.manager, 1, advance_clock=1)[0] # tx2 will be the tip now self.assertCountEqual( self.manager.tx_storage.indexes.mempool_tips.get(), set([tx2.hash])) tx3 = Transaction.create_from_struct(tx2.get_struct()) tx3.parents = [tx2.parents[1], tx2.parents[0]] tx3.resolve() # Propagate a conflicting twin transaction with tx2 self.manager.propagate_tx(tx3) meta1 = tx2.get_metadata(force_reload=True) self.assertEqual(meta1.conflict_with, [tx3.hash]) self.assertEqual(meta1.voided_by, {tx2.hash}) self.assertEqual(meta1.twins, [tx3.hash]) self.assertCountEqual( self.manager.tx_storage.indexes.mempool_tips.get(), set([tx1.hash])) self.manager.reactor.advance(10) # Creating a new block that confirms tx3, then is will become valid and voiding tx2 new_block = add_new_block(self.manager, propagate=False) new_block.parents = [new_block.parents[0], tx1.hash, tx3.hash] new_block.resolve() new_block.verify() self.manager.propagate_tx(new_block, fails_silently=False) self.manager.reactor.advance(10) self.assertIsNone( self.manager.tx_storage.get_metadata(tx3.hash).voided_by) self.assertIsNotNone( self.manager.tx_storage.get_metadata(tx2.hash).voided_by) # The block confirms tx3, so it's not a tip self.assertCountEqual( self.manager.tx_storage.indexes.mempool_tips.get(), set())
def test_balance_update5(self): # Tx2 spends Tx1 output # Tx3 is twin of Tx1, with less acc weight # So we have conflict between all three txs but tx1 and tx2 are winners and tx3 is voided self.clock.advance(1) # Start balance self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.initial_balance)) address = self.manager.wallet.get_unused_address_bytes() value = self.blocks_tokens[0] - 100 inputs = [ WalletInputInfo(tx_id=self.tx1.hash, index=0, private_key=None) ] outputs = [ WalletOutputInfo(address=address, value=int(value), timelock=None) ] tx2 = self.manager.wallet.prepare_transaction_incomplete_inputs( Transaction, inputs, outputs, self.manager.tx_storage) tx2.weight = 10 tx2.parents = [self.tx1.hash, self.tx1.parents[0]] tx2.timestamp = int(self.clock.seconds()) tx2.resolve() # Change of parents only, so it's a twin. tx3 = Transaction.create_from_struct(self.tx1.get_struct()) tx3.parents = [self.tx1.parents[1], self.tx1.parents[0]] tx3.resolve() # Propagate a conflicting twin transaction self.manager.propagate_tx(tx2) self.manager.propagate_tx(tx3) self.run_to_completion() meta2 = tx2.get_metadata() self.assertEqual(meta2.twins, []) self.assertEqual(meta2.voided_by, None) meta3 = tx3.get_metadata() self.assertEqual(meta3.voided_by, {tx3.hash}) self.assertEqual(meta3.twins, [self.tx1.hash]) # Balance is the same self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.initial_balance))
def test_get_one(self): genesis_tx = next(x for x in self.manager.tx_storage.get_all_genesis() if x.is_block) response_success = yield self.web.get( "transaction", {b'id': bytes(genesis_tx.hash.hex(), 'utf-8')}) data_success = response_success.json_value() self.assertTrue(data_success['success']) dict_test = genesis_tx.to_json(decode_script=True) dict_test['raw'] = genesis_tx.get_struct().hex() dict_test['nonce'] = str(dict_test['nonce']) if genesis_tx.is_block: dict_test['height'] = genesis_tx.calculate_height() self.assertEqual(data_success['tx'], dict_test) # Test sending hash that does not exist response_error1 = yield self.web.get( "transaction", { b'id': b'000000831cff82fa730cbdf8640fae6c130aab1681336e2f8574e314a5533848' }) data_error1 = response_error1.json_value() self.assertFalse(data_error1['success']) # Test sending invalid hash response_error2 = yield self.web.get( "transaction", { b'id': b'000000831cff82fa730cbdf8640fae6c130aab1681336e2f8574e314a553384' }) data_error2 = response_error2.json_value() self.assertFalse(data_error2['success']) # Adding blocks to have funds add_new_blocks(self.manager, 2, advance_clock=1) add_blocks_unlock_reward(self.manager) tx = add_new_transactions(self.manager, 1)[0] tx2 = Transaction.create_from_struct(tx.get_struct()) tx2.parents = [tx.parents[1], tx.parents[0]] tx2.resolve() self.manager.propagate_tx(tx2) # Now we get a tx with conflict, voided_by and twin response_conflict = yield self.web.get( "transaction", {b'id': bytes(tx2.hash.hex(), 'utf-8')}) data_conflict = response_conflict.json_value() self.assertTrue(data_conflict['success'])
def test_twin_tx(self): add_new_blocks(self.manager, 5, advance_clock=15) add_blocks_unlock_reward(self.manager) address = self.get_address(0) value1 = 100 value2 = 101 outputs = [ WalletOutputInfo(address=decode_address(address), value=int(value1), timelock=None), WalletOutputInfo(address=decode_address(address), value=int(value2), timelock=None) ] tx1 = self.manager.wallet.prepare_transaction_compute_inputs( Transaction, outputs, self.manager.tx_storage) tx1.weight = 10 tx1.parents = self.manager.get_new_tx_parents() tx1.timestamp = int(self.clock.seconds()) tx1.resolve() # Change of parents only, so it's a twin tx2 = Transaction.create_from_struct(tx1.get_struct()) tx2.parents = [tx1.parents[1], tx1.parents[0]] tx2.resolve() self.assertNotEqual(tx1.hash, tx2.hash) self.manager.propagate_tx(tx1) self.run_to_completion() wallet_data = self.manager.tx_storage.wallet_index.get_from_address( address) self.assertEqual(len(wallet_data), 1) self.assertEqual(wallet_data, [tx1.hash]) # Propagate a conflicting twin transaction self.manager.propagate_tx(tx2) self.run_to_completion() wallet_data = self.manager.tx_storage.wallet_index.get_from_address( address) self.assertEqual(len(wallet_data), 2) self.assertEqual(set(wallet_data), set([tx1.hash, tx2.hash]))
def render_GET(self, request): """ Get request /decode_tx/ that returns the signed tx, if success Expects 'hex_tx' as GET parameter :rtype: string (json) """ request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'GET') if b'hex_tx' in request.args: requested_decode = request.args[b'hex_tx'][0].decode('utf-8') else: return get_missing_params_msg('hex_tx') pattern = r'[a-fA-F\d]+' if re.match(pattern, requested_decode) and len(requested_decode) % 2 == 0: tx_bytes = bytes.fromhex(requested_decode) prepare_to_send = False if b'prepare_to_send' in request.args: _prepare_to_send = request.args[b'prepare_to_send'][0].decode( 'utf-8') prepare_to_send = _prepare_to_send == 'true' try: tx = Transaction.create_from_struct(tx_bytes) tx.storage = self.manager.tx_storage self.manager.wallet.sign_transaction(tx, self.manager.tx_storage) if prepare_to_send: 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() data = {'hex_tx': tx.get_struct().hex(), 'success': True} except struct.error: data = {'success': False, 'message': 'Transaction invalid'} else: data = {'success': False, 'message': 'Transaction invalid'} return json.dumps(data).encode('utf-8')
def setUp(self): super().setUp() self.resource = self.create_resource() self.web = StubSite(self.resource) # Unlocking wallet self.manager.wallet.unlock(b'MYPASS') # Creating blocks, txs and a conflict tx to test graphviz with it add_new_blocks(self.manager, 2, advance_clock=2) add_blocks_unlock_reward(self.manager) txs = add_new_transactions(self.manager, 2, advance_clock=2) tx = txs[0] self.tx2 = Transaction.create_from_struct(tx.get_struct()) self.tx2.parents = [tx.parents[1], tx.parents[0]] self.tx2.resolve() self.manager.propagate_tx(self.tx2)
def test_balance_update6(self): # Tx2 is twin of tx1, so both voided # Tx3 has tx1 as parent, so increases tx1 acc weight, then tx1 is winner against tx2 self.manager.reactor.advance(1) # 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. tx2 = Transaction.create_from_struct(self.tx1.get_struct()) tx2.parents = [self.tx1.parents[1], self.tx1.parents[0]] tx2.resolve() address = self.get_address(0) value = 100 outputs = [ WalletOutputInfo(address=decode_address(address), value=int(value), timelock=None) ] tx3 = self.manager.wallet.prepare_transaction_compute_inputs( Transaction, outputs) tx3.weight = 10 tx3.parents = [self.tx1.hash, self.tx1.parents[0]] tx3.timestamp = int(self.clock.seconds()) tx3.resolve() # Propagate a conflicting twin transaction self.manager.propagate_tx(tx2) self.manager.propagate_tx(tx3) self.run_to_completion() # Balance is the same self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.initial_balance - 100))
def test_spend_multisig(self): # Adding funds to the wallet # XXX: note further down the test, 20.00 HTR will be used, block_count must yield at least that amount block_count = 3 # 3 * 8.00 -> 24.00 HTR is enough blocks = add_new_blocks(self.manager, block_count, advance_clock=15) add_blocks_unlock_reward(self.manager) blocks_tokens = [sum(txout.value for txout in blk.outputs) for blk in blocks] available_tokens = sum(blocks_tokens) self.assertEqual(self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, available_tokens)) # First we send tokens to a multisig address block_reward = blocks_tokens[0] outputs = [WalletOutputInfo(address=self.multisig_address, value=block_reward, timelock=None)] tx1 = self.manager.wallet.prepare_transaction_compute_inputs(Transaction, outputs, self.manager.tx_storage) tx1.weight = 10 tx1.parents = self.manager.get_new_tx_parents() tx1.timestamp = int(self.clock.seconds()) tx1.resolve() self.manager.propagate_tx(tx1) self.clock.advance(10) wallet_balance = WalletBalance(0, available_tokens - block_reward) self.assertEqual(self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], wallet_balance) # Then we create a new tx that spends this tokens from multisig wallet tx = Transaction.create_from_struct(tx1.get_struct()) tx.weight = 10 tx.parents = self.manager.get_new_tx_parents() tx.timestamp = int(self.clock.seconds()) multisig_script = create_output_script(self.multisig_address) multisig_output = TxOutput(200, multisig_script) wallet_output = TxOutput(300, create_output_script(self.address)) outside_output = TxOutput(block_reward - 200 - 300, create_output_script(self.outside_address)) tx.outputs = [multisig_output, wallet_output, outside_output] tx_input = TxInput(tx1.hash, 0, b'') tx.inputs = [tx_input] signatures = [] for private_key_hex in self.private_keys: signature = generate_signature(tx, bytes.fromhex(private_key_hex), password=b'1234') signatures.append(signature) parser = create_parser() # Generate spend tx args = parser.parse_args([ tx.get_struct().hex(), '{},{}'.format(signatures[0].hex(), signatures[1].hex()), self.redeem_script.hex() ]) f = StringIO() with capture_logs(): with redirect_stdout(f): execute(args) # Transforming prints str in array output = f.getvalue().strip().splitlines() tx_raw = output[0].split(':')[1].strip() tx = Transaction.create_from_struct(bytes.fromhex(tx_raw)) self.assertTrue(self.manager.propagate_tx(tx, False))
def test_spend_multisig(self): # Adding funds to the wallet blocks = add_new_blocks(self.manager, 2, advance_clock=15) add_blocks_unlock_reward(self.manager) self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, sum(blk.outputs[0].value for blk in blocks))) first_block_amount = blocks[0].outputs[0].value # First we send tokens to a multisig address outputs = [ WalletOutputInfo(address=self.multisig_address, value=first_block_amount, timelock=int(self.clock.seconds()) + 15) ] tx1 = self.manager.wallet.prepare_transaction_compute_inputs( Transaction, outputs) tx1.weight = 10 tx1.parents = self.manager.get_new_tx_parents() tx1.timestamp = int(self.clock.seconds()) tx1.resolve() self.manager.propagate_tx(tx1) self.clock.advance(10) self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, first_block_amount)) # Then we create a new tx that spends this tokens from multisig wallet tx = Transaction.create_from_struct(tx1.get_struct()) tx.weight = 10 tx.parents = self.manager.get_new_tx_parents() tx.timestamp = int(self.clock.seconds()) multisig_script = create_output_script(self.multisig_address) multisig_output = TxOutput(200, multisig_script) wallet_output = TxOutput(300, create_output_script(self.address)) outside_output = TxOutput(first_block_amount - 200 - 300, create_output_script(self.outside_address)) tx.outputs = [multisig_output, wallet_output, outside_output] tx_input = TxInput(tx1.hash, 0, b'') tx.inputs = [tx_input] signatures = [] for private_key_hex in self.private_keys: signature = generate_signature(tx, bytes.fromhex(private_key_hex), password=b'1234') signatures.append(signature) input_data = MultiSig.create_input_data(self.redeem_script, signatures) tx.inputs[0].data = input_data tx.resolve() # Transaction is still locked self.assertFalse(self.manager.propagate_tx(tx)) self.clock.advance(6) tx.timestamp = int(self.clock.seconds()) tx.resolve() # First we try to propagate with a P2PKH input private_key_obj = get_private_key_from_bytes(bytes.fromhex( self.private_keys[0]), password=b'1234') pubkey_obj = private_key_obj.public_key() public_key_compressed = get_public_key_bytes_compressed(pubkey_obj) p2pkh_input_data = P2PKH.create_input_data(public_key_compressed, signatures[0]) tx2 = Transaction.create_from_struct(tx.get_struct()) tx2.inputs[0].data = p2pkh_input_data tx2.resolve() self.assertFalse(self.manager.propagate_tx(tx2)) # Now we propagate the correct self.assertTrue(self.manager.propagate_tx(tx)) self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, first_block_amount + 300)) # Testing the MultiSig class methods cls_script = parse_address_script(multisig_script) self.assertTrue(isinstance(cls_script, MultiSig)) self.assertEqual(cls_script.address, self.multisig_address_b58) expected_dict = { 'type': 'MultiSig', 'address': self.multisig_address_b58, 'timelock': None } self.assertEqual(cls_script.to_human_readable(), expected_dict) script_eval(tx, tx_input, tx1) # Script error with self.assertRaises(ScriptError): create_output_script( base58.b58decode('55d14K5jMqsN2uwUEFqiPG5SoD7Vr1BfnH'))
def test_balance_update_twin_tx(self): # Start balance self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.initial_balance)) wallet_address = self.manager.wallet.get_unused_address() outputs2 = [ WalletOutputInfo(address=decode_address(wallet_address), value=500, timelock=None) ] tx2 = self.manager.wallet.prepare_transaction_compute_inputs( Transaction, outputs2) tx2.weight = 10 tx2.parents = self.manager.get_new_tx_parents() tx2.timestamp = int(self.clock.seconds()) tx2.resolve() self.manager.propagate_tx(tx2) self.run_to_completion() outputs3 = [ WalletOutputInfo(address=decode_address(wallet_address), value=self.blocks_tokens[0], timelock=None) ] tx3 = self.manager.wallet.prepare_transaction_compute_inputs( Transaction, outputs3) tx3.weight = 10 tx3.parents = self.manager.get_new_tx_parents() tx3.timestamp = int(self.clock.seconds()) tx3.resolve() self.manager.propagate_tx(tx3) self.run_to_completion() self.clock.advance(1) new_address = self.manager.wallet.get_unused_address_bytes() inputs = [WalletInputInfo(tx_id=tx3.hash, index=0, private_key=None)] outputs = [ WalletOutputInfo(address=new_address, value=self.blocks_tokens[0], timelock=None) ] tx4 = self.manager.wallet.prepare_transaction_incomplete_inputs( Transaction, inputs, outputs, self.manager.tx_storage) tx4.weight = 10 tx4.parents = [tx3.hash, tx3.parents[0]] tx4.timestamp = int(self.clock.seconds()) tx4.resolve() self.manager.propagate_tx(tx4) self.run_to_completion() # Change of parents only, so it's a twin. tx5 = Transaction.create_from_struct(tx4.get_struct()) tx5.parents = [tx4.parents[1], tx4.parents[0]] tx5.weight = 10 tx5.resolve() # Propagate a conflicting twin transaction self.manager.propagate_tx(tx5) self.run_to_completion() meta4 = tx4.get_metadata(force_reload=True) self.assertEqual(meta4.twins, [tx5.hash]) meta5 = tx5.get_metadata(force_reload=True) self.assertEqual(meta5.voided_by, {tx5.hash}) # Balance is the same self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.initial_balance))
def test_tx_tips_voided(self): from hathor.wallet.base_wallet import WalletOutputInfo add_new_blocks(self.manager, 5, advance_clock=15) add_blocks_unlock_reward(self.manager) address1 = self.get_address(0) address2 = self.get_address(1) address3 = self.get_address(2) output1 = WalletOutputInfo(address=decode_address(address1), value=123, timelock=None) output2 = WalletOutputInfo(address=decode_address(address2), value=234, timelock=None) output3 = WalletOutputInfo(address=decode_address(address3), value=345, timelock=None) outputs = [output1, output2, output3] tx1 = self.manager.wallet.prepare_transaction_compute_inputs( Transaction, outputs, self.manager.tx_storage) tx1.weight = 2.0 tx1.parents = self.manager.get_new_tx_parents() tx1.timestamp = int(self.clock.seconds()) tx1.resolve() self.assertTrue(self.manager.propagate_tx(tx1, False)) self.assertEqual( { tx.hash for tx in self.manager.tx_storage.indexes.mempool_tips.iter( self.manager.tx_storage) }, {tx1.hash}) tx2 = self.manager.wallet.prepare_transaction_compute_inputs( Transaction, outputs, self.manager.tx_storage) tx2.weight = 2.0 tx2.parents = [tx1.hash] + self.manager.get_new_tx_parents()[1:] self.assertIn(tx1.hash, tx2.parents) tx2.timestamp = int(self.clock.seconds()) + 1 tx2.resolve() self.assertTrue(self.manager.propagate_tx(tx2, False)) self.assertEqual( { tx.hash for tx in self.manager.tx_storage.indexes.mempool_tips.iter( self.manager.tx_storage) }, {tx2.hash}) tx3 = Transaction.create_from_struct(tx2.get_struct()) tx3.weight = 3.0 # tx3.timestamp = tx2.timestamp + 1 tx3.parents = tx1.parents # self.assertIn(tx1.hash, tx3.parents) tx3.resolve() self.assertNotEqual(tx2.hash, tx3.hash) self.assertTrue(self.manager.propagate_tx(tx3, False)) # self.assertIn(tx3.hash, tx2.get_metadata().voided_by) self.assertIn(tx3.hash, tx2.get_metadata().conflict_with) self.assertEqual( { tx.hash for tx in self.manager.tx_storage.indexes.mempool_tips.iter( self.manager.tx_storage) }, # XXX: what should we expect here? I don't think we should exclude both tx2 and tx3, but maybe let the # function using the index decide {tx1.hash, tx3.hash})
def test_struct(self): tx = self._gen_tx_spending_genesis_block() data = tx.get_struct() tx_read = Transaction.create_from_struct(data) self.assertEqual(tx, tx_read)
def render_PUT(self, request): """ Updates a nano contract tx and returns it in hexadecimal format. Post data should be a json with the following items: hex_tx: tx being updated, in hex value new_values: List[{'address', 'value'}], with bet address and value input_value: amount this wallet should stake in the nano contract :rtype: string (json) """ request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'PUT') try: data = json.loads(request.content.read().decode('utf-8')) except json.JSONDecodeError: return json.dumps({'success': False, 'message': 'Invalid format for post data'}).encode('utf-8') for param in PARAMS_PUT: if param not in data: return get_missing_params_msg(param) try: decoded_params = self.decode_put_params(data) except ValueError as e: return json.dumps({'success': False, 'message': e.message}).encode('utf-8') try: tx = Transaction.create_from_struct(decoded_params.tx_bytes) except struct.error: return json.dumps({'success': False, 'message': 'Could not decode hex transaction'}).encode('utf-8') tx_outputs = [] nano_contract = None for _output in tx.outputs: _nano_contract = NanoContractMatchValues.parse_script(_output.script) if _nano_contract: total_value = _output.value nano_contract = _nano_contract else: tx_outputs.append(_output) if not nano_contract: return json.dumps({'success': False, 'message': 'Nano contract not found'}).encode('utf-8') for address, value in decoded_params.new_value_dict.items(): nano_contract.value_dict[address] = value tx.outputs = tx_outputs inputs, total_inputs_amount = self.manager.wallet.get_inputs_from_amount( decoded_params.input_value, self.manager.tx_storage ) change_tx = self.manager.wallet.handle_change_tx(total_inputs_amount, decoded_params.input_value) if change_tx: tx.outputs.append(TxOutput(change_tx.value, P2PKH.create_output_script(change_tx.address))) tx.outputs.insert(0, TxOutput(total_value, nano_contract.create_output_script())) for txin in inputs: tx.inputs.append(TxInput(txin.tx_id, txin.index, b'')) ret = {'success': True, 'hex_tx': tx.get_struct().hex()} return json.dumps(ret).encode('utf-8')
def execute(args: Namespace) -> None: from hathor.transaction import Transaction # Get tx you want to create a twin if args.url and args.hash: get_tx_url = urllib.parse.urljoin(args.url, 'transaction/') response = requests.get(get_tx_url, {b'id': bytes(args.hash, 'utf-8')}) try: data = response.json() except JSONDecodeError as e: print('Error decoding transaction data') print(e) return tx_bytes = bytes.fromhex(data['tx']['raw']) elif args.raw_tx: tx_bytes = bytes.fromhex(args.raw_tx) else: print('The command expects raw_tx or hash and url as parameters') return try: # Create new tx from the twin twin = Transaction.create_from_struct(tx_bytes) assert len(twin.parents) == 2 if args.parents: # If we want new parents we get the tips and select new ones get_tips_url = urllib.parse.urljoin(args.url, 'tips/') response = requests.get(get_tips_url) try: data = response.json() except JSONDecodeError as e: print('Error decoding tips') print(e) return parents = data[:2] if len(parents) == 0: print('No available tips to be selected as parents') return elif len(parents) == 1: parents = [parents[0], twin.parents[0].hex()] twin.parents = [ bytes.fromhex(parents[0]), bytes.fromhex(parents[1]) ] else: # Otherwise we just change the parents position, so we can have a different hash twin.parents = [twin.parents[1], twin.parents[0]] if args.weight: twin.weight = args.weight twin.resolve() if args.human: print(twin.to_json()) else: print(twin.get_struct().hex()) except (struct.error, ValueError): print('Error getting transaction from bytes') return