Beispiel #1
0
    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()
Beispiel #2
0
    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
Beispiel #5
0
    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)
Beispiel #7
0
    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)
Beispiel #9
0
    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)
Beispiel #10
0
    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))
Beispiel #12
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))