# Distributed under the MIT/X11 software license. See the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#Right now, PyBitmessage only support connecting to stream 1. It doesn't yet contain logic to expand into further streams.
softwareVersion='0.1.0'
verbose=2
maximumAgeOfAnObjectThatIAmWillingToAccept=216000#Equals two days and 12 hours.
lengthOfTimeToLeaveObjectsInInventory=237600#Equals two days and 18 hours. This should be longer than maximumAgeOfAnObjectThatIAmWillingToAccept so that we don't process messages twice.
maximumAgeOfObjectsThatIAdvertiseToOthers=216000#Equals two days and 12 hours
maximumAgeOfNodesThatIAdvertiseToOthers=10800#Equals three hours
importsys
try:
fromPyQt4.QtCoreimport*
fromPyQt4.QtGuiimport*
exceptException,err:
print'Bitmessage requires PyQt. You can download it from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\' (without quotes).'
print'Error message:',err
sys.exit()
importConfigParser
frombitmessageuiimport*
fromnewaddressdialogimport*
fromnewsubscriptiondialogimport*
fromsettingsimport*
fromaboutimport*
fromhelpimport*
fromiconglossaryimport*
fromaddressesimport*
importQueue
fromdefaultKnownNodesimport*
importtime
importsocket
importthreading
importrsa
fromrsa.bigfileimport*
importhashlib
fromstructimport*
importpickle
importrandom
importsqlite3
importthreading#used for the locks, not for the threads
importcStringIO
fromemail.parserimportParser
fromtimeimportstrftime,localtime
importos
#For each stream to which we connect, one outgoingSynSender thread will exist and will create 8 connections with peers.
classoutgoingSynSender(QThread):
def__init__(self,parent=None):
QThread.__init__(self,parent)
self.selfInitiatedConnectionList=[]#This is a list of current connections (the thread pointers at least)
self.alreadyAttemptedConnectionsList=[]#This is a list of nodes to which we have already attempted a connection
defsetup(self,streamNumber):
self.streamNumber=streamNumber
defrun(self):
time.sleep(1)
resetTime=int(time.time())#used below to clear out the alreadyAttemptedConnectionsList periodically so that we will retry connecting to hosts to which we have already tried to connect.
whileTrue:
#time.sleep(999999)#I'm using this to prevent connections for testing.
iflen(self.selfInitiatedConnectionList)<8:#maximum number of outgoing connections = 8
#Clear out the alreadyAttemptedConnectionsList every half hour so that this program will again attempt a connection to any nodes, even ones it has already tried.
if(int(time.time())-timeLastSeen)>172800:# for nodes older than 48 hours old, delete from the knownNodes data-structure.
iflen(knownNodes[self.streamNumber])>1000:#as long as we have more than 1000 hosts in our list
delknownNodes[self.streamNumber][HOST]
print'deleting ',HOST,'from knownNodes because it is more than 48 hours old and we could not connect to it.'
time.sleep(1)
#Only one singleListener thread will ever exist. It creates the receiveDataThread and sendDataThread for each incoming connection. Note that it cannot set the stream number because it is not known yet- the other node will have to tell us its stream number in a version message. If we don't care about their stream, we will close the connection (within the recversion function of the recieveData thread)
classsingleListener(QThread):
def__init__(self,parent=None):
QThread.__init__(self,parent)
defrun(self):
print'bitmessage listener running'
HOST=''# Symbolic name meaning all available interfaces
self.sock.settimeout(600)#We'll send out a pong every 5 minutes to make sure the connection stays alive if there has been no other traffic to send lately.
self.connectionIsOrWasFullyEstablished=False#set to true after the remote node and I accept each other's version messages. This is needed to allow the user interface to accurately reflect the current number of connections.
ifself.streamNumber==-1:#This was an incoming connection. Send out a version message if we accept the other node's version message.
self.initiatedConnection=False
else:
self.initiatedConnection=True
defrun(self):
whileTrue:
try:
self.data=self.data+self.sock.recv(65536)
exceptsocket.timeout:
printLock.acquire()
print'Timeout occurred waiting for data. Closing thread.'
ifself.connectionIsOrWasFullyEstablished:#We don't want to decrement the number of connections and show the result if we never incremented it in the first place (which we only do if the connection is fully established- meaning that both nodes accepted each other's version packets.)
#The time we've last seen this node is obviously right now since we just received valid data from it. So update the knownNodes list so that other peers can be made aware of its existance.
ifself.initiatedConnection:#The remote port is only something we should share with others if it is the remote node's incoming port (rather than some random operating-system-assigned outgoing port).
print'Inventory (in memory) already has object that we received in an inv message.'
delself.objectsThatWeHaveYetToGet[objectHash]
elifisInSqlInventory(objectHash):
print'Inventory (SQL on disk) already has object that we received in an inv message.'
delself.objectsThatWeHaveYetToGet[objectHash]
else:
print'processData function making request for object:',repr(objectHash)
self.sendgetdata(objectHash)
delself.objectsThatWeHaveYetToGet[objectHash]#It is possible that the remote node doesn't respond with the object. In that case, we'll very likely get it from someone else anyway.
break
print'within processData, length of objectsThatWeHaveYetToGet is now',len(self.objectsThatWeHaveYetToGet)
self.processData()
#if self.versionSent == 0:
# self.sendversion()
'''if (self.versionRec == 1) and (self.getaddrthreadalreadycreated == 0):
while(threading.activeCount()>maxGlobalThreads):#If there too many open threads, just wait. This includes full and half-open.
time.sleep(0.1)
printLock.acquire()
print'Starting a new getaddrSender thread'
printLock.release()
bar=getaddrSender((self.sock,self.addr))
bar.daemon=True
self.getaddrthreadalreadycreated=1#we're about to start it
whileTrue:#My goal is to try bar.start() until it succeedes
while(threading.activeCount()>maxGlobalThreads):#If there too many open threads, just wait. This includes full and half-open.
time.sleep(0.1)
try:
bar.start()
except:
maxGlobalThreads=maxGlobalThreads-20
sys.stderr.write('Problem! Your OS did not allow the starting of another thread. Reducing the number of threads to %s and trying again.\n'%maxGlobalThreads)
continue#..the while loop again right now
break'''
else:
print'Checksum incorrect. Clearing messages and waiting for new data.'
ifnumberOfObjectsInInvMessage>=50000:#We can only send a max of 50000 items per inv message but we may have more objects to advertise. They must be split up into multiple inv messages.
#Cannot decode incoming broadcast versions higher than 1. Assuming the sender isn\' being silly, you should upgrade Bitmessage because this message shall be ignored.
#Cannot decode senderAddressVersion higher than 1. Assuming the sender isn\' being silly, you should upgrade Bitmessage because this message shall be ignored.
#At this point, this message claims to be from sendersHash and we are interested in it. We still have to hash the public key to make sure it is truly the key that matches the hash, and also check the signiture.
print'The stream number encoded in this msg ('+streamNumberAsClaimedByMsg+') message does not match the stream number on which it was received. Ignoring it.'
return
readPosition+=streamNumberAsClaimedByMsgLength
#This msg message is valid. Let's let our peers know about it.
sqlSubmitQueue.put('UPDATE sent SET status=? WHERE ackdata=?')
sqlSubmitQueue.put(t)
sqlReturnQueue.get()
sqlLock.release()
self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),self.data[readPosition:24+self.payloadLength],'Acknowledgement of the message received just now.')
flushInventory()#so that we won't accidentially receive this message twice if the user restarts Bitmessage soon.
return
else:
printLock.acquire()
print'This was NOT an acknowledgement bound for me. Msg potential ack data:',repr(self.data[readPosition:24+self.payloadLength])
#The initial decryption passed though there is a small chance that the message isn't actually for me. We'll need to check that the 20 zeros are present.
#print 'initial decryption successful using key', repr(key)
initialDecryptionSuccessful=True
printLock.acquire()
print'Initial decryption passed'
printLock.release()
break
exceptException,err:
infile.seek(0)
#print 'Exception:', err
#print 'outfile len is:', len(outfile.getvalue()),'data is:', repr(outfile.getvalue())
#print 'Initial decryption failed using key', value
#decryption failed for this key. The message is for someone else (or for a different key of mine).
ifinitialDecryptionSuccessfulandoutfile.getvalue()[:20]=='\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00':#this run of 0s allows the true message receiver to identify his message
#This is clearly a message bound for me.
flushInventory()#so that we won't accidentially receive this message twice if the user restarts Bitmessage soon.
outfile.seek(0)
data=outfile.getvalue()
readPosition=20#To start reading past the 20 zero bytes
#We have reached the end of the public key. Let's store it in case we want to reply to this person.
#We don't have the correct nonce in order to send out a pubkey message so we'll just fill it with 1's. We won't be able to send this pubkey to others (without doing the proof of work ourselves, which this program is programmed to not do.)
t=(ripe.digest(),False,'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'+data[20+messageVersionLength:endOfThePublicKeyPosition],int(time.time())+2419200)#after one month we may remove this pub key from our database.
sqlLock.acquire()
sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?)''')
sqlSubmitQueue.put(t)
sqlReturnQueue.get()
sqlLock.release()
blockMessage=False#Set to True if the user shouldn't see the message according to black or white lists.
print'messageEncodingType == 0. Doing nothing with the message. They probably just sent it so that we would store their public key or send their ack data for them.'
print'The POW is strong enough that this ackdataPayload will be accepted by the Bitmessage network.'
#Currently PyBitmessage only supports sending a message with the acknowledgement in the form of a msg message. But future versions, and other clients, could send any object and this software will relay them. This can be used to relay identifying information, like your public key, through another Bitmessage host in case you believe that your Internet connection is being individually watched. You may pick a random address, hope its owner is online, and send a message with encoding type 0 so that they ignore the message but send your acknowledgement data over the network. If you send and receive many messages, it would also be clever to take someone else's acknowledgement data and use it for your own. Assuming that your message is delivered successfully, both will be acknowledged simultaneously (though if it is not delivered successfully, you will be in a pickle.)
#inventory[inventoryHash] = (objectType, self.streamNumber, ackData[24:], embeddedTime) #We should probably be storing the embeddedTime of the ackData, not the embeddedTime of the original incoming msg message, but this is probably close enough.
#print 'sending the inv for the msg which is actually an acknowledgement (within sendmsg function)'
#inventory[inventoryHash] = (objectType, self.streamNumber, ackData[24:], embeddedTime) #We should probably be storing the embeddedTime of the ackData, not the embeddedTime of the original incoming msg message, but this is probably close enough.
#print 'sending the inv for the getpubkey which is actually an acknowledgement (within sendmsg function)'
#inventory[inventoryHash] = (objectType, self.streamNumber, ackData[24:], embeddedTime) #We should probably be storing the embeddedTime of the ackData, not the embeddedTime of the original incoming msg message, but this is probably close enough.
#print 'sending the inv for a pubkey which is actually an acknowledgement (within sendmsg function)'
#inventory[inventoryHash] = (objectType, self.streamNumber, ackData[24:], embeddedTime) #We should probably be storing the embeddedTime of the ackData, not the embeddedTime of the original incoming msg message, but this is probably close enough.
#print 'sending the inv for a broadcast which is actually an acknowledgement (within sendmsg function)'
print'Error: Cannot decode incoming msg versions higher than 1. Assuming the sender isn\' being silly, you should upgrade Bitmessage because this message shall be ignored.'
statusbar='Error: Cannot decode incoming msg versions higher than 1. Assuming the sender isn\' being silly, you should upgrade Bitmessage because this message shall be ignored.'
target=2**64/((len(payload)+payloadLengthExtraBytes+8)*averageProofOfWorkNonceTrialsPerByte)#The 108 added to the payload length is 8 for the size of the nonce + 50 as an extra penalty simply for having this seperate message exist.
print'(For pubkey message) Doing proof of work...'
print'(For pubkey message) Found proof of work',trialValue,'Nonce:',nonce
payload=pack('>Q',nonce)+payload
t=(self.data[36+addressVersionLength+streamNumberLength:56+addressVersionLength+streamNumberLength],True,payload,int(time.time())+1209600)#after two weeks (1,209,600 seconds), we may remove our own pub key from our database. It will be regenerated and put back in the database if it is requested.
sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?)''')
sqlSubmitQueue.put(t)
queryreturn=sqlReturnQueue.get()
#Now that we have the key either from getting it earlier or making it and storing it ourselves...
foriinrange(numberOfItemsInInv):#upon finishing dealing with an incoming message, the receiveDataThread will request a random object from the peer. This way if we get multiple inv messages from multiple peers which list mostly the same objects, we will make getdata requests for different random objects from the various peers.
#print 'Adding object to self.objectsThatWeHaveYetToGet.'