def _render_GET_thread(self, request: Request) -> bytes: """ GET request /graphviz/neighbours.{format} Returns the rendered graph file """ set_cors(request, 'GET') tx_storage = self.manager.tx_storage tx_hex = request.args[b'tx'][0].decode('utf-8') success, message = validate_tx_hash(tx_hex, tx_storage) if not success: return json.dumps({ 'success': False, 'message': message }, indent=4).encode('utf-8') graph_type = request.args[b'graph_type'][0].decode('utf-8') max_level = min(int(request.args[b'max_level'][0]), settings.MAX_GRAPH_LEVEL) tx = tx_storage.get_transaction(bytes.fromhex(tx_hex)) graphviz = GraphvizVisualizer(tx_storage) dot = graphviz.tx_neighborhood(tx, format=self.format.dot, max_level=max_level, graph_type=graph_type) request.setHeader(b'content-type', self.format.content_type) if self.format is FileFormat.DOT: return str(dot).encode('utf-8') return dot.pipe()
def _render_GET_thread(self, request: Request) -> bytes: """ GET request /graphviz/full.{format} Returns the rendered graph file """ set_cors(request, 'GET') tx_storage = self.manager.tx_storage graphviz = GraphvizVisualizer(tx_storage) if b'weight' in request.args: graphviz.show_weight = self.parse_bool_arg( request.args[b'weight'][0].decode('utf-8')) if b'acc_weight' in request.args: graphviz.show_acc_weight = self.parse_bool_arg( request.args[b'acc_weight'][0].decode('utf-8')) if b'verifications' in request.args: graphviz.include_verifications = self.parse_bool_arg( request.args[b'verifications'][0].decode('utf-8')) if b'funds' in request.args: graphviz.include_funds = self.parse_bool_arg( request.args[b'funds'][0].decode('utf-8')) if b'only_blocks' in request.args: graphviz.only_blocks = self.parse_bool_arg( request.args[b'only_blocks'][0].decode('utf-8')) dot = graphviz.dot(format=self.format.dot) request.setHeader(b'content-type', self.format.content_type) if self.format is FileFormat.DOT: return str(dot).encode('utf-8') return dot.pipe()
def test_two_conflicts_intertwined_once(self): manager1 = self.create_peer() manager1.allow_mining_without_peers() miner1 = self.simulator.create_miner(manager1, hashpower=10e6) miner1.start() self.simulator.run(60) gen_tx1 = self.simulator.create_tx_generator(manager1, rate=3 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx1.start() self.simulator.run(300) gen_tx1.stop() # Our full node wallet has a callLater that checks for new utxos every 10 seconds. # If we don't run 10 seconds, the utxos generated on the create_tx_generator won't be available, # then we might get an insufficient fund error to create the next tx self.simulator.run(10) self.graphviz = GraphvizVisualizer(manager1.tx_storage, include_verifications=True, include_funds=True) address = manager1.wallet.get_unused_address(mark_as_used=False) value = 10 initial = gen_new_tx(manager1, address, value) initial.weight = 25 initial.update_hash() manager1.propagate_tx(initial, fails_silently=False) self.graphviz.labels[initial.hash] = 'initial' x = initial x = self.do_step(0, manager1, x)
def on_new_tx(self, key: HathorEvents, args: EventArguments) -> None: """ This method is called every change in the DAG. It saves a new snapshot in disk. """ if not self.is_running: return n = self.sequence tx_storage = self.manager.tx_storage graphviz = GraphvizVisualizer(tx_storage) graphviz.include_verifications = True graphviz.include_funds = True dot = graphviz.dot(format=self.format) dot.render(os.path.join(self.dirname, 'seq_{:010d}'.format(n))) self.sequence += 1
def test_mempool_basic(self): # 10 blocks self._add_new_blocks(2) # N blocks to unlock the reward add_blocks_unlock_reward(self.manager1) # 5 transactions to be confirmed by the next blocks self._add_new_transactions(5) # 2 more blocks self._add_new_blocks(2) # 30 transactions in the mempool self._add_new_transactions(30) debug_pdf = False if debug_pdf: dot1 = GraphvizVisualizer(self.manager1.tx_storage, include_verifications=True, include_funds=True).dot() dot1.render('mempool-test') manager2 = self.create_peer(self.network, enable_sync_v1=True) self.assertEqual(manager2.state, manager2.NodeState.READY) conn = FakeConnection(self.manager1, manager2) for _ in range(1000): if conn.is_empty(): break conn.run_one_step(debug=True) self.clock.advance(1) self.assertConsensusValid(self.manager1) self.assertConsensusValid(manager2) self.assertConsensusEqual(self.manager1, manager2) # 3 genesis # 25 blocks # Unlock reward blocks # 8 txs self.assertEqual(len(manager2.tx_storage.indexes.mempool_tips.get()), 1) self.assertEqual( len(self.manager1.tx_storage.indexes.mempool_tips.get()), 1)
def test_conflict_with_parent_tx(self): manager1 = self.create_peer() manager1.allow_mining_without_peers() self.graphviz = GraphvizVisualizer(manager1.tx_storage, include_verifications=True, include_funds=True) b1 = manager1.generate_mining_block() b1.nonce = self.rng.getrandbits(32) b1.update_hash() self.graphviz.labels[b1.hash] = 'b1' self.assertTrue(manager1.propagate_tx(b1)) self.simulator.run(10) self.create_chain(manager1, b1.hash, 20, '') txA1 = gen_custom_tx(manager1, [(b1, 0)]) self.graphviz.labels[txA1.hash] = 'txA1' self.assertTrue(manager1.propagate_tx(txA1)) self.simulator.run(10) txA2 = gen_custom_tx(manager1, [(b1, 0)]) self.graphviz.labels[txA2.hash] = 'txA2' self.assertTrue(manager1.propagate_tx(txA2)) self.simulator.run(10) b2 = manager1.generate_mining_block() b2.weight = max(b2.weight, 40) b2.parents[1:] = [txA1.parents[0], txA1.hash] b2.nonce = self.rng.getrandbits(32) b2.update_hash() self.graphviz.labels[b2.hash] = 'b2' self.assertTrue(manager1.propagate_tx(b2, fails_silently=False)) self.simulator.run(10) self.assertIsNone(txA1.get_metadata().voided_by) self.assertEqual({txA2.hash}, txA2.get_metadata().voided_by) txC1 = gen_custom_tx(manager1, [(txA2, 0)]) self.graphviz.labels[txC1.hash] = 'txC1' self.assertTrue(manager1.propagate_tx(txC1)) txC2 = gen_custom_tx(manager1, [(txA2, 0)]) self.graphviz.labels[txC2.hash] = 'txC2' self.assertTrue(manager1.propagate_tx(txC2)) self.assertEqual({txA2.hash, txC1.hash}, txC1.get_metadata().voided_by) self.assertEqual({txA2.hash, txC2.hash}, txC2.get_metadata().voided_by)
def test_conflict_with_parent_tx(self): manager1 = self.create_peer() manager1.allow_mining_without_peers() self.graphviz = GraphvizVisualizer(manager1.tx_storage, include_verifications=True, include_funds=True) b1 = manager1.generate_mining_block() b1.nonce = self.rng.getrandbits(32) b1.update_hash() self.graphviz.labels[b1.hash] = 'b1' self.assertTrue(manager1.propagate_tx(b1)) self.simulator.run(10) A_list = self.create_chain(manager1, b1.hash, 15, 'A-') tx1 = gen_custom_tx(manager1, [(A_list[0], 0)]) tx1.parents = manager1.get_new_tx_parents(tx1.timestamp) tx1.update_hash() self.graphviz.labels[tx1.hash] = 'tx1' self.assertTrue(manager1.propagate_tx(tx1)) tx2 = gen_custom_tx(manager1, [(tx1, 0)]) tx2.parents = manager1.get_new_tx_parents(tx2.timestamp) tx2.update_hash() self.graphviz.labels[tx2.hash] = 'tx2' self.assertTrue(manager1.propagate_tx(tx2)) tx31 = gen_custom_tx(manager1, [(tx2, 0)]) self.graphviz.labels[tx31.hash] = 'tx3-1' self.assertTrue(manager1.propagate_tx(tx31)) tx32 = gen_custom_tx(manager1, [(tx2, 0)]) tx32.parents = [tx31.hash, tx2.hash] tx32.timestamp = tx31.timestamp + 1 tx32.update_hash() self.graphviz.labels[tx32.hash] = 'tx3-2' self.assertTrue(manager1.propagate_tx(tx32)) self.assertIsNone(tx31.get_metadata().voided_by) self.assertEqual({tx32.hash}, tx32.get_metadata().voided_by) self.create_chain(manager1, b1.hash, 20, 'B-', tx_parents=b1.parents[1:]) self.assertEqual({A_list[0].hash, tx31.hash}, tx31.get_metadata().voided_by) self.assertEqual({A_list[0].hash, tx31.hash, tx32.hash}, tx32.get_metadata().voided_by)
def test_split_brain(self): debug_pdf = False manager1 = self.create_peer() manager1.allow_mining_without_peers() manager2 = self.create_peer() manager2.allow_mining_without_peers() miner11 = self.simulator.create_miner(manager1, hashpower=10e6) miner11.start() gen_tx11 = self.simulator.create_tx_generator(manager1, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx11.start() gen_tx12 = self.simulator.create_tx_generator(manager1, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx12.enable_double_spending() gen_tx12.start() miner21 = self.simulator.create_miner(manager2, hashpower=10e6) miner21.start() gen_tx21 = self.simulator.create_tx_generator(manager2, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx21.start() gen_tx22 = self.simulator.create_tx_generator(manager2, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx22.enable_double_spending() gen_tx22.start() self.simulator.run(400) if debug_pdf: dot1 = GraphvizVisualizer(manager1.tx_storage, include_verifications=True, include_funds=True).dot() dot1.render('dot1-pre') dot2 = GraphvizVisualizer(manager2.tx_storage, include_verifications=True, include_funds=True).dot() dot2.render('dot2-pre') self.assertTipsNotEqual(manager1, manager2) self.assertConsensusValid(manager1) self.assertConsensusValid(manager2) # input('Press enter to continue...') miner11.stop() gen_tx11.stop() gen_tx12.stop() miner21.stop() gen_tx21.stop() gen_tx22.stop() conn12 = FakeConnection(manager1, manager2) self.simulator.add_connection(conn12) self.simulator.run(300) if debug_pdf: dot1 = GraphvizVisualizer(manager1.tx_storage, include_verifications=True).dot() dot1.render('dot1-post') dot2 = GraphvizVisualizer(manager2.tx_storage, include_verifications=True).dot() dot2.render('dot2-post') node_sync = conn12.proto1.state.sync_manager self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp) node_sync = conn12.proto2.state.sync_manager self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp) self.assertTipsEqual(manager1, manager2) self.assertConsensusEqual(manager1, manager2) self.assertConsensusValid(manager1) self.assertConsensusValid(manager2)
def test_split_brain(self): debug_pdf = False manager1 = self.create_peer(self.network, unlock_wallet=True) manager1.avg_time_between_blocks = 3 manager2 = self.create_peer(self.network, unlock_wallet=True) manager2.avg_time_between_blocks = 3 for _ in range(10): add_new_block(manager1, advance_clock=1) add_blocks_unlock_reward(manager1) add_new_block(manager2, advance_clock=1) add_blocks_unlock_reward(manager2) self.clock.advance(10) for _ in range(random.randint(3, 10)): add_new_transactions(manager1, random.randint(2, 4)) add_new_transactions(manager2, random.randint(3, 7)) add_new_double_spending(manager1) add_new_double_spending(manager2) self.clock.advance(10) self.clock.advance(20) self.assertTipsNotEqual(manager1, manager2) self.assertConsensusValid(manager1) self.assertConsensusValid(manager2) if debug_pdf: dot1 = GraphvizVisualizer(manager1.tx_storage, include_verifications=True).dot() dot1.render('dot1-pre') conn = FakeConnection(manager1, manager2) conn.run_one_step() # HELLO conn.run_one_step() # PEER-ID empty_counter = 0 for i in range(1000): if conn.is_empty(): empty_counter += 1 if empty_counter > 10: break else: empty_counter = 0 conn.run_one_step() self.clock.advance(0.2) if debug_pdf: dot1 = GraphvizVisualizer(manager1.tx_storage, include_verifications=True).dot() dot1.render('dot1-post') dot2 = GraphvizVisualizer(manager2.tx_storage, include_verifications=True).dot() dot2.render('dot2-post') node_sync = conn.proto1.state.get_sync_plugin() self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp) self.assertTipsEqual(manager1, manager2) self.assertConsensusEqual(manager1, manager2) # self.assertConsensusValid(manager1) self.assertConsensusValid(manager2)
def _render_GET_thread(self, request: Request) -> bytes: """ GET request /graphviz/ Expects 'format' parameter in request to set the content-type of the graph Format options are 'pdf', 'png' and 'jpg'. Default format is 'pdf' Returns the file """ set_cors(request, 'GET') contenttype = { 'pdf': b'application/pdf', 'png': b'image/png', 'jpg': b'image/jpeg', 'dot': b'application/dot', } dotformat = 'pdf' if b'format' in request.args: dotformat = request.args[b'format'][0].decode('utf-8') tx_storage = self.manager.tx_storage if b'tx' in request.args: # Getting tx neightborhood tx_hex = request.args[b'tx'][0].decode('utf-8') success, message = validate_tx_hash(tx_hex, tx_storage) if not success: return json.dumps({ 'success': False, 'message': message }, indent=4).encode('utf-8') else: graph_type = request.args[b'graph_type'][0].decode('utf-8') max_level = int(request.args[b'max_level'][0]) if max_level > settings.MAX_GRAPH_LEVEL: return json.dumps( { 'success': False, 'message': 'Graph max level is {}'.format( settings.MAX_GRAPH_LEVEL) }, indent=4).encode('utf-8') tx = tx_storage.get_transaction(bytes.fromhex(tx_hex)) graphviz = GraphvizVisualizer(tx_storage) dot = graphviz.tx_neighborhood(tx, format=dotformat, max_level=max_level, graph_type=graph_type) else: weight = False if b'weight' in request.args: weight = self.parseBoolArg( request.args[b'weight'][0].decode('utf-8')) acc_weight = False if b'acc_weight' in request.args: acc_weight = self.parseBoolArg( request.args[b'acc_weight'][0].decode('utf-8')) include_verifications = True if b'verifications' in request.args: include_verifications = self.parseBoolArg( request.args[b'verifications'][0].decode('utf-8')) include_funds = False if b'funds' in request.args: include_funds = self.parseBoolArg( request.args[b'funds'][0].decode('utf-8')) only_blocks = False if b'only_blocks' in request.args: only_blocks = self.parseBoolArg( request.args[b'only_blocks'][0].decode('utf-8')) graphviz = GraphvizVisualizer(tx_storage) graphviz.include_verifications = include_verifications graphviz.include_funds = include_funds graphviz.only_blocks = only_blocks graphviz.show_weight = weight graphviz.show_acc_weight = acc_weight dot = graphviz.dot(format=dotformat) if dotformat == 'dot': request.setHeader(b'content-type', contenttype[dotformat]) return str(dot).encode('utf-8') request.setHeader(b'content-type', contenttype[dotformat]) return dot.pipe()
def test_soft_voided(self): soft_voided_tx_ids = set([ bytes.fromhex( '30d49cf336ceb8528a918bed25b729febd3ebc3a8449d5e840aac865d0ca407f' ), bytes.fromhex( '875ef2cdf7405f82f20e0ba115cd2fe07d8c60c50f763f7c48e8d673f56ff4e4' ), bytes.fromhex( 'ef0b5e356b82253c8b0f7f078396bd435605dc297b38af3d9623b88ffab43b41' ), bytes.fromhex( 'aba55041658213b0bfda880045e5a321cb3d0a88cffa2833e620a2ed1ba27451' ), bytes.fromhex( '43f1782cc7d5702378d067daefea0460cd1976e5479e17accb6f412be8d26ff5' ), bytes.fromhex( '24fe23d0128c85bc149963ff904aefe1c11b44b0e0cddad344b308b34d1dbac8' ), bytes.fromhex( 'a6b7b21e4020a70d77fd7467184c65bfb2e4443a0b465b6ace05c1b11b82355b' ), bytes.fromhex( '982e9a2a3f0b1d22eead5ca8cf9a86a5d9edd981ac06f977245a2e664a752e5f' ), bytes.fromhex( 'b6cddd95fe02c035fde820add901545662c7b3ae85010cde5914b86cb0a6505e' ), bytes.fromhex( '3a84cff789d3263fea4da636da5c54cb0981a4a0caec1579037a4fa06457f8f3' ), ]) manager1 = self.create_peer(soft_voided_tx_ids=soft_voided_tx_ids) manager1.allow_mining_without_peers() miner1 = self.simulator.create_miner(manager1, hashpower=10e6) miner1.start() self.simulator.run(60) gen_tx1 = self.simulator.create_tx_generator(manager1, rate=3 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx1.start() self.simulator.run(300) self.graphviz = GraphvizVisualizer(manager1.tx_storage, include_verifications=True, include_funds=True) address = manager1.wallet.get_unused_address(mark_as_used=False) value = 10 initial = gen_new_tx(manager1, address, value) initial.weight = 25 initial.update_hash() manager1.propagate_tx(initial, fails_silently=False) self.graphviz.labels[initial.hash] = 'initial' x = initial b0 = self.gen_block(manager1, x) self.graphviz.labels[b0.hash] = 'b0' x = self.do_step(0, manager1, x) b1 = self.gen_block(manager1, x, parent_block=b0) self.graphviz.labels[b1.hash] = 'b1' x = self.do_step(1, manager1, x) b2 = self.gen_block(manager1, x, parent_block=b1) self.graphviz.labels[b2.hash] = 'b2' x = self.do_step(2, manager1, x) b3 = self.gen_block(manager1, x, parent_block=b2) self.graphviz.labels[b3.hash] = 'b3' x = self.do_step(3, manager1, x) b4 = self.gen_block(manager1, x, parent_block=b3) self.graphviz.labels[b4.hash] = 'b4' x = self.do_step(4, manager1, x) b5 = self.gen_block(manager1, x, parent_block=b4) self.graphviz.labels[b5.hash] = 'b5' self.assertIsNone(b0.get_metadata().voided_by) self.assertIsNone(b1.get_metadata().voided_by) self.assertIsNone(b2.get_metadata().voided_by) self.assertIsNone(b3.get_metadata().voided_by) self.assertIsNone(b4.get_metadata().voided_by) self.assertIsNone(b5.get_metadata().voided_by) for tx in manager1.tx_storage.get_all_transactions(): meta = tx.get_metadata() voided_by = meta.voided_by or set() if settings.SOFT_VOIDED_ID in voided_by: self.assertTrue({settings.SOFT_VOIDED_ID, tx.hash}.issubset(voided_by)) txF1 = self.txF1_0 txF2 = self.txF2_0 txB = self.txB_0 txB_meta = txB.get_metadata() txB_spent_list = txB_meta.spent_outputs[0] self.assertEqual(set(txB_spent_list), {txF1.hash, txF2.hash}) self.assertIsNone(txB_meta.get_output_spent_by(0)) txD1 = self.txD1_0 txD1_meta = txD1.get_metadata() txD1_spent_list = txD1_meta.spent_outputs[0] self.assertEqual([txF2.hash], txD1_spent_list) self.assertIsNone(txD1_meta.get_output_spent_by(0))
def test_soft_voided(self): txA_hash = bytes.fromhex('4586c5428e8d666ea59684c1cd9286d2b9d9e89b4939207db47412eeaabc48b2') txB_hash = bytes.fromhex('d19bee149abdce63bbfd523c90c3cf110563970a34100b2a62583a1eba48dfc8') soft_voided_tx_ids = set([ txA_hash, txB_hash, ]) manager1 = self.create_peer(soft_voided_tx_ids=soft_voided_tx_ids) manager1.allow_mining_without_peers() miner1 = self.simulator.create_miner(manager1, hashpower=5e6) miner1.start() self.simulator.run(60) gen_tx1 = self.simulator.create_tx_generator(manager1, rate=3 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx1.start() self.simulator.run(300) manager2 = self.create_peer(soft_voided_tx_ids=soft_voided_tx_ids) manager2.soft_voided_tx_ids = soft_voided_tx_ids self.graphviz = GraphvizVisualizer(manager2.tx_storage, include_verifications=True, include_funds=True) conn12 = FakeConnection(manager1, manager2, latency=0.001) self.simulator.add_connection(conn12) miner2 = self.simulator.create_miner(manager2, hashpower=10e6) miner2.start() gen_tx2 = self.simulator.create_tx_generator(manager2, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx2.start() self.simulator.run(900) miner2.stop() gen_tx2.stop() txB = manager2.tx_storage.get_transaction(txB_hash) # Get the tx confirmed by the soft voided that will be voided tx_base = manager2.tx_storage.get_transaction(txB.parents[0]) txC = gen_new_double_spending(manager2, use_same_parents=False, tx=tx_base) txC.weight = 25 txC.parents = tx_base.parents txC.update_hash() self.graphviz.labels[txC.hash] = 'txC' self.assertTrue(manager2.propagate_tx(txC, fails_silently=False)) metaC = txC.get_metadata() self.assertIsNone(metaC.voided_by) meta_base = tx_base.get_metadata() self.assertEqual(meta_base.voided_by, {tx_base.hash}) # Create 2 blocks confirming C in order to keep this voidance when we add # the block confirming the soft voided tx blk1 = manager2.generate_mining_block() if txC.hash not in blk1.parents: blk1.parents[1] = txC.hash blk1.nonce = self.rng.getrandbits(32) blk1.update_hash() self.assertTrue(manager2.propagate_tx(blk1, fails_silently=False)) blk1meta = blk1.get_metadata() self.assertIsNone(blk1meta.voided_by) blk2 = manager2.generate_mining_block() if txC.hash not in blk2.parents: blk2.parents[1] = txC.hash blk2.nonce = self.rng.getrandbits(32) blk2.update_hash() self.assertTrue(manager2.propagate_tx(blk2, fails_silently=False)) blk2meta = blk2.get_metadata() self.assertIsNone(blk2meta.voided_by) # Create block that confirms soft voided blk3 = manager2.generate_mining_block() if txB.hash not in blk3.parents: blk3.parents[1] = txB.hash blk3.nonce = self.rng.getrandbits(32) blk3.update_hash() self.assertTrue(manager2.propagate_tx(blk3, fails_silently=False)) blk3meta = blk3.get_metadata() self.simulator.run(10) txD = add_custom_tx(manager2, [(txC, 0)], base_parent=txB) # dot = self.graphviz.dot() # dot.render('dot0') blk3meta = blk3.get_metadata() self.assertEqual(blk3meta.voided_by, {tx_base.hash, blk3meta.hash}) metaD = txD.get_metadata() self.assertEqual(metaD.voided_by, {tx_base.hash})
def test_double_spending_attempt_1(self): manager = self.manager1 add_new_blocks(manager, 5, advance_clock=15) add_blocks_unlock_reward(manager) from hathor.crypto.util import decode_address from hathor.graphviz import GraphvizVisualizer from hathor.transaction import Transaction from hathor.wallet.base_wallet import WalletInputInfo, WalletOutputInfo graphviz = GraphvizVisualizer(manager.tx_storage, include_verifications=True, include_funds=True) addr = manager.wallet.get_unused_address() outputs = [] outputs.append(WalletOutputInfo(decode_address(addr), 1, None)) outputs.append(WalletOutputInfo(decode_address(addr), 1000, None)) outputs.append( WalletOutputInfo(decode_address(addr), 6400 - 1001, None)) tx_fund0 = manager.wallet.prepare_transaction_compute_inputs( Transaction, outputs, manager.tx_storage) tx_fund0.weight = 1 tx_fund0.parents = manager.get_new_tx_parents() tx_fund0.timestamp = int(self.clock.seconds()) tx_fund0.resolve() self.assertTrue(manager.propagate_tx(tx_fund0)) 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 tx = tx_fund0 N = 10 for _ in range(N): tx = do_step(tx) block = add_new_block(manager) self.assertIn(tx.hash, block.parents) dot = graphviz.dot() dot.render('dot0') meta = tx.get_metadata() self.assertIsNone(meta.conflict_with) self.assertIsNone(meta.voided_by) self.assertEqual(tx.outputs[1].value, 1000 * 2**N) self.assertConsensusValid(manager)
def test_soft_voided(self): txA_hash = bytes.fromhex( '4586c5428e8d666ea59684c1cd9286d2b9d9e89b4939207db47412eeaabc48b2') soft_voided_tx_ids = set([ txA_hash, ]) manager1 = self.create_peer(soft_voided_tx_ids=soft_voided_tx_ids) manager1.allow_mining_without_peers() miner1 = self.simulator.create_miner(manager1, hashpower=5e6) miner1.start() self.simulator.run(60) gen_tx1 = self.simulator.create_tx_generator(manager1, rate=3 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx1.start() self.simulator.run(300) manager2 = self.create_peer(soft_voided_tx_ids=soft_voided_tx_ids) manager2.soft_voided_tx_ids = soft_voided_tx_ids graphviz = GraphvizVisualizer(manager2.tx_storage, include_verifications=True, include_funds=True) conn12 = FakeConnection(manager1, manager2, latency=0.001) self.simulator.add_connection(conn12) miner2 = self.simulator.create_miner(manager2, hashpower=10e6) miner2.start() gen_tx2 = self.simulator.create_tx_generator(manager2, rate=10 / 60., hashpower=1e6, ignore_no_funds=True) gen_tx2.start() self.simulator.run(900) txA = manager2.tx_storage.get_transaction(txA_hash) metaA = txA.get_metadata() self.assertEqual({settings.SOFT_VOIDED_ID, txA.hash}, metaA.voided_by) graphviz.labels[txA.hash] = 'txA' txB = add_custom_tx(manager2, [(txA, 0)]) metaB = txB.get_metadata() self.assertEqual({txA.hash}, metaB.voided_by) graphviz.labels[txB.hash] = 'txB' txD1 = add_custom_tx(manager2, [(txB, 0)]) metaD1 = txD1.get_metadata() self.assertEqual({txA.hash}, metaD1.voided_by) graphviz.labels[txD1.hash] = 'txD1' txD2 = add_custom_tx(manager2, [(txB, 0)]) metaD2 = txD2.get_metadata() self.assertEqual({txA.hash, txD2.hash}, metaD2.voided_by) graphviz.labels[txD2.hash] = 'txD2' metaD1 = txD1.get_metadata() self.assertEqual({txA.hash, txD1.hash}, metaD1.voided_by) address = manager2.wallet.get_unused_address(mark_as_used=False) value = 1 txC = gen_new_tx(manager2, address, value) txC.parents[0] = txA.hash txC.timestamp = max(txC.timestamp, txA.timestamp + 1) txC.weight = 25 txC.update_hash() self.assertTrue(manager2.propagate_tx(txC, fails_silently=False)) metaC = txC.get_metadata() self.assertIsNone(metaC.voided_by) graphviz.labels[txC.hash] = 'txC' blk1 = manager2.generate_mining_block() self.assertNoParentsAreSoftVoided(blk1) blk1.parents[1] = txA.hash blk1.nonce = self.rng.getrandbits(32) blk1.update_hash() self.assertTrue(manager2.propagate_tx(blk1, fails_silently=False)) blk1meta = blk1.get_metadata() self.assertIsNone(blk1meta.voided_by) graphviz.labels[blk1.hash] = 'b1' blk2 = manager2.generate_mining_block() self.assertNoParentsAreSoftVoided(blk2) if txD1.hash not in blk2.parents: blk2.parents[1] = txD1.hash blk2.nonce = self.rng.getrandbits(32) blk2.update_hash() self.assertTrue(manager2.propagate_tx(blk2, fails_silently=False)) blk2meta = blk2.get_metadata() self.assertIsNone(blk2meta.voided_by) graphviz.labels[blk2.hash] = 'b2' blk3 = manager2.generate_mining_block() self.assertNoParentsAreSoftVoided(blk3) blk3.parents[1] = txD2.hash blk3.nonce = self.rng.getrandbits(32) blk3.update_hash() self.assertTrue(manager2.propagate_tx(blk3, fails_silently=False)) blk3meta = blk3.get_metadata() self.assertIsNone(blk3meta.voided_by) graphviz.labels[blk3.hash] = 'b3' for tx in manager1.tx_storage.get_all_transactions(): meta = tx.get_metadata() voided_by = meta.voided_by or set() if settings.SOFT_VOIDED_ID in voided_by: self.assertTrue({settings.SOFT_VOIDED_ID, tx.hash}.issubset(voided_by))