예제 #1
0
    def test_request_full_message(self, m_reactor, m_logger):
        """
        MessageReceipt is only for Blocks and Transactions, both of which have hashes.
        When you receive the MessageReceipt for them, if you find that you don't have that message corresponding to the
        hash, this is when you call request_full_message()

        If the local node already has the Message, its hash will be in self.master_mr._hash_msg (perhaps it got it
        from another peer). So in that case we check if we requested this hash from any other peer.
        if yes then delete that request and then we go through peer list, who broadcasted this same MR.

        and we request from any one of those peer, and we add that peer into our list, that we already have requested
        from this peer.

        We also make the callLater to the request_full_message, so that in a given interval of time,
        if peer doesn't respond, then we can request to next peer otherwise we can delete all the request, if we
        received it from the peer
        """
        # The local node does not already have this message.
        mrData = qrllegacy_pb2.MRData(type=qrllegacy_pb2.LegacyMessage.SL, hash=b'1234')

        # You can get this Message from channel_1. No, we have not requested this Message from channel_1 yet.
        message_request = MessageRequest()
        message_request.peers_connection_list.append(self.channel_1)
        message_request.already_requested_peers = []
        self.factory.master_mr.requested_hash[b'1234'] = message_request

        self.factory.request_full_message(mrData)

        self.channel_1.send.assert_called_once()

        # We use reactor.callLater() to schedule a call to request_full_message() again in the future.
        # So if this peer doesn't respond, when this function is next called again, it will ignore this peer
        # because of already_requested_peers and use the next one in peers_list.
        m_reactor.callLater.assert_called_once()
예제 #2
0
    def test_handle_message_received_ignores_blocks(self):
        """
        handle_message_received() should ignore MessageReceipts for blocks that are too far away from our current block
        height, or if it's an orphan block.
        """
        self.channel.factory.chain_height = 10  # Right now, our node is on block 10.
        self.channel.factory.master_mr.contains.return_value = False  # No, we do not already have this Message.
        self.channel.factory.master_mr.is_callLater_active.return_value = False  # No, we haven't requested this Message yet.

        # Ignore blocks that are config.dev_max_margin_block_number ahead.
        mrData = qrllegacy_pb2.MRData(type=qrllegacy_pb2.LegacyMessage.BK,
                                      block_number=10 +
                                      config.dev.max_margin_block_number + 1)
        msg = make_message(func_name=qrllegacy_pb2.LegacyMessage.MR,
                           mrData=mrData)
        P2PTxManagement.handle_message_received(self.channel, msg)
        self.channel.factory.request_full_message.assert_not_called()

        # Ignore blocks that are config.dev_min_margin_block_number behind
        msg.mrData.block_number = 2
        P2PTxManagement.handle_message_received(self.channel, msg)
        self.channel.factory.request_full_message.assert_not_called()

        msg.mrData.block_number = 10  # Yes, it's another block 10!
        self.channel.factory.is_block_present.return_value = False  # But we don't have its previous block.
        P2PTxManagement.handle_message_received(self.channel, msg)
        self.channel.factory.request_full_message.assert_not_called()

        msg.mrData.block_number = 11  # now we have a follow-up block.
        self.channel.factory.is_block_present.return_value = True  # We do have its previous block.
        P2PTxManagement.handle_message_received(self.channel, msg)
        self.channel.factory.request_full_message.assert_called_once_with(
            msg.mrData)
예제 #3
0
    def test_request_full_message_already_requested_this_message_from_another_peer(
            self, m_reactor, m_logger):
        """
        If we have already requested this Message (from another peer), this function should still go ahead
        and request it from the peer we are currently dealing with.
        """
        # The local node does not already have this message.
        mrData = qrllegacy_pb2.MRData(type=qrllegacy_pb2.LegacyMessage.SL,
                                      hash=b'1234')

        # You can get this Message from channel_1. Also, we already requested this Message from channel_2.
        message_request = MessageRequest()
        message_request.peers_connection_list.append(self.channel_1)
        message_request.already_requested_peers = [self.channel_2]
        self.factory.master_mr.requested_hash[b'1234'] = message_request

        self.factory.request_full_message(mrData)

        # But, the code should ignore channel_2 and ask channel_1 anyway.
        self.channel_1.send.assert_called_once()

        # We use reactor.callLater() to schedule a call to request_full_message() again in the future.
        # So if this peer doesn't respond, when this function is next called again, it will ignore this peer
        # because of already_requested_peers and use the next one in peers_list.
        m_reactor.callLater.assert_called_once()
예제 #4
0
    def test_handle_message_received_ignores_unknown_message_types(self):
        mrData = qrllegacy_pb2.MRData(type=999)
        msg = make_message(func_name=qrllegacy_pb2.LegacyMessage.MR,
                           mrData=mrData)

        P2PTxManagement.handle_message_received(self.channel, msg)

        self.channel.factory.request_full_message.assert_not_called()
예제 #5
0
    def broadcast_block(self, block: Block):
        # logger.info('<<<Transmitting block: ', block.headerhash)
        data = qrllegacy_pb2.MRData()
        data.stake_selector = block.transactions[0].public_key
        data.block_number = block.block_number
        data.prev_headerhash = bytes(block.prev_headerhash)

        self.register_and_broadcast(qrllegacy_pb2.LegacyMessage.BK, block.headerhash, block.pbdata, data)
예제 #6
0
    def test_handle_message_received_transactions_ignored_while_unsynced(self):
        self.channel.factory.sync_state.state = ESyncState.syncing

        mrData = qrllegacy_pb2.MRData(type=qrllegacy_pb2.LegacyMessage.TX)
        msg = make_message(func_name=qrllegacy_pb2.LegacyMessage.MR,
                           mrData=mrData)

        P2PTxManagement.handle_message_received(self.channel, msg)

        self.channel.factory.request_full_message.assert_not_called()
예제 #7
0
    def test_handle_message_received_ignores_messages_it_already_has(self):
        self.channel.factory.master_mr.contains.return_value = True

        mrData = qrllegacy_pb2.MRData(type=qrllegacy_pb2.LegacyMessage.SL)
        msg = make_message(func_name=qrllegacy_pb2.LegacyMessage.MR,
                           mrData=mrData)

        P2PTxManagement.handle_message_received(self.channel, msg)

        self.channel.factory.request_full_message.assert_not_called()
예제 #8
0
    def test_handle_message_received_ignores_messages_it_already_has_requested(
            self):
        self.channel.factory.master_mr.contains.return_value = False  # No, we don't have this message yet.
        self.channel.factory.master_mr.is_callLater_active.return_value = True  # Yes, we have requested it.

        mrData = qrllegacy_pb2.MRData(type=qrllegacy_pb2.LegacyMessage.SL)
        msg = make_message(func_name=qrllegacy_pb2.LegacyMessage.MR,
                           mrData=mrData)

        P2PTxManagement.handle_message_received(self.channel, msg)

        self.channel.factory.request_full_message.assert_not_called()
예제 #9
0
    def test_request_full_message_we_have_already_forgotten_about_this_hash(self, m_reactor, m_logger):
        """
        If we couldn't download the full Message from any peer, then we would've forgotten about this MessageReceipt
        and its hash.
        The next time request_full_message() is called, we find that we have forgotten about the MR and thus do nothing.
        """
        mrData = qrllegacy_pb2.MRData(type=qrllegacy_pb2.LegacyMessage.SL, hash=b'1234')

        self.factory.request_full_message(mrData)

        self.channel_1.send.assert_not_called()
        m_reactor.callLater.assert_not_called()
예제 #10
0
 def test_handle_block_bad_block(self, m_Block, m_logger):
     """
     If the block couldn't be constructed from the message, the function should return without doing
     anything else.
     :return:
     """
     m_Block.side_effect = Exception
     msg = make_message(func_name=qrllegacy_pb2.LegacyMessage.BK,
                        block=qrl_pb2.Block(),
                        mrData=qrllegacy_pb2.MRData())
     self.manager.handle_block(self.channel, msg)
     self.channel.factory.master_mr.register.assert_not_called()
     self.channel.factory.pow.pre_block_logic.assert_not_called()
예제 #11
0
    def test_request_full_message_we_already_have_this_message(self, m_reactor, m_logger):
        # The local node already has this message!
        mrData = qrllegacy_pb2.MRData(type=qrllegacy_pb2.LegacyMessage.SL, hash=b'1234')
        self.factory.master_mr._hash_msg[b'1234'] = Mock(autospec=Message)
        message_request = MessageRequest()
        message_request.peers_connection_list.append(self.channel_1)
        self.factory.master_mr.requested_hash[b'1234'] = message_request

        self.factory.request_full_message(mrData)

        # Because we already have this message, channel_1 is left alone.
        self.channel_1.send.assert_not_called()
        # Also, this hash should no longer appear in the Master MessageReceipt.
        self.assertIsNone(self.factory.master_mr.requested_hash.get(b'1234'))
예제 #12
0
    def request_full_message(self, mr_data: qrllegacy_pb2.MRData):
        """
        Request Full Message
        This function request for the full message against,
        the Message Receipt received.
        :return:
        """

        # FIXME: Again, breaking encasulation
        # FIXME: Huge amount of lookups in dictionaries
        msg_hash = mr_data.hash

        if msg_hash in self.master_mr._hash_msg:
            if msg_hash in self.master_mr.requested_hash:
                del self.master_mr.requested_hash[msg_hash]
            return

        if msg_hash not in self.master_mr.requested_hash:
            return

        peers_list = self.master_mr.requested_hash[
            msg_hash].peers_connection_list
        message_request = self.master_mr.requested_hash[msg_hash]
        for peer in peers_list:
            if peer in message_request.already_requested_peers:
                continue
            message_request.already_requested_peers.append(peer)

            msg = qrllegacy_pb2.LegacyMessage(
                func_name=qrllegacy_pb2.LegacyMessage.SFM,
                mrData=qrllegacy_pb2.MRData(hash=mr_data.hash,
                                            type=mr_data.type))

            peer.send(msg)

            call_later_obj = reactor.callLater(
                config.dev.message_receipt_timeout, self.request_full_message,
                mr_data)

            message_request.callLater = call_later_obj
            return

        # If execution reach to this line, then it means no peer was able to provide
        # Full message for this hash thus the hash has to be deleted.
        # Moreover, negative points could be added to the peers, for this behavior
        if msg_hash in self.master_mr.requested_hash:
            del self.master_mr.requested_hash[msg_hash]
예제 #13
0
    def test_request_full_message_no_peer_could_provide_full_message(self, m_reactor, m_logger):
        """
        If this happens, we should forget about the MessageReceipt and its hash completely.
        Optionally punish peers.
        """
        # The local node does not already have this message.
        mrData = qrllegacy_pb2.MRData(type=qrllegacy_pb2.LegacyMessage.SL, hash=b'1234')

        # No idea where we can get this Message from. We haven't requested this Message from anybody.
        message_request = MessageRequest()
        message_request.peers_connection_list = []
        message_request.already_requested_peers = []
        self.factory.master_mr.requested_hash[b'1234'] = message_request

        self.factory.request_full_message(mrData)

        self.channel_1.send.assert_not_called()
        self.assertIsNone(self.factory.master_mr.requested_hash.get(b'1234'))
예제 #14
0
    def test_handle_message_received(self):
        """
        handle_message_received() handles MessageReceipts. MessageReceipts are metadata for Messages, so peers know
        which peer has which blocks/transactions/whatever, but can request the full Message at their discretion.
        :return:
        """
        mrData = qrllegacy_pb2.MRData(type=qrllegacy_pb2.LegacyMessage.SL)
        msg = make_message(func_name=qrllegacy_pb2.LegacyMessage.MR,
                           mrData=mrData)

        # No, we do not already have this particular TX.
        self.channel.factory.master_mr.contains.return_value = False
        # No, we have not already requested the Message behind this MessageReceipt.
        self.channel.factory.master_mr.is_callLater_active.return_value = False

        P2PTxManagement.handle_message_received(self.channel, msg)

        self.channel.factory.request_full_message.assert_called_once_with(
            mrData)
예제 #15
0
    def test_handle_full_message_request(self):
        """
        A peer has requested a full message that corresponds to a MessageReceipt.
        This function checks if we actually have the full message for the given hash.
        If it does, send to peer, otherwise ignore.
        """

        # We do have the Message the peer requested.
        mrData = qrllegacy_pb2.MRData(type=qrllegacy_pb2.LegacyMessage.SL)
        msg = make_message(func_name=qrllegacy_pb2.LegacyMessage.MR,
                           mrData=mrData)
        P2PTxManagement.handle_full_message_request(self.channel, msg)
        self.channel.send.assert_called()

        # We don't have the Message the peer requested.
        self.channel.send.reset_mock()
        self.channel.factory.master_mr.get.return_value = None
        P2PTxManagement.handle_full_message_request(self.channel, msg)
        self.channel.send.assert_not_called()
예제 #16
0
    def test_handle_block(self, m_logger):
        """
        1. A peer has found a new block. It broadcasts the MessageReceipt for that block.
        2. This node finds that it hasn't got that block yet, so it requests that block.
        3. The peer sends the new block, which is handled by this function.
        :return:
        """
        msg = make_message(func_name=qrllegacy_pb2.LegacyMessage.BK,
                           block=qrl_pb2.Block(),
                           mrData=qrllegacy_pb2.MRData())
        self.manager.handle_block(self.channel, msg)
        self.channel.factory.master_mr.register.assert_called()

        # But if we didn't request this block, we shouldn't process it.
        self.channel.factory.master_mr.register.reset_mock()
        self.channel.factory.master_mr.isRequested.return_value = False

        self.manager.handle_block(self.channel, msg)
        self.channel.factory.master_mr.register.assert_not_called()
예제 #17
0
    def test_request_full_message_already_requested_this_message_from_same_peer(self, m_reactor, m_logger):
        # The local node does not already have this message.
        mrData = qrllegacy_pb2.MRData(type=qrllegacy_pb2.LegacyMessage.SL, hash=b'1234')

        # You can get this Message from channel_1 and channel_2. Also, we already requested this Message from channel_1.
        message_request = MessageRequest()
        message_request.peers_connection_list = [self.channel_1, self.channel_2]
        message_request.already_requested_peers = [self.channel_1]
        self.factory.master_mr.requested_hash[b'1234'] = message_request

        self.factory.request_full_message(mrData)

        # We should leave channel_1 alone, but we ask channel_2 for the Message.
        self.channel_1.send.assert_not_called()
        self.channel_2.send.assert_called_once()

        # We use reactor.callLater() to schedule a call to request_full_message() again in the future.
        # So if this peer doesn't respond, when this function is next called again, it will ignore this peer
        # because of already_requested_peers and use the next one in peers_list.
        m_reactor.callLater.assert_called_once()
예제 #18
0
    def broadcast(self, msg_type, msg_hash: bytes, mr_data=None):
        """
        Broadcast
        This function sends the Message Receipt to all connected peers.
        :return:
        """
        ignore_peers = []
        if msg_hash in self.master_mr.requested_hash:
            ignore_peers = self.master_mr.requested_hash[msg_hash].peers_connection_list

        if not mr_data:
            mr_data = qrllegacy_pb2.MRData()

        mr_data.hash = msg_hash
        mr_data.type = msg_type
        data = qrllegacy_pb2.LegacyMessage(func_name=qrllegacy_pb2.LegacyMessage.MR,
                                           mrData=mr_data)

        for peer in self._peer_connections:
            if peer not in ignore_peers:
                peer.send(data)
예제 #19
0
    def test_handle_message_received_transactions_ignored_while_mempool_full(
            self, mock_logger):
        self.channel.factory.sync_state.state = ESyncState.synced
        self.channel.factory.master_mr.contains.return_value = False  # No, we do not already have this Message.
        self.channel.factory.master_mr.is_callLater_active.return_value = False  # No, we haven't requested this Message yet.

        # First, test when the mempool is NOT full
        self.channel.factory._chain_manager.tx_pool.is_full_pending_transaction_pool.return_value = False
        mrData = qrllegacy_pb2.MRData(type=qrllegacy_pb2.LegacyMessage.TX)
        msg = make_message(func_name=qrllegacy_pb2.LegacyMessage.MR,
                           mrData=mrData)

        P2PTxManagement.handle_message_received(self.channel, msg)

        self.channel.factory.request_full_message.assert_called()

        # Now, test when the mempool IS full
        self.channel.factory.request_full_message.reset_mock()
        self.channel.factory._chain_manager.tx_pool.is_full_pending_transaction_pool.return_value = True

        P2PTxManagement.handle_message_received(self.channel, msg)

        self.channel.factory.request_full_message.assert_not_called()