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
print'PyBitmessage requires PyQt. You can download it from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\' (without quotes).'
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.
#Notice that I have divided the averageProofOfWorkNonceTrialsPerByte by two. This makes the POW requirement easier. This gives us wiggle-room: if we decide that we want to make the POW easier, the change won't obsolete old clients because they already expect a lower POW. If we decide that the current work done by clients feels approperate then we can remove this division by 2 and make the requirement match what is actually done by a sending node. If we want to raise the POW requirement then old nodes will HAVE to upgrade no matter what.
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.
#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 (?,?,?,?)''')
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.'
#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.)
#We'll need to make sure that our client will properly process the ackData; if the packet is malformed, we could clear out self.data and an attacker could use that behavior to determine that we were capable of decoding this message.
ackDataValidThusFar=True
iflen(ackData)<24:
print'The length of Ackdata is unreasonably short. Not sending ackData.'
ackDataValidThusFar=False
ifackData[0:4]!='\xe9\xbe\xb4\xd9':
print'Ackdata magic bytes were wrong. Not sending ackData.'
ackDataValidThusFar=False
ifackDataValidThusFar:
ackDataPayloadLength,=unpack('>L',ackData[16:20])
iflen(ackData)-24!=ackDataPayloadLength:
print'ackData payload length doesn\'t match the payload length specified in the header. Not sending ackdata.'
#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.'
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...