def gen_new_double_spending(manager: HathorManager, *, use_same_parents: bool = False) -> Transaction: tx_interval = random.choice(list(manager.tx_storage.get_tx_tips())) tx = manager.tx_storage.get_transaction(tx_interval.data) txin = random.choice(tx.inputs) from hathor.transaction.scripts import P2PKH, parse_address_script spent_tx = tx.get_spent_tx(txin) spent_txout = spent_tx.outputs[txin.index] p2pkh = parse_address_script(spent_txout.script) assert isinstance(p2pkh, P2PKH) from hathor.wallet.base_wallet import WalletInputInfo, WalletOutputInfo value = spent_txout.value private_key = manager.wallet.get_private_key(p2pkh.address) inputs = [WalletInputInfo(tx_id=txin.tx_id, index=txin.index, private_key=private_key)] address = manager.wallet.get_unused_address(mark_as_used=True) outputs = [WalletOutputInfo(address=decode_address(address), value=int(value), timelock=None)] tx2 = manager.wallet.prepare_transaction(Transaction, inputs, outputs, manager.tx_storage) tx2.storage = manager.tx_storage tx2.weight = 1 tx2.timestamp = max(tx.timestamp + 1, int(manager.reactor.seconds())) if use_same_parents: tx2.parents = list(tx.parents) else: tx2.parents = manager.get_new_tx_parents(tx2.timestamp) tx2.resolve() return tx2
def gen_custom_tx(manager: HathorManager, tx_inputs: List[Tuple[Transaction, int]], *, n_outputs: int = 1, base_parent: Optional[Transaction] = None, weight: Optional[float] = None) -> Transaction: """Generate a custom tx based on the inputs and outputs. It gives full control to the inputs and can be used to generate conflicts and specific patterns in the DAG.""" inputs = [] value = 0 parents = [] for tx_base, txout_index in tx_inputs: assert tx_base.hash is not None spent_tx = tx_base spent_txout = spent_tx.outputs[txout_index] p2pkh = parse_address_script(spent_txout.script) assert isinstance(p2pkh, P2PKH) from hathor.wallet.base_wallet import WalletInputInfo, WalletOutputInfo value += spent_txout.value wallet = manager.wallet assert wallet is not None assert spent_tx.hash is not None private_key = wallet.get_private_key(p2pkh.address) inputs.append(WalletInputInfo(tx_id=spent_tx.hash, index=txout_index, private_key=private_key)) if not tx_base.is_block: parents.append(tx_base.hash) assert wallet is not None address = wallet.get_unused_address(mark_as_used=True) if n_outputs == 1: outputs = [WalletOutputInfo(address=decode_address(address), value=int(value), timelock=None)] elif n_outputs == 2: assert int(value) > 1 outputs = [ WalletOutputInfo(address=decode_address(address), value=int(value) - 1, timelock=None), WalletOutputInfo(address=decode_address(address), value=1, timelock=None), ] else: raise NotImplementedError tx2 = wallet.prepare_transaction(Transaction, inputs, outputs) tx2.storage = manager.tx_storage tx2.timestamp = max(tx_base.timestamp + 1, int(manager.reactor.seconds())) tx2.parents = parents[:2] if len(tx2.parents) < 2: if base_parent: assert base_parent.hash is not None tx2.parents.append(base_parent.hash) elif not tx_base.is_block: tx2.parents.append(tx_base.parents[0]) else: tx2.parents.extend(manager.get_new_tx_parents(tx2.timestamp)) tx2.parents = tx2.parents[:2] assert len(tx2.parents) == 2 tx2.weight = weight or 25 tx2.update_hash() return tx2
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 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_transaction_and_balance(self): # generate a new block and check if we increase balance new_address = self.wallet.get_unused_address() out = WalletOutputInfo(decode_address(new_address), self.TOKENS, timelock=None) block = add_new_block(self.manager) block.verify() utxo = self.wallet.unspent_txs[settings.HATHOR_TOKEN_UID].get((block.hash, 0)) self.assertIsNotNone(utxo) self.assertEqual(self.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.BLOCK_TOKENS)) # create transaction spending this value, but sending to same wallet new_address2 = self.wallet.get_unused_address() out = WalletOutputInfo(decode_address(new_address2), self.TOKENS, timelock=None) tx1 = self.wallet.prepare_transaction_compute_inputs(Transaction, outputs=[out]) tx1.update_hash() tx1.verify_script(tx1.inputs[0], block) tx1.storage = self.tx_storage self.wallet.on_new_tx(tx1) self.tx_storage.save_transaction(tx1) self.assertEqual(len(self.wallet.spent_txs), 1) utxo = self.wallet.unspent_txs[settings.HATHOR_TOKEN_UID].get((tx1.hash, 0)) self.assertIsNotNone(utxo) self.assertEqual(self.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.TOKENS)) # pass inputs and outputs to prepare_transaction, but not the input keys # spend output last transaction input_info = WalletInputInfo(tx1.hash, 0, None) new_address3 = self.wallet.get_unused_address() out = WalletOutputInfo(decode_address(new_address3), self.TOKENS, timelock=None) tx2 = self.wallet.prepare_transaction_incomplete_inputs(Transaction, inputs=[input_info], outputs=[out], tx_storage=self.tx_storage) tx2.storage = self.tx_storage tx2.update_hash() tx2.storage = self.tx_storage tx2.verify_script(tx2.inputs[0], tx1) self.tx_storage.save_transaction(tx2) self.wallet.on_new_tx(tx2) self.assertEqual(len(self.wallet.spent_txs), 2) self.assertEqual(self.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, self.TOKENS)) # Test getting more unused addresses than the gap limit for i in range(3): kwargs = {'mark_as_used': True} if i == 2: # Last one we dont mark as used kwargs['mark_as_used'] = False self.wallet.get_unused_address(**kwargs)
def gen_new_double_spending(manager: HathorManager, *, use_same_parents: bool = False, tx: Optional[Transaction] = None, weight: float = 1) -> Transaction: if tx is None: tx_candidates = manager.get_new_tx_parents() genesis = manager.tx_storage.get_all_genesis() genesis_txs = [tx for tx in genesis if not tx.is_block] # XXX: it isn't possible to double-spend a genesis transaction, thus we remove it from tx_candidates for genesis_tx in genesis_txs: if genesis_tx.hash in tx_candidates: tx_candidates.remove(genesis_tx.hash) if not tx_candidates: raise NoCandidatesError() # assert tx_candidates, 'Must not be empty, otherwise test was wrongly set up' tx_hash = manager.rng.choice(tx_candidates) tx = cast(Transaction, manager.tx_storage.get_transaction(tx_hash)) txin = manager.rng.choice(tx.inputs) from hathor.transaction.scripts import P2PKH, parse_address_script spent_tx = tx.get_spent_tx(txin) spent_txout = spent_tx.outputs[txin.index] p2pkh = parse_address_script(spent_txout.script) assert isinstance(p2pkh, P2PKH) from hathor.wallet.base_wallet import WalletInputInfo, WalletOutputInfo value = spent_txout.value wallet = manager.wallet assert wallet is not None private_key = wallet.get_private_key(p2pkh.address) inputs = [WalletInputInfo(tx_id=txin.tx_id, index=txin.index, private_key=private_key)] address = wallet.get_unused_address(mark_as_used=True) outputs = [WalletOutputInfo(address=decode_address(address), value=int(value), timelock=None)] tx2 = wallet.prepare_transaction(Transaction, inputs, outputs) tx2.storage = manager.tx_storage tx2.weight = weight tx2.timestamp = max(tx.timestamp + 1, int(manager.reactor.seconds())) if use_same_parents: tx2.parents = list(tx.parents) else: tx2.parents = manager.get_new_tx_parents(tx2.timestamp) tx2.resolve() return tx2
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_wallet_create_transaction(self): genesis_private_key_bytes = get_private_key_bytes( self.genesis_private_key, encryption_algorithm=serialization.BestAvailableEncryption( PASSWORD)) genesis_address = get_address_b58_from_public_key( self.genesis_public_key) # create wallet with genesis block key key_pair = KeyPair(private_key_bytes=genesis_private_key_bytes, address=genesis_address, used=True) keys = {} keys[key_pair.address] = key_pair w = Wallet(keys=keys, directory=self.directory) w.unlock(PASSWORD) genesis_blocks = [ tx for tx in get_genesis_transactions(None) if tx.is_block ] genesis_block = genesis_blocks[0] genesis_value = sum([output.value for output in genesis_block.outputs]) # wallet will receive genesis block and store in unspent_tx w.on_new_tx(genesis_block) for index in range(len(genesis_block.outputs)): utxo = w.unspent_txs[settings.HATHOR_TOKEN_UID].get( (genesis_block.hash, index)) self.assertIsNotNone(utxo) self.assertEqual(w.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, genesis_value)) # create transaction spending this value, but sending to same wallet new_address = w.get_unused_address() out = WalletOutputInfo(decode_address(new_address), 100, timelock=None) tx1 = w.prepare_transaction_compute_inputs(Transaction, outputs=[out]) tx1.storage = self.storage tx1.update_hash() self.storage.save_transaction(tx1) w.on_new_tx(tx1) self.assertEqual(len(w.spent_txs), 1) self.assertEqual(w.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, genesis_value)) # pass inputs and outputs to prepare_transaction, but not the input keys # spend output last transaction input_info = WalletInputInfo(tx1.hash, 1, None) new_address = w.get_unused_address() key2 = w.keys[new_address] out = WalletOutputInfo(decode_address(key2.address), 100, timelock=None) tx2 = w.prepare_transaction_incomplete_inputs(Transaction, inputs=[input_info], outputs=[out], tx_storage=self.storage) tx2.storage = self.storage tx2.update_hash() self.storage.save_transaction(tx2) w.on_new_tx(tx2) self.assertEqual(len(w.spent_txs), 2) self.assertEqual(w.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, genesis_value)) # test keypair exception with self.assertRaises(WalletLocked): key_pair.get_private_key(None)
def test_timelock(self): blocks = add_new_blocks(self.manager, 5, advance_clock=15) blocks_tokens = [ sum(txout.value for txout in blk.outputs) for blk in blocks ] add_blocks_unlock_reward(self.manager) address = self.manager.wallet.get_unused_address() outside_address = self.get_address(0) outputs = [ WalletOutputInfo(address=decode_address(address), value=500, timelock=int(self.clock.seconds()) + 10), WalletOutputInfo(address=decode_address(address), value=700, timelock=int(self.clock.seconds()) - 10), WalletOutputInfo(address=decode_address(address), value=sum(blocks_tokens[:2]) - 500 - 700, 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.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(500, sum(blocks_tokens) - 500)) self.clock.advance(1) outputs1 = [ WalletOutputInfo(address=decode_address(outside_address), value=500, timelock=None) ] inputs1 = [WalletInputInfo(tx_id=tx1.hash, index=0, private_key=None)] tx2 = self.manager.wallet.prepare_transaction_incomplete_inputs( Transaction, inputs1, outputs1, self.manager.tx_storage) tx2.weight = 10 tx2.parents = self.manager.get_new_tx_parents() tx2.timestamp = int(self.clock.seconds()) tx2.resolve() propagated = self.manager.propagate_tx(tx2) self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(500, sum(blocks_tokens) - 500)) self.assertFalse(propagated) self.clock.advance(1) outputs2 = [ WalletOutputInfo(address=decode_address(outside_address), value=700, timelock=None) ] inputs2 = [WalletInputInfo(tx_id=tx1.hash, index=1, private_key=None)] tx3 = self.manager.wallet.prepare_transaction_incomplete_inputs( Transaction, inputs2, outputs2, self.manager.tx_storage) tx3.weight = 10 tx3.parents = self.manager.get_new_tx_parents() tx3.timestamp = int(self.clock.seconds()) tx3.resolve() propagated = self.manager.propagate_tx(tx3, False) self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(500, sum(blocks_tokens) - 500 - 700)) self.assertTrue(propagated) self.clock.advance(1) outputs3 = [ WalletOutputInfo(address=decode_address(outside_address), value=sum(blocks_tokens[:2]) - 500 - 700, timelock=None) ] inputs3 = [WalletInputInfo(tx_id=tx1.hash, index=2, private_key=None)] tx4 = self.manager.wallet.prepare_transaction_incomplete_inputs( Transaction, inputs3, outputs3, self.manager.tx_storage) tx4.weight = 10 tx4.parents = self.manager.get_new_tx_parents() tx4.timestamp = int(self.clock.seconds()) tx4.resolve() propagated = self.manager.propagate_tx(tx4, False) self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(500, sum(blocks_tokens[:3]))) self.assertTrue(propagated) self.clock.advance(8) tx2.timestamp = int(self.clock.seconds()) tx2.resolve() propagated = self.manager.propagate_tx(tx2, False) self.assertEqual( self.manager.wallet.balance[settings.HATHOR_TOKEN_UID], WalletBalance(0, sum(blocks_tokens[:3]))) self.assertTrue(propagated)
def render_POST(self, request): """ POST request for /wallet/send_tokens/ We expect 'data' as request args 'data': stringified json with an array of inputs and array of outputs If inputs array is empty we use 'prepare_compute_inputs', that calculate the inputs We return success (bool) :rtype: string (json) """ request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'POST') post_data = json.loads(request.content.read().decode('utf-8')) data = post_data['data'] outputs = [] for output in data['outputs']: try: address = decode_address(output['address']) # bytes except InvalidAddress: return self.return_POST( False, 'The address {} is invalid'.format(output['address'])) value = int(output['value']) timelock = output.get('timelock') token_uid = output.get('token_uid') if token_uid: outputs.append( WalletOutputInfo(address=address, value=value, timelock=timelock, token_uid=token_uid)) else: outputs.append( WalletOutputInfo(address=address, value=value, timelock=timelock)) timestamp = None if 'timestamp' in data: if data['timestamp'] > 0: timestamp = data['timestamp'] else: timestamp = int(self.manager.reactor.seconds()) if len(data['inputs']) == 0: try: inputs, outputs = self.manager.wallet.prepare_compute_inputs( outputs, self.manager.tx_storage, timestamp) except InsufficientFunds as e: return self.return_POST( False, 'Insufficient funds, {}'.format(str(e))) else: inputs = [] for input_tx in data['inputs']: input_tx['private_key'] = None input_tx['index'] = int(input_tx['index']) input_tx['tx_id'] = bytes.fromhex(input_tx['tx_id']) inputs.append(WalletInputInfo(**input_tx)) try: inputs = self.manager.wallet.prepare_incomplete_inputs( inputs, self.manager.tx_storage) except (PrivateKeyNotFound, InputDuplicated): return self.return_POST(False, 'Invalid input to create transaction') storage = self.manager.tx_storage if timestamp is None: max_ts_spent_tx = max( storage.get_transaction(txin.tx_id).timestamp for txin in inputs) timestamp = max(max_ts_spent_tx + 1, int(self.manager.reactor.seconds())) parents = self.manager.get_new_tx_parents(timestamp) values = { 'inputs': inputs, 'outputs': outputs, 'storage': storage, 'weight': data.get('weight'), 'parents': parents, 'timestamp': timestamp, } deferred = threads.deferToThread(self._render_POST_thread, values, request) deferred.addCallback(self._cb_tx_resolve, request) deferred.addErrback(self._err_tx_resolve, request) from twisted.web.server import NOT_DONE_YET return NOT_DONE_YET
def test_spending_voided(self) -> Generator: self.manager.wallet.unlock(b'MYPASS') add_new_blocks(self.manager, 5, advance_clock=15) add_blocks_unlock_reward(self.manager) # Push a first tx tx = self.get_tx() tx_hex = tx.get_struct().hex() response = yield self.push_tx({'hex_tx': tx_hex}) data = response.json_value() self.assertTrue(data['success']) wallet = self.manager.wallet # Pushing a tx that spends this first tx works txout = tx.outputs[0] p2pkh = parse_address_script(txout.script) assert p2pkh is not None private_key = wallet.get_private_key(p2pkh.address) assert tx.hash is not None inputs = [ WalletInputInfo(tx_id=tx.hash, index=0, private_key=private_key) ] outputs = [ WalletOutputInfo(address=decode_address(p2pkh.address), value=txout.value, timelock=None), ] tx2 = self.get_tx(inputs, outputs) tx2_hex = tx2.get_struct().hex() response = yield self.push_tx({'hex_tx': tx2_hex}) data = response.json_value() self.assertTrue(data['success']) # Now we set this tx2 as voided and try to push a tx3 that spends tx2 tx_meta = tx2.get_metadata() assert tx2.hash is not None tx_meta.voided_by = {tx2.hash} self.manager.tx_storage.save_transaction(tx2, only_metadata=True) inputs = [ WalletInputInfo(tx_id=tx2.hash, index=0, private_key=private_key) ] outputs = [ WalletOutputInfo(address=decode_address(p2pkh.address), value=txout.value, timelock=None), ] tx3 = self.get_tx(inputs, outputs) tx3_hex = tx3.get_struct().hex() response = yield self.push_tx({'hex_tx': tx3_hex}) data = response.json_value() self.assertFalse(data['success']) # Now we set this tx2 as voided and try to push a tx3 that spends tx2 tx_meta = tx2.get_metadata() tx_meta.voided_by = {settings.SOFT_VOIDED_ID} self.manager.tx_storage.save_transaction(tx2, only_metadata=True) # Try to push again with soft voided id as voided by response = yield self.push_tx({'hex_tx': tx3_hex}) data = response.json_value() self.assertFalse(data['success']) # Now without voided_by the push tx must succeed tx_meta = tx2.get_metadata() tx_meta.voided_by = None self.manager.tx_storage.save_transaction(tx2, only_metadata=True) response = yield self.push_tx({'hex_tx': tx3_hex}) data = response.json_value() self.assertTrue(data['success'])
def do_step(tx_fund): inputs = [ WalletInputInfo(tx_fund.hash, 0, manager.wallet.get_private_key(addr)) ] outputs = [WalletOutputInfo(decode_address(addr), 1, None)] tx1 = manager.wallet.prepare_transaction(Transaction, inputs, outputs, tx_fund.timestamp + 1) tx1.weight = 1 tx1.parents = manager.get_new_tx_parents(tx1.timestamp) tx1.resolve() self.assertTrue(manager.propagate_tx(tx1)) inputs = [] inputs.append( WalletInputInfo(tx1.hash, 0, manager.wallet.get_private_key(addr))) inputs.append( WalletInputInfo(tx_fund.hash, 1, manager.wallet.get_private_key(addr))) outputs = [ WalletOutputInfo(decode_address(addr), tx_fund.outputs[1].value + 1, None) ] tx2 = manager.wallet.prepare_transaction(Transaction, inputs, outputs, tx1.timestamp + 1) tx2.weight = 1 tx2.parents = manager.get_new_tx_parents(tx2.timestamp) tx2.resolve() self.assertTrue(manager.propagate_tx(tx2)) inputs = [ WalletInputInfo(tx_fund.hash, 0, manager.wallet.get_private_key(addr)) ] outputs = [WalletOutputInfo(decode_address(addr), 1, None)] tx3 = manager.wallet.prepare_transaction(Transaction, inputs, outputs, tx_fund.timestamp + 1) tx3.weight = tx1.weight + tx2.weight + 0.1 tx3.parents = manager.get_new_tx_parents(tx3.timestamp) tx3.resolve() self.assertTrue(manager.propagate_tx(tx3)) inputs = [ WalletInputInfo(tx_fund.hash, 1, manager.wallet.get_private_key(addr)) ] outputs = [ WalletOutputInfo(decode_address(addr), tx_fund.outputs[1].value, None) ] tx4 = manager.wallet.prepare_transaction(Transaction, inputs, outputs, tx_fund.timestamp + 1) tx4.weight = 1 tx4.parents = manager.get_new_tx_parents(tx4.timestamp) tx4.resolve() self.assertTrue(manager.propagate_tx(tx4)) inputs = [] inputs.append( WalletInputInfo(tx2.hash, 0, manager.wallet.get_private_key(addr))) inputs.append( WalletInputInfo(tx4.hash, 0, manager.wallet.get_private_key(addr))) outputs = [] outputs.append(WalletOutputInfo(decode_address(addr), 1, None)) outputs.append( WalletOutputInfo(decode_address(addr), 2 * tx_fund.outputs[1].value, None)) tx5 = manager.wallet.prepare_transaction(Transaction, inputs, outputs, tx2.timestamp + 1) tx5.weight = tx3.weight - tx1.weight + 0.1 tx5.parents = [tx2.hash, tx4.hash] tx5.resolve() self.assertTrue(manager.propagate_tx(tx5)) return tx5
def test_double_spending_propagation(self): blocks = add_new_blocks(self.manager1, 4, advance_clock=15) add_blocks_unlock_reward(self.manager1) from hathor.transaction import Transaction from hathor.wallet.base_wallet import WalletInputInfo, WalletOutputInfo # --- # tx1 and tx4 spends the same output (double spending) # tx2 spends one tx1's input # tx3 verifies tx1, but does not spend any of tx1's inputs # tx5 spends one tx4's input # tx6 is a twin of tx3, but verifying tx4 and tx5 # tx7 verifies tx4, but does not spend any of tx4's inputs # --- # tx1.weight = 5 # tx2.weight = 5 # tx3.weight = 5 # tx4.weight = 5 # tx5.weight = 5 # tx6.weight = 1 # tx7.weight = 10 # --- address = self.manager1.wallet.get_unused_address_bytes() value = 100 outputs = [WalletOutputInfo(address=address, value=int(value), timelock=None)] self.clock.advance(1) tx1 = self.manager1.wallet.prepare_transaction_compute_inputs(Transaction, outputs, self.manager1.tx_storage) tx1.weight = 5 tx1.parents = self.manager1.get_new_tx_parents() tx1.timestamp = int(self.clock.seconds()) tx1.resolve() address = self.manager1.wallet.get_unused_address_bytes() value = 500 tx_total_value = sum(txout.value for txout in tx1.outputs) outputs = [WalletOutputInfo(address=address, value=value, timelock=None), WalletOutputInfo(address=address, value=tx_total_value - 500, timelock=None)] self.clock.advance(1) inputs = [WalletInputInfo(i.tx_id, i.index, b'') for i in tx1.inputs] tx4 = self.manager1.wallet.prepare_transaction_incomplete_inputs(Transaction, inputs, outputs, self.manager1.tx_storage) tx4.weight = 5 tx4.parents = self.manager1.get_new_tx_parents() tx4.timestamp = int(self.clock.seconds()) tx4.resolve() self.assertEqual(tx1.inputs[0].tx_id, tx4.inputs[0].tx_id) self.assertEqual(tx1.inputs[0].index, tx4.inputs[0].index) # --- self.clock.advance(15) self.assertTrue(self.manager1.propagate_tx(tx1)) print('tx1', tx1.hash.hex()) self.clock.advance(15) # --- address = self.manager1.wallet.get_unused_address_bytes() value = 100 inputs = [WalletInputInfo(tx_id=tx1.hash, index=1, private_key=None)] outputs = [WalletOutputInfo(address=address, value=int(value), timelock=None)] self.clock.advance(1) tx2 = self.manager1.wallet.prepare_transaction_incomplete_inputs(Transaction, inputs, outputs, self.manager1.tx_storage) tx2.weight = 5 tx2.parents = tx1.parents tx2.timestamp = int(self.clock.seconds()) tx2.resolve() self.clock.advance(15) self.manager1.propagate_tx(tx2) print('tx2', tx2.hash.hex()) self.clock.advance(15) self.assertGreater(tx2.timestamp, tx1.timestamp) # --- address = self.manager1.wallet.get_unused_address_bytes() value = 500 outputs = [WalletOutputInfo(address=address, value=int(value), timelock=None)] self.clock.advance(1) tx3 = self.manager1.wallet.prepare_transaction_compute_inputs(Transaction, outputs, self.manager1.tx_storage) self.assertNotEqual(tx3.inputs[0].tx_id, tx1.hash) self.assertNotEqual(tx3.inputs[0].tx_id, tx2.hash) tx3.weight = 5 tx3.parents = [tx1.hash, tx1.parents[0]] tx3.timestamp = int(self.clock.seconds()) tx3.resolve() self.clock.advance(15) self.assertTrue(self.manager1.propagate_tx(tx3)) print('tx3', tx3.hash.hex()) self.clock.advance(15) # --- self.clock.advance(15) self.assertTrue(self.manager1.propagate_tx(tx4, False)) print('tx4', tx4.hash.hex()) self.clock.advance(15) self.run_to_completion() meta1 = tx1.get_metadata(force_reload=True) meta4 = tx4.get_metadata(force_reload=True) self.assertEqual(meta1.conflict_with, [tx4.hash]) self.assertEqual(meta1.voided_by, None) self.assertEqual(meta4.conflict_with, [tx1.hash]) self.assertEqual(meta4.voided_by, {tx4.hash}) # --- address = self.manager1.wallet.get_unused_address_bytes() value = 500 inputs = [WalletInputInfo(tx_id=tx4.hash, index=0, private_key=None)] outputs = [WalletOutputInfo(address=address, value=int(value), timelock=None)] self.clock.advance(1) tx5 = self.manager1.wallet.prepare_transaction_incomplete_inputs(Transaction, inputs, outputs, force=True, tx_storage=self.manager1.tx_storage) tx5.weight = 5 tx5.parents = tx1.parents tx5.timestamp = int(self.clock.seconds()) tx5.resolve() self.clock.advance(15) self.manager1.propagate_tx(tx5) print('tx5', tx5.hash.hex()) self.clock.advance(15) meta5 = tx5.get_metadata() self.assertEqual(meta5.conflict_with, None) self.assertEqual(meta5.voided_by, {tx4.hash}) # --- self.clock.advance(1) tx6 = Transaction.create_from_struct(tx3.get_struct()) tx6.weight = 1 tx6.parents = [tx4.hash, tx5.hash] tx6.timestamp = int(self.clock.seconds()) tx6.resolve() self.clock.advance(15) self.manager1.propagate_tx(tx6) print('tx6', tx6.hash.hex()) self.clock.advance(15) meta6 = tx6.get_metadata() self.assertEqual(meta6.conflict_with, [tx3.hash]) self.assertEqual(meta6.voided_by, {tx4.hash, tx6.hash}) # --- address = self.manager1.wallet.get_unused_address_bytes() value = blocks[3].outputs[0].value inputs = [WalletInputInfo(tx_id=blocks[3].hash, index=0, private_key=None)] outputs = [WalletOutputInfo(address=address, value=value, timelock=None)] self.clock.advance(1) tx7 = self.manager1.wallet.prepare_transaction_incomplete_inputs(Transaction, inputs, outputs, self.manager1.tx_storage) tx7.weight = 10 tx7.parents = [tx4.hash, tx5.hash] tx7.timestamp = int(self.clock.seconds()) tx7.resolve() self.clock.advance(15) self.manager1.propagate_tx(tx7, False) print('tx7', tx7.hash.hex()) self.clock.advance(15) meta1 = tx1.get_metadata(force_reload=True) meta2 = tx2.get_metadata(force_reload=True) meta3 = tx3.get_metadata(force_reload=True) self.assertEqual(meta1.voided_by, {tx1.hash}) self.assertEqual(meta2.voided_by, {tx1.hash}) self.assertEqual(meta3.voided_by, {tx1.hash, tx3.hash}) meta4 = tx4.get_metadata(force_reload=True) meta5 = tx5.get_metadata(force_reload=True) meta6 = tx6.get_metadata(force_reload=True) meta7 = tx7.get_metadata(force_reload=True) self.assertEqual(meta4.voided_by, None) self.assertEqual(meta5.voided_by, None) self.assertEqual(meta6.voided_by, None) self.assertEqual(meta7.voided_by, None) blocks = add_new_blocks(self.manager1, 1, advance_clock=15) add_blocks_unlock_reward(self.manager1) self._add_new_transactions(self.manager1, 10) blocks = add_new_blocks(self.manager1, 1, advance_clock=15) add_blocks_unlock_reward(self.manager1) self._add_new_transactions(self.manager1, 10) blocks = add_new_blocks(self.manager1, 1, advance_clock=15) self.assertConsensusValid(self.manager1)