コード例 #1
0
ファイル: test_utils.py プロジェクト: BitPhone/drogulus
 def test_sort_contacts_no_longer_than_k(self):
     """
     Ensure that no more than constants.K contacts are returned from the
     sort_contacts function despite a longer list being passed in.
     """
     contacts = []
     for i in range(512):
         contact = Contact(2 ** i, "192.168.0.%d" % i, 9999, self.version,
                           0)
         contacts.append(contact)
     target_key = long_to_hex(2 ** 256)
     result = sort_contacts(contacts, target_key)
     self.assertEqual(constants.K, len(result))
コード例 #2
0
ファイル: routingtable.py プロジェクト: BitPhone/drogulus
    def find_close_nodes(self, key, rpc_node_id=None):
        """
        Finds up to "K" number of known nodes closest to the node/value with
        the specified key. If rpc_node_id is supplied the referenced node will
        be excluded from the returned contacts.

        The result is a list of "K" node contacts of type dht.contact.Contact.
        Will only return fewer than "K" contacts if not enough contacts are
        known.

        The result is ordered from closest to furthest away from the target
        key.
        """
        bucket_index = self._kbucket_index(key)
        closest_nodes = self._buckets[bucket_index].get_contacts(
            constants.K, rpc_node_id)
        # How far away to jump beyond the containing bucket of the given key.
        bucket_jump = 1
        number_of_buckets = len(self._buckets)
        # Flags that indicate if it's possible to jump higher or lower through
        # the buckets.
        can_go_lower = bucket_index - bucket_jump >= 0
        can_go_higher = bucket_index + bucket_jump < number_of_buckets
        while (len(closest_nodes) <
               constants.K and (can_go_lower or can_go_higher)):
            # Continue to fill the closestNodes list with contacts from the
            # nearest unchecked neighbouring k-buckets. Have chosen to opt for
            # readability rather than conciseness.
            if can_go_lower:
                # Neighbours lower in the key index.
                remaining_slots = constants.K - len(closest_nodes)
                jump_to_neighbour = bucket_index - bucket_jump
                neighbour = self._buckets[jump_to_neighbour]
                contacts = neighbour.get_contacts(remaining_slots, rpc_node_id)
                closest_nodes.extend(contacts)
                can_go_lower = bucket_index - (bucket_jump + 1) >= 0
            if can_go_higher:
                # Neighbours higher in the key index.
                remaining_slots = constants.K - len(closest_nodes)
                jump_to_neighbour = bucket_index + bucket_jump
                neighbour = self._buckets[jump_to_neighbour]
                contacts = neighbour.get_contacts(remaining_slots, rpc_node_id)
                closest_nodes.extend(contacts)
                can_go_higher = (bucket_index + (bucket_jump + 1) <
                                 number_of_buckets)
            bucket_jump += 1

        # Order the nodes from closest to furthest away from the target key and
        # ensure we only return K contacts (in certain circumstances K+1
        # results are generated).
        return sort_contacts(closest_nodes, key)
コード例 #3
0
ファイル: test_utils.py プロジェクト: BitPhone/drogulus
    def test_sort_contacts(self):
        """
        Ensures that the sort_contacts function returns the list ordered in
        such a way that the contacts closest to the target key are at the head
        of the list.
        """
        contacts = []
        for i in range(512):
            contact = Contact(2 ** i, "192.168.0.%d" % i, 9999, self.version,
                              0)
            contacts.append(contact)
        target_key = long_to_hex(2 ** 256)
        result = sort_contacts(contacts, target_key)

        # Ensure results are in the correct order.
        def key(node):
            return distance(node.id, target_key)
        sorted_nodes = sorted(result, key=key)
        self.assertEqual(sorted_nodes, result)
        # Ensure the order is from lowest to highest in terms of distance
        distances = [distance(x.id, target_key) for x in result]
        self.assertEqual(sorted(distances), distances)
コード例 #4
0
ファイル: node.py プロジェクト: juilyoon/drogulus
    def _handle_response(self, uuid, contact, response):
        """
        Callback to handle expected responses (unexpected responses result in
        the remote node being blacklisted and a TypeError being thrown).

        When a response to a request is returned successfully remove the
        request from self.pending_requests.

        If it's a FindValue message and a suitable value is returned (see note
        at the end of these comments) cancel all the other pending calls in
        self.pending_requests and fire a callback with with the returned value.
        If the value is invalid blacklist the node, remove it from
        self.shortlist and start from step 3 again without cancelling the other
        pending calls.

        If a list of closer nodes is returned by a peer add them to
        self.shortlist and sort - making sure nodes in self.contacted are not
        mistakenly re-added to the shortlist.

        If the nearest node in the newly sorted self.shortlist is closer to the
        target than self.nearest_node then set self.nearest_node to the new
        closer node and start from step 3 again.

        If self.nearest_node remains unchanged DO NOT start a new lookup call.

        If there are no other requests in self.pending_requests then check that
        the constants.K nearest nodes in the self.contacted set are all closer
        than the nearest node in self.shortlist. If they are, and it's a
        FindNode message call back with the constants.K nearest nodes found in
        the self.contacted set. If the message is a FindValue, errback with a
        ValueNotFound error.

        If there are still nearer nodes in self.shortlist to some of those in
        the constants.K nearest nodes in the self.contacted set then start
        from step 3 again (forcing the local node to contact the close nodes
        that have yet to be contacted).

        Note on validating values: In the future there may be constraints added
        to the FindValue query (such as only accepting values created after
        time T).
        """
        # Remove originating request from pending requests.
        del self.pending_requests[uuid]

        # Ensure the response is of the expected type[s].
        if not ((isinstance(response, Value) and
                 self.message_type == FindValue) or
                isinstance(response, Nodes)):
            # Blacklist the problem contact from the routing table (since it
            # doesn't behave).
            self._blacklist(contact)
            raise TypeError("Unexpected response type from %r" % contact)

        # Is the response the expected Value we're looking for..?
        if isinstance(response, Value):
            # Check if it's a suitable value (the key matches)
            if response.key == self.target:
                # Ensure the Value has not expired.
                if response.expires < time.time():
                    # Do not blacklist expired nodes but simply remove them
                    # from the shortlist (handled by the errback).
                    raise ValueError("Expired value returned by %r" % contact)
                # Cancel outstanding requests.
                self._cancel_pending_requests()
                # Ensure the returning contact is removed from the shortlist
                # (so it's possible to discern the closest non-returning node)
                if contact in self.shortlist:
                    self.shortlist.remove(contact)

                # Success! The correct Value has been found. Fire the instance
                # with the result.
                self.callback(response)
            else:
                # Blacklist the problem contact from the routing table since
                # it's not behaving properly.
                self._blacklist(contact)
                raise ValueError("Value with wrong key returned by %r" %
                                 contact)
        else:
            # Otherwise it must be a Nodes message containing closer nodes.
            # Add the returned nodes to the shortlist. Sort the shortlist in
            # order of closeness to the target and ensure the shortlist never
            # gets longer than K.
            candidate_contacts = [candidate for candidate in response.nodes
                                  if candidate not in self.shortlist]
            self.shortlist = sort_contacts(candidate_contacts +
                                           self.shortlist, self.target)
            # Check if the nearest_node remains unchanged.
            if self.nearest_node == self.shortlist[0]:
                # Check for remaining pending requests.
                if not self.pending_requests:
                    # Check all the candidates in the shortlist have been
                    # contacted.
                    candidates = [candidate for candidate in self.shortlist if
                                  candidate in self.contacted]
                    if len(candidates) == len(self.shortlist):
                        # There is a result.
                        if self.message_type == FindValue:
                            # Can't find a value at the key.
                            msg = ("Unable to find value for key: %r" %
                                   self.target)
                            self.errback(ValueNotFound(msg))
                        else:
                            # Success! Found nodes close to the specified
                            # target key.
                            self.callback(self.shortlist)
                    else:
                        # There are still un-contacted peers in the shortlist
                        # so restart the lookup in order to check them.
                        self._lookup()
                else:
                    # There are still pending requests to complete but do not
                    # restart the lookup
                    pass
            else:
                # There is a new nearest node.
                self.nearest_node = self.shortlist[0]
                # Restart the lookup given the newly found nodes in the
                # shortlist.
                self._lookup()