From bd520a340f78c1255d6fc6fb1818b4d090174893 Mon Sep 17 00:00:00 2001
From: Peter Surda <surda@economicsofbitcoin.com>
Date: Thu, 12 Jan 2017 06:58:35 +0100
Subject: [PATCH] Trustedpeer fix and more refactoring

- fixed trustedPeer (thanks to anonymous bug reporter)
- moved trustedPeer and Peer into state.py
---
 src/class_outgoingSynSender.py |  8 ++++----
 src/class_receiveDataThread.py | 16 ++++++++--------
 src/class_sendDataThread.py    |  2 +-
 src/class_singleListener.py    |  4 +++-
 src/defaultKnownNodes.py       | 21 +++++++++++----------
 src/helper_bootstrap.py        | 12 ++++++------
 src/helper_startup.py          |  2 +-
 src/shared.py                  | 14 --------------
 src/state.py                   | 16 ++++++++++++++++
 9 files changed, 50 insertions(+), 45 deletions(-)

diff --git a/src/class_outgoingSynSender.py b/src/class_outgoingSynSender.py
index 1754962e..b2e0f984 100644
--- a/src/class_outgoingSynSender.py
+++ b/src/class_outgoingSynSender.py
@@ -32,9 +32,9 @@ class outgoingSynSender(threading.Thread, StoppableThread):
         # If the user has specified a trusted peer then we'll only
         # ever connect to that. Otherwise we'll pick a random one from
         # the known nodes
-        if shared.trustedPeer:
+        if state.trustedPeer:
             shared.knownNodesLock.acquire()
-            peer = shared.trustedPeer
+            peer = state.trustedPeer
             shared.knownNodes[self.streamNumber][peer] = time.time()
             shared.knownNodesLock.release()
         else:
@@ -65,7 +65,7 @@ class outgoingSynSender(threading.Thread, StoppableThread):
         try:
             return peer
         except NameError:
-            return shared.Peer('127.0.0.1', 8444)
+            return state.Peer('127.0.0.1', 8444)
         
     def stopThread(self):
         super(outgoingSynSender, self).stopThread()
@@ -79,7 +79,7 @@ class outgoingSynSender(threading.Thread, StoppableThread):
             self.stop.wait(2)
         while BMConfigParser().safeGetBoolean('bitmessagesettings', 'sendoutgoingconnections') and not self._stopped:
             self.name = "outgoingSynSender"
-            maximumConnections = 1 if shared.trustedPeer else 8 # maximum number of outgoing connections = 8
+            maximumConnections = 1 if state.trustedPeer else 8 # maximum number of outgoing connections = 8
             while len(self.selfInitiatedConnections[self.streamNumber]) >= maximumConnections:
                 self.stop.wait(10)
             if shared.shutdown:
diff --git a/src/class_receiveDataThread.py b/src/class_receiveDataThread.py
index 3ce3b35e..396b9b8c 100644
--- a/src/class_receiveDataThread.py
+++ b/src/class_receiveDataThread.py
@@ -58,7 +58,7 @@ class receiveDataThread(threading.Thread):
         objectHashHolderInstance):
         
         self.sock = sock
-        self.peer = shared.Peer(HOST, port)
+        self.peer = state.Peer(HOST, port)
         self.name = "receiveData-" + self.peer.host.replace(":", ".") # ":" log parser field separator
         self.streamNumber = streamNumber
         self.objectsThatWeHaveYetToGetFromThisPeer = {}
@@ -380,7 +380,7 @@ class receiveDataThread(threading.Thread):
         # We don't need to do the timing attack mitigation if we are
         # only connected to the trusted peer because we can trust the
         # peer not to attack
-        if sleepTime > 0 and doTimingAttackMitigation and shared.trustedPeer == None:
+        if sleepTime > 0 and doTimingAttackMitigation and state.trustedPeer == None:
             logger.debug('Timing attack mitigation: Sleeping for ' + str(sleepTime) + ' seconds.')
             time.sleep(sleepTime)
             
@@ -450,7 +450,7 @@ class receiveDataThread(threading.Thread):
             logger.info('inv message doesn\'t contain enough data. Ignoring.')
             return
         if numberOfItemsInInv == 1:  # we'll just request this data from the person who advertised the object.
-            if totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers > 200000 and len(self.objectsThatWeHaveYetToGetFromThisPeer) > 1000 and shared.trustedPeer == None:  # inv flooding attack mitigation
+            if totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers > 200000 and len(self.objectsThatWeHaveYetToGetFromThisPeer) > 1000 and state.trustedPeer == None:  # inv flooding attack mitigation
                 logger.debug('We already have ' + str(totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers) + ' items yet to retrieve from peers and over 1000 from this node in particular. Ignoring this inv message.')
                 return
             self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware[
@@ -470,7 +470,7 @@ class receiveDataThread(threading.Thread):
             objectsNewToMe = advertisedSet - Inventory().hashes_by_stream(self.streamNumber)
             logger.info('inv message lists %s objects. Of those %s are new to me. It took %s seconds to figure that out.', numberOfItemsInInv, len(objectsNewToMe), time.time()-startTime)
             for item in objectsNewToMe:  
-                if totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers > 200000 and len(self.objectsThatWeHaveYetToGetFromThisPeer) > 1000 and shared.trustedPeer == None:  # inv flooding attack mitigation
+                if totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers > 200000 and len(self.objectsThatWeHaveYetToGetFromThisPeer) > 1000 and state.trustedPeer == None:  # inv flooding attack mitigation
                     logger.debug('We already have ' + str(totalNumberOfobjectsThatWeHaveYetToGetFromAllPeers) + ' items yet to retrieve from peers and over ' + str(len(self.objectsThatWeHaveYetToGetFromThisPeer)), ' from this node in particular. Ignoring the rest of this inv message.')
                     break
                 self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware[item] = 0 # helps us keep from sending inv messages to peers that already know about the objects listed therein
@@ -593,7 +593,7 @@ class receiveDataThread(threading.Thread):
             if recaddrStream not in shared.knownNodes:  # knownNodes is a dictionary of dictionaries with one outer dictionary for each stream. If the outer stream dictionary doesn't exist yet then we must make it.
                 with shared.knownNodesLock:
                     shared.knownNodes[recaddrStream] = {}
-            peerFromAddrMessage = shared.Peer(hostStandardFormat, recaddrPort)
+            peerFromAddrMessage = state.Peer(hostStandardFormat, recaddrPort)
             if peerFromAddrMessage not in shared.knownNodes[recaddrStream]:
                 if len(shared.knownNodes[recaddrStream]) < 20000 and timeSomeoneElseReceivedMessageFromThisNode > (int(time.time()) - 10800) and timeSomeoneElseReceivedMessageFromThisNode < (int(time.time()) + 10800):  # If we have more than 20000 nodes in our list already then just forget about adding more. Also, make sure that the time that someone else received a message from this node is within three hours from now.
                     with shared.knownNodesLock:
@@ -637,7 +637,7 @@ class receiveDataThread(threading.Thread):
                     # if current connection is over a proxy, sent our own onion address at a random position
                     if ownPosition == i and ".onion" in BMConfigParser().get("bitmessagesettings", "onionhostname") and \
                         hasattr(self.sock, "getproxytype") and self.sock.getproxytype() != "none" and not sentOwn:
-                        peer = shared.Peer(BMConfigParser().get("bitmessagesettings", "onionhostname"), BMConfigParser().getint("bitmessagesettings", "onionport"))
+                        peer = state.Peer(BMConfigParser().get("bitmessagesettings", "onionhostname"), BMConfigParser().getint("bitmessagesettings", "onionport"))
                     else:
                     # still may contain own onion address, but we don't change it
                         peer, = random.sample(shared.knownNodes[self.streamNumber], 1)
@@ -802,9 +802,9 @@ class receiveDataThread(threading.Thread):
 
         if not isHostInPrivateIPRange(self.peer.host):
             with shared.knownNodesLock:
-                shared.knownNodes[self.streamNumber][shared.Peer(self.peer.host, self.remoteNodeIncomingPort)] = int(time.time())
+                shared.knownNodes[self.streamNumber][state.Peer(self.peer.host, self.remoteNodeIncomingPort)] = int(time.time())
                 if not self.initiatedConnection:
-                    shared.knownNodes[self.streamNumber][shared.Peer(self.peer.host, self.remoteNodeIncomingPort)] -= 162000 # penalise inbound, 2 days minus 3 hours
+                    shared.knownNodes[self.streamNumber][state.Peer(self.peer.host, self.remoteNodeIncomingPort)] -= 162000 # penalise inbound, 2 days minus 3 hours
                 shared.needToWriteKnownNodesToDisk = True
 
         self.sendverack()
diff --git a/src/class_sendDataThread.py b/src/class_sendDataThread.py
index 60babb1f..f6d15c26 100644
--- a/src/class_sendDataThread.py
+++ b/src/class_sendDataThread.py
@@ -38,7 +38,7 @@ class sendDataThread(threading.Thread):
         streamNumber,
             someObjectsOfWhichThisRemoteNodeIsAlreadyAware):
         self.sock = sock
-        self.peer = shared.Peer(HOST, PORT)
+        self.peer = state.Peer(HOST, PORT)
         self.name = "sendData-" + self.peer.host.replace(":", ".") # log parser field separator
         self.streamNumber = streamNumber
         self.services = 0
diff --git a/src/class_singleListener.py b/src/class_singleListener.py
index 896a34bc..0ad82333 100644
--- a/src/class_singleListener.py
+++ b/src/class_singleListener.py
@@ -10,6 +10,8 @@ import protocol
 import errno
 import re
 
+import state
+
 # 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
@@ -60,7 +62,7 @@ class singleListener(threading.Thread, StoppableThread):
     def run(self):
         # If there is a trusted peer then we don't want to accept
         # incoming connections so we'll just abandon the thread
-        if shared.trustedPeer:
+        if state.trustedPeer:
             return
 
         while BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect') and shared.shutdown == 0:
diff --git a/src/defaultKnownNodes.py b/src/defaultKnownNodes.py
index f91e6fe6..986fbf6d 100644
--- a/src/defaultKnownNodes.py
+++ b/src/defaultKnownNodes.py
@@ -6,21 +6,22 @@ import random
 import sys
 from time import strftime, localtime
 import shared
+import state
 
 def createDefaultKnownNodes(appdata):
     ############## Stream 1 ################
     stream1 = {}
 
-    #stream1[shared.Peer('2604:2000:1380:9f:82e:148b:2746:d0c7', 8080)] = int(time.time())
-    stream1[shared.Peer('5.45.99.75', 8444)] = int(time.time())
-    stream1[shared.Peer('75.167.159.54', 8444)] = int(time.time())
-    stream1[shared.Peer('95.165.168.168', 8444)] = int(time.time())
-    stream1[shared.Peer('85.180.139.241', 8444)] = int(time.time())
-    stream1[shared.Peer('158.222.211.81', 8080)] = int(time.time())
-    stream1[shared.Peer('178.62.12.187', 8448)] = int(time.time())
-    stream1[shared.Peer('24.188.198.204', 8111)] = int(time.time())
-    stream1[shared.Peer('109.147.204.113', 1195)] = int(time.time())
-    stream1[shared.Peer('178.11.46.221', 8444)] = int(time.time())
+    #stream1[state.Peer('2604:2000:1380:9f:82e:148b:2746:d0c7', 8080)] = int(time.time())
+    stream1[state.Peer('5.45.99.75', 8444)] = int(time.time())
+    stream1[state.Peer('75.167.159.54', 8444)] = int(time.time())
+    stream1[state.Peer('95.165.168.168', 8444)] = int(time.time())
+    stream1[state.Peer('85.180.139.241', 8444)] = int(time.time())
+    stream1[state.Peer('158.222.211.81', 8080)] = int(time.time())
+    stream1[state.Peer('178.62.12.187', 8448)] = int(time.time())
+    stream1[state.Peer('24.188.198.204', 8111)] = int(time.time())
+    stream1[state.Peer('109.147.204.113', 1195)] = int(time.time())
+    stream1[state.Peer('178.11.46.221', 8444)] = int(time.time())
     
     ############# Stream 2 #################
     stream2 = {}
diff --git a/src/helper_bootstrap.py b/src/helper_bootstrap.py
index 4acf7105..5361b9be 100644
--- a/src/helper_bootstrap.py
+++ b/src/helper_bootstrap.py
@@ -24,7 +24,7 @@ def knownNodes():
             for node_tuple in nodes.items():
                 try:
                     host, (port, lastseen) = node_tuple
-                    peer = shared.Peer(host, port)
+                    peer = state.Peer(host, port)
                 except:
                     peer, lastseen = node_tuple
                 shared.knownNodes[stream][peer] = lastseen
@@ -32,7 +32,7 @@ def knownNodes():
         shared.knownNodes = defaultKnownNodes.createDefaultKnownNodes(state.appdata)
     # your own onion address, if setup
     if BMConfigParser().has_option('bitmessagesettings', 'onionhostname') and ".onion" in BMConfigParser().get('bitmessagesettings', 'onionhostname'):
-        shared.knownNodes[1][shared.Peer(BMConfigParser().get('bitmessagesettings', 'onionhostname'), BMConfigParser().getint('bitmessagesettings', 'onionport'))] = int(time.time())
+        shared.knownNodes[1][state.Peer(BMConfigParser().get('bitmessagesettings', 'onionhostname'), BMConfigParser().getint('bitmessagesettings', 'onionport'))] = int(time.time())
     if BMConfigParser().getint('bitmessagesettings', 'settingsversion') > 10:
         logger.error('Bitmessage cannot read future versions of the keys file (keys.dat). Run the newer version of Bitmessage.')
         raise SystemExit
@@ -47,17 +47,17 @@ def dns():
         try:
             for item in socket.getaddrinfo('bootstrap8080.bitmessage.org', 80):
                 logger.info('Adding ' + item[4][0] + ' to knownNodes based on DNS bootstrap method')
-                shared.knownNodes[1][shared.Peer(item[4][0], 8080)] = int(time.time())
+                shared.knownNodes[1][state.Peer(item[4][0], 8080)] = int(time.time())
         except:
             logger.error('bootstrap8080.bitmessage.org DNS bootstrapping failed.')
         try:
             for item in socket.getaddrinfo('bootstrap8444.bitmessage.org', 80):
                 logger.info ('Adding ' + item[4][0] + ' to knownNodes based on DNS bootstrap method')
-                shared.knownNodes[1][shared.Peer(item[4][0], 8444)] = int(time.time())
+                shared.knownNodes[1][state.Peer(item[4][0], 8444)] = int(time.time())
         except:
             logger.error('bootstrap8444.bitmessage.org DNS bootstrapping failed.')
     elif BMConfigParser().get('bitmessagesettings', 'socksproxytype') == 'SOCKS5':
-        shared.knownNodes[1][shared.Peer('quzwelsuziwqgpt2.onion', 8444)] = int(time.time())
+        shared.knownNodes[1][state.Peer('quzwelsuziwqgpt2.onion', 8444)] = int(time.time())
         logger.debug("Adding quzwelsuziwqgpt2.onion:8444 to knownNodes.")
         for port in [8080, 8444]:
             logger.debug("Resolving %i through SOCKS...", port)
@@ -90,7 +90,7 @@ def dns():
             else:
                 if ip is not None:
                     logger.info ('Adding ' + ip + ' to knownNodes based on SOCKS DNS bootstrap method')
-                    shared.knownNodes[1][shared.Peer(ip, port)] = time.time()
+                    shared.knownNodes[1][state.Peer(ip, port)] = time.time()
     else:
         logger.info('DNS bootstrap skipped because the proxy type does not support DNS resolution.')
 
diff --git a/src/helper_startup.py b/src/helper_startup.py
index 86d3de42..a07bffb8 100644
--- a/src/helper_startup.py
+++ b/src/helper_startup.py
@@ -25,7 +25,7 @@ def _loadTrustedPeer():
         return
 
     host, port = trustedPeer.split(':')
-    shared.trustedPeer = shared.Peer(host, int(port))
+    state.trustedPeer = state.Peer(host, int(port))
 
 def loadConfig():
     if state.appdata:
diff --git a/src/shared.py b/src/shared.py
index d5438b1c..f67a2d07 100644
--- a/src/shared.py
+++ b/src/shared.py
@@ -93,18 +93,6 @@ ridiculousDifficulty = 20000000
 # namecoin integration to "namecoind".
 namecoinDefaultRpcPort = "8336"
 
-# If the trustedpeer option is specified in keys.dat then this will
-# contain a Peer which will be connected to instead of using the
-# addresses advertised by other peers. The client will only connect to
-# this peer and the timing attack mitigation will be disabled in order
-# to download data faster. The expected use case is where the user has
-# a fast connection to a trusted server where they run a BitMessage
-# daemon permanently. If they then run a second instance of the client
-# on a local machine periodically when they want to check for messages
-# it will sync with the network a lot faster without compromising
-# security.
-trustedPeer = None
-
 def isAddressInMyAddressBook(address):
     queryreturn = sqlQuery(
         '''select address from addressbook where address=?''',
@@ -442,8 +430,6 @@ def decryptAndCheckPubkeyPayload(data, address):
         logger.critical('Pubkey decryption was UNsuccessful because of an unhandled exception! This is definitely a bug! \n%s' % traceback.format_exc())
         return 'failed'
 
-Peer = collections.namedtuple('Peer', ['host', 'port'])
-
 def checkAndShareObjectWithPeers(data):
     """
     This function is called after either receiving an object off of the wire
diff --git a/src/state.py b/src/state.py
index f3fb8a53..9e546b51 100644
--- a/src/state.py
+++ b/src/state.py
@@ -1,3 +1,5 @@
+import collections
+
 neededPubkeys = {}
 streamsInWhichIAmParticipating = {}
 sendDataQueues = [] #each sendData thread puts its queue in this list.
@@ -12,3 +14,17 @@ socksIP = None
 networkProtocolLastFailed = {'IPv4': 0, 'IPv6': 0, 'onion': 0}
 
 appdata = '' #holds the location of the application data storage directory
+
+# If the trustedpeer option is specified in keys.dat then this will
+# contain a Peer which will be connected to instead of using the
+# addresses advertised by other peers. The client will only connect to
+# this peer and the timing attack mitigation will be disabled in order
+# to download data faster. The expected use case is where the user has
+# a fast connection to a trusted server where they run a BitMessage
+# daemon permanently. If they then run a second instance of the client
+# on a local machine periodically when they want to check for messages
+# it will sync with the network a lot faster without compromising
+# security.
+trustedPeer = None
+
+Peer = collections.namedtuple('Peer', ['host', 'port'])