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
import lbrynet . dht . node
import lbrynet . dht . constants
import lbrynet . dht . datastore
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 """
self . failUnlessEqual ( type ( self . node . id ) , str , ' Node does not have a valid ID ' )
self . failUnlessEqual ( len ( self . node . id ) , 20 , ' Node ID length is incorrect! Expected 160 bits, got %d bits. ' % ( len ( self . node . id ) * 8 ) )
def testUniqueness ( self ) :
""" Tests the uniqueness of the values created by the NodeID generator
"""
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 )
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
self . failUnlessEqual ( len ( id ) , 20 , ' Length of generated ID is incorrect! Expected 160 bits, got %d bits. ' % ( len ( id ) * 8 ) )
class NodeDataTest ( unittest . TestCase ) :
""" Test case for the Node class ' s data-related functions """
def setUp ( self ) :
import lbrynet . dht . contact
h = hashlib . sha1 ( )
h . update ( ' test ' )
self . node = lbrynet . dht . node . Node ( )
self . contact = lbrynet . dht . contact . Contact ( h . digest ( ) , ' 127.0.0.1 ' , 12345 , self . node . _protocol )
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 ) )
#(('a', 'hello there\nthis is a test'),
# ('b', unicode('jasdklfjklsdj;f2352352ljklzsdlkjkasf\ndsjklafsd')),
# ('e', 123),
# ('f', [('this', 'is', 1), {'complex': 'data entry'}]),
# ('aMuchLongerKeyThanAnyOfThePreviousOnes', 'some data'))
def testStore ( self ) :
def check_val_in_result ( r , peer_info ) :
self . failUnless
""" Tests if the node can store (and privately retrieve) some data """
for key , value in self . cases :
self . node . store ( key , { ' port ' : value , ' bbid ' : self . contact . id , ' token ' : self . token } , self . contact . id , _rpcNodeContact = self . contact )
for key , value in self . cases :
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 ) ) )
class NodeContactTest ( unittest . TestCase ) :
""" Test case for the Node class ' s contact management-related functions """
def setUp ( self ) :
self . node = lbrynet . dht . node . Node ( )
def testAddContact ( self ) :
""" Tests if a contact can be added and retrieved correctly """
import lbrynet . dht . contact
# Create the contact
h = hashlib . sha1 ( )
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 )
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() ' )
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
contact = lbrynet . dht . contact . Contact ( self . node . id , ' 127.0.0.1 ' , 91824 , None )
# Now try to add it
self . node . addContact ( contact )
# ...and request the closest nodes to it using FIND_NODE
closestNodes = self . node . _routingTable . findCloseNodes ( self . node . id , lbrynet . dht . constants . k )
self . failIf ( contact in closestNodes , ' Node added itself as a contact ' )
#class NodeLookupTest(unittest.TestCase):
# """ Test case for the Node class's iterative node lookup algorithm """
# def setUp(self):
# import entangled.kademlia.contact
# self.node = entangled.kademlia.node.Node()
# self.remoteNodes = []
# for i in range(10):
# remoteNode = entangled.kademlia.node.Node()
# remoteContact = entangled.kademlia.contact.Contact(remoteNode.id, '127.0.0.1', 91827+i, self.node._protocol)
# self.remoteNodes.append(remoteNode)
# self.node.addContact(remoteContact)
# def testIterativeFindNode(self):
# """ Ugly brute-force test to see if the iterative node lookup algorithm runs without failing """
# import entangled.kademlia.protocol
# entangled.kademlia.protocol.reactor.listenUDP(91826, self.node._protocol)
# for i in range(10):
# entangled.kademlia.protocol.reactor.listenUDP(91827+i, self.remoteNodes[i]._protocol)
# df = self.node.iterativeFindNode(self.node.id)
# df.addBoth(lambda _: entangled.kademlia.protocol.reactor.stop())
# entangled.kademlia.protocol.reactor.run()
""" Some scaffolding for the NodeLookupTest class. Allows isolated node testing by simulating remote node responses """
from twisted . internet import protocol , defer , selectreactor
from lbrynet . dht . msgtypes import ResponseMessage
2016-10-14 08:13:37 +02:00
2015-08-20 17:27:15 +02:00
class FakeRPCProtocol ( protocol . DatagramProtocol ) :
def __init__ ( self ) :
self . reactor = selectreactor . SelectReactor ( )
self . testResponse = None
self . network = None
2016-10-14 08:13:37 +02:00
2015-08-20 17:27:15 +02:00
def createNetwork ( self , contactNetwork ) :
""" 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
""" Fake RPC protocol; allows entangled.kademlia.contact.Contact objects to " send " RPCs """
def sendRPC ( self , contact , method , args , rawResponse = False ) :
#print method + " " + str(args)
if method == " findNode " :
# get the specific contacts closest contacts
closestContacts = [ ]
#print "contact" + contact.id
for contactTuple in self . network :
#print contactTuple[0].id
if contact == contactTuple [ 0 ] :
# get the list of closest contacts for this contact
closestContactsList = contactTuple [ 1 ]
#print "contact" + contact.id
# Pack the closest contacts into a ResponseMessage
for closeContact in closestContactsList :
#print closeContact.id
closestContacts . append ( ( closeContact . id , closeContact . address , closeContact . port ) )
message = ResponseMessage ( " rpcId " , contact . id , closestContacts )
df = defer . Deferred ( )
df . callback ( ( message , ( contact . address , contact . port ) ) )
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 :
closestContacts . append ( ( closeContact . id , closeContact . address , closeContact . port ) )
response = closestContacts
# Create the response message
message = ResponseMessage ( " rpcId " , contact . id , response )
df = defer . Deferred ( )
df . callback ( ( message , ( contact . address , contact . port ) ) )
return df
2016-10-14 08:13:37 +02:00
2015-08-20 17:27:15 +02:00
def _send ( self , data , rpcID , address ) :
""" fake sending data """
class NodeLookupTest ( unittest . TestCase ) :
""" Test case for the Node class ' s iterativeFind node lookup algorithm """
def setUp ( self ) :
# create a fake protocol to imitate communication with other nodes
self . _protocol = FakeRPCProtocol ( )
# Note: The reactor is never started for this test. All deferred calls run sequentially,
# since there is no asynchronous network communication
# create the node to be tested in isolation
self . node = lbrynet . dht . node . Node ( None , 4000 , None , None , self . _protocol )
self . updPort = 81173
# create a dummy reactor
#self._protocol.reactor.listenUDP(self.updPort, self._protocol)
self . contactsAmount = 80
# set the node ID manually for testing
self . node . id = ' 12345678901234567800 '
# Reinitialise the routing table
self . node . _routingTable = lbrynet . dht . routingtable . OptimizedTreeRoutingTable ( self . node . id )
# create 160 bit node ID's for test purposes
self . testNodeIDs = [ ]
#idNum = long(self.node.id.encode('hex'), 16)
idNum = int ( self . node . id )
for i in range ( self . contactsAmount ) :
# create the testNodeIDs in ascending order, away from the actual node ID, with regards to the distance metric
self . testNodeIDs . append ( idNum + i + 1 )
2016-10-14 08:13:37 +02:00
2015-08-20 17:27:15 +02:00
# generate contacts
self . contacts = [ ]
for i in range ( self . contactsAmount ) :
contact = lbrynet . dht . contact . Contact ( str ( self . testNodeIDs [ i ] ) , " 127.0.0.1 " , self . updPort + i + 1 , self . _protocol )
self . contacts . append ( contact )
# create the network of contacts in format: (contact, closest contacts)
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 :
contacts_with_datastores . append ( ( contact_tuple [ 0 ] , contact_tuple [ 1 ] , lbrynet . dht . datastore . DictDataStore ( ) ) )
self . _protocol . createNetwork ( contacts_with_datastores )
def testNodeBootStrap ( self ) :
""" Test bootstrap with the closest possible contacts """
df = self . node . _iterativeFind ( self . node . id , self . contacts [ 0 : 8 ] )
# Set the expected result
expectedResult = [ ]
for item in self . contacts [ 0 : 6 ] :
expectedResult . append ( item . id )
#print item.id
# Get the result from the deferred
activeContacts = df . result
# Check the length of the active contacts
self . failUnlessEqual ( activeContacts . __len__ ( ) , expectedResult . __len__ ( ) , \
" More active contacts should exist, there should be %d contacts " % expectedResult . __len__ ( ) )
# Check that the received active contacts are the same as the input contacts
self . failUnlessEqual ( activeContacts , expectedResult , \
" Active should only contain the closest possible contacts which were used as input for the boostrap " )
# def testFindingCloserNodes(self):
# """ Test discovery of closer contacts"""
#
# # Use input contacts that have knowledge of closer contacts,
# df = self.node._iterativeFind(self.node.id, self.contacts[50:53])
# #set the expected result
# expectedResult = []
# #print "############ Expected Active contacts #################"
# for item in self.contacts[0:9]:
# expectedResult.append(item.id)
# #print item.id
# #print "#######################################################"
#
# # Get the result from the deferred
# activeContacts = df.result
#
# #print "!!!!!!!!!!! Receieved Active contacts !!!!!!!!!!!!!!!"
# #for item in activeContacts:
# # print item.id
# #print "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
#
# # Check the length of the active contacts
# self.failUnlessEqual(activeContacts.__len__(), expectedResult.__len__(), \
# "Length of received active contacts not as expected, should be %d" %expectedResult.__len__())
#
#
# # Check that the received active contacts are now closer to this node
# self.failUnlessEqual(activeContacts, expectedResult, \
# "Active contacts should now only contain the closest possible contacts")
# def testIterativeStore(self):
# """ test storing values """
#
# # create the network of contacts in format: (contact, closest contacts)
# contactNetwork = ((self.contacts[0], self.contacts[0:8]),
# (self.contacts[1], self.contacts[0:8]),
# (self.contacts[2], self.contacts[0:8]),
# (self.contacts[3], self.contacts[0:8]),
# (self.contacts[4], self.contacts[0:8]),
# (self.contacts[5], self.contacts[0:8]),
# (self.contacts[6], self.contacts[0:8]),
# (self.contacts[7], self.contacts[0:8]),
# (self.contacts[8], self.contacts[0:8]),
# (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]))
# contacts_with_datastores = []
#
# for contact_tuple in contactNetwork:
# contacts_with_datastores.append((contact_tuple[0], contact_tuple[1], lbrynet.dht.datastore.DictDataStore()))
#
# self._protocol.createNetwork(contacts_with_datastores)
#
#
# #self._protocol.createNetwork(contactNetwork)
#
#
# # Test storing a value that has an hash id close to the known contacts
# # The value should only be stored at those nodes
# value = 'value'
# valueID = self.contacts[40].id
#
# # Manually populate the routing table with contacts that have ID's close to the valueID
# for contact in self.contacts[40:48]:
# self.node.addContact(contact)
#
# # Manually populate the routing table with contacts that have ID's far away from the valueID
# for contact in self.contacts[0:8]:
# self.node.addContact(contact)
#
# # Store the value
# df = self.node.announceHaveBlob(valueID, value)
#
# storageNodes = df.result
#
# storageNodeIDs = []
# for item in storageNodes:
# storageNodeIDs.append(item.id)
# storageNodeIDs.sort()
# #print storageNodeIDs
#
# expectedIDs = []
# for item in self.contacts[40:43]:
# expectedIDs.append(item.id)
# #print expectedIDs
#
# #print '#### storage nodes ####'
# #for node in storageNodes:
# # print node.id
#
#
# # check that the value has been stored at nodes with ID's close to the valueID
# self.failUnlessEqual(storageNodeIDs, expectedIDs, \
# "Value not stored at nodes with ID's close to the valueID")
#
# def testFindValue(self):
# # create test values using the contact ID as the key
# testValues = ({self.contacts[0].id: "some test data"},
# {self.contacts[1].id: "some more test data"},
# {self.contacts[8].id: "and more data"}
# )
#
#
# # create the network of contacts in format: (contact, closest contacts, values)
# contactNetwork = ((self.contacts[0], self.contacts[0:6], testValues[0]),
# (self.contacts[1], self.contacts[0:6], testValues[1]),
# (self.contacts[2], self.contacts[0:6], {'2':'2'}),
# (self.contacts[3], self.contacts[0:6], {'4':'5'}),
# (self.contacts[4], self.contacts[0:6], testValues[2]),
# (self.contacts[5], self.contacts[0:6], {'2':'2'}),
# (self.contacts[6], self.contacts[0:6], {'2':'2'}))
#
# self._protocol.createNetwork(contactNetwork)
#
# # Initialise the routing table with some contacts
# for contact in self.contacts[0:4]:
# self.node.addContact(contact)
#
# # Initialise the node with some known contacts
# #self.node._iterativeFind(self.node.id, self.contacts[0:3])
#
# df = self.node.iterativeFindValue(testValues[1].keys()[0])
#
# resultDict = df.result
# keys = resultDict.keys()
#
# for key in keys:
# if key == 'closestNodeNoValue':
# print "closest contact without data " + " " + resultDict.get(key).id
# else:
# print "data key :" + key + "; " + "data: " + resultDict.get(key)
def suite ( ) :
suite = unittest . TestSuite ( )
suite . addTest ( unittest . makeSuite ( NodeIDTest ) )
suite . addTest ( unittest . makeSuite ( NodeDataTest ) )
suite . addTest ( unittest . makeSuite ( NodeContactTest ) )
suite . addTest ( unittest . makeSuite ( NodeLookupTest ) )
return suite
if __name__ == ' __main__ ' :
# If this module is executed from the commandline, run all its tests
unittest . TextTestRunner ( ) . run ( suite ( ) )