async def test_update_token(self): loop = asyncio.get_event_loop() with dht_mocks.mock_network_loop(loop): node_id1 = constants.generate_id() peer1 = KademliaProtocol(loop, PeerManager(loop), node_id1, '1.2.3.4', 4444, 3333) peer2 = KademliaProtocol(loop, PeerManager(loop), constants.generate_id(), '1.2.3.5', 4444, 3333) await loop.create_datagram_endpoint(lambda: peer1, ('1.2.3.4', 4444)) await loop.create_datagram_endpoint(lambda: peer2, ('1.2.3.5', 4444)) peer = peer2.peer_manager.get_kademlia_peer(node_id1, '1.2.3.4', udp_port=4444) self.assertEqual(None, peer2.peer_manager.get_node_token(peer.node_id)) await peer2.get_rpc_peer(peer).find_value(b'1' * 48) self.assertNotEqual( None, peer2.peer_manager.get_node_token(peer.node_id)) peer1.stop() peer2.stop() peer1.disconnect() peer2.disconnect()
class PeerTest(AsyncioTestCase): def setUp(self): self.loop = asyncio.get_event_loop() self.peer_manager = PeerManager(self.loop) self.node_ids = [generate_id(), generate_id(), generate_id()] self.first_contact = self.peer_manager.get_kademlia_peer( self.node_ids[1], '127.0.0.1', udp_port=1000) self.second_contact = self.peer_manager.get_kademlia_peer( self.node_ids[0], '192.168.0.1', udp_port=1000) def test_make_contact_error_cases(self): self.assertRaises(ValueError, self.peer_manager.get_kademlia_peer, self.node_ids[1], '192.168.1.20', 100000) self.assertRaises(ValueError, self.peer_manager.get_kademlia_peer, self.node_ids[1], '192.168.1.20.1', 1000) self.assertRaises(ValueError, self.peer_manager.get_kademlia_peer, self.node_ids[1], 'this is not an ip', 1000) self.assertRaises(ValueError, self.peer_manager.get_kademlia_peer, self.node_ids[1], '192.168.1.20', -1000) self.assertRaises(ValueError, self.peer_manager.get_kademlia_peer, b'not valid node id', '192.168.1.20', 1000) def test_boolean(self): self.assertNotEqual(self.first_contact, self.second_contact) self.assertEqual( self.second_contact, self.peer_manager.get_kademlia_peer(self.node_ids[0], '192.168.0.1', udp_port=1000)) def test_compact_ip(self): self.assertEqual(self.first_contact.compact_ip(), b'\x7f\x00\x00\x01') self.assertEqual(self.second_contact.compact_ip(), b'\xc0\xa8\x00\x01')
def setUp(self): self.loop = asyncio.get_event_loop() self.peer_manager = PeerManager(self.loop) self.node_ids = [generate_id(), generate_id(), generate_id()] self.first_contact = self.peer_manager.get_kademlia_peer( self.node_ids[1], '127.0.0.1', udp_port=1000) self.second_contact = self.peer_manager.get_kademlia_peer( self.node_ids[0], '192.168.0.1', udp_port=1000)
class PeerTest(AsyncioTestCase): def setUp(self): self.loop = asyncio.get_event_loop() self.peer_manager = PeerManager(self.loop) self.node_ids = [generate_id(), generate_id(), generate_id()] self.first_contact = make_kademlia_peer(self.node_ids[1], '1.0.0.1', udp_port=1000) self.second_contact = make_kademlia_peer(self.node_ids[0], '1.0.0.2', udp_port=1000) def test_peer_is_good_unknown_peer(self): # Scenario: peer replied, but caller doesn't know the node_id. # Outcome: We can't say it's good or bad. # (yes, we COULD tell the node id, but not here. It would be # a side effect and the caller is responsible to discover it) peer = make_kademlia_peer(None, '1.2.3.4', 4444) self.peer_manager.report_last_requested('1.2.3.4', 4444) self.peer_manager.report_last_replied('1.2.3.4', 4444) self.assertIsNone(self.peer_manager.peer_is_good(peer)) def test_make_contact_error_cases(self): self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '1.2.3.4', 100000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '1.2.3.4.5', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], 'this is not an ip', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '1.2.3.4', -1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '1.2.3.4', 0) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '1.2.3.4', 70000) self.assertRaises(ValueError, make_kademlia_peer, b'not valid node id', '1.2.3.4', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '0.0.0.0', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '10.0.0.1', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '100.64.0.1', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '127.0.0.1', 1000) self.assertIsNotNone(make_kademlia_peer(self.node_ids[1], '127.0.0.1', 1000, allow_localhost=True)) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.168.0.1', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '172.16.0.1', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '169.254.1.1', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.0.0.2', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.0.2.2', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.88.99.2', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '198.18.1.1', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '198.51.100.2', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '198.51.100.2', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '203.0.113.4', 1000) for i in range(32): self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], f"{224 + i}.0.0.0", 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '255.255.255.255', 1000) def test_boolean(self): self.assertNotEqual(self.first_contact, self.second_contact) self.assertEqual( self.second_contact, make_kademlia_peer(self.node_ids[0], '1.0.0.2', udp_port=1000) ) def test_compact_ip(self): self.assertEqual(b'\x01\x00\x00\x01', self.first_contact.compact_ip()) self.assertEqual(b'\x01\x00\x00\x02', self.second_contact.compact_ip())
async def test_store_to_peer(self): loop = asyncio.get_event_loop() with dht_mocks.mock_network_loop(loop): node_id1 = constants.generate_id() peer1 = KademliaProtocol(loop, PeerManager(loop), node_id1, '1.2.3.4', 4444, 3333) peer2 = KademliaProtocol(loop, PeerManager(loop), constants.generate_id(), '1.2.3.5', 4444, 3333) await loop.create_datagram_endpoint(lambda: peer1, ('1.2.3.4', 4444)) await loop.create_datagram_endpoint(lambda: peer2, ('1.2.3.5', 4444)) peer = make_kademlia_peer(node_id1, '1.2.3.4', udp_port=4444) peer2_from_peer1 = make_kademlia_peer(peer2.node_id, peer2.external_ip, udp_port=peer2.udp_port) peer2_from_peer1.update_tcp_port(3333) peer3 = make_kademlia_peer(constants.generate_id(), '1.2.3.6', udp_port=4444) store_result = await peer2.store_to_peer(b'2' * 48, peer) self.assertEqual(store_result[0], peer.node_id) self.assertEqual(True, store_result[1]) self.assertEqual(True, peer1.data_store.has_peers_for_blob(b'2' * 48)) self.assertEqual(False, peer1.data_store.has_peers_for_blob(b'3' * 48)) self.assertListEqual([peer2_from_peer1], peer1.data_store.get_storing_contacts()) peer1.data_store.completed_blobs.add( binascii.hexlify(b'2' * 48).decode()) find_value_response = peer1.node_rpc.find_value(peer3, b'2' * 48) self.assertEqual(len(find_value_response[b'contacts']), 0) self.assertSetEqual( {b'2' * 48, b'token', b'protocolVersion', b'contacts', b'p'}, set(find_value_response.keys())) self.assertEqual(2, len(find_value_response[b'2' * 48])) self.assertEqual(find_value_response[b'2' * 48][0], peer2_from_peer1.compact_address_tcp()) self.assertDictEqual(bdecode(bencode(find_value_response)), find_value_response) find_value_page_above_pages_response = peer1.node_rpc.find_value( peer3, b'2' * 48, page=10) self.assertNotIn(b'2' * 48, find_value_page_above_pages_response) peer1.stop() peer2.stop() peer1.disconnect() peer2.disconnect()
def __init__(self, conf: Config, analytics_manager=None, skip_components=None, peer_manager=None, **override_components): self.conf = conf self.skip_components = skip_components or [] self.loop = asyncio.get_event_loop() self.analytics_manager = analytics_manager self.component_classes = {} self.components = set() self.started = asyncio.Event(loop=self.loop) self.peer_manager = peer_manager or PeerManager( asyncio.get_event_loop_policy().get_event_loop()) for component_name, component_class in self.default_component_classes.items( ): if component_name in override_components: component_class = override_components.pop(component_name) if component_name not in self.skip_components: self.component_classes[component_name] = component_class if override_components: raise SyntaxError("unexpected components: %s" % override_components) for component_class in self.component_classes.values(): self.components.add(component_class(self))
async def main(host: str, port: int, db_file_path: str, bootstrap_node: Optional[str], prometheus_port: int): loop = asyncio.get_event_loop() conf = Config() storage = SQLiteStorage(conf, db_file_path, loop, loop.time) if bootstrap_node: nodes = bootstrap_node.split(':') nodes = [(nodes[0], int(nodes[1]))] else: nodes = conf.known_dht_nodes await storage.open() node = Node(loop, PeerManager(loop), generate_id(), port, port, 3333, None, storage=storage) if prometheus_port > 0: metrics = SimpleMetrics(prometheus_port, node) await metrics.start() node.start(host, nodes) while True: await asyncio.sleep(10) PEERS.labels('main').set(len(node.protocol.routing_table.get_peers())) BLOBS_STORED.labels('main').set( len(node.protocol.data_store.get_storing_contacts())) log.info( "Known peers: %d. Storing contact information for %d blobs from %d peers.", len(node.protocol.routing_table.get_peers()), len(node.protocol.data_store), len(node.protocol.data_store.get_storing_contacts()))
async def add_peer(self, node_id, address, add_to_routing_table=True): #print('add', node_id.hex()[:8], address) n = Node(self.loop, PeerManager(self.loop), node_id, 4444, 4444, 3333, address) await n.start_listening(address) self.nodes.update({len(self.nodes): n}) if add_to_routing_table: self.add_peer_to_routing_table(self.node, n)
async def _make_protocol(self, other_peer, node_id, address, udp_port, tcp_port): proto = KademliaProtocol( self.loop, PeerManager(self.loop), node_id, address, udp_port, tcp_port ) await self.loop.create_datagram_endpoint(lambda: proto, (address, 4444)) proto.start() return proto, make_kademlia_peer(node_id, address, udp_port=udp_port)
async def asyncSetUp(self): self.loop = asyncio.get_event_loop() self.client_dir = tempfile.mkdtemp() self.server_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.client_dir) self.addCleanup(shutil.rmtree, self.server_dir) self.server_config = Config(data_dir=self.server_dir, download_dir=self.server_dir, wallet=self.server_dir, reflector_servers=[]) self.server_storage = SQLiteStorage(self.server_config, os.path.join(self.server_dir, "lbrynet.sqlite")) self.server_blob_manager = BlobManager(self.loop, self.server_dir, self.server_storage, self.server_config) self.server = BlobServer(self.loop, self.server_blob_manager, 'bQEaw42GXsgCAGio1nxFncJSyRmnztSCjP') self.client_config = Config(data_dir=self.client_dir, download_dir=self.client_dir, wallet=self.client_dir, reflector_servers=[]) self.client_storage = SQLiteStorage(self.client_config, os.path.join(self.client_dir, "lbrynet.sqlite")) self.client_blob_manager = BlobManager(self.loop, self.client_dir, self.client_storage, self.client_config) self.client_peer_manager = PeerManager(self.loop) self.server_from_client = make_kademlia_peer(b'1' * 48, "127.0.0.1", tcp_port=33333, allow_localhost=True) await self.client_storage.open() await self.server_storage.open() await self.client_blob_manager.setup() await self.server_blob_manager.setup() self.server.start_server(33333, '127.0.0.1') self.addCleanup(self.server.stop_server) await self.server.started_listening.wait()
async def add_peer(self, node_id, address, add_to_routing_table=True): n = Node(self.loop, PeerManager(self.loop), node_id, 4444, 4444, 3333, address) await n.start_listening(address) self.nodes.update({len(self.nodes): n}) if add_to_routing_table: self.node.protocol.add_peer( make_kademlia_peer(n.protocol.node_id, n.protocol.external_ip, n.protocol.udp_port))
async def setup_node(self, peer_addresses, address, node_id): self.nodes: typing.Dict[int, Node] = {} self.advance = dht_mocks.get_time_accelerator(self.loop, self.loop.time()) self.conf = Config() self.storage = SQLiteStorage(self.conf, ":memory:", self.loop, self.loop.time) await self.storage.open() self.peer_manager = PeerManager(self.loop) self.node = Node(self.loop, self.peer_manager, node_id, 4444, 4444, 3333, address) await self.node.start_listening(address) self.blob_announcer = BlobAnnouncer(self.loop, self.node, self.storage) for node_id, address in peer_addresses: await self.add_peer(node_id, address) self.node.joined.set() self.node._refresh_task = self.loop.create_task( self.node.refresh_node())
async def test_add_peer_after_handle_request(self): with dht_mocks.mock_network_loop(self.loop): node_id1 = constants.generate_id() node_id2 = constants.generate_id() node_id3 = constants.generate_id() node_id4 = constants.generate_id() peer1 = KademliaProtocol( self.loop, PeerManager(self.loop), node_id1, '1.2.3.4', 4444, 3333 ) await self.loop.create_datagram_endpoint(lambda: peer1, ('1.2.3.4', 4444)) peer1.start() peer2, peer_2_from_peer_1 = await self._make_protocol(peer1, node_id2, '1.2.3.5', 4444, 3333) peer3, peer_3_from_peer_1 = await self._make_protocol(peer1, node_id3, '1.2.3.6', 4444, 3333) peer4, peer_4_from_peer_1 = await self._make_protocol(peer1, node_id4, '1.2.3.7', 4444, 3333) # peers who reply should be added await peer1.get_rpc_peer(peer_2_from_peer_1).ping() await asyncio.sleep(0.5) self.assertListEqual([peer_2_from_peer_1], peer1.routing_table.get_peers()) peer1.routing_table.remove_peer(peer_2_from_peer_1) # peers not known by be good/bad should be enqueued to maybe-ping peer1_from_peer3 = peer3.get_rpc_peer(make_kademlia_peer(node_id1, '1.2.3.4', 4444)) self.assertEqual(0, len(peer1.ping_queue._pending_contacts)) pong = await peer1_from_peer3.ping() self.assertEqual(b'pong', pong) self.assertEqual(1, len(peer1.ping_queue._pending_contacts)) peer1.ping_queue._pending_contacts.clear() # peers who are already good should be added peer1_from_peer4 = peer4.get_rpc_peer(make_kademlia_peer(node_id1, '1.2.3.4', 4444)) peer1.peer_manager.update_contact_triple(node_id4,'1.2.3.7', 4444) peer1.peer_manager.report_last_replied('1.2.3.7', 4444) self.assertEqual(0, len(peer1.ping_queue._pending_contacts)) pong = await peer1_from_peer4.ping() self.assertEqual(b'pong', pong) await asyncio.sleep(0.5) self.assertEqual(1, len(peer1.routing_table.get_peers())) self.assertEqual(0, len(peer1.ping_queue._pending_contacts)) peer1.routing_table.buckets[0].peers.clear() # peers who are known to be bad recently should not be added or maybe-pinged peer1_from_peer4 = peer4.get_rpc_peer(make_kademlia_peer(node_id1, '1.2.3.4', 4444)) peer1.peer_manager.update_contact_triple(node_id4,'1.2.3.7', 4444) peer1.peer_manager.report_failure('1.2.3.7', 4444) peer1.peer_manager.report_failure('1.2.3.7', 4444) self.assertEqual(0, len(peer1.ping_queue._pending_contacts)) pong = await peer1_from_peer4.ping() self.assertEqual(b'pong', pong) self.assertEqual(0, len(peer1.routing_table.get_peers())) self.assertEqual(0, len(peer1.ping_queue._pending_contacts)) for p in [peer1, peer2, peer3, peer4]: p.stop() p.disconnect()
class PeerTest(AsyncioTestCase): def setUp(self): self.loop = asyncio.get_event_loop() self.peer_manager = PeerManager(self.loop) self.node_ids = [generate_id(), generate_id(), generate_id()] self.first_contact = make_kademlia_peer(self.node_ids[1], '127.0.0.1', udp_port=1000) self.second_contact = make_kademlia_peer(self.node_ids[0], '192.168.0.1', udp_port=1000) def test_peer_is_good_unknown_peer(self): # Scenario: peer replied, but caller doesn't know the node_id. # Outcome: We can't say it's good or bad. # (yes, we COULD tell the node id, but not here. It would be # a side effect and the caller is responsible to discover it) peer = make_kademlia_peer(None, '1.2.3.4', 4444) self.peer_manager.report_last_requested('1.2.3.4', 4444) self.peer_manager.report_last_replied('1.2.3.4', 4444) self.assertIsNone(self.peer_manager.peer_is_good(peer)) def test_make_contact_error_cases(self): self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.168.1.20', 100000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.168.1.20.1', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], 'this is not an ip', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.168.1.20', -1000) self.assertRaises(ValueError, make_kademlia_peer, b'not valid node id', '192.168.1.20', 1000) def test_boolean(self): self.assertNotEqual(self.first_contact, self.second_contact) self.assertEqual( self.second_contact, make_kademlia_peer(self.node_ids[0], '192.168.0.1', udp_port=1000)) def test_compact_ip(self): self.assertEqual(self.first_contact.compact_ip(), b'\x7f\x00\x00\x01') self.assertEqual(self.second_contact.compact_ip(), b'\xc0\xa8\x00\x01')
async def test_cant_add_peer_without_a_node_id_gracefully(self): loop = asyncio.get_event_loop() node = Node(loop, PeerManager(loop), constants.generate_id(), 4444, 4444, 3333, '1.2.3.4') bad_peer = make_kademlia_peer(None, '1.2.3.4', 5555) with self.assertLogs(level='WARNING') as logged: self.assertFalse(await node.protocol._add_peer(bad_peer)) self.assertEqual(1, len(logged.output)) self.assertTrue(logged.output[0].endswith( 'Tried adding a peer with no node id!'))
async def test_ping(self): loop = asyncio.get_event_loop() with dht_mocks.mock_network_loop(loop): node_id1 = constants.generate_id() peer1 = KademliaProtocol( loop, PeerManager(loop), node_id1, '1.2.3.4', 4444, 3333 ) peer2 = KademliaProtocol( loop, PeerManager(loop), constants.generate_id(), '1.2.3.5', 4444, 3333 ) await loop.create_datagram_endpoint(lambda: peer1, ('1.2.3.4', 4444)) await loop.create_datagram_endpoint(lambda: peer2, ('1.2.3.5', 4444)) peer = make_kademlia_peer(node_id1, '1.2.3.4', udp_port=4444) result = await peer2.get_rpc_peer(peer).ping() self.assertEqual(result, b'pong') peer1.stop() peer2.stop() peer1.disconnect() peer2.disconnect()
async def setup_network(self, size: int, start_port=40000, seed_nodes=1): for i in range(size): node_port = start_port + i node = Node(self.loop, PeerManager(self.loop), node_id=constants.generate_id(i), udp_port=node_port, internal_udp_port=node_port, peer_port=3333, external_ip='127.0.0.1') self.nodes.append(node) self.known_node_addresses.append(('127.0.0.1', node_port)) await node.start_listening('127.0.0.1') self.addCleanup(node.stop) for node in self.nodes: node.protocol.rpc_timeout = .5 node.protocol.ping_queue._default_delay = .5 node.start('127.0.0.1', self.known_node_addresses[:seed_nodes]) await asyncio.gather(*[node.joined.wait() for node in self.nodes])
async def create_node(self, node_id, port, external_ip='127.0.0.1'): storage = SQLiteStorage(Config(), ":memory:", self.loop, self.loop.time) await storage.open() node = Node(self.loop, PeerManager(self.loop), node_id=node_id, udp_port=port, internal_udp_port=port, peer_port=3333, external_ip=external_ip, storage=storage) self.addCleanup(node.stop) node.protocol.rpc_timeout = .5 node.protocol.ping_queue._default_delay = .5 return node
async def test_split_buckets(self): loop = asyncio.get_event_loop() peer_addresses = [ (constants.generate_id(1), '1.2.3.1'), ] for i in range(2, 200): peer_addresses.append((constants.generate_id(i), f'1.2.3.{i}')) with dht_mocks.mock_network_loop(loop): nodes = { i: Node(loop, PeerManager(loop), node_id, 4444, 4444, 3333, address) for i, (node_id, address) in enumerate(peer_addresses) } node_1 = nodes[0] for i in range(1, len(peer_addresses)): node = nodes[i] peer = node_1.protocol.peer_manager.get_kademlia_peer( node.protocol.node_id, node.protocol.external_ip, udp_port=node.protocol.udp_port) # set all of the peers to good (as to not attempt pinging stale ones during split) node_1.protocol.peer_manager.report_last_replied( peer.address, peer.udp_port) node_1.protocol.peer_manager.report_last_replied( peer.address, peer.udp_port) await node_1.protocol._add_peer(peer) # check that bucket 0 is always the one covering the local node id self.assertEqual( True, node_1.protocol.routing_table.buckets[0].key_in_range( node_1.protocol.node_id)) self.assertEqual(40, len(node_1.protocol.routing_table.get_peers())) self.assertEqual(len(expected_ranges), len(node_1.protocol.routing_table.buckets)) covered = 0 for (expected_min, expected_max), bucket in zip( expected_ranges, node_1.protocol.routing_table.buckets): self.assertEqual(expected_min, bucket.range_min) self.assertEqual(expected_max, bucket.range_max) covered += bucket.range_max - bucket.range_min self.assertEqual(2**384, covered) for node in nodes.values(): node.stop()
async def main(host: str, port: int, db_file_path: str, bootstrap_node: Optional[str], prometheus_port: int, export: bool): loop = asyncio.get_event_loop() conf = Config() if not db_file_path.startswith(':memory:'): node_id_file_path = db_file_path + 'node_id' if os.path.exists(node_id_file_path): with open(node_id_file_path, 'rb') as node_id_file: node_id = node_id_file.read() else: with open(node_id_file_path, 'wb') as node_id_file: node_id = generate_id() node_id_file.write(node_id) storage = SQLiteStorage(conf, db_file_path, loop, loop.time) if bootstrap_node: nodes = bootstrap_node.split(':') nodes = [(nodes[0], int(nodes[1]))] else: nodes = conf.known_dht_nodes await storage.open() node = Node(loop, PeerManager(loop), node_id, port, port, 3333, None, storage=storage) if prometheus_port > 0: metrics = SimpleMetrics(prometheus_port, node if export else None) await metrics.start() node.start(host, nodes) log.info("Peer with id %s started", node_id.hex()) while True: await asyncio.sleep(10) log.info( "Known peers: %d. Storing contact information for %d blobs from %d peers.", len(node.protocol.routing_table.get_peers()), len(node.protocol.data_store), len(node.protocol.data_store.get_storing_contacts()))
async def setup_node(self, peer_addresses, address, node_id): self.nodes: typing.Dict[int, Node] = {} self.advance = dht_mocks.get_time_accelerator(self.loop) self.instant_advance = dht_mocks.get_time_accelerator(self.loop) self.conf = Config() self.peer_manager = PeerManager(self.loop) self.node = Node(self.loop, self.peer_manager, node_id, 4444, 4444, 3333, address) await self.node.start_listening(address) await asyncio.gather(*[self.add_peer(node_id, address) for node_id, address in peer_addresses]) for first_peer in self.nodes.values(): for second_peer in self.nodes.values(): if first_peer == second_peer: continue self.add_peer_to_routing_table(first_peer, second_peer) self.add_peer_to_routing_table(second_peer, first_peer) await self.advance(0.1) # just to make pings go through self.node.joined.set() self.node._refresh_task = self.loop.create_task(self.node.refresh_node()) self.storage = SQLiteStorage(self.conf, ":memory:", self.loop, self.loop.time) await self.storage.open() self.blob_announcer = BlobAnnouncer(self.loop, self.node, self.storage)
async def test_fill_one_bucket(self): loop = asyncio.get_event_loop() peer_addresses = [ (constants.generate_id(1), '1.2.3.1'), (constants.generate_id(2), '1.2.3.2'), (constants.generate_id(3), '1.2.3.3'), (constants.generate_id(4), '1.2.3.4'), (constants.generate_id(5), '1.2.3.5'), (constants.generate_id(6), '1.2.3.6'), (constants.generate_id(7), '1.2.3.7'), (constants.generate_id(8), '1.2.3.8'), (constants.generate_id(9), '1.2.3.9'), ] with dht_mocks.mock_network_loop(loop): nodes = { i: Node(loop, PeerManager(loop), node_id, 4444, 4444, 3333, address) for i, (node_id, address) in enumerate(peer_addresses) } node_1 = nodes[0] contact_cnt = 0 for i in range(1, len(peer_addresses)): self.assertEqual( len(node_1.protocol.routing_table.get_peers()), contact_cnt) node = nodes[i] peer = node_1.protocol.peer_manager.get_kademlia_peer( node.protocol.node_id, node.protocol.external_ip, udp_port=node.protocol.udp_port) added = await node_1.protocol._add_peer(peer) self.assertEqual(True, added) contact_cnt += 1 self.assertEqual(len(node_1.protocol.routing_table.get_peers()), 8) self.assertEqual( node_1.protocol.routing_table.buckets_with_contacts(), 1) for node in nodes.values(): node.protocol.stop()
class DataStoreTests(TestCase): def setUp(self): self.loop = mock.Mock(spec=asyncio.BaseEventLoop) self.loop.time = lambda: 0.0 self.peer_manager = PeerManager(self.loop) self.data_store = DictDataStore(self.loop, self.peer_manager) def _test_add_peer_to_blob(self, blob=b'2' * 48, node_id=b'1' * 48, address='1.2.3.4', tcp_port=3333, udp_port=4444): peer = self.peer_manager.get_kademlia_peer(node_id, address, udp_port) peer.update_tcp_port(tcp_port) before = self.data_store.get_peers_for_blob(blob) self.data_store.add_peer_to_blob(peer, blob) self.assertListEqual(before + [peer], self.data_store.get_peers_for_blob(blob)) return peer def test_refresh_peer_to_blob(self): blob = b'f' * 48 self.assertListEqual([], self.data_store.get_peers_for_blob(blob)) peer = self._test_add_peer_to_blob(blob=blob, node_id=b'a' * 48, address='1.2.3.4') self.assertTrue(self.data_store.has_peers_for_blob(blob)) self.assertEqual(len(self.data_store.get_peers_for_blob(blob)), 1) self.assertEqual(self.data_store._data_store[blob][0][1], 0) self.loop.time = lambda: 100.0 self.assertEqual(self.data_store._data_store[blob][0][1], 0) self.data_store.add_peer_to_blob(peer, blob) self.assertEqual(self.data_store._data_store[blob][0][1], 100) def test_add_peer_to_blob(self, blob=b'f' * 48, peers=None): peers = peers or [ (b'a' * 48, '1.2.3.4'), (b'b' * 48, '1.2.3.5'), (b'c' * 48, '1.2.3.6'), ] self.assertListEqual([], self.data_store.get_peers_for_blob(blob)) peer_objects = [] for (node_id, address) in peers: peer_objects.append( self._test_add_peer_to_blob(blob=blob, node_id=node_id, address=address)) self.assertTrue(self.data_store.has_peers_for_blob(blob)) self.assertEqual(len(self.data_store.get_peers_for_blob(blob)), len(peers)) return peer_objects def test_get_storing_contacts(self, peers=None, blob1=b'd' * 48, blob2=b'e' * 48): peers = peers or [ (b'a' * 48, '1.2.3.4'), (b'b' * 48, '1.2.3.5'), (b'c' * 48, '1.2.3.6'), ] peer_objs1 = self.test_add_peer_to_blob(blob=blob1, peers=peers) self.assertEqual(len(peers), len(peer_objs1)) self.assertEqual(len(peers), len(self.data_store.get_storing_contacts())) peer_objs2 = self.test_add_peer_to_blob(blob=blob2, peers=peers) self.assertEqual(len(peers), len(peer_objs2)) self.assertEqual(len(peers), len(self.data_store.get_storing_contacts())) for o1, o2 in zip(peer_objs1, peer_objs2): self.assertIs(o1, o2) def test_remove_expired_peers(self): peers = [ (b'a' * 48, '1.2.3.4'), (b'b' * 48, '1.2.3.5'), (b'c' * 48, '1.2.3.6'), ] blob1 = b'd' * 48 blob2 = b'e' * 48 self.data_store.removed_expired_peers() # nothing should happen self.test_get_storing_contacts(peers, blob1, blob2) self.assertEqual(len(self.data_store.get_peers_for_blob(blob1)), len(peers)) self.assertEqual(len(self.data_store.get_peers_for_blob(blob2)), len(peers)) self.assertEqual(len(self.data_store.get_storing_contacts()), len(peers)) # expire the first peer from blob1 first = self.data_store._data_store[blob1][0][0] self.data_store._data_store[blob1][0] = (first, -86401) self.assertEqual(len(self.data_store.get_storing_contacts()), len(peers)) self.data_store.removed_expired_peers() self.assertEqual(len(self.data_store.get_peers_for_blob(blob1)), len(peers) - 1) self.assertEqual(len(self.data_store.get_peers_for_blob(blob2)), len(peers)) self.assertEqual(len(self.data_store.get_storing_contacts()), len(peers)) # expire the first peer from blob2 first = self.data_store._data_store[blob2][0][0] self.data_store._data_store[blob2][0] = (first, -86401) self.data_store.removed_expired_peers() self.assertEqual(len(self.data_store.get_peers_for_blob(blob1)), len(peers) - 1) self.assertEqual(len(self.data_store.get_peers_for_blob(blob2)), len(peers) - 1) self.assertEqual(len(self.data_store.get_storing_contacts()), len(peers) - 1) # expire the second and third peers from blob1 first = self.data_store._data_store[blob2][0][0] self.data_store._data_store[blob1][0] = (first, -86401) second = self.data_store._data_store[blob2][1][0] self.data_store._data_store[blob1][1] = (second, -86401) self.data_store.removed_expired_peers() self.assertEqual(len(self.data_store.get_peers_for_blob(blob1)), 0) self.assertEqual(len(self.data_store.get_peers_for_blob(blob2)), len(peers) - 1) self.assertEqual(len(self.data_store.get_storing_contacts()), len(peers) - 1)
class TestBlobAnnouncer(AsyncioTestCase): async def setup_node(self, peer_addresses, address, node_id): self.nodes: typing.Dict[int, Node] = {} self.advance = dht_mocks.get_time_accelerator(self.loop, self.loop.time()) self.conf = Config() self.storage = SQLiteStorage(self.conf, ":memory:", self.loop, self.loop.time) await self.storage.open() self.peer_manager = PeerManager(self.loop) self.node = Node(self.loop, self.peer_manager, node_id, 4444, 4444, 3333, address) await self.node.start_listening(address) self.blob_announcer = BlobAnnouncer(self.loop, self.node, self.storage) for node_id, address in peer_addresses: await self.add_peer(node_id, address) self.node.joined.set() self.node._refresh_task = self.loop.create_task( self.node.refresh_node()) async def add_peer(self, node_id, address, add_to_routing_table=True): n = Node(self.loop, PeerManager(self.loop), node_id, 4444, 4444, 3333, address) await n.start_listening(address) self.nodes.update({len(self.nodes): n}) if add_to_routing_table: self.node.protocol.add_peer( self.peer_manager.get_kademlia_peer(n.protocol.node_id, n.protocol.external_ip, n.protocol.udp_port)) @contextlib.asynccontextmanager async def _test_network_context(self, peer_addresses=None): self.peer_addresses = peer_addresses or [ (constants.generate_id(2), '1.2.3.2'), (constants.generate_id(3), '1.2.3.3'), (constants.generate_id(4), '1.2.3.4'), (constants.generate_id(5), '1.2.3.5'), (constants.generate_id(6), '1.2.3.6'), (constants.generate_id(7), '1.2.3.7'), (constants.generate_id(8), '1.2.3.8'), (constants.generate_id(9), '1.2.3.9'), ] try: with dht_mocks.mock_network_loop(self.loop): await self.setup_node(self.peer_addresses, '1.2.3.1', constants.generate_id(1)) yield finally: self.blob_announcer.stop() self.node.stop() for n in self.nodes.values(): n.stop() async def chain_peer(self, node_id, address): previous_last_node = self.nodes[len(self.nodes) - 1] await self.add_peer(node_id, address, False) last_node = self.nodes[len(self.nodes) - 1] peer = last_node.protocol.get_rpc_peer( last_node.protocol.peer_manager.get_kademlia_peer( previous_last_node.protocol.node_id, previous_last_node.protocol.external_ip, previous_last_node.protocol.udp_port)) await peer.ping() return peer async def test_announce_blobs(self): blob1 = binascii.hexlify(b'1' * 48).decode() blob2 = binascii.hexlify(b'2' * 48).decode() async with self._test_network_context(): await self.storage.add_blobs((blob1, 1024), (blob2, 1024), finished=True) await self.storage.db.execute( "update blob set next_announce_time=0, should_announce=1 where blob_hash in (?, ?)", (blob1, blob2)) to_announce = await self.storage.get_blobs_to_announce() self.assertEqual(2, len(to_announce)) self.blob_announcer.start( batch_size=1) # so it covers batching logic # takes 60 seconds to start, but we advance 120 to ensure it processed all batches await self.advance(60.0 * 2) to_announce = await self.storage.get_blobs_to_announce() self.assertEqual(0, len(to_announce)) self.blob_announcer.stop() # test that we can route from a poorly connected peer all the way to the announced blob await self.chain_peer(constants.generate_id(10), '1.2.3.10') await self.chain_peer(constants.generate_id(11), '1.2.3.11') await self.chain_peer(constants.generate_id(12), '1.2.3.12') await self.chain_peer(constants.generate_id(13), '1.2.3.13') await self.chain_peer(constants.generate_id(14), '1.2.3.14') await self.advance(61.0) last = self.nodes[len(self.nodes) - 1] search_q, peer_q = asyncio.Queue(loop=self.loop), asyncio.Queue( loop=self.loop) search_q.put_nowait(blob1) _, task = last.accumulate_peers(search_q, peer_q) found_peers = await peer_q.get() task.cancel() self.assertEqual(1, len(found_peers)) self.assertEqual(self.node.protocol.node_id, found_peers[0].node_id) self.assertEqual(self.node.protocol.external_ip, found_peers[0].address) self.assertEqual(self.node.protocol.peer_port, found_peers[0].tcp_port) async def test_popular_blob(self): peer_count = 150 addresses = [ (constants.generate_id(i + 1), socket.inet_ntoa(int(i + 1).to_bytes(length=4, byteorder='big'))) for i in range(peer_count) ] blob_hash = b'1' * 48 async with self._test_network_context(peer_addresses=addresses): total_seen = set() announced_to = self.nodes[0] for i in range(1, peer_count): node = self.nodes[i] kad_peer = announced_to.protocol.peer_manager.get_kademlia_peer( node.protocol.node_id, node.protocol.external_ip, node.protocol.udp_port) await announced_to.protocol._add_peer(kad_peer) peer = node.protocol.get_rpc_peer( node.protocol.peer_manager.get_kademlia_peer( announced_to.protocol.node_id, announced_to.protocol.external_ip, announced_to.protocol.udp_port)) response = await peer.store(blob_hash) self.assertEqual(response, b'OK') peers_for_blob = await peer.find_value(blob_hash, 0) if i == 1: self.assertTrue(blob_hash not in peers_for_blob) self.assertEqual(peers_for_blob[b'p'], 0) else: self.assertEqual(len(peers_for_blob[blob_hash]), min(i - 1, constants.k)) self.assertEqual( len( announced_to.protocol.data_store. get_peers_for_blob(blob_hash)), i) if i - 1 > constants.k: self.assertEqual(len(peers_for_blob[b'contacts']), constants.k) self.assertEqual(peers_for_blob[b'p'], ((i - 1) // (constants.k + 1)) + 1) seen = set(peers_for_blob[blob_hash]) self.assertEqual(len(seen), constants.k) self.assertEqual(len(peers_for_blob[blob_hash]), len(seen)) for pg in range(1, peers_for_blob[b'p']): page_x = await peer.find_value(blob_hash, pg) self.assertNotIn(b'contacts', page_x) page_x_set = set(page_x[blob_hash]) self.assertEqual(len(page_x[blob_hash]), len(page_x_set)) self.assertTrue(len(page_x_set) > 0) self.assertSetEqual(seen.intersection(page_x_set), set()) seen.intersection_update(page_x_set) total_seen.update(page_x_set) else: self.assertEqual(len(peers_for_blob[b'contacts']), i - 1) self.assertEqual(len(total_seen), peer_count - 2)
def setUp(self): self.loop = asyncio.get_event_loop() self.address_generator = address_generator() self.peer_manager = PeerManager(self.loop) self.kbucket = KBucket(self.peer_manager, 0, 2**constants.hash_bits, generate_id())
class TestKBucket(AsyncioTestCase): def setUp(self): self.loop = asyncio.get_event_loop() self.address_generator = address_generator() self.peer_manager = PeerManager(self.loop) self.kbucket = KBucket(self.peer_manager, 0, 2**constants.hash_bits, generate_id()) def test_add_peer(self): peer = KademliaPeer(None, '1.2.3.4', constants.generate_id(2), udp_port=4444) peer_update2 = KademliaPeer(None, '1.2.3.4', constants.generate_id(2), udp_port=4445) self.assertListEqual([], self.kbucket.peers) # add the peer self.kbucket.add_peer(peer) self.assertListEqual([peer], self.kbucket.peers) # re-add it self.kbucket.add_peer(peer) self.assertListEqual([peer], self.kbucket.peers) self.assertEqual(self.kbucket.peers[0].udp_port, 4444) # add a new peer object with the same id and address but a different port self.kbucket.add_peer(peer_update2) self.assertListEqual([peer_update2], self.kbucket.peers) self.assertEqual(self.kbucket.peers[0].udp_port, 4445) # modify the peer object to have a different port peer_update2.udp_port = 4444 self.kbucket.add_peer(peer_update2) self.assertListEqual([peer_update2], self.kbucket.peers) self.assertEqual(self.kbucket.peers[0].udp_port, 4444) self.kbucket.peers.clear() # Test if contacts can be added to empty list # Add k contacts to bucket for i in range(constants.k): peer = self.peer_manager.get_kademlia_peer(generate_id(), next(self.address_generator), 4444) self.assertTrue(self.kbucket.add_peer(peer)) self.assertEqual(peer, self.kbucket.peers[i]) # Test if contact is not added to full list peer = self.peer_manager.get_kademlia_peer(generate_id(), next(self.address_generator), 4444) self.assertFalse(self.kbucket.add_peer(peer)) # Test if an existing contact is updated correctly if added again existing_peer = self.kbucket.peers[0] self.assertTrue(self.kbucket.add_peer(existing_peer)) self.assertEqual(existing_peer, self.kbucket.peers[-1]) # def testGetContacts(self): # # try and get 2 contacts from empty list # result = self.kbucket.getContacts(2) # self.assertFalse(len(result) != 0, "Returned list should be empty; returned list length: %d" % # (len(result))) # # # Add k-2 contacts # node_ids = [] # if constants.k >= 2: # for i in range(constants.k-2): # node_ids.append(generate_id()) # tmpContact = self.contact_manager.make_contact(node_ids[-1], next(self.address_generator), 4444, 0, # None) # self.kbucket.addContact(tmpContact) # else: # # add k contacts # for i in range(constants.k): # node_ids.append(generate_id()) # tmpContact = self.contact_manager.make_contact(node_ids[-1], next(self.address_generator), 4444, 0, # None) # self.kbucket.addContact(tmpContact) # # # try to get too many contacts # # requested count greater than bucket size; should return at most k contacts # contacts = self.kbucket.getContacts(constants.k+3) # self.assertTrue(len(contacts) <= constants.k, # 'Returned list should not have more than k entries!') # # # verify returned contacts in list # for node_id, i in zip(node_ids, range(constants.k-2)): # self.assertFalse(self.kbucket._contacts[i].id != node_id, # "Contact in position %s not same as added contact" % (str(i))) # # # try to get too many contacts # # requested count one greater than number of contacts # if constants.k >= 2: # result = self.kbucket.getContacts(constants.k-1) # self.assertFalse(len(result) != constants.k-2, # "Too many contacts in returned list %s - should be %s" % # (len(result), constants.k-2)) # else: # result = self.kbucket.getContacts(constants.k-1) # # if the count is <= 0, it should return all of it's contats # self.assertFalse(len(result) != constants.k, # "Too many contacts in returned list %s - should be %s" % # (len(result), constants.k-2)) # result = self.kbucket.getContacts(constants.k-3) # self.assertFalse(len(result) != constants.k-3, # "Too many contacts in returned list %s - should be %s" % # (len(result), constants.k-3)) def test_remove_peer(self): # try remove contact from empty list peer = self.peer_manager.get_kademlia_peer(generate_id(), next(self.address_generator), 4444) self.assertRaises(ValueError, self.kbucket.remove_peer, peer) added = [] # Add couple contacts for i in range(constants.k-2): peer = self.peer_manager.get_kademlia_peer(generate_id(), next(self.address_generator), 4444) self.assertTrue(self.kbucket.add_peer(peer)) added.append(peer) while added: peer = added.pop() self.assertIn(peer, self.kbucket.peers) self.kbucket.remove_peer(peer) self.assertNotIn(peer, self.kbucket.peers)
async def test_ping_queue_discover(self): loop = asyncio.get_event_loop() loop.set_debug(False) peer_addresses = [ (constants.generate_id(1), '1.2.3.1'), (constants.generate_id(2), '1.2.3.2'), (constants.generate_id(3), '1.2.3.3'), (constants.generate_id(4), '1.2.3.4'), (constants.generate_id(5), '1.2.3.5'), (constants.generate_id(6), '1.2.3.6'), (constants.generate_id(7), '1.2.3.7'), (constants.generate_id(8), '1.2.3.8'), (constants.generate_id(9), '1.2.3.9'), ] with dht_mocks.mock_network_loop(loop): advance = dht_mocks.get_time_accelerator(loop) # start the nodes nodes: typing.Dict[int, Node] = { i: Node(loop, PeerManager(loop), node_id, 4444, 4444, 3333, address) for i, (node_id, address) in enumerate(peer_addresses) } for i, n in nodes.items(): n.start(peer_addresses[i][1], []) await advance(1) node_1 = nodes[0] # ping 8 nodes from node_1, this will result in a delayed return ping futs = [] for i in range(1, len(peer_addresses)): node = nodes[i] assert node.protocol.node_id != node_1.protocol.node_id peer = make_kademlia_peer(node.protocol.node_id, node.protocol.external_ip, udp_port=node.protocol.udp_port) futs.append(node_1.protocol.get_rpc_peer(peer).ping()) await advance(3) replies = await asyncio.gather(*tuple(futs)) self.assertTrue(all(map(lambda reply: reply == b"pong", replies))) # run for long enough for the delayed pings to have been sent by node 1 await advance(1000) # verify all of the previously pinged peers have node_1 in their routing tables for n in nodes.values(): peers = n.protocol.routing_table.get_peers() if n is node_1: self.assertEqual(8, len(peers)) # TODO: figure out why this breaks # else: # self.assertEqual(1, len(peers)) # self.assertEqual((peers[0].node_id, peers[0].address, peers[0].udp_port), # (node_1.protocol.node_id, node_1.protocol.external_ip, node_1.protocol.udp_port)) # run long enough for the refresh loop to run await advance(3600) # verify all the nodes know about each other for n in nodes.values(): if n is node_1: continue peers = n.protocol.routing_table.get_peers() self.assertEqual(8, len(peers)) self.assertSetEqual( { n_id[0] for n_id in peer_addresses if n_id[0] != n.protocol.node_id }, {c.node_id for c in peers}) self.assertSetEqual( { n_addr[1] for n_addr in peer_addresses if n_addr[1] != n.protocol.external_ip }, {c.address for c in peers}) # teardown for n in nodes.values(): n.stop()
async def test_losing_connection(self): async def wait_for(check_ok, insist, timeout=20): start = time.time() while time.time() - start < timeout: if check_ok(): break await asyncio.sleep(0) else: insist() loop = self.loop loop.set_debug(False) peer_addresses = [('1.2.3.4', 40000 + i) for i in range(10)] node_ids = [constants.generate_id(i) for i in range(10)] nodes = [ Node(loop, PeerManager(loop), node_id, udp_port, udp_port, 3333, address, storage=SQLiteStorage(Config(), ":memory:", self.loop, self.loop.time)) for node_id, (address, udp_port) in zip(node_ids, peer_addresses) ] dht_network = { peer_addresses[i]: node.protocol for i, node in enumerate(nodes) } num_seeds = 3 with dht_mocks.mock_network_loop(loop, dht_network): for i, n in enumerate(nodes): await n._storage.open() self.addCleanup(n.stop) n.start(peer_addresses[i][0], peer_addresses[:num_seeds]) await asyncio.gather(*[n.joined.wait() for n in nodes]) node = nodes[-1] advance = dht_mocks.get_time_accelerator(loop) await advance(500) # Join the network, assert that at least the known peers are in RT self.assertTrue(node.joined.is_set()) self.assertTrue( len(node.protocol.routing_table.get_peers()) >= num_seeds) # Refresh, so that the peers are persisted self.assertFalse( len(await node._storage.get_persisted_kademlia_peers()) > num_seeds) await advance(4000) self.assertTrue( len(await node._storage.get_persisted_kademlia_peers()) > num_seeds) # We lost internet connection - all the peers stop responding dht_network.pop( (node.protocol.external_ip, node.protocol.udp_port)) # The peers are cleared on refresh from RT and storage await advance(4000) self.assertListEqual([], await node._storage.get_persisted_kademlia_peers()) await wait_for( lambda: len(node.protocol.routing_table.get_peers()) == 0, lambda: self.assertListEqual( node.protocol.routing_table.get_peers(), [])) # Reconnect dht_network[(node.protocol.external_ip, node.protocol.udp_port)] = node.protocol # Check that node reconnects at least to them await advance(1000) await wait_for( lambda: len(node.protocol.routing_table.get_peers()) >= num_seeds, lambda: self.assertGreaterEqual( len(node.protocol.routing_table.get_peers()), num_seeds))
import asyncio from aiohttp import web from lbry.blob_exchange.serialization import BlobRequest, BlobResponse from lbry.dht.constants import generate_id from lbry.dht.node import Node from lbry.dht.peer import make_kademlia_peer, PeerManager from lbry.extras.daemon.storage import SQLiteStorage loop = asyncio.get_event_loop() NODE = Node(loop, PeerManager(loop), generate_id(), 60600, 60600, 3333, None, storage=SQLiteStorage(None, ":memory:", loop, loop.time)) async def check_p2p(ip, port): writer = None try: reader, writer = await asyncio.open_connection(ip, port) writer.write( BlobRequest.make_request_for_blob_hash('0' * 96).serialize()) return BlobResponse.deserialize( await reader.readuntil(b'}')).get_address_response().lbrycrd_address except OSError: return None
def setUp(self): self.loop = mock.Mock(spec=asyncio.BaseEventLoop) self.loop.time = lambda: 0.0 self.peer_manager = PeerManager(self.loop) self.data_store = DictDataStore(self.loop, self.peer_manager)