lbry-sdk/lbrynet/tests/dht/test_node.py

274 lines
13 KiB
Python
Raw Normal View History

2015-08-20 17:27:15 +02:00
#!/usr/bin/env python
#
# This library is free software, distributed under the terms of
# the GNU Lesser General Public License Version 3, or any later version.
# See the COPYING file included in this archive
import hashlib
import unittest
import struct
2017-10-10 19:31:18 +02:00
from twisted.internet import protocol, defer, selectreactor
from lbrynet.dht.msgtypes import ResponseMessage
2015-08-20 17:27:15 +02:00
import lbrynet.dht.node
import lbrynet.dht.constants
import lbrynet.dht.datastore
2017-10-10 19:31:18 +02:00
2015-08-20 17:27:15 +02:00
class NodeIDTest(unittest.TestCase):
""" Test case for the Node class's ID """
def setUp(self):
self.node = lbrynet.dht.node.Node()
def testAutoCreatedID(self):
""" Tests if a new node has a valid node ID """
2017-10-10 19:31:18 +02:00
self.failUnlessEqual(type(self.node.node_id), str, 'Node does not have a valid ID')
self.failUnlessEqual(len(self.node.node_id), 48, 'Node ID length is incorrect! '
'Expected 384 bits, got %d bits.' %
(len(self.node.node_id) * 8))
2015-08-20 17:27:15 +02:00
def testUniqueness(self):
2017-10-10 19:31:18 +02:00
""" Tests the uniqueness of the values created by the NodeID generator """
2015-08-20 17:27:15 +02:00
generatedIDs = []
for i in range(100):
newID = self.node._generateID()
# ugly uniqueness test
self.failIf(newID in generatedIDs, 'Generated ID #%d not unique!' % (i+1))
generatedIDs.append(newID)
2017-10-10 19:31:18 +02:00
2015-08-20 17:27:15 +02:00
def testKeyLength(self):
""" Tests the key Node ID key length """
for i in range(20):
id = self.node._generateID()
# Key length: 20 bytes == 160 bits
2017-10-10 19:31:18 +02:00
self.failUnlessEqual(len(id), 48,
'Length of generated ID is incorrect! Expected 384 bits, '
'got %d bits.' % (len(id)*8))
2015-08-20 17:27:15 +02:00
class NodeDataTest(unittest.TestCase):
""" Test case for the Node class's data-related functions """
def setUp(self):
import lbrynet.dht.contact
2017-10-10 19:31:18 +02:00
h = hashlib.sha384()
2015-08-20 17:27:15 +02:00
h.update('test')
self.node = lbrynet.dht.node.Node()
2017-10-10 19:31:18 +02:00
self.contact = lbrynet.dht.contact.Contact(h.digest(), '127.0.0.1', 12345,
self.node._protocol)
2015-08-20 17:27:15 +02:00
self.token = self.node.make_token(self.contact.compact_ip())
self.cases = []
for i in xrange(5):
h.update(str(i))
self.cases.append((h.digest(), 5000+2*i))
self.cases.append((h.digest(), 5001+2*i))
2017-10-25 02:05:38 +02:00
@defer.inlineCallbacks
2015-08-20 17:27:15 +02:00
def testStore(self):
""" Tests if the node can store (and privately retrieve) some data """
for key, value in self.cases:
2017-10-10 19:31:18 +02:00
request = {
'port': value,
'lbryid': self.contact.id,
'token': self.token
}
2017-10-25 02:05:38 +02:00
yield self.node.store(key, request, self.contact.id, _rpcNodeContact=self.contact)
2015-08-20 17:27:15 +02:00
for key, value in self.cases:
2017-10-10 19:31:18 +02:00
expected_result = self.contact.compact_ip() + str(struct.pack('>H', value)) + \
self.contact.id
self.failUnless(self.node._dataStore.hasPeersForBlob(key),
'Stored key not found in node\'s DataStore: "%s"' % key)
self.failUnless(expected_result in self.node._dataStore.getPeersForBlob(key),
'Stored val not found in node\'s DataStore: key:"%s" port:"%s" %s'
% (key, value, self.node._dataStore.getPeersForBlob(key)))
2015-08-20 17:27:15 +02:00
class NodeContactTest(unittest.TestCase):
""" Test case for the Node class's contact management-related functions """
def setUp(self):
self.node = lbrynet.dht.node.Node()
2017-10-10 19:31:18 +02:00
2015-08-20 17:27:15 +02:00
def testAddContact(self):
""" Tests if a contact can be added and retrieved correctly """
import lbrynet.dht.contact
# Create the contact
2017-10-25 02:05:38 +02:00
h = hashlib.sha384()
2015-08-20 17:27:15 +02:00
h.update('node1')
contactID = h.digest()
contact = lbrynet.dht.contact.Contact(contactID, '127.0.0.1', 91824, self.node._protocol)
# Now add it...
self.node.addContact(contact)
# ...and request the closest nodes to it using FIND_NODE
closestNodes = self.node._routingTable.findCloseNodes(contactID, lbrynet.dht.constants.k)
2017-10-10 19:31:18 +02:00
self.failUnlessEqual(len(closestNodes), 1, 'Wrong amount of contacts returned; '
'expected 1, got %d' % len(closestNodes))
self.failUnless(contact in closestNodes, 'Added contact not found by issueing '
'_findCloseNodes()')
2015-08-20 17:27:15 +02:00
def testAddSelfAsContact(self):
""" Tests the node's behaviour when attempting to add itself as a contact """
import lbrynet.dht.contact
# Create a contact with the same ID as the local node's ID
2017-10-10 19:31:18 +02:00
contact = lbrynet.dht.contact.Contact(self.node.node_id, '127.0.0.1', 91824, None)
2015-08-20 17:27:15 +02:00
# Now try to add it
self.node.addContact(contact)
# ...and request the closest nodes to it using FIND_NODE
2017-10-10 19:31:18 +02:00
closestNodes = self.node._routingTable.findCloseNodes(self.node.node_id,
lbrynet.dht.constants.k)
2015-08-20 17:27:15 +02:00
self.failIf(contact in closestNodes, 'Node added itself as a contact')
class FakeRPCProtocol(protocol.DatagramProtocol):
def __init__(self):
2017-10-10 19:31:18 +02:00
self.reactor = selectreactor.SelectReactor()
2015-08-20 17:27:15 +02:00
self.testResponse = None
self.network = None
2015-08-20 17:27:15 +02:00
def createNetwork(self, contactNetwork):
2017-10-10 19:31:18 +02:00
"""
set up a list of contacts together with their closest contacts
@param contactNetwork: a sequence of tuples, each containing a contact together with its
closest contacts: C{(<contact>, <closest contact 1, ...,closest contact n>)}
"""
self.network = contactNetwork
2015-08-20 17:27:15 +02:00
def sendRPC(self, contact, method, args, rawResponse=False):
2017-10-10 19:31:18 +02:00
""" Fake RPC protocol; allows entangled.kademlia.contact.Contact objects to "send" RPCs"""
2017-10-25 02:05:38 +02:00
h = hashlib.sha384()
h.update('rpcId')
rpc_id = h.digest()[:20]
2017-10-10 19:31:18 +02:00
if method == "findNode":
2015-08-20 17:27:15 +02:00
# get the specific contacts closest contacts
closestContacts = []
2017-10-10 19:31:18 +02:00
closestContactsList = []
2015-08-20 17:27:15 +02:00
for contactTuple in self.network:
if contact == contactTuple[0]:
# get the list of closest contacts for this contact
closestContactsList = contactTuple[1]
2017-10-10 19:31:18 +02:00
# Pack the closest contacts into a ResponseMessage
2015-08-20 17:27:15 +02:00
for closeContact in closestContactsList:
closestContacts.append((closeContact.id, closeContact.address, closeContact.port))
2017-10-25 02:05:38 +02:00
message = ResponseMessage(rpc_id, contact.id, closestContacts)
2015-08-20 17:27:15 +02:00
df = defer.Deferred()
2017-10-10 19:31:18 +02:00
df.callback((message, (contact.address, contact.port)))
2015-08-20 17:27:15 +02:00
return df
elif method == "findValue":
for contactTuple in self.network:
if contact == contactTuple[0]:
# Get the data stored by this remote contact
dataDict = contactTuple[2]
dataKey = dataDict.keys()[0]
data = dataDict.get(dataKey)
# Check if this contact has the requested value
if dataKey == args[0]:
# Return the data value
response = dataDict
print "data found at contact: " + contact.id
else:
# Return the closest contact to the requested data key
print "data not found at contact: " + contact.id
closeContacts = contactTuple[1]
closestContacts = []
for closeContact in closeContacts:
2017-10-10 19:31:18 +02:00
closestContacts.append((closeContact.id, closeContact.address,
closeContact.port))
2015-08-20 17:27:15 +02:00
response = closestContacts
2017-10-10 19:31:18 +02:00
2015-08-20 17:27:15 +02:00
# Create the response message
2017-10-25 02:05:38 +02:00
message = ResponseMessage(rpc_id, contact.id, response)
2015-08-20 17:27:15 +02:00
df = defer.Deferred()
2017-10-10 19:31:18 +02:00
df.callback((message, (contact.address, contact.port)))
2015-08-20 17:27:15 +02:00
return df
2015-08-20 17:27:15 +02:00
def _send(self, data, rpcID, address):
""" fake sending data """
2017-10-10 19:31:18 +02:00
2015-08-20 17:27:15 +02:00
class NodeLookupTest(unittest.TestCase):
""" Test case for the Node class's iterativeFind node lookup algorithm """
2017-10-10 19:31:18 +02:00
2015-08-20 17:27:15 +02:00
def setUp(self):
# create a fake protocol to imitate communication with other nodes
self._protocol = FakeRPCProtocol()
2017-10-10 19:31:18 +02:00
# Note: The reactor is never started for this test. All deferred calls run sequentially,
2015-08-20 17:27:15 +02:00
# since there is no asynchronous network communication
# create the node to be tested in isolation
2017-10-25 02:05:38 +02:00
h = hashlib.sha384()
h.update('node1')
node_id = str(h.digest())
self.node = lbrynet.dht.node.Node(node_id, 4000, None, None, self._protocol)
2015-08-20 17:27:15 +02:00
self.updPort = 81173
self.contactsAmount = 80
# Reinitialise the routing table
2017-10-10 19:31:18 +02:00
self.node._routingTable = lbrynet.dht.routingtable.OptimizedTreeRoutingTable(
self.node.node_id)
2015-08-20 17:27:15 +02:00
# create 160 bit node ID's for test purposes
self.testNodeIDs = []
2017-10-25 02:05:38 +02:00
idNum = int(self.node.node_id.encode('hex'), 16)
2015-08-20 17:27:15 +02:00
for i in range(self.contactsAmount):
2017-10-10 19:31:18 +02:00
# create the testNodeIDs in ascending order, away from the actual node ID,
# with regards to the distance metric
2017-10-25 02:05:38 +02:00
self.testNodeIDs.append(str("%X" % (idNum + i + 1)).decode('hex'))
2015-08-20 17:27:15 +02:00
# generate contacts
self.contacts = []
for i in range(self.contactsAmount):
2017-10-25 02:05:38 +02:00
contact = lbrynet.dht.contact.Contact(self.testNodeIDs[i], "127.0.0.1",
2017-10-10 19:31:18 +02:00
self.updPort + i + 1, self._protocol)
2015-08-20 17:27:15 +02:00
self.contacts.append(contact)
2017-10-10 19:31:18 +02:00
# create the network of contacts in format: (contact, closest contacts)
2015-08-20 17:27:15 +02:00
contactNetwork = ((self.contacts[0], self.contacts[8:15]),
(self.contacts[1], self.contacts[16:23]),
(self.contacts[2], self.contacts[24:31]),
(self.contacts[3], self.contacts[32:39]),
(self.contacts[4], self.contacts[40:47]),
(self.contacts[5], self.contacts[48:55]),
(self.contacts[6], self.contacts[56:63]),
(self.contacts[7], self.contacts[64:71]),
(self.contacts[8], self.contacts[72:79]),
(self.contacts[40], self.contacts[41:48]),
(self.contacts[41], self.contacts[41:48]),
(self.contacts[42], self.contacts[41:48]),
(self.contacts[43], self.contacts[41:48]),
(self.contacts[44], self.contacts[41:48]),
(self.contacts[45], self.contacts[41:48]),
(self.contacts[46], self.contacts[41:48]),
(self.contacts[47], self.contacts[41:48]),
(self.contacts[48], self.contacts[41:48]),
(self.contacts[50], self.contacts[0:7]),
(self.contacts[51], self.contacts[8:15]),
(self.contacts[52], self.contacts[16:23]))
contacts_with_datastores = []
for contact_tuple in contactNetwork:
2017-10-10 19:31:18 +02:00
contacts_with_datastores.append((contact_tuple[0], contact_tuple[1],
lbrynet.dht.datastore.DictDataStore()))
2015-08-20 17:27:15 +02:00
self._protocol.createNetwork(contacts_with_datastores)
2017-10-10 19:31:18 +02:00
2017-10-25 02:05:38 +02:00
@defer.inlineCallbacks
2015-08-20 17:27:15 +02:00
def testNodeBootStrap(self):
""" Test bootstrap with the closest possible contacts """
2017-10-10 19:31:18 +02:00
2017-10-25 02:05:38 +02:00
activeContacts = yield self.node._iterativeFind(self.node.node_id, self.contacts[0:8])
2015-08-20 17:27:15 +02:00
# Set the expected result
2017-10-25 02:05:38 +02:00
expectedResult = set()
2015-08-20 17:27:15 +02:00
for item in self.contacts[0:6]:
2017-10-25 02:05:38 +02:00
expectedResult.add(item.id)
2015-08-20 17:27:15 +02:00
# Get the result from the deferred
2017-10-10 19:31:18 +02:00
# Check the length of the active contacts
self.failUnlessEqual(activeContacts.__len__(), expectedResult.__len__(),
"More active contacts should exist, there should be %d "
2017-10-25 02:05:38 +02:00
"contacts but there are %d" % (len(expectedResult),
len(activeContacts)))
2017-10-10 19:31:18 +02:00
# Check that the received active contacts are the same as the input contacts
2017-10-25 02:05:38 +02:00
self.failUnlessEqual({contact.id for contact in activeContacts}, expectedResult,
2017-10-10 19:31:18 +02:00
"Active should only contain the closest possible contacts"
" which were used as input for the boostrap")