Beispiel #1
0
 def test_handle_response_wrong_value_for_findnode_message(self):
     """
     Ensures that if a Value message is returned for a FindNode request
     then the misbehaving peer is blacklisted and the error is handled
     correctly.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     uuid = [uuid for uuid in lookup.pending_requests.keys()][0]
     contact = lookup.shortlist[0]
     msg = Value(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, self.target,
                 'value', time.time(), time.time() + 99999, self.version,
                 PUBLIC_KEY, 'name', 'signature')
     response = asyncio.Future()
     response.set_result(msg)
     lookup._blacklist = mock.MagicMock()
     lookup._handle_error = mock.MagicMock()
     lookup._handle_response(uuid, contact, response)
     lookup._blacklist.assert_called_once_with(contact)
     self.assertEqual(lookup._handle_error.call_count, 1)
     args = lookup._handle_error.call_args[0]
     self.assertEqual(args[0], uuid)
     self.assertEqual(args[1], contact)
     self.assertIsInstance(args[2], TypeError)
     self.assertEqual(args[2].args[0],
                      "Unexpected response type from {}".format(contact))
Beispiel #2
0
 def test_handle_response_still_nodes_uncontacted_in_shortlist(self):
     """
     Ensure that if there are no more pending requests but there are still
     uncontacted nodes in the shortlist then restart the lookup.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     lookup._lookup = mock.MagicMock()
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     # Only one item in pending_requests
     for i in range(1, len(uuids)):
         del lookup.pending_requests[uuids[i]]
     self.assertEqual(1, len(lookup.pending_requests))
     # Add K-1 items from shortlist to the contacted set.
     for i in range(K - 1):
         lookup.contacted.add(lookup.shortlist[i])
     # Ensure lookup is called with the 20th (uncontacted) contact.
     not_contacted = lookup.shortlist[K - 1]
     self.assertNotIn(not_contacted, lookup.contacted)
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal,
                 self.contacts)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.assertEqual(lookup._lookup.call_count, 1)
     self.node.send_find.called_once_with(not_contacted, self.target,
                                          FindNode)
Beispiel #3
0
 def test_lookup_adds_callback(self):
     """
     Ensure the _lookup method add the expected callback to the Future that
     represents the request to the remote node in the DHT.
     """
     # Reset event_loop so we start in a clean state.
     loop = asyncio.new_event_loop()
     asyncio.set_event_loop(loop)
     self.event_loop = asyncio.get_event_loop()
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     lookup._handle_response = mock.MagicMock()
     keys = []
     for k, v in lookup.pending_requests.items():
         keys.append(k)
         v.set_result('foo')
         self.event_loop.run_until_complete(v)
     self.assertEqual(lookup._handle_response.call_count, 3)
     for i, key in enumerate(keys):
         # check the callback called _handle_response with the correct
         # arguments.
         arg_key = lookup._handle_response.call_args_list[i][0][0]
         self.assertEqual(arg_key, key)
         arg_contact = lookup._handle_response.call_args_list[i][0][1]
         self.assertIn(arg_contact, lookup.contacted)
         arg_future = lookup._handle_response.call_args_list[i][0][2]
         self.assertEqual(arg_future.result(), 'foo')
Beispiel #4
0
 def test_lookup_adds_callback(self):
     """
     Ensure the _lookup method add the expected callback to the Future that
     represents the request to the remote node in the DHT.
     """
     # Reset event_loop so we start in a clean state.
     loop = asyncio.new_event_loop()
     asyncio.set_event_loop(loop)
     self.event_loop = asyncio.get_event_loop()
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     lookup._handle_response = mock.MagicMock()
     keys = []
     for k, v in lookup.pending_requests.items():
         keys.append(k)
         v.set_result('foo')
         self.event_loop.run_until_complete(v)
     self.assertEqual(lookup._handle_response.call_count, 3)
     for i, key in enumerate(keys):
         # check the callback called _handle_response with the correct
         # arguments.
         arg_key = lookup._handle_response.call_args_list[i][0][0]
         self.assertEqual(arg_key, key)
         arg_contact = lookup._handle_response.call_args_list[i][0][1]
         self.assertIn(arg_contact, lookup.contacted)
         arg_future = lookup._handle_response.call_args_list[i][0][2]
         self.assertEqual(arg_future.result(), 'foo')
Beispiel #5
0
 def test_handle_response_still_nodes_uncontacted_in_shortlist(self):
     """
     Ensure that if there are no more pending requests but there are still
     uncontacted nodes in the shortlist then restart the lookup.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     lookup._lookup = mock.MagicMock()
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     # Only one item in pending_requests
     for i in range(1, len(uuids)):
         del lookup.pending_requests[uuids[i]]
     self.assertEqual(1, len(lookup.pending_requests))
     # Add K-1 items from shortlist to the contacted set.
     for i in range(K - 1):
         lookup.contacted.add(lookup.shortlist[i])
     # Ensure lookup is called with the 20th (uncontacted) contact.
     not_contacted = lookup.shortlist[K - 1]
     self.assertNotIn(not_contacted, lookup.contacted)
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal,
                 self.contacts)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.assertEqual(lookup._lookup.call_count, 1)
     self.node.send_find.called_once_with(not_contacted, self.target,
                                          FindNode)
Beispiel #6
0
 def test_handle_response_value_message_wrong_key(self):
     """
     If a valid Value response is received but the key doesn't match the
     one being requested then the misbehaving node is blacklisted and
     appropriately dealt with.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     msg = Value(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, 'f00baa',
                 'value', time.time(), time.time() + 99999, self.version,
                 PUBLIC_KEY, 'name', 'signature')
     response = asyncio.Future()
     response.set_result(msg)
     lookup._blacklist = mock.MagicMock()
     lookup._handle_error = mock.MagicMock()
     lookup._handle_response(uuid, contact, response)
     lookup._blacklist.assert_called_once_with(contact)
     self.assertEqual(lookup._handle_error.call_count, 1)
     args = lookup._handle_error.call_args[0]
     self.assertEqual(args[0], uuid)
     self.assertEqual(args[1], contact)
     self.assertIsInstance(args[2], ValueError)
     self.assertEqual(args[2].args[0],
                      "Value with wrong key returned by {}"
                      .format(contact))
Beispiel #7
0
 def test_handle_response_value_results_in_node_lookup_callback(self):
     """
     Tests that if a valid Value message is handled then all the other
     pending requests for the lookup are cancelled and the lookup has its
     set_result method called with the Value.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     other_request1 = lookup.pending_requests[uuids[1]]
     other_request2 = lookup.pending_requests[uuids[2]]
     msg = Value(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, self.target,
                 'value', time.time(), time.time() + 99999, self.version,
                 PUBLIC_KEY, 'name', 'signature')
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.event_loop.run_until_complete(blip())
     # Check the lookup has fired correctly.
     self.assertTrue(lookup.done())
     self.assertEqual(lookup.result(), msg)
     # Check the other requests are cancelled.
     self.assertTrue(other_request1.cancelled())
     self.assertTrue(other_request2.cancelled())
     # Make sure the pending_requests dict is empty.
     self.assertEqual(0, len(lookup.pending_requests))
     # Ensure the contact that provided the result is NOT in the shortlist.
     self.assertNotIn(contact, lookup.shortlist)
Beispiel #8
0
 def test_cancel_already_done(self):
     """
     If the lookup is already done, ensure a call to cancel returns the
     expected (False) result.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     lookup.set_result('foo')
     result = lookup.cancel()
     self.assertFalse(result)
Beispiel #9
0
 def test_init_no_shortlist(self):
     """
     Ensure the Future is marked as done with a RoutingTableEmpty exception.
     """
     # Create an empty routing table.
     self.node.routing_table = RoutingTable(self.node.network_id)
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     self.assertEqual(True, lookup.done())
     self.assertRaises(RoutingTableEmpty, lookup.result)
Beispiel #10
0
 def test_init_no_shortlist(self):
     """
     Ensure the Future is marked as done with a RoutingTableEmpty exception.
     """
     # Create an empty routing table.
     self.node.routing_table = RoutingTable(self.node.network_id)
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     self.assertEqual(True, lookup.done())
     self.assertRaises(RoutingTableEmpty, lookup.result)
Beispiel #11
0
 def test_cancel_already_done(self):
     """
     If the lookup is already done, ensure a call to cancel returns the
     expected (False) result.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     lookup.set_result('foo')
     result = lookup.cancel()
     self.assertFalse(result)
Beispiel #12
0
 def test_blacklist(self):
     """
     Ensure a blacklist operation (where misbehaving peer nodes are marked
     as to be ignored) works as expected.
     """
     self.node.routing_table.blacklist = mock.MagicMock()
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     contact = lookup.shortlist[0]
     lookup._blacklist(contact)
     self.assertNotIn(contact, lookup.shortlist)
     self.node.routing_table.blacklist.assert_called_once_with(contact)
Beispiel #13
0
 def test_blacklist(self):
     """
     Ensure a blacklist operation (where misbehaving peer nodes are marked
     as to be ignored) works as expected.
     """
     self.node.routing_table.blacklist = mock.MagicMock()
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     contact = lookup.shortlist[0]
     lookup._blacklist(contact)
     self.assertNotIn(contact, lookup.shortlist)
     self.node.routing_table.blacklist.assert_called_once_with(contact)
Beispiel #14
0
 def test_cancel_pending_requests(self):
     """
     Ensure all the tasks in the lookup's pending_requests dict are
     cancelled.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     # Sanity check
     self.assertEqual(3, len(lookup.pending_requests))
     tasks = lookup.pending_requests.values()
     lookup._cancel_pending_requests()
     self.event_loop.run_until_complete(blip())
     self.assertEqual(lookup.pending_requests, {})
     for task in tasks:
         self.assertTrue(task.cancelled())
Beispiel #15
0
 def test_cancel(self):
     """
     Ensure that the expected operations happen when the lookup's cancel
     method is called (the pending requests also need to be cancelled).
     """
     patcher = mock.patch('asyncio.Future.cancel')
     mock_cancel = patcher.start()
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     lookup._cancel_pending_requests = mock.MagicMock()
     result = lookup.cancel()
     self.assertTrue(result)
     self.assertEqual(lookup._cancel_pending_requests.call_count, 1)
     self.assertEqual(mock_cancel.call_count, 1)
     patcher.stop()
Beispiel #16
0
 def test_cancel(self):
     """
     Ensure that the expected operations happen when the lookup's cancel
     method is called (the pending requests also need to be cancelled).
     """
     patcher = mock.patch('asyncio.Future.cancel')
     mock_cancel = patcher.start()
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     lookup._cancel_pending_requests = mock.MagicMock()
     result = lookup.cancel()
     self.assertTrue(result)
     self.assertEqual(lookup._cancel_pending_requests.call_count, 1)
     self.assertEqual(mock_cancel.call_count, 1)
     patcher.stop()
Beispiel #17
0
 def test_lookup_none_pending_all_contacted(self):
     """
     Ensures the _lookup method works with no pending requests and all known
     peer nodes having been contacted.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     # Put the lookup object in the state to test.
     lookup.pending_requests = {}
     for contact in lookup.shortlist:
         lookup.contacted.add(contact)
     self.node.send_find.call_count = 0
     # Re-run _lookup and test
     lookup._lookup()
     self.assertEqual(self.node.send_find.call_count, 0)
Beispiel #18
0
 def test_lookup_all_pending(self):
     """
     If no more pending slots are available ensure no further network calls
     are made.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     # Sanity check that ALPHA slots are full.
     self.assertEqual(self.node.send_find.call_count, ALPHA)
     self.assertEqual(len(lookup.pending_requests), ALPHA)
     self.assertEqual(len(lookup.contacted), ALPHA)
     self.assertEqual(len(lookup.shortlist), K)
     # Re-run _lookup and ensure no further network calls have been made.
     lookup._lookup()
     self.assertEqual(self.node.send_find.call_count, ALPHA)
Beispiel #19
0
 def test_cancel_pending_requests(self):
     """
     Ensure all the tasks in the lookup's pending_requests dict are
     cancelled.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     # Sanity check
     self.assertEqual(3, len(lookup.pending_requests))
     tasks = lookup.pending_requests.values()
     lookup._cancel_pending_requests()
     self.event_loop.run_until_complete(blip())
     self.assertEqual(lookup.pending_requests, {})
     for task in tasks:
         self.assertTrue(task.cancelled())
Beispiel #20
0
 def test_lookup_none_pending_all_contacted(self):
     """
     Ensures the _lookup method works with no pending requests and all known
     peer nodes having been contacted.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     # Put the lookup object in the state to test.
     lookup.pending_requests = {}
     for contact in lookup.shortlist:
         lookup.contacted.add(contact)
     self.node.send_find.call_count = 0
     # Re-run _lookup and test
     lookup._lookup()
     self.assertEqual(self.node.send_find.call_count, 0)
Beispiel #21
0
 def test_lookup_all_pending(self):
     """
     If no more pending slots are available ensure no further network calls
     are made.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     # Sanity check that ALPHA slots are full.
     self.assertEqual(self.node.send_find.call_count, ALPHA)
     self.assertEqual(len(lookup.pending_requests), ALPHA)
     self.assertEqual(len(lookup.contacted), ALPHA)
     self.assertEqual(len(lookup.shortlist), K)
     # Re-run _lookup and ensure no further network calls have been made.
     lookup._lookup()
     self.assertEqual(self.node.send_find.call_count, ALPHA)
Beispiel #22
0
 def test_handle_response_all_shortlist_contacted_value_not_found(self):
     """
     If there are no more pending requests and all the nodes in the
     shortlist have been contacted then return the shortlist of nearest
     peer nodes to the target if the lookup is a FindNode.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     lookup._lookup = mock.MagicMock()
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     # Only one item in pending_requests
     for i in range(1, len(uuids)):
         del lookup.pending_requests[uuids[i]]
     self.assertEqual(1, len(lookup.pending_requests))
     # Add K items from shortlist to the contacted set.
     for contact in lookup.shortlist:
         lookup.contacted.add(contact)
     # Cause the lookup to fire.
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal,
                 self.contacts)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     # The _lookup method should not be called.
     self.assertEqual(lookup._lookup.call_count, 0)
     # The lookup task has fired.
     self.assertTrue(lookup.done())
     with self.assertRaises(ValueNotFound) as result:
         lookup.result()
     self.assertIsInstance(result.exception, ValueNotFound)
     self.assertEqual(result.exception.args[0],
                      "Unable to find value for key: {}"
                      .format(self.target))
Beispiel #23
0
 def test_handle_response_value_never_expires(self):
     """
     Ensures an expired Value is handled correctly.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     msg = Value(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, self.target,
                 'value', time.time(), 0.0, self.version, PUBLIC_KEY,
                 'name', 'signature')
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.assertEqual(lookup.result(), msg)
Beispiel #24
0
 def test_init(self):
     """
     Ensure instantiating the Lookup class creates an object with the
     expected state.
     """
     patcher = mock.patch('asyncio.base_events.BaseEventLoop.call_later')
     mock_call_later = patcher.start()
     self.node.routing_table.touch_bucket = mock.MagicMock()
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     self.assertIsInstance(lookup, asyncio.Future)
     self.assertEqual(lookup.message_type, FindNode)
     self.assertEqual(lookup.target, self.target)
     self.assertEqual(lookup.local_node, self.node)
     self.assertEqual(lookup.event_loop, self.event_loop)
     self.assertIsInstance(lookup.contacted, set)
     self.assertEqual(3, len(lookup.contacted))
     self.assertIsInstance(lookup.pending_requests, dict)
     self.assertEqual(3, len(lookup.pending_requests))
     mock_call_later.assert_called_once_with(LOOKUP_TIMEOUT,
                                             lookup.cancel)
     self.assertEqual(len(lookup.shortlist), len(self.contacts))
     self.node.routing_table.touch_bucket.\
         assert_called_once_with(self.target)
     self.assertEqual(lookup.nearest_node, lookup.shortlist[0])
     self.assertEqual(3, self.node.send_find.call_count)
     patcher.stop()
Beispiel #25
0
 def test_handle_response_value_never_expires(self):
     """
     Ensures an expired Value is handled correctly.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     msg = Value(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, self.target,
                 'value', time.time(), 0.0, self.version, PUBLIC_KEY,
                 'name', 'signature')
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.assertEqual(lookup.result(), msg)
Beispiel #26
0
 def test_init_skips_touch_bucket_if_local_network_id_is_key(self):
     """
     Ensure touch_bucket doesn't happen if the target key is the local
     node's network_id.
     """
     self.node.routing_table.touch_bucket = mock.MagicMock()
     Lookup(FindNode, self.node.network_id, self.node, self.event_loop)
     self.assertEqual(self.node.routing_table.touch_bucket.call_count, 0)
Beispiel #27
0
 def test_handle_response_nodes_no_duplicates_in_shortlist(self):
     """
     If the response contains peer nodes that are already found in the
     lookup's shortlist they are not duplicated.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     shortlist = tuple([(p.public_key, p.version, p.uri) for p
                        in lookup.shortlist])
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, shortlist)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.assertEqual(lookup.shortlist, [PeerNode(*n) for n in shortlist])
Beispiel #28
0
 def test_handle_response_nodes_no_duplicates_in_shortlist(self):
     """
     If the response contains peer nodes that are already found in the
     lookup's shortlist they are not duplicated.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     shortlist = tuple([(p.public_key, p.version, p.uri) for p
                        in lookup.shortlist])
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, shortlist)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.assertEqual(lookup.shortlist, [PeerNode(*n) for n in shortlist])
Beispiel #29
0
 def test_handle_response_remove_request_from_pending(self):
     """
     Ensure the pending request that triggered the response being handled
     by the _handle_response callback is removed from the pending_requests
     dict.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     uuid = [uuid for uuid in lookup.pending_requests.keys()][0]
     contact = lookup.shortlist[0]
     msg = Value(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, self.target,
                 'value', time.time(), time.time() + 99999, self.version,
                 PUBLIC_KEY, 'name', 'signature')
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.assertNotIn(uuid, lookup.pending_requests.keys())
Beispiel #30
0
 def test_handle_response_remove_request_from_pending(self):
     """
     Ensure the pending request that triggered the response being handled
     by the _handle_response callback is removed from the pending_requests
     dict.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     uuid = [uuid for uuid in lookup.pending_requests.keys()][0]
     contact = lookup.shortlist[0]
     msg = Value(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, self.target,
                 'value', time.time(), time.time() + 99999, self.version,
                 PUBLIC_KEY, 'name', 'signature')
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.assertNotIn(uuid, lookup.pending_requests.keys())
Beispiel #31
0
    def test_lookup_some_pending_some_contacted(self):
        """
        Ensures the _lookup method works with some pending slots available and
        some nodes previously contacted.
        """
        lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
        # Reset in order to manually create the correct state.
        lookup.pending_requests = {}
        lookup.contacted = set()
        self.node.send_find.call_count = 0

        # Add a single pending request.
        pending_uuid = str(uuid.uuid4())
        pending_future = asyncio.Future()
        lookup.pending_requests[pending_uuid] = pending_future
        # Add a single contact to the contacted list.
        lookup.contacted.add(lookup.shortlist[0])
        # Sanity check.
        self.assertEqual(1, len(lookup.pending_requests))
        self.assertEqual(1, len(lookup.contacted))
        # Re-run _lookup and check state has been correctly updated.
        lookup._lookup()
        self.assertEqual(ALPHA - 1, self.node.send_find.call_count)
        self.assertEqual(ALPHA, len(lookup.pending_requests))
        self.assertEqual(ALPHA, len(lookup.contacted))
Beispiel #32
0
 def test_handle_response_all_shortlist_contacted_value_not_found(self):
     """
     If there are no more pending requests and all the nodes in the
     shortlist have been contacted then return the shortlist of nearest
     peer nodes to the target if the lookup is a FindNode.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     lookup._lookup = mock.MagicMock()
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     # Only one item in pending_requests
     for i in range(1, len(uuids)):
         del lookup.pending_requests[uuids[i]]
     self.assertEqual(1, len(lookup.pending_requests))
     # Add K items from shortlist to the contacted set.
     for contact in lookup.shortlist:
         lookup.contacted.add(contact)
     # Cause the lookup to fire.
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal,
                 self.contacts)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     # The _lookup method should not be called.
     self.assertEqual(lookup._lookup.call_count, 0)
     # The lookup task has fired.
     self.assertTrue(lookup.done())
     with self.assertRaises(ValueNotFound) as result:
         lookup.result()
     self.assertIsInstance(result.exception, ValueNotFound)
     self.assertEqual(result.exception.args[0],
                      "Unable to find value for key: {}"
                      .format(self.target))
Beispiel #33
0
 def test_lookup_added_callbacks_work_when_cancelled(self):
     """
     Ensures that the callback added to pending requests by the _lookup
     method handles cancelled results. This may happen if the lookup is
     finished because a suitable value has been found (so everything else
     can be stopped ASAP).
     """
     # Reset event_loop so we start in a clean state.
     loop = asyncio.new_event_loop()
     asyncio.set_event_loop(loop)
     self.event_loop = asyncio.get_event_loop()
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     lookup._handle_response = mock.MagicMock()
     lookup._cancel_pending_requests()
     for k, v in lookup.pending_requests.items():
         v.set_result('foo')
         self.event_loop.run_until_complete(v)
     self.assertEqual(lookup._handle_response.call_count, 0)
Beispiel #34
0
 def test_handle_response_nodes_adds_closest_nodes_to_shortlist(self):
     """
     Ensures a Nodes message causes the referenced peer nodes to be added
     to the shortlist in the correct order (closest to the target at the
     head of the list).
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal,
                 self.remote_nodes)
     response = asyncio.Future()
     response.set_result(msg)
     self.assertNotEqual(lookup.shortlist, list(self.nodes))
     lookup._handle_response(uuid, contact, response)
     self.assertEqual(lookup.shortlist, list(self.nodes))
Beispiel #35
0
 def test_handle_response_nodes_adds_closest_nodes_to_shortlist(self):
     """
     Ensures a Nodes message causes the referenced peer nodes to be added
     to the shortlist in the correct order (closest to the target at the
     head of the list).
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal,
                 self.remote_nodes)
     response = asyncio.Future()
     response.set_result(msg)
     self.assertNotEqual(lookup.shortlist, list(self.nodes))
     lookup._handle_response(uuid, contact, response)
     self.assertEqual(lookup.shortlist, list(self.nodes))
Beispiel #36
0
 def test_lookup_added_callbacks_work_when_cancelled(self):
     """
     Ensures that the callback added to pending requests by the _lookup
     method handles cancelled results. This may happen if the lookup is
     finished because a suitable value has been found (so everything else
     can be stopped ASAP).
     """
     # Reset event_loop so we start in a clean state.
     loop = asyncio.new_event_loop()
     asyncio.set_event_loop(loop)
     self.event_loop = asyncio.get_event_loop()
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     lookup._handle_response = mock.MagicMock()
     lookup._cancel_pending_requests()
     for k, v in lookup.pending_requests.items():
         v.set_result('foo')
         self.event_loop.run_until_complete(v)
     self.assertEqual(lookup._handle_response.call_count, 0)
Beispiel #37
0
 def test_handle_error(self):
     """
     The _handle_error method cleanly deals with the fallout of
     encountering an error generated from an interaction with a peer node.
     """
     patcher = mock.patch('drogulus.dht.lookup.log.info')
     mock_info = patcher.start()
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     lookup._lookup = mock.MagicMock()
     uuid = [uuid for uuid in lookup.pending_requests.keys()][0]
     pending_task = lookup.pending_requests[uuid]
     contact = lookup.shortlist[0]
     lookup._handle_error(uuid, contact, Exception('Foo'))
     self.assertNotIn(contact, lookup.shortlist)
     self.assertNotIn(uuid, lookup.pending_requests)
     self.assertTrue(pending_task.cancelled())
     # Log the error and associated exception (2 calls)
     self.assertEqual(mock_info.call_count, 2)
     self.assertEqual(lookup._lookup.call_count, 1)
     patcher.stop()
Beispiel #38
0
 def test_handle_response_nodes_update_nearest_node(self):
     """
     If the response contains peer nodes that are nearer to the target then
     the nearest_node variable is updated to reflect this change of state
     and a new lookup call is kicked off.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     lookup._lookup = mock.MagicMock()
     old_nearest_node = lookup.nearest_node
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal,
                 self.remote_nodes)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.assertNotEqual(lookup.nearest_node, old_nearest_node)
     self.assertEqual(lookup.nearest_node, lookup.shortlist[0])
     self.assertEqual(lookup._lookup.call_count, 1)
Beispiel #39
0
 def test_handle_response_nodes_update_nearest_node(self):
     """
     If the response contains peer nodes that are nearer to the target then
     the nearest_node variable is updated to reflect this change of state
     and a new lookup call is kicked off.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     lookup._lookup = mock.MagicMock()
     old_nearest_node = lookup.nearest_node
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal,
                 self.remote_nodes)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.assertNotEqual(lookup.nearest_node, old_nearest_node)
     self.assertEqual(lookup.nearest_node, lookup.shortlist[0])
     self.assertEqual(lookup._lookup.call_count, 1)
Beispiel #40
0
    def test_lookup_some_pending_some_contacted(self):
        """
        Ensures the _lookup method works with some pending slots available and
        some nodes previously contacted.
        """
        lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
        # Reset in order to manually create the correct state.
        lookup.pending_requests = {}
        lookup.contacted = set()
        self.node.send_find.call_count = 0

        # Add a single pending request.
        pending_uuid = str(uuid.uuid4())
        pending_future = asyncio.Future()
        lookup.pending_requests[pending_uuid] = pending_future
        # Add a single contact to the contacted list.
        lookup.contacted.add(lookup.shortlist[0])
        # Sanity check.
        self.assertEqual(1, len(lookup.pending_requests))
        self.assertEqual(1, len(lookup.contacted))
        # Re-run _lookup and check state has been correctly updated.
        lookup._lookup()
        self.assertEqual(ALPHA - 1, self.node.send_find.call_count)
        self.assertEqual(ALPHA, len(lookup.pending_requests))
        self.assertEqual(ALPHA, len(lookup.contacted))
Beispiel #41
0
 def test_handle_response_wrong_value_for_findnode_message(self):
     """
     Ensures that if a Value message is returned for a FindNode request
     then the misbehaving peer is blacklisted and the error is handled
     correctly.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     uuid = [uuid for uuid in lookup.pending_requests.keys()][0]
     contact = lookup.shortlist[0]
     msg = Value(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, self.target,
                 'value', time.time(), time.time() + 99999, self.version,
                 PUBLIC_KEY, 'name', 'signature')
     response = asyncio.Future()
     response.set_result(msg)
     lookup._blacklist = mock.MagicMock()
     lookup._handle_error = mock.MagicMock()
     lookup._handle_response(uuid, contact, response)
     lookup._blacklist.assert_called_once_with(contact)
     self.assertEqual(lookup._handle_error.call_count, 1)
     args = lookup._handle_error.call_args[0]
     self.assertEqual(args[0], uuid)
     self.assertEqual(args[1], contact)
     self.assertIsInstance(args[2], TypeError)
     self.assertEqual(args[2].args[0],
                      "Unexpected response type from {}".format(contact))
Beispiel #42
0
 def test_handle_response_value_results_in_node_lookup_callback(self):
     """
     Tests that if a valid Value message is handled then all the other
     pending requests for the lookup are cancelled and the lookup has its
     set_result method called with the Value.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     other_request1 = lookup.pending_requests[uuids[1]]
     other_request2 = lookup.pending_requests[uuids[2]]
     msg = Value(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, self.target,
                 'value', time.time(), time.time() + 99999, self.version,
                 PUBLIC_KEY, 'name', 'signature')
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.event_loop.run_until_complete(blip())
     # Check the lookup has fired correctly.
     self.assertTrue(lookup.done())
     self.assertEqual(lookup.result(), msg)
     # Check the other requests are cancelled.
     self.assertTrue(other_request1.cancelled())
     self.assertTrue(other_request2.cancelled())
     # Make sure the pending_requests dict is empty.
     self.assertEqual(0, len(lookup.pending_requests))
     # Ensure the contact that provided the result is NOT in the shortlist.
     self.assertNotIn(contact, lookup.shortlist)
Beispiel #43
0
 def test_handle_response_value_message_wrong_key(self):
     """
     If a valid Value response is received but the key doesn't match the
     one being requested then the misbehaving node is blacklisted and
     appropriately dealt with.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     msg = Value(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, 'f00baa',
                 'value', time.time(), time.time() + 99999, self.version,
                 PUBLIC_KEY, 'name', 'signature')
     response = asyncio.Future()
     response.set_result(msg)
     lookup._blacklist = mock.MagicMock()
     lookup._handle_error = mock.MagicMock()
     lookup._handle_response(uuid, contact, response)
     lookup._blacklist.assert_called_once_with(contact)
     self.assertEqual(lookup._handle_error.call_count, 1)
     args = lookup._handle_error.call_args[0]
     self.assertEqual(args[0], uuid)
     self.assertEqual(args[1], contact)
     self.assertIsInstance(args[2], ValueError)
     self.assertEqual(args[2].args[0],
                      "Value with wrong key returned by {}"
                      .format(contact))
Beispiel #44
0
 def test_handle_error(self):
     """
     The _handle_error method cleanly deals with the fallout of
     encountering an error generated from an interaction with a peer node.
     """
     patcher = mock.patch('drogulus.dht.lookup.log.info')
     mock_info = patcher.start()
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     lookup._lookup = mock.MagicMock()
     uuid = [uuid for uuid in lookup.pending_requests.keys()][0]
     pending_task = lookup.pending_requests[uuid]
     contact = lookup.shortlist[0]
     lookup.event_loop.call_soon = mock.MagicMock()
     lookup._handle_error(uuid, contact, Exception('Foo'))
     self.assertNotIn(contact, lookup.shortlist)
     self.assertNotIn(uuid, lookup.pending_requests)
     lookup.event_loop.call_soon.assert_called_once_with(
         pending_task.cancel)
     # Log the error and associated exception (2 calls)
     self.assertEqual(mock_info.call_count, 2)
     self.assertEqual(lookup._lookup.call_count, 1)
     patcher.stop()
Beispiel #45
0
 def test_handle_response_nodes_do_not_update_nearest_node(self):
     """
     If the response contains peer nodes that are NOT closer to the target
     than the current nearest known node then nearest_node is NOT
     updated and a new lookup is NOT triggered.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     lookup._lookup = mock.MagicMock()
     old_nearest_node = lookup.nearest_node
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     shortlist = tuple([(p.public_key, p.version, p.uri) for p
                        in lookup.shortlist])
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, shortlist)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.assertEqual(lookup.nearest_node, old_nearest_node)
     self.assertEqual(lookup.nearest_node, lookup.shortlist[0])
     self.assertEqual(lookup._lookup.call_count, 0)
Beispiel #46
0
 def test_lookup_none_pending_none_contacted(self):
     """
     Ensure the _lookup method works with no pending requests nor any nodes
     previously contacted (i.e. from a clean state).
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     # The _lookup method is called by __init__.
     # No more than ALPHA requests should be made.
     self.assertEqual(self.node.send_find.call_count, ALPHA)
     # Associated ALPHA number of pending_requests.
     self.assertEqual(len(lookup.pending_requests), ALPHA)
     # Associated contacts in the "contacted" set.
     self.assertEqual(len(lookup.contacted), ALPHA)
Beispiel #47
0
 def test_handle_response_nodes_do_not_update_nearest_node(self):
     """
     If the response contains peer nodes that are NOT closer to the target
     than the current nearest known node then nearest_node is NOT
     updated and a new lookup is NOT triggered.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     lookup._lookup = mock.MagicMock()
     old_nearest_node = lookup.nearest_node
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     shortlist = tuple([(p.public_key, p.version, p.uri) for p
                        in lookup.shortlist])
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, shortlist)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     self.assertEqual(lookup.nearest_node, old_nearest_node)
     self.assertEqual(lookup.nearest_node, lookup.shortlist[0])
     self.assertEqual(lookup._lookup.call_count, 0)
Beispiel #48
0
 def test_handle_response_value_expired(self):
     """
     Ensures an expired Value is handled correctly.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     msg = Value(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, self.target,
                 'value', time.time(), time.time() - 99999, self.version,
                 PUBLIC_KEY, 'name', 'signature')
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_error = mock.MagicMock()
     lookup._handle_response(uuid, contact, response)
     self.assertEqual(lookup._handle_error.call_count, 1)
     args = lookup._handle_error.call_args[0]
     self.assertEqual(args[0], uuid)
     self.assertEqual(args[1], contact)
     self.assertIsInstance(args[2], ValueError)
     self.assertEqual(args[2].args[0],
                      "Expired value returned by {}".format(contact))
Beispiel #49
0
 def test_handle_response_value_expired(self):
     """
     Ensures an expired Value is handled correctly.
     """
     lookup = Lookup(FindValue, self.target, self.node, self.event_loop)
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     msg = Value(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal, self.target,
                 'value', time.time(), time.time() - 99999, self.version,
                 PUBLIC_KEY, 'name', 'signature')
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_error = mock.MagicMock()
     lookup._handle_response(uuid, contact, response)
     self.assertEqual(lookup._handle_error.call_count, 1)
     args = lookup._handle_error.call_args[0]
     self.assertEqual(args[0], uuid)
     self.assertEqual(args[1], contact)
     self.assertIsInstance(args[2], ValueError)
     self.assertEqual(args[2].args[0],
                      "Expired value returned by {}".format(contact))
Beispiel #50
0
 def test_handle_response_wrong_message_type(self):
     """
     Ensure that a response that isn't a Nodes or Value message results in
     the responding peer node being blacklisted and the error being
     correctly handled.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     uuid = [uuid for uuid in lookup.pending_requests.keys()][0]
     contact = lookup.shortlist[0]
     msg = OK(uuid, self.node.network_id, self.node.network_id,
              self.reply_port, self.version, self.seal)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._blacklist = mock.MagicMock()
     lookup._handle_error = mock.MagicMock()
     lookup._handle_response(uuid, contact, response)
     lookup._blacklist.assert_called_once_with(contact)
     self.assertEqual(lookup._handle_error.call_count, 1)
     args = lookup._handle_error.call_args[0]
     self.assertEqual(args[0], uuid)
     self.assertEqual(args[1], contact)
     self.assertIsInstance(args[2], TypeError)
     self.assertEqual(args[2].args[0],
                      "Unexpected response type from {}".format(contact))
Beispiel #51
0
 def test_handle_response_all_shortlist_contacted_return_nodes(self):
     """
     If there are no more pending requests and all the nodes in the
     shortlist have been contacted then return the shortlist of nearest
     peer nodes to the target if the lookup is a FindNode.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     lookup._lookup = mock.MagicMock()
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     # Only one item in pending_requests
     for i in range(1, len(uuids)):
         del lookup.pending_requests[uuids[i]]
     self.assertEqual(1, len(lookup.pending_requests))
     # Add K items from shortlist to the contacted set.
     for contact in lookup.shortlist:
         lookup.contacted.add(contact)
     # Cause the lookup to fire.
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal,
                 self.contacts)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     # The _lookup method should not be called.
     self.assertEqual(lookup._lookup.call_count, 0)
     # The lookup task has fired.
     self.assertTrue(lookup.done())
     # Check the result is the ordered shortlist of contacts that are
     # closest to the target.
     # It should be a list...
     self.assertIsInstance(lookup.result(), list)
     # It should be a list that's the lookup's shortlist...
     self.assertEqual(lookup.result(), lookup.shortlist)
     # It should be a list that's the lookup's shortlist in order.
     ordered = sort_peer_nodes(lookup.shortlist, self.target)
     self.assertEqual(lookup.result(), ordered)
Beispiel #52
0
 def test_handle_response_all_shortlist_contacted_return_nodes(self):
     """
     If there are no more pending requests and all the nodes in the
     shortlist have been contacted then return the shortlist of nearest
     peer nodes to the target if the lookup is a FindNode.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     lookup._lookup = mock.MagicMock()
     uuids = [uuid for uuid in lookup.pending_requests.keys()]
     uuid = uuids[0]
     contact = lookup.shortlist[0]
     # Only one item in pending_requests
     for i in range(1, len(uuids)):
         del lookup.pending_requests[uuids[i]]
     self.assertEqual(1, len(lookup.pending_requests))
     # Add K items from shortlist to the contacted set.
     for contact in lookup.shortlist:
         lookup.contacted.add(contact)
     # Cause the lookup to fire.
     msg = Nodes(uuid, self.node.network_id, self.node.network_id,
                 self.reply_port, self.version, self.seal,
                 self.contacts)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._handle_response(uuid, contact, response)
     # The _lookup method should not be called.
     self.assertEqual(lookup._lookup.call_count, 0)
     # The lookup task has fired.
     self.assertTrue(lookup.done())
     # Check the result is the ordered shortlist of contacts that are
     # closest to the target.
     # It should be a list...
     self.assertIsInstance(lookup.result(), list)
     # It should be a list that's the lookup's shortlist...
     self.assertEqual(lookup.result(), lookup.shortlist)
     # It should be a list that's the lookup's shortlist in order.
     ordered = sort_peer_nodes(lookup.shortlist, self.target)
     self.assertEqual(lookup.result(), ordered)
Beispiel #53
0
 def test_handle_response_wrong_message_type(self):
     """
     Ensure that a response that isn't a Nodes or Value message results in
     the responding peer node being blacklisted and the error being
     correctly handled.
     """
     lookup = Lookup(FindNode, self.target, self.node, self.event_loop)
     uuid = [uuid for uuid in lookup.pending_requests.keys()][0]
     contact = lookup.shortlist[0]
     msg = OK(uuid, self.node.network_id, self.node.network_id,
              self.reply_port, self.version, self.seal)
     response = asyncio.Future()
     response.set_result(msg)
     lookup._blacklist = mock.MagicMock()
     lookup._handle_error = mock.MagicMock()
     lookup._handle_response(uuid, contact, response)
     lookup._blacklist.assert_called_once_with(contact)
     self.assertEqual(lookup._handle_error.call_count, 1)
     args = lookup._handle_error.call_args[0]
     self.assertEqual(args[0], uuid)
     self.assertEqual(args[1], contact)
     self.assertIsInstance(args[2], TypeError)
     self.assertEqual(args[2].args[0],
                      "Unexpected response type from {}".format(contact))