diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..474ae9ab --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,9 @@ +version: 2 + +python: + version: 2.7 + install: + - requirements: docs/requirements.txt + - method: setuptools + path: . + system_packages: true diff --git a/src/addresses.py b/src/addresses.py index b83f3f6e..bb0c9ec5 100644 --- a/src/addresses.py +++ b/src/addresses.py @@ -1,7 +1,5 @@ """ -src/addresses.py -================ - +Operations with addresses """ # pylint: disable=redefined-outer-name,inconsistent-return-statements @@ -18,8 +16,9 @@ ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" def encodeBase58(num, alphabet=ALPHABET): """Encode a number in Base X - `num`: The number to encode - `alphabet`: The alphabet to use for encoding + Args: + num: The number to encode + alphabet: The alphabet to use for encoding """ if num == 0: return alphabet[0] @@ -27,7 +26,6 @@ def encodeBase58(num, alphabet=ALPHABET): base = len(alphabet) while num: rem = num % base - # print 'num is:', num num = num // base arr.append(alphabet[rem]) arr.reverse() @@ -37,9 +35,9 @@ def encodeBase58(num, alphabet=ALPHABET): def decodeBase58(string, alphabet=ALPHABET): """Decode a Base X encoded string into the number - Arguments: - - `string`: The encoded string - - `alphabet`: The alphabet to use for encoding + Args: + string: The encoded string + alphabet: The alphabet to use for encoding """ base = len(alphabet) num = 0 diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 440d36b2..c7fa6390 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -11,6 +11,7 @@ import sys import textwrap import threading import time +import base64 from datetime import datetime, timedelta from sqlite3 import register_adapter @@ -161,6 +162,8 @@ class MyForm(settingsmixin.SMainWindow): "clicked()"), self.click_pushButtonTTL) QtCore.QObject.connect(self.ui.pushButtonClear, QtCore.SIGNAL( "clicked()"), self.click_pushButtonClear) + QtCore.QObject.connect(self.ui.pushButtonAttach, QtCore.SIGNAL( + "clicked()"), self.click_pushButtonAttach) QtCore.QObject.connect(self.ui.pushButtonSend, QtCore.SIGNAL( "clicked()"), self.click_pushButtonSend) QtCore.QObject.connect(self.ui.pushButtonFetchNamecoinID, QtCore.SIGNAL( @@ -1951,6 +1954,23 @@ class MyForm(settingsmixin.SMainWindow): self.ui.textEditMessage.reset() self.ui.comboBoxSendFrom.setCurrentIndex(0) + def click_pushButtonAttach(self): + """Launch a file picker and append to the current message the base64-encoded contents of the chosen file.""" + filename = QtGui.QFileDialog.getOpenFileName(self, "Attach File") + if filename: + f = open(filename, 'rb') + data = f.read() + f.close() + data_b64 = base64.b64encode(data) + html_data = '' \ + + os.path.basename(unicode(filename)) + '' + if self.ui.tabWidgetSend.currentIndex() == self.ui.tabWidgetSend.indexOf(self.ui.sendDirect): + # send direct message + self.ui.textEditMessage.insertPlainText(html_data) + else: + # send broadcast message + self.ui.textEditMessageBroadcast.insertPlainText(html_data) + def click_pushButtonSend(self): encoding = 3 if QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier else 2 diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py index 30d054d0..ca103dd5 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -340,6 +340,9 @@ class Ui_MainWindow(object): self.pushButtonClear = QtGui.QPushButton(self.send) self.pushButtonClear.setObjectName(_fromUtf8("pushButtonClear")) self.horizontalLayout_5.addWidget(self.pushButtonClear, 0, QtCore.Qt.AlignRight) + self.pushButtonAttach = QtGui.QPushButton(self.send) + self.pushButtonAttach.setObjectName(_fromUtf8("pushButtonAttach")) + self.horizontalLayout_5.addWidget(self.pushButtonAttach, 0, QtCore.Qt.AlignRight) self.pushButtonSend = QtGui.QPushButton(self.send) self.pushButtonSend.setObjectName(_fromUtf8("pushButtonSend")) self.horizontalLayout_5.addWidget(self.pushButtonSend, 0, QtCore.Qt.AlignRight) @@ -713,6 +716,7 @@ class Ui_MainWindow(object): pass self.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, hours)) self.pushButtonClear.setText(_translate("MainWindow", "Clear", None)) + self.pushButtonAttach.setText(_translate("MainWindow", "Attach File", None)) self.pushButtonSend.setText(_translate("MainWindow", "Send", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.send), _translate("MainWindow", "Send", None)) self.treeWidgetSubscriptions.headerItem().setText(0, _translate("MainWindow", "Subscriptions", None)) diff --git a/src/bitmessageqt/bitmessageui.ui b/src/bitmessageqt/bitmessageui.ui index fef40be6..b8b92b68 100644 --- a/src/bitmessageqt/bitmessageui.ui +++ b/src/bitmessageqt/bitmessageui.ui @@ -594,6 +594,19 @@ p, li { white-space: pre-wrap; } + + + + + 16777215 + 16777215 + + + + Attach File + + + diff --git a/src/bitmessageqt/messageview.py b/src/bitmessageqt/messageview.py index 45f3a79a..7a6432b0 100644 --- a/src/bitmessageqt/messageview.py +++ b/src/bitmessageqt/messageview.py @@ -5,6 +5,8 @@ src/bitmessageqt/messageview.py """ from PyQt4 import QtCore, QtGui +import re +import base64 from safehtmlparser import SafeHTMLParser @@ -64,6 +66,9 @@ class MessageView(QtGui.QTextBrowser): def confirmURL(self, link): """Show a dialog requesting URL opening confirmation""" + link_str = link.toString() + datablob_re = r'^data:.*/.*;base64,.*' + datablob_match = re.match(datablob_re, link_str) if link.scheme() == "mailto": window = QtGui.QApplication.activeWindow() window.ui.lineEditTo.setText(link.path()) @@ -80,19 +85,29 @@ class MessageView(QtGui.QTextBrowser): ) window.ui.textEditMessage.setFocus() return - reply = QtGui.QMessageBox.warning( - self, - QtGui.QApplication.translate( - "MessageView", - "Follow external link"), - QtGui.QApplication.translate( - "MessageView", - "The link \"%1\" will open in a browser. It may be a security risk, it could de-anonymise you" - " or download malicious data. Are you sure?").arg(unicode(link.toString())), - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) - if reply == QtGui.QMessageBox.Yes: - QtGui.QDesktopServices.openUrl(link) + if datablob_match: + name = QtGui.QFileDialog.getSaveFileName(self, 'Save File') + if name: + f = open(name, 'wb') + data_begin_pos = re.finditer(";base64,", link_str).next() + data_b64 = link_str[data_begin_pos.span()[1]:] + data = base64.b64decode(data_b64) + f.write(data) + f.close() + else: + reply = QtGui.QMessageBox.warning( + self, + QtGui.QApplication.translate( + "MessageView", + "Follow external link"), + QtGui.QApplication.translate( + "MessageView", + "The link \"%1\" will open in a browser. It may be a security risk, it could de-anonymise you" + " or download malicious data. Are you sure?").arg(unicode(link.toString())), + QtGui.QMessageBox.Yes, + QtGui.QMessageBox.No) + if reply == QtGui.QMessageBox.Yes: + QtGui.QDesktopServices.openUrl(link) def loadResource(self, restype, name): """ diff --git a/src/highlevelcrypto.py b/src/highlevelcrypto.py index 3d894ae8..f392fe4a 100644 --- a/src/highlevelcrypto.py +++ b/src/highlevelcrypto.py @@ -1,6 +1,10 @@ """ -src/highlevelcrypto.py -====================== +High level cryptographic functions based on `.pyelliptic` OpenSSL bindings. + +.. note:: + Upstream pyelliptic was upgraded from SHA1 to SHA256 for signing. + We must upgrade PyBitmessage gracefully. + `More discussion. `_ """ from binascii import hexlify @@ -12,12 +16,13 @@ from pyelliptic import arithmetic as a def makeCryptor(privkey): - """Return a private pyelliptic.ECC() instance""" + """Return a private `.pyelliptic.ECC` instance""" private_key = a.changebase(privkey, 16, 256, minlen=32) public_key = pointMult(private_key) privkey_bin = '\x02\xca\x00\x20' + private_key pubkey_bin = '\x02\xca\x00\x20' + public_key[1:-32] + '\x00\x20' + public_key[-32:] - cryptor = pyelliptic.ECC(curve='secp256k1', privkey=privkey_bin, pubkey=pubkey_bin) + cryptor = pyelliptic.ECC( + curve='secp256k1', privkey=privkey_bin, pubkey=pubkey_bin) return cryptor @@ -29,7 +34,7 @@ def hexToPubkey(pubkey): def makePubCryptor(pubkey): - """Return a public pyelliptic.ECC() instance""" + """Return a public `.pyelliptic.ECC` instance""" pubkey_bin = hexToPubkey(pubkey) return pyelliptic.ECC(curve='secp256k1', pubkey=pubkey_bin) @@ -43,7 +48,8 @@ def privToPub(privkey): def encrypt(msg, hexPubkey): """Encrypts message with hex public key""" - return pyelliptic.ECC(curve='secp256k1').encrypt(msg, hexToPubkey(hexPubkey)) + return pyelliptic.ECC(curve='secp256k1').encrypt( + msg, hexToPubkey(hexPubkey)) def decrypt(msg, hexPrivkey): @@ -52,36 +58,38 @@ def decrypt(msg, hexPrivkey): def decryptFast(msg, cryptor): - """Decrypts message with an existing pyelliptic.ECC.ECC object""" + """Decrypts message with an existing `.pyelliptic.ECC` object""" return cryptor.decrypt(msg) def sign(msg, hexPrivkey): - """Signs with hex private key""" - # pyelliptic is upgrading from SHA1 to SHA256 for signing. We must - # upgrade PyBitmessage gracefully. - # https://github.com/yann2192/pyelliptic/pull/33 - # More discussion: https://github.com/yann2192/pyelliptic/issues/32 - digestAlg = BMConfigParser().safeGet('bitmessagesettings', 'digestalg', 'sha1') + """ + Signs with hex private key using SHA1 or SHA256 depending on + "digestalg" setting + """ + digestAlg = BMConfigParser().safeGet( + 'bitmessagesettings', 'digestalg', 'sha1') if digestAlg == "sha1": # SHA1, this will eventually be deprecated - return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.digest_ecdsa_sha1) + return makeCryptor(hexPrivkey).sign( + msg, digest_alg=OpenSSL.digest_ecdsa_sha1) elif digestAlg == "sha256": # SHA256. Eventually this will become the default return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.EVP_sha256) else: - raise ValueError("Unknown digest algorithm %s" % (digestAlg)) + raise ValueError("Unknown digest algorithm %s" % digestAlg) def verify(msg, sig, hexPubkey): - """Verifies with hex public key""" + """Verifies with hex public key using SHA1 or SHA256""" # As mentioned above, we must upgrade gracefully to use SHA256. So # let us check the signature using both SHA1 and SHA256 and if one # of them passes then we will be satisfied. Eventually this can # be simplified and we'll only check with SHA256. try: # old SHA1 algorithm. - sigVerifyPassed = makePubCryptor(hexPubkey).verify(sig, msg, digest_alg=OpenSSL.digest_ecdsa_sha1) + sigVerifyPassed = makePubCryptor(hexPubkey).verify( + sig, msg, digest_alg=OpenSSL.digest_ecdsa_sha1) except: sigVerifyPassed = False if sigVerifyPassed: @@ -89,7 +97,8 @@ def verify(msg, sig, hexPubkey): return True # The signature check using SHA1 failed. Let us try it with SHA256. try: - return makePubCryptor(hexPubkey).verify(sig, msg, digest_alg=OpenSSL.EVP_sha256) + return makePubCryptor(hexPubkey).verify( + sig, msg, digest_alg=OpenSSL.EVP_sha256) except: return False @@ -106,7 +115,8 @@ def pointMult(secret): """ while True: try: - k = OpenSSL.EC_KEY_new_by_curve_name(OpenSSL.get_curve('secp256k1')) + k = OpenSSL.EC_KEY_new_by_curve_name( + OpenSSL.get_curve('secp256k1')) priv_key = OpenSSL.BN_bin2bn(secret, 32, None) group = OpenSSL.EC_KEY_get0_group(k) pub_key = OpenSSL.EC_POINT_new(group) diff --git a/src/protocol.py b/src/protocol.py index ec8fc9dd..cdd50dce 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -1,7 +1,8 @@ -# pylint: disable=too-many-boolean-expressions,too-many-return-statements,too-many-locals,too-many-statements """ Low-level protocol-related functions. """ +# pylint: disable=too-many-boolean-expressions,too-many-return-statements +# pylint: disable=too-many-locals,too-many-statements import base64 import hashlib @@ -9,7 +10,6 @@ import random import socket import sys import time -import traceback from binascii import hexlify from struct import pack, unpack, Struct @@ -24,10 +24,18 @@ from fallback import RIPEMD160Hash from helper_sql import sqlExecute from version import softwareVersion - # Service flags +#: This is a normal network node NODE_NETWORK = 1 +#: This node supports SSL/TLS in the current connect (python < 2.7.9 +#: only supports an SSL client, so in that case it would only have this +#: on when the connection is a client). NODE_SSL = 2 +# (Proposal) This node may do PoW on behalf of some its peers +# (PoW offloading/delegating), but it doesn't have to. Clients may have +# to meet additional requirements (e.g. TLS authentication) +# NODE_POW = 4 +#: Node supports dandelion NODE_DANDELION = 8 # Bitfield flags @@ -89,7 +97,8 @@ def isBitSetWithinBitfield(fourByteString, n): def encodeHost(host): """Encode a given host to be used in low-level socket operations""" if host.find('.onion') > -1: - return '\xfd\x87\xd8\x7e\xeb\x43' + base64.b32decode(host.split(".")[0], True) + return '\xfd\x87\xd8\x7e\xeb\x43' + base64.b32decode( + host.split(".")[0], True) elif host.find(':') == -1: return '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \ socket.inet_aton(host) @@ -134,7 +143,10 @@ def network_group(host): def checkIPAddress(host, private=False): - """Returns hostStandardFormat if it is a valid IP address, otherwise returns False""" + """ + Returns hostStandardFormat if it is a valid IP address, + otherwise returns False + """ if host[0:12] == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF': hostStandardFormat = socket.inet_ntop(socket.AF_INET, host[12:]) return checkIPv4Address(host[12:], hostStandardFormat, private) @@ -150,35 +162,46 @@ def checkIPAddress(host, private=False): except ValueError: return False if hostStandardFormat == "": - # This can happen on Windows systems which are not 64-bit compatible - # so let us drop the IPv6 address. + # This can happen on Windows systems which are + # not 64-bit compatible so let us drop the IPv6 address. return False return checkIPv6Address(host, hostStandardFormat, private) def checkIPv4Address(host, hostStandardFormat, private=False): - """Returns hostStandardFormat if it is an IPv4 address, otherwise returns False""" + """ + Returns hostStandardFormat if it is an IPv4 address, + otherwise returns False + """ if host[0] == '\x7F': # 127/8 if not private: - logger.debug('Ignoring IP address in loopback range: %s', hostStandardFormat) + logger.debug( + 'Ignoring IP address in loopback range: %s', + hostStandardFormat) return hostStandardFormat if private else False if host[0] == '\x0A': # 10/8 if not private: - logger.debug('Ignoring IP address in private range: %s', hostStandardFormat) + logger.debug( + 'Ignoring IP address in private range: %s', hostStandardFormat) return hostStandardFormat if private else False if host[0:2] == '\xC0\xA8': # 192.168/16 if not private: - logger.debug('Ignoring IP address in private range: %s', hostStandardFormat) + logger.debug( + 'Ignoring IP address in private range: %s', hostStandardFormat) return hostStandardFormat if private else False if host[0:2] >= '\xAC\x10' and host[0:2] < '\xAC\x20': # 172.16/12 if not private: - logger.debug('Ignoring IP address in private range: %s', hostStandardFormat) + logger.debug( + 'Ignoring IP address in private range: %s', hostStandardFormat) return hostStandardFormat if private else False return False if private else hostStandardFormat def checkIPv6Address(host, hostStandardFormat, private=False): - """Returns hostStandardFormat if it is an IPv6 address, otherwise returns False""" + """ + Returns hostStandardFormat if it is an IPv6 address, + otherwise returns False + """ if host == ('\x00' * 15) + '\x01': if not private: logger.debug('Ignoring loopback address: %s', hostStandardFormat) @@ -189,7 +212,8 @@ def checkIPv6Address(host, hostStandardFormat, private=False): return hostStandardFormat if private else False if (ord(host[0]) & 0xfe) == 0xfc: if not private: - logger.debug('Ignoring unique local address: %s', hostStandardFormat) + logger.debug( + 'Ignoring unique local address: %s', hostStandardFormat) return hostStandardFormat if private else False return False if private else hostStandardFormat @@ -210,31 +234,29 @@ def haveSSL(server=False): def checkSocksIP(host): """Predicate to check if we're using a SOCKS proxy""" + sockshostname = BMConfigParser().safeGet( + 'bitmessagesettings', 'sockshostname') try: - if state.socksIP is None or not state.socksIP: - state.socksIP = socket.gethostbyname(BMConfigParser().get("bitmessagesettings", "sockshostname")) - # uninitialised - except NameError: - state.socksIP = socket.gethostbyname(BMConfigParser().get("bitmessagesettings", "sockshostname")) - # resolving failure - except socket.gaierror: - state.socksIP = BMConfigParser().get("bitmessagesettings", "sockshostname") + if not state.socksIP: + state.socksIP = socket.gethostbyname(sockshostname) + except NameError: # uninitialised + state.socksIP = socket.gethostbyname(sockshostname) + except (TypeError, socket.gaierror): # None, resolving failure + state.socksIP = sockshostname return state.socksIP == host -def isProofOfWorkSufficient(data, - nonceTrialsPerByte=0, - payloadLengthExtraBytes=0, - recvTime=0): +def isProofOfWorkSufficient( + data, nonceTrialsPerByte=0, payloadLengthExtraBytes=0, recvTime=0): """ - Validate an object's Proof of Work using method described in: - https://bitmessage.org/wiki/Proof_of_work + Validate an object's Proof of Work using method described + `here `_ Arguments: - int nonceTrialsPerByte (default: from default.py) - int payloadLengthExtraBytes (default: from default.py) + int nonceTrialsPerByte (default: from `.defaults`) + int payloadLengthExtraBytes (default: from `.defaults`) float recvTime (optional) UNIX epoch time when object was - received from the network (default: current system time) + received from the network (default: current system time) Returns: True if PoW valid and sufficient, False in all other cases """ @@ -246,18 +268,20 @@ def isProofOfWorkSufficient(data, TTL = endOfLifeTime - (int(recvTime) if recvTime else int(time.time())) if TTL < 300: TTL = 300 - POW, = unpack('>Q', hashlib.sha512(hashlib.sha512(data[ - :8] + hashlib.sha512(data[8:]).digest()).digest()).digest()[0:8]) - return POW <= 2 ** 64 / (nonceTrialsPerByte * - (len(data) + payloadLengthExtraBytes + - ((TTL * (len(data) + payloadLengthExtraBytes)) / (2 ** 16)))) + POW, = unpack('>Q', hashlib.sha512(hashlib.sha512( + data[:8] + hashlib.sha512(data[8:]).digest() + ).digest()).digest()[0:8]) + return POW <= 2 ** 64 / ( + nonceTrialsPerByte * ( + len(data) + payloadLengthExtraBytes + + ((TTL * (len(data) + payloadLengthExtraBytes)) / (2 ** 16)))) # Packet creation def CreatePacket(command, payload=''): - """Construct and return a number of bytes from a payload""" + """Construct and return a packet""" payload_length = len(payload) checksum = hashlib.sha512(payload).digest()[0:4] @@ -267,8 +291,13 @@ def CreatePacket(command, payload=''): return bytes(b) -def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=False, nodeid=None): - """Construct the payload of a version message, return the resultng bytes of running CreatePacket() on it""" +def assembleVersionMessage( + remoteHost, remotePort, participatingStreams, server=False, nodeid=None +): + """ + Construct the payload of a version message, + return the resulting bytes of running `CreatePacket` on it + """ payload = '' payload += pack('>L', 3) # protocol version. # bitflags of the services I offer. @@ -280,9 +309,10 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server= ) payload += pack('>q', int(time.time())) - payload += pack( - '>q', 1) # boolservices of remote connection; ignored by the remote host. - if checkSocksIP(remoteHost) and server: # prevent leaking of tor outbound IP + # boolservices of remote connection; ignored by the remote host. + payload += pack('>q', 1) + if checkSocksIP(remoteHost) and server: + # prevent leaking of tor outbound IP payload += encodeHost('127.0.0.1') payload += pack('>H', 8444) else: @@ -301,21 +331,25 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server= (NODE_SSL if haveSSL(server) else 0) | (NODE_DANDELION if state.dandelion else 0) ) - # = 127.0.0.1. This will be ignored by the remote host. The actual remote connected IP will be used. - payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + pack('>L', 2130706433) + # = 127.0.0.1. This will be ignored by the remote host. + # The actual remote connected IP will be used. + payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + pack( + '>L', 2130706433) # we have a separate extPort and incoming over clearnet # or outgoing through clearnet extport = BMConfigParser().safeGetInt('bitmessagesettings', 'extport') if ( extport and ((server and not checkSocksIP(remoteHost)) or ( - BMConfigParser().get('bitmessagesettings', 'socksproxytype') == - 'none' and not server)) + BMConfigParser().get('bitmessagesettings', 'socksproxytype') + == 'none' and not server)) ): payload += pack('>H', extport) elif checkSocksIP(remoteHost) and server: # incoming connection over Tor - payload += pack('>H', BMConfigParser().getint('bitmessagesettings', 'onionport')) + payload += pack( + '>H', BMConfigParser().getint('bitmessagesettings', 'onionport')) else: # no extport and not incoming over Tor - payload += pack('>H', BMConfigParser().getint('bitmessagesettings', 'port')) + payload += pack( + '>H', BMConfigParser().getint('bitmessagesettings', 'port')) if nodeid is not None: payload += nodeid[0:8] @@ -339,7 +373,10 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server= def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''): - """Construct the payload of an error message, return the resultng bytes of running CreatePacket() on it""" + """ + Construct the payload of an error message, + return the resulting bytes of running `CreatePacket` on it + """ payload = encodeVarint(fatal) payload += encodeVarint(banTime) payload += encodeVarint(len(inventoryVector)) @@ -476,7 +513,7 @@ def decryptAndCheckPubkeyPayload(data, address): except Exception: logger.critical( 'Pubkey decryption was UNsuccessful because of' - ' an unhandled exception! This is definitely a bug! \n%s', - traceback.format_exc() + ' an unhandled exception! This is definitely a bug!', + exc_info=True ) return 'failed' diff --git a/src/shared.py b/src/shared.py index 1a2add28..90cea89d 100644 --- a/src/shared.py +++ b/src/shared.py @@ -80,7 +80,9 @@ def isAddressInMySubscriptionsList(address): def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address): - """Am I subscribed to this address, is it in my addressbook or whitelist?""" + """ + Am I subscribed to this address, is it in my addressbook or whitelist? + """ if isAddressInMyAddressBook(address): return True @@ -100,8 +102,12 @@ def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address): return False -def decodeWalletImportFormat(WIFstring): # pylint: disable=inconsistent-return-statements - """Convert private key from base58 that's used in the config file to 8-bit binary string""" +def decodeWalletImportFormat(WIFstring): + # pylint: disable=inconsistent-return-statements + """ + Convert private key from base58 that's used in the config file to + 8-bit binary string + """ fullString = arithmetic.changebase(WIFstring, 58, 256) privkey = fullString[:-4] if fullString[-4:] != \ @@ -126,14 +132,15 @@ def decodeWalletImportFormat(WIFstring): # pylint: disable=inconsistent-retur def reloadMyAddressHashes(): - """Reinitialise runtime data (e.g. encryption objects, address hashes) from the config file""" + """Reload keys for user's addresses from the config file""" logger.debug('reloading keys from keys.dat file') myECCryptorObjects.clear() myAddressesByHash.clear() myAddressesByTag.clear() # myPrivateKeys.clear() - keyfileSecure = checkSensitiveFilePermissions(state.appdata + 'keys.dat') + keyfileSecure = checkSensitiveFilePermissions(os.path.join( + state.appdata, 'keys.dat')) hasEnabledKeys = False for addressInKeysFile in BMConfigParser().addresses(): isEnabled = BMConfigParser().getboolean(addressInKeysFile, 'enabled') @@ -162,11 +169,15 @@ def reloadMyAddressHashes(): ) if not keyfileSecure: - fixSensitiveFilePermissions(state.appdata + 'keys.dat', hasEnabledKeys) + fixSensitiveFilePermissions(os.path.join( + state.appdata, 'keys.dat'), hasEnabledKeys) def reloadBroadcastSendersForWhichImWatching(): - """Reinitialise runtime data for the broadcasts I'm subscribed to from the config file""" + """ + Reinitialize runtime data for the broadcasts I'm subscribed to + from the config file + """ broadcastSendersForWhichImWatching.clear() MyECSubscriptionCryptorObjects.clear() queryreturn = sqlQuery('SELECT address FROM subscriptions where enabled=1') diff --git a/src/shutdown.py b/src/shutdown.py index c81a519a..dbc2af04 100644 --- a/src/shutdown.py +++ b/src/shutdown.py @@ -16,7 +16,9 @@ from queues import ( def doCleanShutdown(): - """Used to tell proof of work worker threads and the objectProcessorThread to exit.""" + """ + Used to tell all the treads to finish work and exit. + """ state.shutdown = 1 objectProcessorQueue.put(('checkShutdownVariable', 'no data')) @@ -52,9 +54,11 @@ def doCleanShutdown(): time.sleep(.25) for thread in threading.enumerate(): - if (thread is not threading.currentThread() and - isinstance(thread, StoppableThread) and - thread.name != 'SQL'): + if ( + thread is not threading.currentThread() + and isinstance(thread, StoppableThread) + and thread.name != 'SQL' + ): logger.debug("Waiting for thread %s", thread.name) thread.join() diff --git a/src/state.py b/src/state.py index f5526029..58e1106a 100644 --- a/src/state.py +++ b/src/state.py @@ -16,8 +16,8 @@ appdata = '' shutdown = 0 """ - Set to 1 by the doCleanShutdown function. - Used to tell the proof of work worker threads to exit. +Set to 1 by the `.shutdown.doCleanShutdown` function. +Used to tell the threads to exit. """ # Component control flags - set on startup, do not change during runtime @@ -25,7 +25,7 @@ shutdown = 0 enableNetwork = True """enable network threads""" enableObjProc = True -"""enable object processing threads""" +"""enable object processing thread""" enableAPI = True """enable API (if configured)""" enableGUI = True @@ -35,7 +35,7 @@ enableSTDIO = False curses = False sqlReady = False -"""set to true by sqlTread when ready for processing""" +"""set to true by `.threads.sqlThread` when ready for processing""" maximumNumberOfHalfOpenConnections = 0