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 = xrdlegacy_pb2.MRData(type=xrdlegacy_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()
def broadcast_block(self, block: Block): # logger.info('<<<Transmitting block: ', block.headerhash) data = xrdlegacy_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(xrdlegacy_pb2.LegacyMessage.BK, block.headerhash, block.pbdata, data)
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 = xrdlegacy_pb2.MRData(type=xrdlegacy_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()
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=xrdlegacy_pb2.LegacyMessage.BK, block=xrd_pb2.Block(), mrData=xrdlegacy_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()
def test_request_full_message_we_already_have_this_message(self, m_reactor, m_logger): # The local node already has this message! mrData = xrdlegacy_pb2.MRData(type=xrdlegacy_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'))
def request_full_message(self, mr_data: xrdlegacy_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 = xrdlegacy_pb2.LegacyMessage( func_name=xrdlegacy_pb2.LegacyMessage.SFM, mrData=xrdlegacy_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]
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 = xrdlegacy_pb2.MRData(type=xrdlegacy_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'))
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=xrdlegacy_pb2.LegacyMessage.BK, block=xrd_pb2.Block(), mrData=xrdlegacy_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()
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 = xrdlegacy_pb2.MRData(type=xrdlegacy_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()
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 = xrdlegacy_pb2.MRData() mr_data.hash = msg_hash mr_data.type = msg_type data = xrdlegacy_pb2.LegacyMessage( func_name=xrdlegacy_pb2.LegacyMessage.MR, mrData=mr_data) for peer in self._peer_connections: if peer not in ignore_peers: peer.send(data)
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 = xrdlegacy_pb2.MRData(type=xrdlegacy_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()