def test_invalid_same_peer_id(self):
     manager3 = self.create_peer(self.network, peer_id=self.peer_id1)
     conn = FakeConnection(self.manager1, manager3)
     conn.run_one_step()  # HELLO
     conn.run_one_step()  # PEER-ID
     self._check_result_only_cmd(conn.peek_tr1_value(), b'ERROR')
     self.assertTrue(conn.tr1.disconnecting)
Example #2
0
    def test_block_sync_new_blocks_and_txs(self):
        self._add_new_blocks(25)
        self._add_new_transactions(3)
        self._add_new_blocks(4)
        self._add_new_transactions(5)

        manager2 = self.create_peer(self.network)
        self.assertEqual(manager2.state, manager2.NodeState.READY)

        conn = FakeConnection(self.manager1, manager2)

        for _ in range(1000):
            conn.run_one_step()
            self.clock.advance(0.1)

        # dot1 = self.manager1.tx_storage.graphviz(format='pdf')
        # dot1.render('dot1')

        # dot2 = manager2.tx_storage.graphviz(format='pdf')
        # dot2.render('dot2')

        node_sync = conn.proto1.state.sync_manager
        self.assertEqual(self.manager1.tx_storage.latest_timestamp,
                         manager2.tx_storage.latest_timestamp)
        self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp)
        self.assertTipsEqual(self.manager1, manager2)
        self.assertConsensusEqual(self.manager1, manager2)
        self.assertConsensusValid(self.manager1)
        self.assertConsensusValid(manager2)
Example #3
0
    def test_match_peer_id(self):
        network = 'testnet'
        peer_id1 = PeerId()
        peer_id2 = PeerId()
        manager1 = self.create_peer(network, peer_id=peer_id1)
        manager2 = self.create_peer(network, peer_id=peer_id2)

        conn = FakeConnection(manager1, manager2)
        self.assertTrue(conn.proto2.is_state(conn.proto2.PeerState.HELLO))

        matcher = NetfilterMatchPeerId(str(peer_id1.id))
        context = NetfilterContext(protocol=conn.proto2)
        self.assertFalse(matcher.match(context))

        conn.run_one_step()
        self.assertTrue(conn.proto2.is_state(conn.proto2.PeerState.PEER_ID))
        self.assertFalse(matcher.match(context))

        # Success because the connection is ready and proto2 is connected to proto1.
        conn.run_one_step()
        conn.run_one_step()
        self.assertTrue(conn.proto2.is_state(conn.proto2.PeerState.READY))
        self.assertTrue(matcher.match(context))

        # Fail because proto1 is connected to proto2, and the peer id cannot match.
        context = NetfilterContext(protocol=conn.proto1)
        self.assertFalse(matcher.match(context))
 def test_invalid_different_network(self):
     manager3 = self.create_peer(network='mainnet')
     conn = FakeConnection(self.manager1, manager3)
     conn.run_one_step()  # HELLO
     self._check_result_only_cmd(conn.peek_tr1_value(), b'ERROR')
     self.assertTrue(conn.tr1.disconnecting)
     conn.run_one_step()  # ERROR
    def test_capabilities(self):
        network = 'testnet'
        manager1 = self.create_peer(
            network, capabilities=[settings.CAPABILITY_WHITELIST])
        manager2 = self.create_peer(network, capabilities=[])

        conn = FakeConnection(manager1, manager2)

        # Run the p2p protocol.
        for _ in range(100):
            conn.run_one_step(debug=True)
            self.clock.advance(0.1)

        # Even if we don't have the capability we must connect because the whitelist url conf is None
        self.assertEqual(conn._proto1.state.state_name, 'READY')
        self.assertEqual(conn._proto2.state.state_name, 'READY')

        manager3 = self.create_peer(
            network, capabilities=[settings.CAPABILITY_WHITELIST])
        manager4 = self.create_peer(
            network, capabilities=[settings.CAPABILITY_WHITELIST])

        conn2 = FakeConnection(manager3, manager4)

        # Run the p2p protocol.
        for _ in range(100):
            conn2.run_one_step(debug=True)
            self.clock.advance(0.1)

        self.assertEqual(conn2._proto1.state.state_name, 'READY')
        self.assertEqual(conn2._proto2.state.state_name, 'READY')
Example #6
0
    def test_block_sync_only_genesis(self):
        manager2 = self.create_peer(self.network)
        self.assertEqual(manager2.state, manager2.NodeState.READY)

        conn = FakeConnection(self.manager1, manager2)

        conn.run_one_step()  # HELLO
        conn.run_one_step()  # PEER-ID
        conn.run_one_step()  # READY

        node_sync = conn.proto1.state.sync_manager
        self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp)
        self.assertTipsEqual(self.manager1, manager2)
    def test_two_connections(self):
        self.conn.run_one_step()  # HELLO
        self.conn.run_one_step()  # PEER-ID
        self.conn.run_one_step()  # READY
        self.conn.run_one_step()  # GET-PEERS
        self.conn.run_one_step()  # GET-TIPS

        manager3 = self.create_peer(self.network)
        conn = FakeConnection(self.manager1, manager3)
        conn.run_one_step()  # HELLO
        conn.run_one_step()  # PEER-ID
        conn.run_one_step()  # READY
        conn.run_one_step()  # GET-PEERS

        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'PEERS')
        self.conn.run_one_step()
Example #8
0
    def test_block_sync_many_new_blocks(self):
        self._add_new_blocks(150)

        manager2 = self.create_peer(self.network)
        self.assertEqual(manager2.state, manager2.NodeState.READY)

        conn = FakeConnection(self.manager1, manager2)

        while not conn.is_empty():
            conn.run_one_step(debug=True)
            self.clock.advance(0.1)

        node_sync = conn.proto1.state.sync_manager
        self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp)
        self.assertTipsEqual(self.manager1, manager2)
        self.assertConsensusEqual(self.manager1, manager2)
        self.assertConsensusValid(self.manager1)
        self.assertConsensusValid(manager2)
Example #9
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)
Example #10
0
    def test_downloader(self):
        from hathor.p2p.node_sync import NodeSyncTimestamp

        blocks = self._add_new_blocks(3)

        manager2 = self.create_peer(self.network)
        self.assertEqual(manager2.state, manager2.NodeState.READY)

        conn = FakeConnection(self.manager1, manager2)

        # Get to PEER-ID state only because when it gets to READY it will automatically sync
        conn.run_one_step()

        self.assertTrue(isinstance(conn.proto1.state, PeerIdState))
        self.assertTrue(isinstance(conn.proto2.state, PeerIdState))

        downloader = conn.proto2.connections._sync_factories[
            SyncVersion.V1].downloader

        node_sync1 = NodeSyncTimestamp(conn.proto1,
                                       downloader,
                                       reactor=conn.proto1.node.reactor)
        node_sync1.start()
        node_sync2 = NodeSyncTimestamp(conn.proto2,
                                       downloader,
                                       reactor=conn.proto2.node.reactor)
        node_sync2.start()

        self.assertTrue(isinstance(conn.proto1.state, PeerIdState))
        self.assertTrue(isinstance(conn.proto2.state, PeerIdState))

        deferred1 = downloader.get_tx(blocks[0].hash, node_sync1)
        deferred1.addCallback(node_sync1.on_tx_success)

        self.assertEqual(len(downloader.pending_transactions), 1)

        details = downloader.pending_transactions[blocks[0].hash]
        self.assertEqual(len(details.connections), 1)
        self.assertEqual(len(downloader.downloading_deque), 1)

        deferred2 = downloader.get_tx(blocks[0].hash, node_sync2)
        deferred2.addCallback(node_sync2.on_tx_success)

        self.assertEqual(len(downloader.pending_transactions), 1)
        self.assertEqual(
            len(downloader.pending_transactions[blocks[0].hash].connections),
            2)
        self.assertEqual(len(downloader.downloading_deque), 1)
        self.assertEqual(deferred1, deferred2)

        details.downloading_deferred.callback(blocks[0])

        self.assertEqual(len(downloader.downloading_deque), 0)
        self.assertEqual(len(downloader.pending_transactions), 0)

        # Getting tx already downloaded
        downloader.get_tx(blocks[0].hash, node_sync1)

        self.assertEqual(len(downloader.downloading_deque), 0)

        # Adding fake tx_id to downloading deque
        downloader.downloading_deque.append('1')

        # Getting new tx
        downloader.get_tx(blocks[1].hash, node_sync1)

        self.assertEqual(len(downloader.pending_transactions), 1)

        details = downloader.pending_transactions[blocks[1].hash]
        self.assertEqual(len(details.connections), 1)
        self.assertEqual(len(downloader.downloading_deque), 2)

        details.downloading_deferred.callback(blocks[1])

        # Still 2 elements because the first one is not downloaded yet
        self.assertEqual(len(downloader.downloading_deque), 2)

        # Remove it
        downloader.downloading_deque.popleft()

        # And try again
        downloader.check_downloading_queue()
        self.assertEqual(len(downloader.downloading_deque), 0)
Example #11
0
class BaseHathorSyncMethodsTestCase(unittest.TestCase):
    __test__ = False

    def setUp(self):
        super().setUp()

        # import sys
        # from twisted.python import log
        # log.startLogging(sys.stdout)

        self.network = 'testnet'
        self.manager1 = self.create_peer(self.network, unlock_wallet=True)
        self.manager1.avg_time_between_blocks = 4

        self.genesis = self.manager1.tx_storage.get_all_genesis()
        self.genesis_blocks = [tx for tx in self.genesis if tx.is_block]

    def _add_new_tx(self, address, value):
        from hathor.transaction import Transaction
        from hathor.wallet.base_wallet import WalletOutputInfo

        outputs = []
        outputs.append(
            WalletOutputInfo(address=decode_address(address),
                             value=int(value),
                             timelock=None))

        tx = self.manager1.wallet.prepare_transaction_compute_inputs(
            Transaction, outputs, self.manager1.tx_storage)
        tx.timestamp = int(self.clock.seconds())
        tx.storage = self.manager1.tx_storage
        tx.weight = 10
        tx.parents = self.manager1.get_new_tx_parents()
        tx.resolve()
        tx.verify()
        self.manager1.propagate_tx(tx)
        self.clock.advance(10)
        return tx

    def _add_new_transactions(self, num_txs):
        txs = []
        for _ in range(num_txs):
            address = self.get_address(0)
            value = random.choice([5, 10, 50, 100, 120])
            tx = self._add_new_tx(address, value)
            txs.append(tx)
        return txs

    def _add_new_block(self, propagate=True):
        block = self.manager1.generate_mining_block()
        self.assertTrue(block.resolve())
        block.verify()
        self.manager1.on_new_tx(block, propagate_to_peers=propagate)
        self.clock.advance(10)
        return block

    def _add_new_blocks(self, num_blocks, propagate=True):
        blocks = []
        for _ in range(num_blocks):
            blocks.append(self._add_new_block(propagate=propagate))
        return blocks

    def test_get_blocks_before(self):
        genesis_block = self.genesis_blocks[0]
        result = self.manager1.tx_storage.get_blocks_before(genesis_block.hash)
        self.assertEqual(0, len(result))

        genesis_tx = [tx for tx in self.genesis if not tx.is_block][0]
        with self.assertRaises(TransactionIsNotABlock):
            self.manager1.tx_storage.get_blocks_before(genesis_tx.hash)

        blocks = self._add_new_blocks(20)
        num_blocks = 5

        for i, block in enumerate(blocks):
            result = self.manager1.tx_storage.get_blocks_before(
                block.hash, num_blocks=num_blocks)

            expected_result = [genesis_block] + blocks[:i]
            expected_result = expected_result[-num_blocks:]
            expected_result = expected_result[::-1]
            self.assertEqual(result, expected_result)

    def test_block_sync_only_genesis(self):
        manager2 = self.create_peer(self.network)
        self.assertEqual(manager2.state, manager2.NodeState.READY)

        conn = FakeConnection(self.manager1, manager2)

        conn.run_one_step()  # HELLO
        conn.run_one_step()  # PEER-ID
        conn.run_one_step()  # READY

        node_sync = conn.proto1.state.sync_manager
        self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp)
        self.assertTipsEqual(self.manager1, manager2)

    def test_block_sync_new_blocks(self):
        self._add_new_blocks(15)

        manager2 = self.create_peer(self.network)
        self.assertEqual(manager2.state, manager2.NodeState.READY)

        conn = FakeConnection(self.manager1, manager2)

        for _ in range(10000):
            if conn.is_empty():
                break
            conn.run_one_step(debug=True)
            self.clock.advance(0.1)

        node_sync = conn.proto1.state.sync_manager
        self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp)
        self.assertTipsEqual(self.manager1, manager2)
        self.assertConsensusEqual(self.manager1, manager2)
        self.assertConsensusValid(self.manager1)
        self.assertConsensusValid(manager2)

    def test_block_sync_many_new_blocks(self):
        self._add_new_blocks(150)

        manager2 = self.create_peer(self.network)
        self.assertEqual(manager2.state, manager2.NodeState.READY)

        conn = FakeConnection(self.manager1, manager2)

        while not conn.is_empty():
            conn.run_one_step(debug=True)
            self.clock.advance(0.1)

        node_sync = conn.proto1.state.sync_manager
        self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp)
        self.assertTipsEqual(self.manager1, manager2)
        self.assertConsensusEqual(self.manager1, manager2)
        self.assertConsensusValid(self.manager1)
        self.assertConsensusValid(manager2)

    def test_block_sync_new_blocks_and_txs(self):
        self._add_new_blocks(25)
        self._add_new_transactions(3)
        self._add_new_blocks(4)
        self._add_new_transactions(5)

        manager2 = self.create_peer(self.network)
        self.assertEqual(manager2.state, manager2.NodeState.READY)

        conn = FakeConnection(self.manager1, manager2)

        for _ in range(1000):
            conn.run_one_step()
            self.clock.advance(0.1)

        # dot1 = self.manager1.tx_storage.graphviz(format='pdf')
        # dot1.render('dot1')

        # dot2 = manager2.tx_storage.graphviz(format='pdf')
        # dot2.render('dot2')

        node_sync = conn.proto1.state.sync_manager
        self.assertEqual(self.manager1.tx_storage.latest_timestamp,
                         manager2.tx_storage.latest_timestamp)
        self.assertEqual(node_sync.synced_timestamp, node_sync.peer_timestamp)
        self.assertTipsEqual(self.manager1, manager2)
        self.assertConsensusEqual(self.manager1, manager2)
        self.assertConsensusValid(self.manager1)
        self.assertConsensusValid(manager2)

    def test_tx_propagation_nat_peers(self):
        """ manager1 <- manager2 <- manager3
        """
        self._add_new_blocks(25)

        self.manager2 = self.create_peer(self.network)
        self.conn1 = FakeConnection(self.manager1, self.manager2)

        for _ in range(1000):
            if self.conn1.is_empty():
                break
            self.conn1.run_one_step()
            self.clock.advance(0.1)
        self.assertTipsEqual(self.manager1, self.manager2)

        self._add_new_blocks(1)

        for _ in range(1000):
            if self.conn1.is_empty():
                break
            self.conn1.run_one_step()
            self.clock.advance(0.1)
        self.assertTipsEqual(self.manager1, self.manager2)

        self.manager3 = self.create_peer(self.network)
        self.conn2 = FakeConnection(self.manager2, self.manager3)

        for _ in range(1000):
            if self.conn1.is_empty() and self.conn2.is_empty():
                break
            self.conn1.run_one_step()
            self.conn2.run_one_step()
            self.clock.advance(0.1)

        self.assertTipsEqual(self.manager1, self.manager2)
        self.assertTipsEqual(self.manager1, self.manager3)

        self._add_new_transactions(1)

        for _ in range(1000):
            if self.conn1.is_empty() and self.conn2.is_empty():
                break
            self.conn1.run_one_step()
            self.conn2.run_one_step()
            self.clock.advance(0.1)

        self.assertTipsEqual(self.manager1, self.manager2)
        self.assertTipsEqual(self.manager1, self.manager3)
        self.assertConsensusEqual(self.manager1, self.manager2)
        self.assertConsensusEqual(self.manager1, self.manager3)
        self.assertConsensusValid(self.manager1)
        self.assertConsensusValid(self.manager2)
        self.assertConsensusValid(self.manager3)
    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), advance_clock=1)
                add_new_transactions(manager2, random.randint(3, 7), advance_clock=1)
                add_new_double_spending(manager1)
                add_new_double_spending(manager2)
                self.clock.advance(10)
        self.clock.advance(20)

        if debug_pdf:
            dot1 = GraphvizVisualizer(manager1.tx_storage, include_verifications=True).dot()
            dot1.render('dot1-pre')

        self.assertTipsNotEqual(manager1, manager2)
        self.assertConsensusValid(manager1)
        self.assertConsensusValid(manager2)

        # input('Press enter to continue...')

        conn = FakeConnection(manager1, manager2)

        conn.run_one_step()  # HELLO
        conn.run_one_step()  # PEER-ID
        conn.run_one_step()  # READY
        conn.run_one_step()  # GET-PEERS
        conn.run_one_step()  # GET-TIPS
        conn.run_one_step()  # PEERS
        conn.run_one_step()  # TIPS

        empty_counter = 0
        for i in range(2000):
            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.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_invalid_same_peer_id2(self):
     """
     We connect nodes 1-2 and 1-3. Nodes 2 and 3 have the same peer_id. The connections
     are established simultaneously, so we do not detect a peer id duplication in PEER_ID
     state, only on READY state.
     """
     # Disable idle timeout before creating any new peer because self.create_peer(...)
     # runs the main loop.
     self.conn.disable_idle_timeout()
     # Create new peer and disable idle timeout.
     manager3 = self.create_peer(self.network, peer_id=self.peer_id2)
     conn = FakeConnection(manager3, self.manager1)
     # Disable idle timeout.
     conn.disable_idle_timeout()
     # HELLO
     self.assertEqual(self.conn.peek_tr1_value().split()[0], b'HELLO')
     self.assertEqual(self.conn.peek_tr2_value().split()[0], b'HELLO')
     self.assertEqual(conn.peek_tr1_value().split()[0], b'HELLO')
     self.assertEqual(conn.peek_tr2_value().split()[0], b'HELLO')
     self.conn.run_one_step()
     conn.run_one_step()
     # PEER-ID
     self.assertEqual(self.conn.peek_tr1_value().split()[0], b'PEER-ID')
     self.assertEqual(self.conn.peek_tr2_value().split()[0], b'PEER-ID')
     self.assertEqual(conn.peek_tr1_value().split()[0], b'PEER-ID')
     self.assertEqual(conn.peek_tr2_value().split()[0], b'PEER-ID')
     self.conn.run_one_step()
     conn.run_one_step()
     # READY
     self.assertEqual(self.conn.peek_tr1_value().split()[0], b'READY')
     self.assertEqual(self.conn.peek_tr2_value().split()[0], b'READY')
     self.assertEqual(conn.peek_tr1_value().split()[0], b'READY')
     self.assertEqual(conn.peek_tr2_value().split()[0], b'READY')
     self.conn.run_one_step()
     conn.run_one_step()
     # continue until messages stop
     self.conn.run_until_complete()
     conn.run_until_complete()
     self.run_to_completion()
     # one of the peers will close the connection. We don't know which on, as it depends
     # on the peer ids
     conn1_value = self.conn.peek_tr1_value() + self.conn.peek_tr2_value()
     conn2_value = conn.peek_tr1_value() + conn.peek_tr2_value()
     if b'ERROR' in conn1_value:
         conn_dead = self.conn
         conn_alive = conn
     elif b'ERROR' in conn2_value:
         conn_dead = conn
         conn_alive = self.conn
     else:
         raise Exception('It should never happen.')
     self._check_result_only_cmd(
         conn_dead.peek_tr1_value() + conn_dead.peek_tr2_value(), b'ERROR')
     # at this point, the connection must be closing as the error was detected on READY state
     self.assertIn(
         True, [conn_dead.tr1.disconnecting, conn_dead.tr2.disconnecting])
     # check connected_peers
     connected_peers = list(
         self.manager1.connections.connected_peers.values())
     self.assertEquals(1, len(connected_peers))
     self.assertIn(connected_peers[0],
                   [conn_alive.proto1, conn_alive.proto2])
     # connection is still up
     self.assertIsConnected(conn_alive)
class BaseHathorProtocolTestCase(unittest.TestCase):
    __test__ = False

    def setUp(self):
        super().setUp()
        self.network = 'testnet'
        self.peer_id1 = PeerId()
        self.peer_id2 = PeerId()
        self.manager1 = self.create_peer(self.network, peer_id=self.peer_id1)
        self.manager2 = self.create_peer(self.network, peer_id=self.peer_id2)
        self.conn = FakeConnection(self.manager1, self.manager2)

    def assertIsConnected(self, conn=None):
        if conn is None:
            conn = self.conn
        self.assertFalse(conn.tr1.disconnecting)
        self.assertFalse(conn.tr2.disconnecting)

    def assertIsNotConnected(self, conn=None):
        if conn is None:
            conn = self.conn
        self.assertTrue(conn.tr1.disconnecting)
        self.assertTrue(conn.tr2.disconnecting)

    def _send_cmd(self, proto, cmd, payload=None):
        if not payload:
            line = '{}\r\n'.format(cmd)
        else:
            line = '{} {}\r\n'.format(cmd, payload)

        if isinstance(line, str):
            line = line.encode('utf-8')

        return proto.dataReceived(line)

    def _check_result_only_cmd(self, result, expected_cmd):
        cmd_list = []
        for line in result.split(b'\r\n'):
            cmd, _, _ = line.partition(b' ')
            cmd_list.append(cmd)
        self.assertIn(expected_cmd, cmd_list)

    def _check_cmd_and_value(self, result, expected):
        result_list = []
        for line in result.split(b'\r\n'):
            cmd, _, data = line.partition(b' ')
            result_list.append((cmd, data))
        self.assertIn(expected, result_list)

    def test_on_connect(self):
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'HELLO')

    def test_invalid_command(self):
        self._send_cmd(self.conn.proto1, 'INVALID-CMD')
        self.conn.proto1.state.handle_error('')
        self.assertTrue(self.conn.tr1.disconnecting)

    def test_rate_limit(self):
        hits = 1
        window = 60

        self.conn.proto1.ratelimit.set_limit(
            HathorProtocol.RateLimitKeys.GLOBAL, hits, window)
        # first will be OK and reach the hits limit per window
        self.conn.run_one_step()  # HELLO
        # second will fail and be throttled
        self.conn.run_one_step()  # PEER-ID

        self._check_cmd_and_value(
            self.conn.peek_tr1_value(),
            (b'THROTTLE', 'global At most {} hits every {} seconds'.format(
                hits, window).encode('utf-8')),
        )

        self.conn.proto1.state.handle_throttle(b'')

        # Test empty disconnect
        self.conn.proto1.state = None
        self.conn.proto1.connections = None
        self.conn.proto1.on_disconnect(Failure(Exception()))

    def test_invalid_size(self):
        self.conn.tr1.clear()
        # Creating big payload
        big_payload = '['
        for x in range(65536):
            big_payload = '{}{}'.format(big_payload, x)
        big_payload = '{}]'.format(big_payload)
        self._send_cmd(self.conn.proto1, 'HELLO', big_payload)
        self.assertTrue(self.conn.tr1.disconnecting)

    def test_invalid_payload(self):
        self.conn.run_one_step()  # HELLO
        self.conn.run_one_step()  # PEER-ID
        self.conn.run_one_step()  # READY
        with self.assertRaises(json.decoder.JSONDecodeError):
            self._send_cmd(self.conn.proto1, 'PEERS', 'abc')

    def test_invalid_hello1(self):
        self.conn.tr1.clear()
        self._send_cmd(self.conn.proto1, 'HELLO')
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'ERROR')
        self.assertTrue(self.conn.tr1.disconnecting)

    def test_invalid_hello2(self):
        self.conn.tr1.clear()
        self._send_cmd(self.conn.proto1, 'HELLO', 'invalid_payload')
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'ERROR')
        self.assertTrue(self.conn.tr1.disconnecting)

    def test_invalid_hello3(self):
        self.conn.tr1.clear()
        self._send_cmd(self.conn.proto1, 'HELLO', '{}')
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'ERROR')
        self.assertTrue(self.conn.tr1.disconnecting)

    def test_invalid_hello4(self):
        self.conn.tr1.clear()
        self._send_cmd(
            self.conn.proto1, 'HELLO',
            '{"app": 0, "remote_address": 1, "network": 2, "genesis_hash": "123", "settings_hash": "456"}'
        )
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'ERROR')
        self.assertTrue(self.conn.tr1.disconnecting)

    def test_invalid_hello5(self):
        # hello with clocks too far apart
        self.conn.tr1.clear()
        data = self.conn.proto2.state._get_hello_data()
        data['timestamp'] = data[
            'timestamp'] + settings.MAX_FUTURE_TIMESTAMP_ALLOWED / 2 + 1
        self._send_cmd(self.conn.proto1, 'HELLO', json.dumps(data))
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'ERROR')
        self.assertTrue(self.conn.tr1.disconnecting)

    def test_valid_hello(self):
        self.conn.run_one_step()  # HELLO
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'PEER-ID')
        self._check_result_only_cmd(self.conn.peek_tr2_value(), b'PEER-ID')
        self.assertFalse(self.conn.tr1.disconnecting)
        self.assertFalse(self.conn.tr2.disconnecting)

    @inlineCallbacks
    def test_invalid_peer_id(self):
        self.conn.run_one_step()  # HELLO
        self.conn.run_one_step()  # PEER-ID
        self.conn.run_one_step()  # READY
        self.conn.run_one_step()  # GET-PEERS
        self.conn.run_one_step()  # GET-TIPS
        self.conn.run_one_step()  # PEERS
        self.conn.run_one_step()  # TIPS
        invalid_payload = {
            'id': '123',
            'entrypoints': ['tcp://localhost:1234']
        }
        yield self._send_cmd(self.conn.proto1, 'PEER-ID',
                             json.dumps(invalid_payload))
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'ERROR')
        self.assertTrue(self.conn.tr1.disconnecting)

    def test_invalid_same_peer_id(self):
        manager3 = self.create_peer(self.network, peer_id=self.peer_id1)
        conn = FakeConnection(self.manager1, manager3)
        conn.run_one_step()  # HELLO
        conn.run_one_step()  # PEER-ID
        self._check_result_only_cmd(conn.peek_tr1_value(), b'ERROR')
        self.assertTrue(conn.tr1.disconnecting)

    def test_invalid_same_peer_id2(self):
        """
        We connect nodes 1-2 and 1-3. Nodes 2 and 3 have the same peer_id. The connections
        are established simultaneously, so we do not detect a peer id duplication in PEER_ID
        state, only on READY state.
        """
        # Disable idle timeout before creating any new peer because self.create_peer(...)
        # runs the main loop.
        self.conn.disable_idle_timeout()
        # Create new peer and disable idle timeout.
        manager3 = self.create_peer(self.network, peer_id=self.peer_id2)
        conn = FakeConnection(manager3, self.manager1)
        # Disable idle timeout.
        conn.disable_idle_timeout()
        # HELLO
        self.assertEqual(self.conn.peek_tr1_value().split()[0], b'HELLO')
        self.assertEqual(self.conn.peek_tr2_value().split()[0], b'HELLO')
        self.assertEqual(conn.peek_tr1_value().split()[0], b'HELLO')
        self.assertEqual(conn.peek_tr2_value().split()[0], b'HELLO')
        self.conn.run_one_step()
        conn.run_one_step()
        # PEER-ID
        self.assertEqual(self.conn.peek_tr1_value().split()[0], b'PEER-ID')
        self.assertEqual(self.conn.peek_tr2_value().split()[0], b'PEER-ID')
        self.assertEqual(conn.peek_tr1_value().split()[0], b'PEER-ID')
        self.assertEqual(conn.peek_tr2_value().split()[0], b'PEER-ID')
        self.conn.run_one_step()
        conn.run_one_step()
        # READY
        self.assertEqual(self.conn.peek_tr1_value().split()[0], b'READY')
        self.assertEqual(self.conn.peek_tr2_value().split()[0], b'READY')
        self.assertEqual(conn.peek_tr1_value().split()[0], b'READY')
        self.assertEqual(conn.peek_tr2_value().split()[0], b'READY')
        self.conn.run_one_step()
        conn.run_one_step()
        # continue until messages stop
        self.conn.run_until_complete()
        conn.run_until_complete()
        self.run_to_completion()
        # one of the peers will close the connection. We don't know which on, as it depends
        # on the peer ids
        conn1_value = self.conn.peek_tr1_value() + self.conn.peek_tr2_value()
        conn2_value = conn.peek_tr1_value() + conn.peek_tr2_value()
        if b'ERROR' in conn1_value:
            conn_dead = self.conn
            conn_alive = conn
        elif b'ERROR' in conn2_value:
            conn_dead = conn
            conn_alive = self.conn
        else:
            raise Exception('It should never happen.')
        self._check_result_only_cmd(
            conn_dead.peek_tr1_value() + conn_dead.peek_tr2_value(), b'ERROR')
        # at this point, the connection must be closing as the error was detected on READY state
        self.assertIn(
            True, [conn_dead.tr1.disconnecting, conn_dead.tr2.disconnecting])
        # check connected_peers
        connected_peers = list(
            self.manager1.connections.connected_peers.values())
        self.assertEquals(1, len(connected_peers))
        self.assertIn(connected_peers[0],
                      [conn_alive.proto1, conn_alive.proto2])
        # connection is still up
        self.assertIsConnected(conn_alive)

    def test_invalid_different_network(self):
        manager3 = self.create_peer(network='mainnet')
        conn = FakeConnection(self.manager1, manager3)
        conn.run_one_step()  # HELLO
        self._check_result_only_cmd(conn.peek_tr1_value(), b'ERROR')
        self.assertTrue(conn.tr1.disconnecting)
        conn.run_one_step()  # ERROR

    def test_valid_hello_and_peer_id(self):
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'HELLO')
        self._check_result_only_cmd(self.conn.peek_tr2_value(), b'HELLO')
        self.conn.run_one_step()  # HELLO
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'PEER-ID')
        self._check_result_only_cmd(self.conn.peek_tr2_value(), b'PEER-ID')
        self.conn.run_one_step()  # PEER-ID
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'READY')
        self._check_result_only_cmd(self.conn.peek_tr2_value(), b'READY')
        self.conn.run_one_step()  # READY
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'GET-PEERS')
        self._check_result_only_cmd(self.conn.peek_tr2_value(), b'GET-PEERS')
        self.conn.run_one_step()  # GET-PEERS
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'GET-TIPS')
        self._check_result_only_cmd(self.conn.peek_tr2_value(), b'GET-TIPS')
        self.conn.run_one_step()  # GET-TIPS
        self.assertIsConnected()
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'PEERS')
        self._check_result_only_cmd(self.conn.peek_tr2_value(), b'PEERS')
        self.conn.run_one_step()  # PEERS
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'TIPS')
        self._check_result_only_cmd(self.conn.peek_tr2_value(), b'TIPS')
        self.conn.run_one_step()  # TIPS
        self.assertIsConnected()

    def test_send_ping(self):
        self.conn.run_one_step()  # HELLO
        self.conn.run_one_step()  # PEER-ID
        self.conn.run_one_step()  # READY
        self.conn.run_one_step()  # GET-PEERS
        self.conn.run_one_step()  # GET-TIPS
        self.conn.run_one_step()  # PEERS
        self.conn.run_one_step()  # TIPS
        self.assertIsConnected()
        self.clock.advance(5)
        self.assertEqual(b'PING\r\n', self.conn.peek_tr1_value())
        self.assertEqual(b'PING\r\n', self.conn.peek_tr2_value())
        self.conn.run_one_step()  # PING
        self.conn.run_one_step()  # GET-TIPS
        self.assertEqual(b'PONG\r\n', self.conn.peek_tr1_value())
        self.assertEqual(b'PONG\r\n', self.conn.peek_tr2_value())
        while b'PONG\r\n' in self.conn.peek_tr1_value():
            self.conn.run_one_step()
        self.assertEqual(self.clock.seconds(), self.conn.proto1.last_message)

    def test_send_invalid_unicode(self):
        # \xff is an invalid unicode.
        self.conn.proto1.dataReceived(b'\xff\r\n')
        self.assertTrue(self.conn.tr1.disconnecting)

    def test_on_disconnect(self):
        self.assertIn(self.conn.proto1,
                      self.manager1.connections.handshaking_peers)
        self.conn.disconnect(Failure(Exception('testing')))
        self.assertNotIn(self.conn.proto1,
                         self.manager1.connections.handshaking_peers)

    def test_on_disconnect_after_hello(self):
        self.conn.run_one_step()  # HELLO
        self.assertIn(self.conn.proto1,
                      self.manager1.connections.handshaking_peers)
        self.conn.disconnect(Failure(Exception('testing')))
        self.assertNotIn(self.conn.proto1,
                         self.manager1.connections.handshaking_peers)

    def test_on_disconnect_after_peer_id(self):
        self.conn.run_one_step()  # HELLO
        self.assertIn(self.conn.proto1,
                      self.manager1.connections.handshaking_peers)
        # No peer id in the peer_storage (known_peers)
        self.assertNotIn(self.peer_id2.id,
                         self.manager1.connections.peer_storage)
        # The peer READY now depends on a message exchange from both peers, so we need one more step
        self.conn.run_one_step()  # PEER-ID
        self.conn.run_one_step()  # READY
        self.assertIn(self.conn.proto1,
                      self.manager1.connections.connected_peers.values())
        # Peer id 2 in the peer_storage (known_peers) after connection
        self.assertIn(self.peer_id2.id, self.manager1.connections.peer_storage)
        self.assertNotIn(self.conn.proto1,
                         self.manager1.connections.handshaking_peers)
        self.conn.disconnect(Failure(Exception('testing')))
        # Peer id 2 in the peer_storage (known_peers) after disconnection but before looping call
        self.assertIn(self.peer_id2.id, self.manager1.connections.peer_storage)
        self.assertNotIn(self.conn.proto1,
                         self.manager1.connections.connected_peers.values())

        self.clock.advance(10)
        # Peer id 2 removed from peer_storage (known_peers) after disconnection and after looping call
        self.assertNotIn(self.peer_id2.id,
                         self.manager1.connections.peer_storage)

    def test_two_connections(self):
        self.conn.run_one_step()  # HELLO
        self.conn.run_one_step()  # PEER-ID
        self.conn.run_one_step()  # READY
        self.conn.run_one_step()  # GET-PEERS
        self.conn.run_one_step()  # GET-TIPS

        manager3 = self.create_peer(self.network)
        conn = FakeConnection(self.manager1, manager3)
        conn.run_one_step()  # HELLO
        conn.run_one_step()  # PEER-ID
        conn.run_one_step()  # READY
        conn.run_one_step()  # GET-PEERS

        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'PEERS')
        self.conn.run_one_step()

    def test_idle_connection(self):
        self.clock.advance(settings.PEER_IDLE_TIMEOUT - 10)
        self.assertIsConnected(self.conn)
        self.clock.advance(15)
        self.assertIsNotConnected(self.conn)

    @inlineCallbacks
    def test_get_data(self):
        self.conn.run_one_step()  # HELLO
        self.conn.run_one_step()  # PEER-ID
        self.conn.run_one_step()  # READY
        self.conn.run_one_step()  # GET-PEERS
        self.conn.run_one_step()  # GET-TIPS
        self.conn.run_one_step()  # PEERS
        self.conn.run_one_step()  # TIPS
        self.assertIsConnected()
        missing_tx = '00000000228dfcd5dec1c9c6263f6430a5b4316bb9e3decb9441a6414bfd8697'
        yield self._send_cmd(self.conn.proto1, 'GET-DATA', missing_tx)
        self._check_result_only_cmd(self.conn.peek_tr1_value(), b'NOT-FOUND')
        self.conn.run_one_step()
Example #15
0
class BaseStatusTest(_BaseResourceTest._ResourceTest):
    __test__ = False

    def setUp(self):
        super().setUp()
        self.web = StubSite(StatusResource(self.manager))

        self.manager2 = self.create_peer('testnet')
        self.conn1 = FakeConnection(self.manager, self.manager2)

    @inlineCallbacks
    def test_get(self):
        response = yield self.web.get("status")
        data = response.json_value()
        server_data = data.get('server')
        self.assertEqual(server_data['app_version'],
                         'Hathor v{}'.format(hathor.__version__))
        self.assertEqual(server_data['network'], 'testnet')
        self.assertGreater(server_data['uptime'], 0)

    @inlineCallbacks
    def test_handshaking(self):
        response = yield self.web.get("status")
        data = response.json_value()
        server_data = data.get('server')
        known_peers = data.get('known_peers')
        connections = data.get('connections')
        self.assertEqual(server_data['app_version'],
                         'Hathor v{}'.format(hathor.__version__))
        self.assertEqual(server_data['network'], 'testnet')
        self.assertGreater(server_data['uptime'], 0)

        handshake_peer = self.conn1.proto1.transport.getPeer()
        handshake_address = '{}:{}'.format(handshake_peer.host,
                                           handshake_peer.port)

        self.assertEqual(len(known_peers), 0)
        self.assertEqual(len(connections['connected_peers']), 0)
        self.assertEqual(len(connections['handshaking_peers']), 1)
        self.assertEqual(connections['handshaking_peers'][0]['address'],
                         handshake_address)

    @inlineCallbacks
    def test_get_with_one_peer(self):
        self.conn1.run_one_step()  # HELLO
        self.conn1.run_one_step()  # PEER-ID
        self.conn1.run_one_step()  # READY
        self.conn1.run_one_step()  # BOTH PEERS ARE READY NOW

        response = yield self.web.get("status")
        data = response.json_value()
        server_data = data.get('server')
        known_peers = data.get('known_peers')
        connections = data.get('connections')
        self.assertEqual(server_data['app_version'],
                         'Hathor v{}'.format(hathor.__version__))
        self.assertEqual(server_data['network'], 'testnet')
        self.assertGreater(server_data['uptime'], 0)

        self.assertEqual(len(known_peers), 1)
        self.assertEqual(known_peers[0]['id'], self.manager2.my_peer.id)

        self.assertEqual(len(connections['connected_peers']), 1)
        self.assertEqual(connections['connected_peers'][0]['id'],
                         self.manager2.my_peer.id)

    @inlineCallbacks
    def test_connecting_peers(self):
        address = '192.168.1.1:54321'
        endpoint = endpoints.clientFromString(self.manager.reactor,
                                              'tcp:{}'.format(address))
        deferred = endpoint.connect
        self.manager.connections.connecting_peers[endpoint] = deferred

        response = yield self.web.get("status")
        data = response.json_value()
        connecting = data['connections']['connecting_peers']
        self.assertEqual(len(connecting), 1)
        self.assertEqual(connecting[0]['address'], address)
        self.assertIsNotNone(connecting[0]['deferred'])