def test_remove_contact_with_cached_replacement(self): """ Ensures that the removed contact is replaced by the most up-to-date contact in the affected k-bucket's cache. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) contact1 = Contact('a', '192.168.0.1', 9999, self.version, 0) contact2 = Contact('b', '192.168.0.2', 9999, self.version, 0) r.add_contact(contact1) # Contact 2 will have the wrong number of failedRPCs r.add_contact(contact2) contact2.failed_RPCs = constants.ALLOWED_RPC_FAILS # Add something into the cache. contact3 = Contact('c', '192.168.0.3', 9999, self.version, 0) r._replacement_cache[0] = [contact3, ] # Sanity check self.assertEqual(len(r._buckets[0]), 2) self.assertEqual(len(r._replacement_cache[0]), 1) r.remove_contact('b') self.assertEqual(len(r._buckets[0]), 2) self.assertEqual(contact1, r._buckets[0]._contacts[0]) self.assertEqual(contact3, r._buckets[0]._contacts[1]) self.assertEqual(len(r._replacement_cache[0]), 0)
def test_remove_contact_with_cached_replacement(self): """ Ensures that the removed contact is replaced by the most up-to-date contact in the affected k-bucket's cache. """ parent_node_id = hex((2 ** 512) + 1)[2:] r = RoutingTable(parent_node_id) cache_key = (r._buckets[0].range_min, r._buckets[0].range_max) contact1 = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.1:9999/', 0) contact2 = PeerNode(BAD_PUBLIC_KEY, self.version, 'netstring://192.168.0.1:9999/', 0) r.add_contact(contact1) r.add_contact(contact2) contact2.failed_RPCs = constants.ALLOWED_RPC_FAILS # Add something into the cache. contact3 = PeerNode(PUBLIC_KEY + 'foo', self.version, 'netstring://192.168.0.1:9999/', 0) contact3.network_id = '3' r._replacement_cache[cache_key] = [contact3, ] # Sanity check self.assertEqual(len(r._buckets[0]), 2) self.assertEqual(len(r._replacement_cache[cache_key]), 1) r.remove_contact(BAD_PUBLIC_KEY) self.assertEqual(len(r._buckets[0]), 2) self.assertEqual(contact1, r._buckets[0]._contacts[0]) self.assertEqual(contact3, r._buckets[0]._contacts[1]) self.assertEqual(len(r._replacement_cache[cache_key]), 0)
def test_add_contact_with_full_replacement_cache(self): """ Ensures that if the replacement cache is full (length = k) then the oldest contact within the cache is replaced with the new contact that was just seen. """ parent_node_id = hex((2 ** 512) + 1)[2:] r = RoutingTable(parent_node_id) # Fill up the bucket and replacement cache for i in range(40): uri = 'netstring://192.168.0.%d:9999/' % i contact = PeerNode(PUBLIC_KEY, self.version, uri, 0) contact.network_id = hex(i) r.add_contact(contact) # Sanity check of the replacement cache. cache_key = (r._buckets[0].range_min, r._buckets[0].range_max) self.assertEqual(len(r._replacement_cache[cache_key]), 20) self.assertEqual(hex(20), r._replacement_cache[cache_key][0].network_id) # Create a new contact that will be added to the replacement cache. new_contact = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.20:9999/', 0) new_contact.network_id = hex(40) r.add_contact(new_contact) self.assertEqual(len(r._replacement_cache[cache_key]), 20) self.assertEqual(new_contact, r._replacement_cache[cache_key][19]) self.assertEqual(hex(21), r._replacement_cache[cache_key][0].network_id)
def test_find_close_nodes_in_correct_order(self): """ Ensures that the nearest nodes are returned in the correct order: from the node closest to the target key to the node furthest away. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) # Fill up the bucket and replacement cache for i in range(512): uri = 'netstring://192.168.0.%d:9999/' % i contact = PeerNode(PUBLIC_KEY, self.version, uri, 0) contact.network_id = hex(2 ** i) r.add_contact(contact) target_key = hex(2 ** 256) result = r.find_close_nodes(target_key) self.assertEqual(constants.K, len(result)) # Ensure results are in the correct order. def key(node): return distance(node.network_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.network_id, target_key) for x in result] self.assertEqual(sorted(distances), distances)
def test_add_contact_with_existing_contact_in_replacement_cache(self): """ Ensures that if the contact to be put in the replacement cache already exists in the replacement cache then it is bumped to the most recent position. """ parent_node_id = hex((2 ** 512) + 1)[2:] r = RoutingTable(parent_node_id) # Fill up the bucket and replacement cache for i in range(40): uri = 'netstring://192.168.0.%d:9999/' % i contact = PeerNode(PUBLIC_KEY, self.version, uri, 0) contact.network_id = hex(i) r.add_contact(contact) # Sanity check of the replacement cache. cache_key = (r._buckets[0].range_min, r._buckets[0].range_max) self.assertEqual(len(r._replacement_cache[cache_key]), 20) self.assertEqual(hex(20), r._replacement_cache[cache_key][0].network_id) # Create a new contact that will be added to the replacement cache. new_contact = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.41:9999/', 0) new_contact.network_id = hex(20) r.add_contact(new_contact) self.assertEqual(len(r._replacement_cache[cache_key]), 20) self.assertEqual(new_contact, r._replacement_cache[cache_key][19]) self.assertEqual(hex(21), r._replacement_cache[cache_key][0].network_id)
def test_get_contact(self): """ Ensures that the correct contact is returned. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) contact1 = Contact('a', '192.168.0.1', 9999, self.version, 0) r.add_contact(contact1) result = r.get_contact('a') self.assertEqual(contact1, result)
def test_get_contact_does_not_exist(self): """ Ensures that a ValueError is returned if the referenced contact does not exist in the routing table. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) contact1 = Contact('a', '192.168.0.1', 9999, self.version, 0) r.add_contact(contact1) self.assertRaises(ValueError, r.get_contact, 'b')
def test_add_contact_with_parent_node_id(self): """ If the newly discovered contact is, in fact, this node then it's not added to the routing table. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) contact = Contact('abc', '192.168.0.1', 9999, 0) r.add_contact(contact) self.assertEqual(len(r._buckets[0]), 0)
def test_add_contact_with_parent_node_id(self): """ If the newly discovered contact is, in fact, this node then it's not added to the routing table. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) contact = PeerNode(PUBLIC_KEY, '192.168.0.1', 9999, 0) contact.network_id = parent_node_id r.add_contact(contact) self.assertEqual(len(r._buckets[0]), 0)
def test_get_contact_does_not_exist(self): """ Ensures that a ValueError is returned if the referenced contact does not exist in the routing table. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) contact1 = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.1:9999/', 0) r.add_contact(contact1) self.assertRaises(ValueError, r.get_contact, 'b')
def test_get_contact(self): """ Ensures that the correct contact is returned. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) contact1 = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.1:9999/', 0) contact1.network_id = 'a' r.add_contact(contact1) result = r.get_contact('a') self.assertEqual(contact1, result)
def test_find_close_nodes_fewer_than_K(self): """ Ensures that all close nodes are returned if their number is < K. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) # Fill up the bucket and replacement cache for i in range(10): contact = Contact(i, "192.168.0.%d" % i, self.version, 0) r.add_contact(contact) result = r.find_close_nodes(1) self.assertEqual(10, len(result))
def test_find_close_nodes_single_kbucket(self): """ Ensures K number of closest nodes get returned. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) # Fill up the bucket and replacement cache for i in range(40): contact = Contact(i, "192.168.0.%d" % i, self.version, 0) r.add_contact(contact) result = r.find_close_nodes(1) self.assertEqual(20, len(result))
def test_add_contact_simple(self): """ Ensures that a newly discovered node in the network is added to the correct kbucket in the routing table. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) contact1 = Contact(2, '192.168.0.1', 9999, 0) contact2 = Contact(4, '192.168.0.2', 9999, 0) r.add_contact(contact1) self.assertEqual(len(r._buckets[0]), 1) r.add_contact(contact2) self.assertEqual(len(r._buckets[0]), 2)
def test_find_close_nodes_exclude_contact(self): """ Ensure that nearest nodes are returned except for the specified excluded node. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) # Fill up the bucket and replacement cache for i in range(20): contact = Contact(str(i), "192.168.0.%d" % i, self.version, 0) r.add_contact(contact) result = r.find_close_nodes("1", rpc_node_id=contact) self.assertEqual(19, len(result))
def test_find_close_nodes_multiple_buckets(self): """ Ensures that nodes are returned from neighbouring k-buckets if the k-bucket containing the referenced ID doesn't contain K entries. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) # Fill up the bucket and replacement cache for i in range(512): contact = Contact(2 ** i, "192.168.0.%d" % i, self.version, 0) r.add_contact(contact) result = r.find_close_nodes(2 ** 256) self.assertEqual(20, len(result))
def test_add_contact_with_blacklisted_contact(self): """ If the newly discovered contact is, in fact, already in the local node's blacklist then ensure it doesn't get re-added. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) contact1 = Contact(2, '192.168.0.1', 9999, 0) contact2 = Contact(4, '192.168.0.2', 9999, 0) r.blacklist(contact2) r.add_contact(contact1) self.assertEqual(len(r._buckets[0]), 1) r.add_contact(contact2) self.assertEqual(len(r._buckets[0]), 1)
def test_find_close_nodes_single_bucket(self): """ Ensures K number of closest nodes get returned. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) # Fill up the bucket and replacement cache for i in range(40): uri = 'netstring://192.168.0.%d:9999/' % i contact = PeerNode(PUBLIC_KEY, self.version, uri, 0) contact.network_id = hex(i) r.add_contact(contact) result = r.find_close_nodes(hex(1)) self.assertEqual(constants.K, len(result))
def test_find_close_nodes_fewer_than_K(self): """ Ensures that all close nodes are returned if their number is < K. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) # Fill up the bucket and replacement cache for i in range(10): uri = 'netstring://192.168.0.%d:9999/' % i contact = PeerNode(PUBLIC_KEY, self.version, uri, 0) contact.network_id = hex(i) r.add_contact(contact) result = r.find_close_nodes(hex(1)) self.assertEqual(10, len(result))
def test_remove_contact_with_unknown_contact(self): """ Ensures that attempting to remove a non-existent contact results in a ValueError exception. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) contact1 = Contact('a', '192.168.0.1', 9999, self.version, 0) r.add_contact(contact1) # Sanity check self.assertEqual(len(r._buckets[0]), 1) result = r.remove_contact('b') self.assertEqual(None, result) self.assertEqual(len(r._buckets[0]), 1) self.assertEqual(contact1, r._buckets[0]._contacts[0])
def test_add_contact_simple(self): """ Ensures that a newly discovered node in the network is added to the correct bucket in the routing table. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) contact1 = PeerNode(PUBLIC_KEY, '192.168.0.1', 9999, 0) contact1.network_id = hex(2) contact2 = PeerNode(PUBLIC_KEY, '192.168.0.2', 9999, 0) contact2.network_id = hex(4) r.add_contact(contact1) self.assertEqual(len(r._buckets[0]), 1) r.add_contact(contact2) self.assertEqual(len(r._buckets[0]), 2)
def test_find_close_nodes_exclude_contact(self): """ Ensure that nearest nodes are returned except for the specified excluded node. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) # Fill up the bucket and replacement cache for i in range(20): uri = 'netstring://192.168.0.%d:9999/' % i contact = PeerNode(PUBLIC_KEY, self.version, uri, 0) contact.network_id = hex(i) r.add_contact(contact) result = r.find_close_nodes(hex(1), excluded_id=contact.network_id) self.assertEqual(constants.K - 1, len(result))
def test_find_close_nodes_multiple_buckets(self): """ Ensures that nodes are returned from neighbouring k-buckets if the k-bucket containing the referenced ID doesn't contain K entries. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) # Fill up the bucket and replacement cache for i in range(512): uri = 'netstring://192.168.0.%d:9999/' % i contact = PeerNode(PUBLIC_KEY, self.version, uri, 0) contact.network_id = hex(2 ** i) r.add_contact(contact) result = r.find_close_nodes(hex(2 ** 256)) self.assertEqual(constants.K, len(result))
def test_add_contact_id_out_of_range(self): """ Ensures a Contact with an out-of-range id cannot be added to the routing table. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) with self.assertRaises(TypeError): # id too small contact = Contact(-1, '192.168.0.1', self.version, 0) r.add_contact(contact) with self.assertRaises(ValueError): # id too big big_id = (2 ** 512) contact = Contact(big_id, '192.168.0.1', self.version, 0) r.add_contact(contact)
def test_remove_contact_with_not_enough_RPC_fails(self): """ Ensures that the contact is not removed if it's failedRPCs counter is less than constants.ALLOWED_RPC_FAILS """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) contact1 = Contact('a', '192.168.0.1', 9999, self.version, 0) contact2 = Contact('b', '192.168.0.2', 9999, self.version, 0) r.add_contact(contact1) r.add_contact(contact2) # Sanity check self.assertEqual(len(r._buckets[0]), 2) r.remove_contact('b') self.assertEqual(len(r._buckets[0]), 2)
def test_add_contact_with_blacklisted_contact(self): """ If the newly discovered contact is, in fact, already in the local node's blacklist then ensure it doesn't get re-added. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) contact1 = PeerNode(PUBLIC_KEY, '192.168.0.1', 9999, 0) contact1.network_id = hex(2) contact2 = PeerNode(BAD_PUBLIC_KEY, '192.168.0.2', 9999, 0) contact2.network_id = hex(4) r.blacklist(contact2) r.add_contact(contact1) self.assertEqual(len(r._buckets[0]), 1) r.add_contact(contact2) self.assertEqual(len(r._buckets[0]), 1)
def test_add_contact_with_bucket_split(self): """ Ensures that newly discovered nodes are added to the appropriate kbucket given a bucket split. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) for i in range(20): contact = Contact(i, '192.168.0.%d' % i, self.version, 0) r.add_contact(contact) # This id will be just over the max range for the bucket in position 0 large_id = ((2 ** 512) / 2) + 1 contact = Contact(large_id, '192.168.0.33', self.version, 0) r.add_contact(contact) self.assertEqual(len(r._buckets), 2) self.assertEqual(len(r._buckets[0]), 20) self.assertEqual(len(r._buckets[1]), 1)
def test_remove_contact_with_unknown_contact(self): """ Ensures that attempting to remove a non-existent contact results in no change. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) contact1 = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.1:9999/', 0) contact1.network_id = 'a' r.add_contact(contact1) # Sanity check self.assertEqual(len(r._buckets[0]), 1) result = r.remove_contact('b') self.assertEqual(None, result) self.assertEqual(len(r._buckets[0]), 1) self.assertEqual(contact1, r._buckets[0]._contacts[0])
def test_add_contact_with_bucket_full(self): """ Checks if a bucket is full and a new contact within the full bucket's range is added then it gets put in the replacement cache. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) # Fill up the bucket for i in range(20): contact = Contact(i, '192.168.0.%d' % i, self.version, 0) r.add_contact(contact) # Create a new contact that will be added to the replacement cache. contact = Contact(20, '192.168.0.20', self.version, 0) r.add_contact(contact) self.assertEqual(len(r._buckets[0]), 20) self.assertTrue(0 in r._replacement_cache) self.assertEqual(contact, r._replacement_cache[0][0])
def test_remove_contact_with_not_enough_RPC_but_forced(self): """ Ensures that the contact is removed despite it's failedRPCs counter being less than constants.ALLOWED_RPC_FAILS because the 'forced' flag is used. """ parent_node_id = 'abc' r = RoutingTable(parent_node_id) contact1 = Contact('a', '192.168.0.1', 9999, self.version, 0) contact2 = Contact('b', '192.168.0.2', 9999, self.version, 0) r.add_contact(contact1) r.add_contact(contact2) # Sanity check self.assertEqual(len(r._buckets[0]), 2) r.remove_contact('b', forced=True) self.assertEqual(len(r._buckets[0]), 1)
def test_remove_contact_with_not_enough_RPC_but_forced(self): """ Ensures that the contact is removed despite it's failedRPCs counter being less than constants.ALLOWED_RPC_FAILS because the 'forced' flag is used. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) contact1 = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.1:9999/', 0) contact2 = PeerNode(BAD_PUBLIC_KEY, self.version, 'netstring://192.168.0.1:9999/', 0) r.add_contact(contact1) r.add_contact(contact2) # Sanity check self.assertEqual(len(r._buckets[0]), 2) r.remove_contact(BAD_PUBLIC_KEY, forced=True) self.assertEqual(len(r._buckets[0]), 1)
def test_bucket_index_out_of_range(self): """ If the requested id is not within the range of the keyspace then a ValueError should be raised. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) # Populate the routing table with contacts. for i in range(512): uri = 'netstring://192.168.0.%d:9999/' % i contact = PeerNode(PUBLIC_KEY, self.version, uri, 0) contact.network_id = hex(2 ** i) r.add_contact(contact) with self.assertRaises(ValueError): # Incoming id that's too small. r.find_close_nodes('-1') with self.assertRaises(ValueError): # Incoming id that's too big big_id = hex(2 ** 512)[2:] r.find_close_nodes(big_id)
def test_remove_contact_with_not_enough_RPC_fails(self): """ Ensures that the contact is not removed if it's failedRPCs counter is less than constants.ALLOWED_RPC_FAILS """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) contact1 = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.1:9999/', 0) contact1.network_id = 'a' contact2 = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.1:9999/', 0) contact2.network_id = 'b' r.add_contact(contact1) r.add_contact(contact2) # Sanity check self.assertEqual(len(r._buckets[0]), 2) r.remove_contact('b') self.assertEqual(len(r._buckets[0]), 2)
def test_remove_contact(self): """ Ensures that a contact is removed, given that it's failedRPCs counter exceeds or is equal to constants.ALLOWED_RPC_FAILS """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) contact1 = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.1:9999/', 0) contact2 = PeerNode(BAD_PUBLIC_KEY, self.version, 'netstring://192.168.0.1:9999/', 0) r.add_contact(contact1) # contact2 will have the wrong number of failedRPCs r.add_contact(contact2) contact2.failed_RPCs = constants.ALLOWED_RPC_FAILS # Sanity check self.assertEqual(len(r._buckets[0]), 2) r.remove_contact(BAD_PUBLIC_KEY) self.assertEqual(len(r._buckets[0]), 1) self.assertEqual(contact1, r._buckets[0]._contacts[0])
def test_add_contact_with_bucket_split(self): """ Ensures that newly discovered nodes are added to the appropriate bucket given a bucket split. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) for i in range(20): uri = 'netstring://192.168.0.%d:9999/' % i contact = PeerNode(PUBLIC_KEY, self.version, uri, 0) contact.network_id = hex(i) r.add_contact(contact) # This id will be just over the max range for the bucket in position 0 contact = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.20:9999/', 0) large_id = int(((2 ** 512) / 2) + 1) contact.network_id = hex(large_id) r.add_contact(contact) self.assertEqual(len(r._buckets), 2) self.assertEqual(len(r._buckets[0]), 20) self.assertEqual(len(r._buckets[1]), 1)
def test_add_contact_with_bucket_full(self): """ Checks if a bucket is full and a new contact within the full bucket's range is added then it gets put in the replacement cache. """ parent_node_id = hex((2 ** 512) + 1)[2:] r = RoutingTable(parent_node_id) # Fill up the bucket for i in range(20): uri = 'netstring://192.168.0.%d:9999/' % i contact = PeerNode(PUBLIC_KEY, self.version, uri, 0) contact.network_id = hex(i) r.add_contact(contact) # Create a new contact that will be added to the replacement cache. contact = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.20:9999/', 0) contact.network_id = hex(20) r.add_contact(contact) cache_key = (r._buckets[0].range_min, r._buckets[0].range_max) self.assertTrue(cache_key in r._replacement_cache) self.assertEqual(len(r._buckets[0]), 20) self.assertEqual(contact, r._replacement_cache[cache_key][0])
def test_remove_contact_removes_from_replacement_cache(self): """ Ensures that if a contact is signalled to be removed it is also cleared from the replacement_cache that would otherwise be another route for it to be re-added to the routing table. """ parent_node_id = 'deadbeef' r = RoutingTable(parent_node_id) contact1 = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.1:9999/', 0) contact2 = PeerNode(BAD_PUBLIC_KEY, self.version, 'netstring://192.168.0.1:9999/', 0) r.add_contact(contact1) r.add_contact(contact2) cache_key = (r._buckets[0].range_min, r._buckets[0].range_max) r._replacement_cache[cache_key] = [] r._replacement_cache[cache_key].append(contact2) # Sanity check self.assertEqual(len(r._buckets[0]), 2) self.assertEqual(len(r._replacement_cache[cache_key]), 1) r.remove_contact(BAD_PUBLIC_KEY, forced=True) self.assertEqual(len(r._buckets[0]), 1) self.assertNotIn(contact2, r._replacement_cache[cache_key])