diff --git a/.travis.yml b/.travis.yml index 5dbda892..8ff13988 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,3 +11,4 @@ install: script: - python checkdeps.py - pybitmessage -t + - python setup.py test diff --git a/checkdeps.py b/checkdeps.py index 28b1e559..96b07a8d 100755 --- a/checkdeps.py +++ b/checkdeps.py @@ -88,25 +88,7 @@ def testCompiler(): # silent, we can't test without setuptools return True - bitmsghash = Extension( - 'bitmsghash', - sources=['src/bitmsghash/bitmsghash.cpp'], - libraries=['pthread', 'crypto'], - ) - - dist = Distribution() - dist.ext_modules = [bitmsghash] - cmd = build_ext(dist) - cmd.initialize_options() - cmd.finalize_options() - cmd.force = True - try: - cmd.run() - except CompileError: - return False - else: - fullPath = os.path.join(cmd.build_lib, cmd.get_ext_filename("bitmsghash")) - return os.path.isfile(fullPath) + return True prereqs = detectPrereqs() diff --git a/packages/pyinstaller/bitmessagemain.spec b/packages/pyinstaller/bitmessagemain.spec index 06cf6e76..e635c90f 100644 --- a/packages/pyinstaller/bitmessagemain.spec +++ b/packages/pyinstaller/bitmessagemain.spec @@ -52,8 +52,8 @@ else: arch=64 a.binaries += [('libeay32.dll', openSSLPath + 'libeay32.dll', 'BINARY'), - (os.path.join('bitmsghash', 'bitmsghash%i.dll' % (arch)), os.path.join(srcPath, 'bitmsghash', 'bitmsghash%i.dll' % (arch)), 'BINARY'), - (os.path.join('bitmsghash', 'bitmsghash.cl'), os.path.join(srcPath, 'bitmsghash', 'bitmsghash.cl'), 'BINARY'), + ("workprover/fastsolver/libfastsolver-{}.dll".format(arch), os.path.join(srcPath, "workprover/fastsolver/libfastsolver-{}.dll".format(arch)), "BINARY"), + ("workprover/gpusolver.cl", os.path.join(srcPath, "workprover/gpusolver.cl"), "BINARY"), (os.path.join('sslkeys', 'cert.pem'), os.path.join(srcPath, 'sslkeys', 'cert.pem'), 'BINARY'), (os.path.join('sslkeys', 'key.pem'), os.path.join(srcPath, 'sslkeys', 'key.pem'), 'BINARY') ] diff --git a/setup.py b/setup.py index 1081b5aa..80104342 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ from src.version import softwareVersion EXTRAS_REQUIRE = { 'gir': ['pygobject'], 'notify2': ['notify2'], - 'pyopencl': ['pyopencl'], + 'pyopencl': ['pyopencl', 'numpy'], 'prctl': ['python_prctl'], # Named threads 'qrcode': ['qrcode'], 'sound;platform_system=="Windows"': ['winsound'], @@ -50,12 +50,6 @@ if __name__ == "__main__": with open(os.path.join(here, 'README.md')) as f: README = f.read() - bitmsghash = Extension( - 'pybitmessage.bitmsghash.bitmsghash', - sources=['src/bitmsghash/bitmsghash.cpp'], - libraries=['pthread', 'crypto'], - ) - installRequires = [] packages = [ 'pybitmessage', @@ -66,7 +60,8 @@ if __name__ == "__main__": 'pybitmessage.pyelliptic', 'pybitmessage.socks', 'pybitmessage.storage', - 'pybitmessage.plugins' + 'pybitmessage.plugins', + 'pybitmessage.workprover' ] # this will silently accept alternative providers of msgpack @@ -108,8 +103,9 @@ if __name__ == "__main__": package_dir={'pybitmessage': 'src'}, packages=packages, package_data={'': [ - 'bitmessageqt/*.ui', 'bitmsghash/*.cl', 'sslkeys/*.pem', - 'translations/*.ts', 'translations/*.qm', + 'bitmessageqt/*.ui', 'translations/*.ts', 'translations/*.qm', + 'workprover/*.cl', 'workprover/fastsolver/*', + 'sslkeys/*.pem', 'images/*.png', 'images/*.ico', 'images/*.icns' ]}, data_files=[ @@ -120,7 +116,6 @@ if __name__ == "__main__": ('share/icons/hicolor/24x24/apps/', ['desktop/icons/24x24/pybitmessage.png']) ], - ext_modules=[bitmsghash], zip_safe=False, entry_points={ 'bitmessage.gui.menu': [ diff --git a/src/api.py b/src/api.py index a32519e4..cf60a6c8 100644 --- a/src/api.py +++ b/src/api.py @@ -10,10 +10,15 @@ This is not what you run to run the Bitmessage API. Instead, enable the API import base64 import hashlib import json +import socket +import threading import time from binascii import hexlify, unhexlify +from random import randint from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer from struct import pack +import errno +import Queue import shared from addresses import ( @@ -23,11 +28,14 @@ from bmconfigparser import BMConfigParser import defaults import helper_inbox import helper_sent +import helper_threading +import helper_random import state import queues import shutdown import network.stats +import protocol # Classes from helper_sql import sqlQuery, sqlExecute, SqlBulkExecute, sqlStoredProcedure @@ -36,11 +44,9 @@ from debug import logger from inventory import Inventory from version import softwareVersion -# Helper Functions -import proofofwork - str_chan = '[chan]' +queuedRawObjects = {} class APIError(Exception): def __init__(self, error_number, error_message): @@ -1031,45 +1037,6 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): }) return json.dumps(data, indent=4, separators=(',', ': ')) - def HandleDisseminatePreEncryptedMsg(self, params): - # The device issuing this command to PyBitmessage supplies a msg - # object that has already been encrypted but which still needs the POW - # to be done. PyBitmessage accepts this msg object and sends it out - # to the rest of the Bitmessage network as if it had generated - # the message itself. Please do not yet add this to the api doc. - if len(params) != 3: - raise APIError(0, 'I need 3 parameter!') - encryptedPayload, requiredAverageProofOfWorkNonceTrialsPerByte, \ - requiredPayloadLengthExtraBytes = params - encryptedPayload = self._decode(encryptedPayload, "hex") - # Let us do the POW and attach it to the front - target = 2**64 / ( - (len(encryptedPayload) + requiredPayloadLengthExtraBytes + 8) - * requiredAverageProofOfWorkNonceTrialsPerByte) - with shared.printLock: - print '(For msg message via API) Doing proof of work. Total required difficulty:', float(requiredAverageProofOfWorkNonceTrialsPerByte) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte, 'Required small message difficulty:', float(requiredPayloadLengthExtraBytes) / defaults.networkDefaultPayloadLengthExtraBytes - powStartTime = time.time() - initialHash = hashlib.sha512(encryptedPayload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) - with shared.printLock: - print '(For msg message via API) Found proof of work', trialValue, 'Nonce:', nonce - try: - print 'POW took', int(time.time() - powStartTime), 'seconds.', nonce / (time.time() - powStartTime), 'nonce trials per second.' - except: - pass - encryptedPayload = pack('>Q', nonce) + encryptedPayload - toStreamNumber = decodeVarint(encryptedPayload[16:26])[0] - inventoryHash = calculateInventoryHash(encryptedPayload) - objectType = 2 - TTL = 2.5 * 24 * 60 * 60 - Inventory()[inventoryHash] = ( - objectType, toStreamNumber, encryptedPayload, - int(time.time()) + TTL, '' - ) - with shared.printLock: - print 'Broadcasting inv for msg(API disseminatePreEncryptedMsg command):', hexlify(inventoryHash) - queues.invQueue.put((toStreamNumber, inventoryHash)) - def HandleTrashSentMessageByAckDAta(self, params): # This API method should only be used when msgid is not available if len(params) == 0: @@ -1078,88 +1045,132 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): sqlExecute("UPDATE sent SET folder='trash' WHERE ackdata=?", ackdata) return 'Trashed sent message (assuming message existed).' - def HandleDissimatePubKey(self, params): - # The device issuing this command to PyBitmessage supplies a pubkey - # object to be disseminated to the rest of the Bitmessage network. - # PyBitmessage accepts this pubkey object and sends it out to the rest - # of the Bitmessage network as if it had generated the pubkey object - # itself. Please do not yet add this to the api doc. - if len(params) != 1: - raise APIError(0, 'I need 1 parameter!') - payload, = params - payload = self._decode(payload, "hex") + def HandleDisseminateRawObject(self, arguments): + if len(arguments) != 1: + raise APIError(0, "1 argument needed") - # Let us do the POW - target = 2 ** 64 / ( - (len(payload) + defaults.networkDefaultPayloadLengthExtraBytes - + 8) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte) - print '(For pubkey message via API) Doing proof of work...' - initialHash = hashlib.sha512(payload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) - print '(For pubkey message via API) Found proof of work', trialValue, 'Nonce:', nonce - payload = pack('>Q', nonce) + payload + payload = self._decode(arguments[0], "hex") - pubkeyReadPosition = 8 # bypass the nonce - if payload[pubkeyReadPosition:pubkeyReadPosition+4] == \ - '\x00\x00\x00\x00': # if this pubkey uses 8 byte time - pubkeyReadPosition += 8 + inventoryHash = protocol.checkAndShareObjectWithPeers(payload) + + if inventoryHash is None: + raise APIError(30, "Invalid object or insufficient POW") else: - pubkeyReadPosition += 4 - addressVersion, addressVersionLength = decodeVarint( - payload[pubkeyReadPosition:pubkeyReadPosition+10]) - pubkeyReadPosition += addressVersionLength - pubkeyStreamNumber = decodeVarint( - payload[pubkeyReadPosition:pubkeyReadPosition+10])[0] - inventoryHash = calculateInventoryHash(payload) - objectType = 1 # TODO: support v4 pubkeys - TTL = 28 * 24 * 60 * 60 - Inventory()[inventoryHash] = ( - objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL, '' - ) - with shared.printLock: - print 'broadcasting inv within API command disseminatePubkey with hash:', hexlify(inventoryHash) - queues.invQueue.put((pubkeyStreamNumber, inventoryHash)) + return hexlify(inventoryHash) - def HandleGetMessageDataByDestinationHash(self, params): - # Method will eventually be used by a particular Android app to - # select relevant messages. Do not yet add this to the api - # doc. - if len(params) != 1: - raise APIError(0, 'I need 1 parameter!') - requestedHash, = params - if len(requestedHash) != 32: - raise APIError( - 19, 'The length of hash should be 32 bytes (encoded in hex' - ' thus 64 characters).') - requestedHash = self._decode(requestedHash, "hex") + def HandleGetRawObject(self, arguments): + if len(arguments) != 1: + raise APIError(0, "1 argument needed") - # This is not a particularly commonly used API function. Before we - # use it we'll need to fill out a field in our inventory database - # which is blank by default (first20bytesofencryptedmessage). - queryreturn = sqlQuery( - "SELECT hash, payload FROM inventory WHERE tag = ''" - " and objecttype = 2") - with SqlBulkExecute() as sql: - for row in queryreturn: - hash01, payload = row - readPosition = 16 # Nonce length + time length - # Stream Number length - readPosition += decodeVarint( - payload[readPosition:readPosition+10])[1] - t = (payload[readPosition:readPosition+32], hash01) - sql.execute("UPDATE inventory SET tag=? WHERE hash=?", *t) + inventoryHash, = arguments - queryreturn = sqlQuery( - "SELECT payload FROM inventory WHERE tag = ?", requestedHash) - data = '{"receivedMessageDatas":[' - for row in queryreturn: - payload, = row - if len(data) > 25: - data += ',' - data += json.dumps( - {'data': hexlify(payload)}, indent=4, separators=(',', ': ')) - data += ']}' - return data + if len(inventoryHash) != 64: + raise APIError(19, "The length of hash should be 32 bytes (encoded in hex thus 64 characters)") + + inventoryHash = self._decode(inventoryHash, "hex") + + try: + inventoryItem = Inventory()[inventoryHash] + except KeyError: + raise APIError(31, "Object not found") + + return json.dumps({ + "hash": hexlify(inventoryHash), + "expiryTime": inventoryItem.expires, + "objectType": inventoryItem.type, + "stream": inventoryItem.stream, + "tag": hexlify(inventoryItem.tag), + "payload": hexlify(inventoryItem.payload) + }, indent = 4, separators = (",", ": ")) + + def HandleListRawObjects(self, arguments): + if len(arguments) != 3: + raise APIError(0, "3 arguments needed") + + objectType, stream, tag = arguments + + if tag is not None: + tag = buffer(self._decode(tag, "hex")) + + result = [] + + inventory = Inventory() + + for i in inventory: + inventoryItem = inventory[str(i)] + + if objectType is not None and inventoryItem.type != objectType: + continue + + if stream is not None and inventoryItem.stream != stream: + continue + + if tag is not None and inventoryItem.tag != tag: + continue + + result.append(hexlify(i)) + + return json.dumps(result, indent = 4, separators = (",", ": ")) + + def HandleQueueRawObject(self, arguments): + if len(arguments) != 2: + raise APIError(0, "2 arguments needed") + + TTL, headlessPayload = arguments + headlessPayload = self._decode(headlessPayload, "hex") + + if type(TTL) is not int or TTL < 300 or TTL > 28 * 24 * 60 * 60: + raise APIError(33, "TTL must be an integer between 300 and 28 * 24 * 60 * 60 seconds") + + ID = helper_random.randomBytes(32) + + queues.workerQueue.put(("sendRawObject", ID, TTL, headlessPayload)) + queuedRawObjects[ID] = "queued", + + return hexlify(ID) + + def HandleCancelQueuedRawObject(self, arguments): + if len(arguments) != 1: + raise APIError(0, "1 argument needed") + + ID, = arguments + + if len(ID) != 64: + raise APIError(19, "The length of queue item ID should be 32 bytes (encoded in hex thus 64 characters)") + + ID = self._decode(ID, 'hex') + + queues.workerQueue.put(("cancelRawObject", ID)) + + def HandleCheckQueuedRawObject(self, arguments): + if len(arguments) != 1: + raise APIError(0, "1 argument needed") + + ID, = arguments + + if len(ID) != 64: + raise APIError(19, "The length of queue item ID should be 32 bytes (encoded in hex thus 64 characters)") + + ID = self._decode(ID, 'hex') + + while True: + try: + queueItem = queues.processedRawObjectsQueue.get_nowait() + command, randomID, arguments = queueItem[0], queueItem[1], queueItem[2: ] + + if command == "sent": + queuedRawObjects[randomID] = command, hexlify(arguments[0]) + else: + queuedRawObjects[randomID] = (command, ) + arguments + except Queue.Empty: + break + + status = queuedRawObjects.get(ID, ("notfound", )) + + if status[0] in ["failed", "sent", "canceled"]: + del queuedRawObjects[ID] + + return status def HandleClientStatus(self, params): if len(network.stats.connectedHostsList()) == 0: @@ -1262,12 +1273,12 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): handlers['addSubscription'] = HandleAddSubscription handlers['deleteSubscription'] = HandleDeleteSubscription handlers['listSubscriptions'] = ListSubscriptions - handlers['disseminatePreEncryptedMsg'] = HandleDisseminatePreEncryptedMsg - handlers['disseminatePubkey'] = HandleDissimatePubKey - handlers['getMessageDataByDestinationHash'] = \ - HandleGetMessageDataByDestinationHash - handlers['getMessageDataByDestinationTag'] = \ - HandleGetMessageDataByDestinationHash + handlers["disseminateRawObject"] = HandleDisseminateRawObject + handlers["getRawObject"] = HandleGetRawObject + handlers["listRawObjects"] = HandleListRawObjects + handlers["queueRawObject"] = HandleQueueRawObject + handlers["cancelQueuedRawObject"] = HandleCancelQueuedRawObject + handlers["checkQueuedRawObject"] = HandleCheckQueuedRawObject handlers['clientStatus'] = HandleClientStatus handlers['decodeAddress'] = HandleDecodeAddress handlers['deleteAndVacuum'] = HandleDeleteAndVacuum @@ -1297,3 +1308,49 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): logger.exception(e) return "API Error 0021: Unexpected API Failure - %s" % e + +# This thread, of which there is only one, runs the API. +class singleAPI(threading.Thread, helper_threading.StoppableThread): + def __init__(self): + threading.Thread.__init__(self, name="singleAPI") + self.initStop() + + def stopThread(self): + super(singleAPI, self).stopThread() + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.connect(( + BMConfigParser().get('bitmessagesettings', 'apiinterface'), + BMConfigParser().getint('bitmessagesettings', 'apiport') + )) + s.shutdown(socket.SHUT_RDWR) + s.close() + except: + pass + + def run(self): + port = BMConfigParser().getint('bitmessagesettings', 'apiport') + try: + from errno import WSAEADDRINUSE + except (ImportError, AttributeError): + errno.WSAEADDRINUSE = errno.EADDRINUSE + for attempt in range(50): + try: + if attempt > 0: + port = randint(32767, 65535) + se = StoppableXMLRPCServer( + (BMConfigParser().get( + 'bitmessagesettings', 'apiinterface'), + port), + MySimpleXMLRPCRequestHandler, True, True) + except socket.error as e: + if e.errno in (errno.EADDRINUSE, errno.WSAEADDRINUSE): + continue + else: + if attempt > 0: + BMConfigParser().set( + "bitmessagesettings", "apiport", str(port)) + BMConfigParser().save() + break + se.register_introspection_functions() + se.serve_forever() diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index fe9a77d2..df654391 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -918,7 +918,7 @@ def loadSent(): # Set status string if status == "awaitingpubkey": statstr = "Waiting for their public key. Will request it again soon" - elif status == "doingpowforpubkey": + elif status == "doingpubkeypow": statstr = "Encryption key request queued" elif status == "msgqueued": statstr = "Message queued" diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 0848b0e4..86611bf7 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -9,66 +9,6 @@ # The software version variable is now held in shared.py -import os -import sys - -app_dir = os.path.dirname(os.path.abspath(__file__)) -os.chdir(app_dir) -sys.path.insert(0, app_dir) - - -import depends -depends.check_dependencies() - -# Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully. -import signal -# The next 3 are used for the API -from singleinstance import singleinstance -import errno -import socket -import ctypes -from struct import pack -from subprocess import call -from time import sleep -from random import randint -import getopt - -from api import MySimpleXMLRPCRequestHandler, StoppableXMLRPCServer -from helper_startup import ( - isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections -) - -import defaults -import shared -import knownnodes -import state -import shutdown -import threading - -# Classes -from class_sqlThread import sqlThread -from class_singleCleaner import singleCleaner -from class_objectProcessor import objectProcessor -from class_singleWorker import singleWorker -from class_addressGenerator import addressGenerator -from bmconfigparser import BMConfigParser - -from inventory import Inventory - -from network.connectionpool import BMConnectionPool -from network.dandelion import Dandelion -from network.networkthread import BMNetworkThread -from network.receivequeuethread import ReceiveQueueThread -from network.announcethread import AnnounceThread -from network.invthread import InvThread -from network.addrthread import AddrThread -from network.downloadthread import DownloadThread - -# Helper Functions -import helper_bootstrap -import helper_generic -import helper_threading - def connectToStream(streamNumber): state.streamsInWhichIAmParticipating.append(streamNumber) selfInitiatedConnections[streamNumber] = {} @@ -155,63 +95,6 @@ def _fixSocket(): socket.IPV6_V6ONLY = 27 -# This thread, of which there is only one, runs the API. -class singleAPI(threading.Thread, helper_threading.StoppableThread): - def __init__(self): - threading.Thread.__init__(self, name="singleAPI") - self.initStop() - - def stopThread(self): - super(singleAPI, self).stopThread() - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - s.connect(( - BMConfigParser().get('bitmessagesettings', 'apiinterface'), - BMConfigParser().getint('bitmessagesettings', 'apiport') - )) - s.shutdown(socket.SHUT_RDWR) - s.close() - except: - pass - - def run(self): - port = BMConfigParser().getint('bitmessagesettings', 'apiport') - try: - from errno import WSAEADDRINUSE - except (ImportError, AttributeError): - errno.WSAEADDRINUSE = errno.EADDRINUSE - for attempt in range(50): - try: - if attempt > 0: - port = randint(32767, 65535) - se = StoppableXMLRPCServer( - (BMConfigParser().get( - 'bitmessagesettings', 'apiinterface'), - port), - MySimpleXMLRPCRequestHandler, True, True) - except socket.error as e: - if e.errno in (errno.EADDRINUSE, errno.WSAEADDRINUSE): - continue - else: - if attempt > 0: - BMConfigParser().set( - "bitmessagesettings", "apiport", str(port)) - BMConfigParser().save() - break - se.register_introspection_functions() - se.serve_forever() - - -# This is a list of current connections (the thread pointers at least) -selfInitiatedConnections = {} - -if shared.useVeryEasyProofOfWorkForTesting: - defaults.networkDefaultProofOfWorkNonceTrialsPerByte = int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte / 100) - defaults.networkDefaultPayloadLengthExtraBytes = int( - defaults.networkDefaultPayloadLengthExtraBytes / 100) - - class Main: def start(self): _fixSocket() @@ -318,7 +201,6 @@ class Main: smtpServerThread = smtpServer() smtpServerThread.start() - # Start the thread that calculates POWs objectProcessorThread = objectProcessor() # DON'T close the main program even the thread remains. # This thread checks the shutdown variable after processing @@ -502,10 +384,84 @@ def main(): mainprogram = Main() mainprogram.start() +# See "workprover/Readme.md" + +import os +import sys + +app_dir = os.path.dirname(os.path.abspath(__file__)) +os.chdir(app_dir) +sys.path.insert(0, app_dir) + +import pyelliptic.openssl +import workprover.dumbsolver + +workprover.dumbsolver.libcrypto = pyelliptic.openssl.OpenSSL._lib if __name__ == "__main__": - main() + import multiprocessing + multiprocessing.freeze_support() + + import depends + depends.check_dependencies() + + # Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully. + import signal + # The next 3 are used for the API + from singleinstance import singleinstance + import socket + import ctypes + from struct import pack + from subprocess import call + from time import sleep + import getopt + + from api import singleAPI + from helper_startup import ( + isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections + ) + + import defaults + import shared + import knownnodes + import state + import shutdown + + # Classes + from class_sqlThread import sqlThread + from class_singleCleaner import singleCleaner + from class_objectProcessor import objectProcessor + from singleworker import singleWorker + from class_addressGenerator import addressGenerator + from bmconfigparser import BMConfigParser + + from inventory import Inventory + + from network.connectionpool import BMConnectionPool + from network.dandelion import Dandelion + from network.networkthread import BMNetworkThread + from network.receivequeuethread import ReceiveQueueThread + from network.announcethread import AnnounceThread + from network.invthread import InvThread + from network.addrthread import AddrThread + from network.downloadthread import DownloadThread + + # Helper Functions + import helper_bootstrap + import helper_generic + import helper_threading + + # This is a list of current connections (the thread pointers at least) + selfInitiatedConnections = {} + + if shared.useVeryEasyProofOfWorkForTesting: + defaults.networkDefaultProofOfWorkNonceTrialsPerByte = int( + defaults.networkDefaultProofOfWorkNonceTrialsPerByte / 100) + defaults.networkDefaultPayloadLengthExtraBytes = int( + defaults.networkDefaultPayloadLengthExtraBytes / 100) + + main() # So far, the creation of and management of the Bitmessage protocol and this # client is a one-man operation. Bitcoin tips are quite appreciated. diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index aab1dd72..66e9a842 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -20,17 +20,15 @@ from debug import logger from tr import _translate from addresses import decodeAddress, addBMIfNotPresent import shared -from bitmessageui import Ui_MainWindow from bmconfigparser import BMConfigParser import defaults import namecoin from messageview import MessageView from migrationwizard import Ui_MigrationWizard from foldertree import ( - AccountMixin, Ui_FolderWidget, Ui_AddressWidget, Ui_SubscriptionWidget, + AccountMixin, AddressBookCompleter, Ui_FolderWidget, Ui_AddressWidget, Ui_SubscriptionWidget, MessageList_AddressWidget, MessageList_SubjectWidget, Ui_AddressBookWidgetItemLabel, Ui_AddressBookWidgetItemAddress) -from settings import Ui_settingsDialog import settingsmixin import support import debug @@ -38,24 +36,24 @@ from helper_ackPayload import genAckPayload from helper_sql import sqlQuery, sqlExecute, sqlExecuteChunked, sqlStoredProcedure import helper_search import l10n -import openclpow from utils import str_broadcast_subscribers, avatarize from account import ( getSortedAccounts, getSortedSubscriptions, accountClass, BMAccount, GatewayAccount, MailchuckAccount, AccountColor) import dialogs -from helper_generic import powQueueSize from network.stats import pendingDownload, pendingUpload from uisignaler import UISignaler import knownnodes import paths -from proofofwork import getPowType import queues import shutdown import state -from statusbar import BMStatusBar from network.asyncore_pollchoose import set_rates import sound +import re +import bitmessage_icons_rc # Loads icon resources +import workprover.utils +import singleworker try: @@ -371,13 +369,19 @@ class MyForm(settingsmixin.SMainWindow): # Popup menu for the Sent page self.ui.sentContextMenuToolbar = QtGui.QToolBar() # Actions - self.actionTrashSentMessage = self.ui.sentContextMenuToolbar.addAction( - _translate( - "MainWindow", "Move to Trash"), self.on_action_SentTrash) self.actionSentClipboard = self.ui.sentContextMenuToolbar.addAction( _translate( "MainWindow", "Copy destination address to clipboard"), self.on_action_SentClipboard) + self.actionEditAndResend = self.ui.sentContextMenuToolbar.addAction( + _translate( + "MainWindow", "Edit and resend"), self.on_action_EditAndResend) + self.actionTrashSentMessage = self.ui.sentContextMenuToolbar.addAction( + _translate( + "MainWindow", "Move to Trash"), self.on_action_SentTrash) + self.actionCancelSending = self.ui.sentContextMenuToolbar.addAction( + _translate( + "MainWindow", "Cancel sending"), self.on_action_CancelSending) self.actionForceSend = self.ui.sentContextMenuToolbar.addAction( _translate( "MainWindow", "Force send"), self.on_action_ForceSend) @@ -585,9 +589,54 @@ class MyForm(settingsmixin.SMainWindow): def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) - self.ui = Ui_MainWindow() + + self.UI = widgets.loadType("bitmessageui.ui")[0] + + self.ui = self.UI() self.ui.setupUi(self) + self.ui.blackwhitelist.retranslateUi() + self.ui.networkstatus.retranslateUi() + + addressBookCompleter = AddressBookCompleter() + addressBookCompleter.setCompletionMode(QtGui.QCompleter.PopupCompletion) + addressBookCompleter.setCaseSensitivity(QtCore.Qt.CaseInsensitive) + addressBookCompleter.setModel(QtGui.QStringListModel()) + + self.ui.lineEditTo.setCompleter(addressBookCompleter) + + self.ui.pushButtonAddAddressBook.resize(200, self.ui.pushButtonAddAddressBook.height()) + self.ui.pushButtonAddChan.resize(200, self.ui.pushButtonAddChan.height()) + self.ui.pushButtonAddSubscription.resize(200, self.ui.pushButtonAddSubscription.height()) + self.ui.pushButtonFetchNamecoinID.resize(200, self.ui.pushButtonFetchNamecoinID.height()) + self.ui.pushButtonNewAddress.resize(200, self.ui.pushButtonNewAddress.height()) + self.ui.tableWidgetAddressBook.resize(200, self.ui.tableWidgetAddressBook.height()) + self.ui.treeWidgetChans.resize(200, self.ui.treeWidgetChans.height()) + self.ui.treeWidgetSubscriptions.resize(200, self.ui.treeWidgetSubscriptions.height()) + self.ui.treeWidgetYourIdentities.resize(200, self.ui.treeWidgetYourIdentities.height()) + + # TODO: why we need splitters here if they are disabled? + + self.ui.horizontalSplitter_2.handle(1).setEnabled(False) + self.ui.horizontalSplitter_6.handle(1).setEnabled(False) + self.ui.horizontalSplitterSearch.handle(1).setEnabled(False) + self.ui.verticalSplitter.handle(1).setEnabled(False) + self.ui.verticalSplitter_2.handle(1).setEnabled(False) + self.ui.verticalSplitter_2.handle(2).setEnabled(False) + self.ui.verticalSplitter_3.handle(1).setEnabled(False) + self.ui.verticalSplitter_4.handle(1).setEnabled(False) + self.ui.verticalSplitter_5.handle(1).setEnabled(False) + self.ui.verticalSplitter_6.handle(1).setEnabled(False) + self.ui.verticalSplitter_7.handle(1).setEnabled(False) + self.ui.verticalSplitter_8.handle(1).setEnabled(False) + self.ui.verticalSplitter_12.handle(1).setEnabled(False) + self.ui.verticalSplitter_17.handle(1).setEnabled(False) + + self.ui.horizontalSliderTTL.setMaximumSize(QtCore.QSize(105, self.ui.pushButtonSend.height())) + + self.statusbar = self.statusBar() + self.statusbar.insertPermanentWidget(0, self.ui.rightStatusBar) + # Ask the user if we may delete their old version 1 addresses if they # have any. for addressInKeysFile in getSortedAccounts(): @@ -704,18 +753,16 @@ class MyForm(settingsmixin.SMainWindow): self.tabWidgetCurrentChanged ) - # Put the colored icon on the status bar - # self.pushButtonStatusIcon.setIcon(QIcon(":/newPrefix/images/yellowicon.png")) - self.setStatusBar(BMStatusBar()) - self.statusbar = self.statusBar() + # Initialize the blacklist or whitelist - self.pushButtonStatusIcon = QtGui.QPushButton(self) - self.pushButtonStatusIcon.setText('') - self.pushButtonStatusIcon.setIcon( - QtGui.QIcon(':/newPrefix/images/redicon.png')) - self.pushButtonStatusIcon.setFlat(True) - self.statusbar.insertPermanentWidget(0, self.pushButtonStatusIcon) - QtCore.QObject.connect(self.pushButtonStatusIcon, QtCore.SIGNAL( + if BMConfigParser().get("bitmessagesettings", "blackwhitelist") == "white": + self.ui.blackwhitelist.radioButtonWhitelist.click() + + self.ui.blackwhitelist.rerenderBlackWhiteList() + + # The coloured icon on the status bar + + QtCore.QObject.connect(self.ui.pushButtonStatusIcon, QtCore.SIGNAL( "clicked()"), self.click_pushButtonStatusIcon) self.numberOfMessagesProcessed = 0 @@ -737,13 +784,15 @@ class MyForm(settingsmixin.SMainWindow): QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "updateStatusBar(PyQt_PyObject)"), self.updateStatusBar) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "updateSentItemStatusByToAddress(PyQt_PyObject,PyQt_PyObject)"), self.updateSentItemStatusByToAddress) + "updateSentItemStatusByToAddress(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.updateSentItemStatusByToAddress) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"), self.updateSentItemStatusByAckdata) + "updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.updateSentItemStatusByAckdata) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "displayNewInboxMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.displayNewInboxMessage) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.displayNewSentMessage) + QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( + "deleteSentItemByAckData(PyQt_PyObject)"), self.deleteSentItemByAckData) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "setStatusIcon(PyQt_PyObject)"), self.setStatusIcon) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( @@ -762,6 +811,8 @@ class MyForm(settingsmixin.SMainWindow): "newVersionAvailable(PyQt_PyObject)"), self.newVersionAvailable) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "displayAlert(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.displayAlert) + QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( + "updateWorkProverStatus(PyQt_PyObject)"), self.updateWorkProverStatus) self.UISignalThread.start() # Key press in tree view @@ -812,6 +863,8 @@ class MyForm(settingsmixin.SMainWindow): ' Fetch Namecoin ID button') self.ui.pushButtonFetchNamecoinID.hide() + self.POWTasksCount = 0 + def updateTTL(self, sliderPosition): TTL = int(sliderPosition ** 3.199 + 3600) self.updateHumanFriendlyTTLDescription(TTL) @@ -1039,7 +1092,7 @@ class MyForm(settingsmixin.SMainWindow): if status == 'awaitingpubkey': statusText = _translate( "MainWindow", "Waiting for their encryption key. Will request it again soon.") - elif status == 'doingpowforpubkey': + elif status == 'doingpubkeypow': statusText = _translate( "MainWindow", "Doing work necessary to request encryption key.") elif status == 'msgqueued': @@ -1057,6 +1110,8 @@ class MyForm(settingsmixin.SMainWindow): elif status == 'ackreceived': statusText = _translate("MainWindow", "Acknowledgement of the message received %1").arg( l10n.formatTimestamp(lastactiontime)) + elif status == "msgcanceled": + statusText = _translate("MainWindow", "Message canceled.") elif status == 'broadcastqueued': statusText = _translate( "MainWindow", "Broadcast queued.") @@ -1066,6 +1121,8 @@ class MyForm(settingsmixin.SMainWindow): elif status == 'broadcastsent': statusText = _translate("MainWindow", "Broadcast on %1").arg( l10n.formatTimestamp(lastactiontime)) + elif status == "broadcastcanceled": + statusText = _translate("MainWindow", "Broadcast canceled.") elif status == 'toodifficult': statusText = _translate("MainWindow", "Problem: The work demanded by the recipient is more difficult than you are willing to do. %1").arg( l10n.formatTimestamp(lastactiontime)) @@ -1566,6 +1623,9 @@ class MyForm(settingsmixin.SMainWindow): def changeEvent(self, event): if event.type() == QtCore.QEvent.LanguageChange: self.ui.retranslateUi(self) + self.ui.blackwhitelist.retranslateUi() + self.ui.networkstatus.retranslateUi() + self.updateHumanFriendlyTTLDescription(BMConfigParser().getint("bitmessagesettings", "ttl")) self.init_inbox_popup_menu(False) self.init_identities_popup_menu(False) self.init_chan_popup_menu(False) @@ -1596,7 +1656,7 @@ class MyForm(settingsmixin.SMainWindow): _notifications_enabled = not BMConfigParser().getboolean( 'bitmessagesettings', 'hidetrayconnectionnotifications') if color == 'red': - self.pushButtonStatusIcon.setIcon( + self.ui.pushButtonStatusIcon.setIcon( QtGui.QIcon(":/newPrefix/images/redicon.png")) shared.statusIconColor = 'red' # if the connection is lost then show a notification @@ -1622,7 +1682,7 @@ class MyForm(settingsmixin.SMainWindow): if color == 'yellow': if self.statusbar.currentMessage() == 'Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect.': self.statusbar.clearMessage() - self.pushButtonStatusIcon.setIcon( + self.ui.pushButtonStatusIcon.setIcon( QtGui.QIcon(":/newPrefix/images/yellowicon.png")) shared.statusIconColor = 'yellow' # if a new connection has been established then show a notification @@ -1640,7 +1700,7 @@ class MyForm(settingsmixin.SMainWindow): if color == 'green': if self.statusbar.currentMessage() == 'Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect.': self.statusbar.clearMessage() - self.pushButtonStatusIcon.setIcon( + self.ui.pushButtonStatusIcon.setIcon( QtGui.QIcon(":/newPrefix/images/greenicon.png")) shared.statusIconColor = 'green' if not self.connected and _notifications_enabled: @@ -1716,7 +1776,7 @@ class MyForm(settingsmixin.SMainWindow): self.unreadCount = count return self.unreadCount - def updateSentItemStatusByToAddress(self, toAddress, textToDisplay): + def updateSentItemStatusByToAddress(self, status, toAddress, textToDisplay): for sent in [self.ui.tableWidgetInbox, self.ui.tableWidgetInboxSubscriptions, self.ui.tableWidgetInboxChans]: treeWidget = self.widgetConvert(sent) if self.getCurrentFolder(treeWidget) != "sent": @@ -1727,6 +1787,16 @@ class MyForm(settingsmixin.SMainWindow): for i in range(sent.rowCount()): rowAddress = sent.item(i, 0).data(QtCore.Qt.UserRole) if toAddress == rowAddress: + tableAckData = str(sent.item(i, 3).data(QtCore.Qt.UserRole).toPyObject()) + + count = sqlQuery(""" + SELECT COUNT(*) FROM "sent" + WHERE "ackdata" == ? AND "status" == ?; + """, tableAckData, status)[0][0] + + if count == 0: + continue + sent.item(i, 3).setToolTip(textToDisplay) try: newlinePosition = textToDisplay.indexOf('\n') @@ -1738,7 +1808,15 @@ class MyForm(settingsmixin.SMainWindow): else: sent.item(i, 3).setText(textToDisplay) - def updateSentItemStatusByAckdata(self, ackdata, textToDisplay): + def updateSentItemStatusByAckdata(self, status, ackdata, textToDisplay): + count = sqlQuery(""" + SELECT COUNT(*) FROM "sent" + WHERE "ackdata" == ? AND "status" == ?; + """, ackdata, status)[0][0] + + if count == 0: + return + if type(ackdata) is str: ackdata = QtCore.QByteArray(ackdata) for sent in [self.ui.tableWidgetInbox, self.ui.tableWidgetInboxSubscriptions, self.ui.tableWidgetInboxChans]: @@ -1793,6 +1871,22 @@ class MyForm(settingsmixin.SMainWindow): if exitAfterUserClicksOk: os._exit(0) + def updateWorkProverStatus(self, status): + self.POWTasksCount = status.tasksCount + + if status.speed == 0: + self.ui.workProverSpeed.setText("") + self.ui.workProverSpeed.setToolTip("") + else: + self.ui.workProverSpeed.setText( + _translate("MainWindow", "%1 kiH / s").arg("{:.1f}".format(status.speed / 1024)) + ) + + self.ui.workProverSpeed.setToolTip("Difficulty: {}, 95 % completion time: {:.1f} s".format( + status.difficulty, + workprover.utils.estimateMaximumIterationsCount(status.difficulty, .95) / status.speed + )) + def rerenderMessagelistFromLabels(self): for messagelist in (self.ui.tableWidgetInbox, self.ui.tableWidgetInboxChans, self.ui.tableWidgetInboxSubscriptions): for i in range(messagelist.rowCount()): @@ -2298,6 +2392,37 @@ class MyForm(settingsmixin.SMainWindow): dialogs.EmailGatewayDialog( self, BMConfigParser(), acct).exec_() + def deleteSentItemByAckData(self, ackData): + ackData = QtCore.QByteArray(ackData) + + for tableWidget in [ + self.ui.tableWidgetInbox, + self.ui.tableWidgetInboxSubscriptions, + self.ui.tableWidgetInboxChans + ]: + treeWidget = self.widgetConvert(tableWidget) + + if self.getCurrentFolder(treeWidget) != "sent": + continue + + i = 0 + + while i < tableWidget.rowCount(): + tableAckData = tableWidget.item(i, 3).data(QtCore.Qt.UserRole).toPyObject() + + if ackData == tableAckData: + textEdit = self.getCurrentMessageTextedit() + + if textEdit is not False: + textEdit.setPlainText("") + + tableWidget.removeRow(i) + tableWidget.selectRow(max(0, i - 1)) + + self.updateStatusBar(_translate("MainWindow", "Moved items to trash.")) + else: + i += 1 + def click_pushButtonAddAddressBook(self, dialog=None): if not dialog: dialog = dialogs.AddAddressDialog(self) @@ -2490,9 +2615,34 @@ class MyForm(settingsmixin.SMainWindow): BMConfigParser().set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(int(float( self.settingsDialogInstance.ui.lineEditSmallMessageDifficulty.text()) * defaults.networkDefaultPayloadLengthExtraBytes))) - if self.settingsDialogInstance.ui.comboBoxOpenCL.currentText().toUtf8() != BMConfigParser().safeGet("bitmessagesettings", "opencl"): - BMConfigParser().set('bitmessagesettings', 'opencl', str(self.settingsDialogInstance.ui.comboBoxOpenCL.currentText())) - queues.workerQueue.put(('resetPoW', '')) + if self.settingsDialogInstance.ui.radioButtonDumbSolver.isChecked(): + BMConfigParser().set("bitmessagesettings", "powsolver", "dumb") + elif self.settingsDialogInstance.ui.radioButtonForkingSolver.isChecked(): + BMConfigParser().set("bitmessagesettings", "powsolver", "forking") + + BMConfigParser().set( + "bitmessagesettings", + "processes", + str(self.settingsDialogInstance.ui.spinBoxForkingSolverParallelism.value()) + ) + elif self.settingsDialogInstance.ui.radioButtonFastSolver.isChecked(): + BMConfigParser().set("bitmessagesettings", "powsolver", "fast") + + BMConfigParser().set( + "bitmessagesettings", + "threads", + str(self.settingsDialogInstance.ui.spinBoxFastSolverParallelism.value()) + ) + elif self.settingsDialogInstance.ui.radioButtonGPUSolver.isChecked(): + BMConfigParser().set("bitmessagesettings", "powsolver", "gpu") + + BMConfigParser().set( + "bitmessagesettings", + "opencl", + str(self.settingsDialogInstance.ui.comboBoxGPUVendor.currentText().toUtf8()) + ) + + singleworker.setBestSolver() acceptableDifficultyChanged = False @@ -2681,6 +2831,14 @@ class MyForm(settingsmixin.SMainWindow): def click_NewAddressDialog(self): dialogs.NewAddressDialog(self) + def updateNetworkSwitchMenuLabel(self, dontconnect = None): + if dontconnect is None: + dontconnect = BMConfigParser().safeGetBoolean("bitmessagesettings", "dontconnect") + + self.ui.actionNetworkSwitch.setText( + _translate("MainWindow", "Go online" if dontconnect else "Go offline", None) + ) + def network_switch(self): dontconnect_option = not BMConfigParser().safeGetBoolean( 'bitmessagesettings', 'dontconnect') @@ -2700,7 +2858,7 @@ class MyForm(settingsmixin.SMainWindow): BMConfigParser().set( 'bitmessagesettings', 'dontconnect', str(dontconnect_option)) BMConfigParser().save() - self.ui.updateNetworkSwitchMenuLabel(dontconnect_option) + self.updateNetworkSwitchMenuLabel(dontconnect_option) self.ui.pushButtonFetchNamecoinID.setHidden( dontconnect_option or self.namecoin.test()[0] == 'failed' @@ -2728,9 +2886,9 @@ class MyForm(settingsmixin.SMainWindow): waitForSync = False # C PoW currently doesn't support interrupting and OpenCL is untested - if getPowType() == "python" and (powQueueSize() > 0 or pendingUpload() > 0): + if self.POWTasksCount > 0 or pendingUpload() > 0: reply = QtGui.QMessageBox.question(self, _translate("MainWindow", "Proof of work pending"), - _translate("MainWindow", "%n object(s) pending proof of work", None, QtCore.QCoreApplication.CodecForTr, powQueueSize()) + ", " + + _translate("MainWindow", "%n object(s) pending proof of work", None, QtCore.QCoreApplication.CodecForTr, self.POWTasksCount) + ", " + _translate("MainWindow", "%n object(s) waiting to be distributed", None, QtCore.QCoreApplication.CodecForTr, pendingUpload()) + "\n\n" + _translate("MainWindow", "Wait until these tasks finish?"), QtGui.QMessageBox.Yes|QtGui.QMessageBox.No|QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel) @@ -2786,10 +2944,10 @@ class MyForm(settingsmixin.SMainWindow): if waitForPow: # check if PoW queue empty maxWorkerQueue = 0 - curWorkerQueue = powQueueSize() + curWorkerQueue = self.POWTasksCount while curWorkerQueue > 0: # worker queue size - curWorkerQueue = powQueueSize() + curWorkerQueue = self.POWTasksCount if curWorkerQueue > maxWorkerQueue: maxWorkerQueue = curWorkerQueue if curWorkerQueue > 0: @@ -3233,47 +3391,159 @@ class MyForm(settingsmixin.SMainWindow): logger.exception('Message not saved', exc_info=True) self.updateStatusBar(_translate("MainWindow", "Write error.")) - # Send item on the Sent tab to trash - def on_action_SentTrash(self): - currentRow = 0 - unread = False + def on_action_EditAndResend(self): tableWidget = self.getCurrentMessagelist() + if not tableWidget: return - folder = self.getCurrentFolder() - shifted = (QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier) > 0 - while tableWidget.selectedIndexes() != []: - currentRow = tableWidget.selectedIndexes()[0].row() - ackdataToTrash = str(tableWidget.item( - currentRow, 3).data(QtCore.Qt.UserRole).toPyObject()) - if folder == "trash" or shifted: - sqlExecute('''DELETE FROM sent WHERE ackdata=?''', ackdataToTrash) - else: - sqlExecute('''UPDATE sent SET folder='trash' WHERE ackdata=?''', ackdataToTrash) - if tableWidget.item(currentRow, 0).unread: - self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(QtCore.Qt.UserRole), folder, self.getCurrentTreeWidget(), -1) - self.getCurrentMessageTextedit().setPlainText("") - tableWidget.removeRow(currentRow) - self.updateStatusBar(_translate( - "MainWindow", "Moved items to trash.")) - self.ui.tableWidgetInbox.selectRow( - currentRow if currentRow == 0 else currentRow - 1) + shiftPressed = QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier != 0 + folder = self.getCurrentFolder() + trash = not (folder == "trash" or shiftPressed) + + for i in tableWidget.selectedIndexes(): + if i.column() != 3: + continue + + ackData = str(i.data(QtCore.Qt.UserRole).toPyObject()) + + queryReturn = sqlQuery(""" + SELECT "status", "toaddress", "fromaddress", "subject", "message", "TTL" FROM "sent" + WHERE "ackdata" == ?; + """, ackData) + + if len(queryReturn) == 0: + continue + + status, destination, address, subject, body, TTL = queryReturn[0] + + if status in ["broadcastqueued", "doingbroadcastpow", "broadcastsent", "broadcastcanceled"]: + self.ui.comboBoxSendFromBroadcast.setCurrentIndex(0) + + for j in range(self.ui.comboBoxSendFromBroadcast.count()): + currentAddress = str(self.ui.comboBoxSendFromBroadcast.itemData(j).toPyObject()) + + if currentAddress == address: + self.ui.comboBoxSendFromBroadcast.setCurrentIndex(j) + + break + + self.ui.lineEditSubjectBroadcast.setText(subject) + self.ui.textEditMessageBroadcast.setText(body) + + self.ui.tabWidgetSend.setCurrentIndex(self.ui.tabWidgetSend.indexOf(self.ui.sendBroadcast)) + else: + self.ui.comboBoxSendFrom.setCurrentIndex(0) + + for j in range(self.ui.comboBoxSendFrom.count()): + currentAddress = str(self.ui.comboBoxSendFrom.itemData(j).toPyObject()) + + if currentAddress == address: + self.ui.comboBoxSendFrom.setCurrentIndex(j) + + break + + self.ui.lineEditTo.setText(destination) + self.ui.lineEditSubject.setText(subject) + self.ui.textEditMessage.setText(body) + + self.ui.tabWidgetSend.setCurrentIndex(self.ui.tabWidgetSend.indexOf(self.ui.sendDirect)) + + # TODO: 3.192 fits better + + self.ui.horizontalSliderTTL.setSliderPosition((TTL - 3600) ** (1 / 3.199)) + + self.ui.tabWidget.setCurrentIndex(self.ui.tabWidget.indexOf(self.ui.send)) + + break + + def on_action_SentTrash(self): + tableWidget = self.getCurrentMessagelist() + + if not tableWidget: + return + + shiftPressed = QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier != 0 + folder = self.getCurrentFolder() + trash = not (folder == "trash" or shiftPressed) + + for i in tableWidget.selectedIndexes(): + if i.column() != 3: + continue + + ackData = str(i.data(QtCore.Qt.UserRole).toPyObject()) + + queryReturn = sqlQuery("""SELECT "status" FROM "sent" WHERE "ackdata" == ?;""", ackData) + + if len(queryReturn) == 0: + continue + + status = queryReturn[0][0] + + if status in ["broadcastqueued", "doingbroadcastpow", "broadcastsent", "broadcastcanceled"]: + queues.workerQueue.put(("cancelBroadcast", ackData, True, trash)) + else: + queues.workerQueue.put(("cancelMessage", ackData, True, trash)) + + def on_action_CancelSending(self): + tableWidget = self.getCurrentMessagelist() + + if not tableWidget: + return + + for i in tableWidget.selectedIndexes(): + if i.column() != 3: + continue + + ackData = str(i.data(QtCore.Qt.UserRole).toPyObject()) + + queryReturn = sqlQuery("""SELECT "status" FROM "sent" WHERE "ackdata" == ?;""", ackData) + + if len(queryReturn) == 0: + continue + + status = queryReturn[0][0] + + if status in ["broadcastqueued", "doingbroadcastpow"]: + queues.workerQueue.put(("cancelBroadcast", ackData, False, None)) + elif status not in ["ackreceived", "msgsentnoackexpected", "badkey", "msgcanceled"]: + queues.workerQueue.put(("cancelMessage", ackData, False, None)) def on_action_ForceSend(self): - currentRow = self.ui.tableWidgetInbox.currentRow() - addressAtCurrentRow = self.ui.tableWidgetInbox.item( - currentRow, 0).data(QtCore.Qt.UserRole) - toRipe = decodeAddress(addressAtCurrentRow)[3] - sqlExecute( - '''UPDATE sent SET status='forcepow' WHERE toripe=? AND status='toodifficult' and folder='sent' ''', - toRipe) - queryreturn = sqlQuery('''select ackdata FROM sent WHERE status='forcepow' ''') - for row in queryreturn: - ackdata, = row - queues.UISignalQueue.put(('updateSentItemStatusByAckdata', ( - ackdata, 'Overriding maximum-difficulty setting. Work queued.'))) - queues.workerQueue.put(('sendmessage', '')) + tableWidget = self.getCurrentMessagelist() + + if not tableWidget: + return + + resendMessages = False + + for i in tableWidget.selectedIndexes(): + if i.column() != 3: + continue + + ackData = str(i.data(QtCore.Qt.UserRole).toPyObject()) + + queryReturn = sqlQuery("""SELECT "status" FROM "sent" WHERE "ackdata" == ?;""", ackData) + + if len(queryReturn) == 0: + continue + + if queryReturn[0][0] == "toodifficult": + sqlExecute(""" + UPDATE "sent" SET "status" = 'forcepow' + WHERE "ackdata" == ? AND "status" == 'toodifficult' AND "folder" = 'sent'; + """, ackData) + + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "forcepow", + ackData, + "Overriding maximum-difficulty setting. Work queued." # TODO: add translation? + ))) + + resendMessages = True + + if resendMessages: + queues.workerQueue.put(("sendmessage", )) def on_action_SentClipboard(self): currentRow = self.ui.tableWidgetInbox.currentRow() @@ -3935,23 +4205,56 @@ class MyForm(settingsmixin.SMainWindow): self.popMenuInbox.exec_(tableWidget.mapToGlobal(point)) def on_context_menuSent(self, point): - self.popMenuSent = QtGui.QMenu(self) - self.popMenuSent.addAction(self.actionSentClipboard) - self.popMenuSent.addAction(self.actionTrashSentMessage) + tableWidget = self.getCurrentMessagelist() - # Check to see if this item is toodifficult and display an additional - # menu option (Force Send) if it is. - currentRow = self.ui.tableWidgetInbox.currentRow() - if currentRow >= 0: - ackData = str(self.ui.tableWidgetInbox.item( - currentRow, 3).data(QtCore.Qt.UserRole).toPyObject()) - queryreturn = sqlQuery('''SELECT status FROM sent where ackdata=?''', ackData) - for row in queryreturn: - status, = row - if status == 'toodifficult': + if not tableWidget: + return + + showMenu = False + cancelSending = False + forceSend = False + + for i in tableWidget.selectedIndexes(): + if i.column() != 3: + continue + + ackData = str(i.data(QtCore.Qt.UserRole).toPyObject()) + + queryReturn = sqlQuery("""SELECT "status" FROM "sent" WHERE "ackdata" == ?;""", ackData) + + if len(queryReturn) == 0: + continue + + status = queryReturn[0][0] + + if status not in [ + "ackreceived", "msgsentnoackexpected", "badkey", "msgcanceled", + "broadcastsent", "broadcastcanceled" + ]: + cancelSending = True + + if status == "toodifficult": + forceSend = True + + showMenu = True + + if showMenu: + self.popMenuSent = QtGui.QMenu(self) + self.popMenuSent.addAction(self.actionSentClipboard) + self.popMenuSent.addSeparator() + self.popMenuSent.addAction(self.actionEditAndResend) + + if forceSend: self.popMenuSent.addAction(self.actionForceSend) - self.popMenuSent.exec_(self.ui.tableWidgetInbox.mapToGlobal(point)) + self.popMenuSent.addSeparator() + + if cancelSending: + self.popMenuSent.addAction(self.actionCancelSending) + + self.popMenuSent.addAction(self.actionTrashSentMessage) + + self.popMenuSent.exec_(tableWidget.mapToGlobal(point)) def inboxSearchLineEditUpdated(self, text): # dynamic search for too short text is slow @@ -4129,8 +4432,11 @@ class settingsDialog(QtGui.QDialog): def __init__(self, parent): QtGui.QWidget.__init__(self, parent) - self.ui = Ui_settingsDialog() + self.UI = widgets.loadType("settings.ui")[0] + + self.ui = self.UI() self.ui.setupUi(self) + self.parent = parent self.ui.checkBoxStartOnLogon.setChecked( BMConfigParser().getboolean('bitmessagesettings', 'startonlogon')) @@ -4176,6 +4482,41 @@ class settingsDialog(QtGui.QDialog): self.ui.checkBoxStartOnLogon.setDisabled(True) self.ui.checkBoxStartOnLogon.setText(_translate( "MainWindow", "Start-on-login not yet supported on your OS.")) + + self.ui.languageComboBox.addItem( + QtGui.QApplication.translate("settingsDialog", "System Settings", "system"), + "system" + ) + + localeRegexp = re.compile("bitmessage_(.*).qm$") + localeNames = {"eo": "Esperanto", "en_pirate": "Pirate English"} + + for i in sorted(os.listdir(os.path.join(paths.codePath(), "translations"))): + match = localeRegexp.match(i) + + if match is None: + continue + + localeCode = match.group(1) + + if localeCode in localeNames: + localeName = localeNames[localeCode] + else: + localeName = QtCore.QLocale(QtCore.QString(localeCode)).nativeLanguageName() + + if localeName == "": + localeName = localeCode + + self.ui.languageComboBox.addItem(localeName, localeCode) + + configuredLocale = BMConfigParser().safeGet("bitmessagesettings", "userlocale", "system") + + for i in range(self.ui.languageComboBox.count()): + if self.ui.languageComboBox.itemData(i) == configuredLocale: + self.ui.languageComboBox.setCurrentIndex(i) + + break + # On the Network settings tab: self.ui.lineEditTCPPort.setText(str( BMConfigParser().get('bitmessagesettings', 'port'))) @@ -4212,6 +4553,9 @@ class settingsDialog(QtGui.QDialog): BMConfigParser().get('bitmessagesettings', 'maxdownloadrate'))) self.ui.lineEditMaxUploadRate.setText(str( BMConfigParser().get('bitmessagesettings', 'maxuploadrate'))) + self.ui.lineEditMaxOutboundConnections.setValidator( + QtGui.QIntValidator(0, 8, self.ui.lineEditMaxOutboundConnections) + ) self.ui.lineEditMaxOutboundConnections.setText(str( BMConfigParser().get('bitmessagesettings', 'maxoutboundconnections'))) @@ -4227,18 +4571,46 @@ class settingsDialog(QtGui.QDialog): self.ui.lineEditMaxAcceptableSmallMessageDifficulty.setText(str((float(BMConfigParser().getint( 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes')) / defaults.networkDefaultPayloadLengthExtraBytes))) - # OpenCL - if openclpow.openclAvailable(): - self.ui.comboBoxOpenCL.setEnabled(True) - else: - self.ui.comboBoxOpenCL.setEnabled(False) - self.ui.comboBoxOpenCL.clear() - self.ui.comboBoxOpenCL.addItem("None") - self.ui.comboBoxOpenCL.addItems(openclpow.vendors) - self.ui.comboBoxOpenCL.setCurrentIndex(0) - for i in range(self.ui.comboBoxOpenCL.count()): - if self.ui.comboBoxOpenCL.itemText(i) == BMConfigParser().safeGet('bitmessagesettings', 'opencl'): - self.ui.comboBoxOpenCL.setCurrentIndex(i) + if "forking" not in singleworker.workProver.availableSolvers: + self.ui.radioButtonForkingSolver.setEnabled(False) + if "fast" not in singleworker.workProver.availableSolvers: + self.ui.radioButtonFastSolver.setEnabled(False) + if "gpu" not in singleworker.workProver.availableSolvers: + self.ui.radioButtonGPUSolver.setEnabled(False) + + solverName = BMConfigParser().safeGet("bitmessagesettings", "powsolver", "gpu") + forkingSolverParallelism = BMConfigParser().safeGetInt("bitmessagesettings", "processes") + fastSolverParallelism = BMConfigParser().safeGetInt("bitmessagesettings", "threads") + GPUVendor = BMConfigParser().safeGet("bitmessagesettings", "opencl") + + if solverName == "dumb": + self.ui.radioButtonDumbSolver.setChecked(True) + elif solverName == "forking": + self.ui.radioButtonForkingSolver.setChecked(True) + elif solverName == "fast": + self.ui.radioButtonFastSolver.setChecked(True) + elif solverName == "gpu": + self.ui.radioButtonGPUSolver.setChecked(True) + + self.ui.spinBoxForkingSolverParallelism.setValue(forkingSolverParallelism) + self.ui.spinBoxFastSolverParallelism.setValue(fastSolverParallelism) + + vendors = set() + + if GPUVendor is not None: + vendors.add(GPUVendor) + + if "gpu" in singleworker.workProver.availableSolvers: + vendors |= set(singleworker.workProver.availableSolvers["gpu"].vendors) + + self.ui.comboBoxGPUVendor.clear() + self.ui.comboBoxGPUVendor.addItems(list(vendors)) + self.ui.comboBoxGPUVendor.setCurrentIndex(0) + + for i in range(self.ui.comboBoxGPUVendor.count()): + if self.ui.comboBoxGPUVendor.itemText(i) == GPUVendor: + self.ui.comboBoxGPUVendor.setCurrentIndex(i) + break # Namecoin integration tab @@ -4444,7 +4816,7 @@ def run(): 'bitmessagesettings', 'dontconnect') if myapp._firstrun: myapp.showConnectDialog() # ask the user if we may connect - myapp.ui.updateNetworkSwitchMenuLabel() + myapp.updateNetworkSwitchMenuLabel() # try: # if BMConfigParser().get('bitmessagesettings', 'mailchuck') < 1: diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py deleted file mode 100644 index cb3578c0..00000000 --- a/src/bitmessageqt/bitmessageui.py +++ /dev/null @@ -1,777 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'bitmessageui.ui' -# -# Created: Mon Mar 23 22:18:07 2015 -# by: PyQt4 UI code generator 4.10.4 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui -from bmconfigparser import BMConfigParser -from foldertree import AddressBookCompleter -from messageview import MessageView -from messagecompose import MessageCompose -import settingsmixin -from networkstatus import NetworkStatus -from blacklist import Blacklist - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s - -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - def _translate(context, text, disambig, encoding = QtCore.QCoreApplication.CodecForTr, n = None): - if n is None: - return QtGui.QApplication.translate(context, text, disambig, _encoding) - else: - return QtGui.QApplication.translate(context, text, disambig, _encoding, n) -except AttributeError: - def _translate(context, text, disambig, encoding = QtCore.QCoreApplication.CodecForTr, n = None): - if n is None: - return QtGui.QApplication.translate(context, text, disambig) - else: - return QtGui.QApplication.translate(context, text, disambig, QtCore.QCoreApplication.CodecForTr, n) - -class Ui_MainWindow(object): - def setupUi(self, MainWindow): - MainWindow.setObjectName(_fromUtf8("MainWindow")) - MainWindow.resize(885, 580) - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-24px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - MainWindow.setWindowIcon(icon) - MainWindow.setTabShape(QtGui.QTabWidget.Rounded) - self.centralwidget = QtGui.QWidget(MainWindow) - self.centralwidget.setObjectName(_fromUtf8("centralwidget")) - self.gridLayout_10 = QtGui.QGridLayout(self.centralwidget) - self.gridLayout_10.setObjectName(_fromUtf8("gridLayout_10")) - self.tabWidget = QtGui.QTabWidget(self.centralwidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.tabWidget.sizePolicy().hasHeightForWidth()) - self.tabWidget.setSizePolicy(sizePolicy) - self.tabWidget.setMinimumSize(QtCore.QSize(0, 0)) - self.tabWidget.setBaseSize(QtCore.QSize(0, 0)) - font = QtGui.QFont() - font.setPointSize(9) - self.tabWidget.setFont(font) - self.tabWidget.setTabPosition(QtGui.QTabWidget.North) - self.tabWidget.setTabShape(QtGui.QTabWidget.Rounded) - self.tabWidget.setObjectName(_fromUtf8("tabWidget")) - self.inbox = QtGui.QWidget() - self.inbox.setObjectName(_fromUtf8("inbox")) - self.gridLayout = QtGui.QGridLayout(self.inbox) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.horizontalSplitter_3 = settingsmixin.SSplitter() - self.horizontalSplitter_3.setObjectName(_fromUtf8("horizontalSplitter_3")) - self.verticalSplitter_12 = settingsmixin.SSplitter() - self.verticalSplitter_12.setObjectName(_fromUtf8("verticalSplitter_12")) - self.verticalSplitter_12.setOrientation(QtCore.Qt.Vertical) - self.treeWidgetYourIdentities = settingsmixin.STreeWidget(self.inbox) - self.treeWidgetYourIdentities.setObjectName(_fromUtf8("treeWidgetYourIdentities")) - self.treeWidgetYourIdentities.resize(200, self.treeWidgetYourIdentities.height()) - icon1 = QtGui.QIcon() - icon1.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/identities.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) - self.treeWidgetYourIdentities.headerItem().setIcon(0, icon1) - self.verticalSplitter_12.addWidget(self.treeWidgetYourIdentities) - self.pushButtonNewAddress = QtGui.QPushButton(self.inbox) - self.pushButtonNewAddress.setObjectName(_fromUtf8("pushButtonNewAddress")) - self.pushButtonNewAddress.resize(200, self.pushButtonNewAddress.height()) - self.verticalSplitter_12.addWidget(self.pushButtonNewAddress) - self.verticalSplitter_12.setStretchFactor(0, 1) - self.verticalSplitter_12.setStretchFactor(1, 0) - self.verticalSplitter_12.setCollapsible(0, False) - self.verticalSplitter_12.setCollapsible(1, False) - self.verticalSplitter_12.handle(1).setEnabled(False) - self.horizontalSplitter_3.addWidget(self.verticalSplitter_12) - self.verticalSplitter_7 = settingsmixin.SSplitter() - self.verticalSplitter_7.setObjectName(_fromUtf8("verticalSplitter_7")) - self.verticalSplitter_7.setOrientation(QtCore.Qt.Vertical) - self.horizontalSplitterSearch = QtGui.QSplitter() - self.horizontalSplitterSearch.setObjectName(_fromUtf8("horizontalSplitterSearch")) - self.inboxSearchLineEdit = QtGui.QLineEdit(self.inbox) - self.inboxSearchLineEdit.setObjectName(_fromUtf8("inboxSearchLineEdit")) - self.horizontalSplitterSearch.addWidget(self.inboxSearchLineEdit) - self.inboxSearchOption = QtGui.QComboBox(self.inbox) - self.inboxSearchOption.setObjectName(_fromUtf8("inboxSearchOption")) - self.inboxSearchOption.addItem(_fromUtf8("")) - self.inboxSearchOption.addItem(_fromUtf8("")) - self.inboxSearchOption.addItem(_fromUtf8("")) - self.inboxSearchOption.addItem(_fromUtf8("")) - self.inboxSearchOption.addItem(_fromUtf8("")) - self.inboxSearchOption.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) - self.horizontalSplitterSearch.addWidget(self.inboxSearchOption) - self.horizontalSplitterSearch.handle(1).setEnabled(False) - self.horizontalSplitterSearch.setStretchFactor(0, 1) - self.horizontalSplitterSearch.setStretchFactor(1, 0) - self.verticalSplitter_7.addWidget(self.horizontalSplitterSearch) - self.tableWidgetInbox = settingsmixin.STableWidget(self.inbox) - self.tableWidgetInbox.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) - self.tableWidgetInbox.setAlternatingRowColors(True) - self.tableWidgetInbox.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) - self.tableWidgetInbox.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self.tableWidgetInbox.setWordWrap(False) - self.tableWidgetInbox.setObjectName(_fromUtf8("tableWidgetInbox")) - self.tableWidgetInbox.setColumnCount(4) - self.tableWidgetInbox.setRowCount(0) - item = QtGui.QTableWidgetItem() - self.tableWidgetInbox.setHorizontalHeaderItem(0, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInbox.setHorizontalHeaderItem(1, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInbox.setHorizontalHeaderItem(2, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInbox.setHorizontalHeaderItem(3, item) - self.tableWidgetInbox.horizontalHeader().setCascadingSectionResizes(True) - self.tableWidgetInbox.horizontalHeader().setDefaultSectionSize(200) - self.tableWidgetInbox.horizontalHeader().setHighlightSections(False) - self.tableWidgetInbox.horizontalHeader().setMinimumSectionSize(27) - self.tableWidgetInbox.horizontalHeader().setSortIndicatorShown(False) - self.tableWidgetInbox.horizontalHeader().setStretchLastSection(True) - self.tableWidgetInbox.verticalHeader().setVisible(False) - self.tableWidgetInbox.verticalHeader().setDefaultSectionSize(26) - self.verticalSplitter_7.addWidget(self.tableWidgetInbox) - self.textEditInboxMessage = MessageView(self.inbox) - self.textEditInboxMessage.setBaseSize(QtCore.QSize(0, 500)) - self.textEditInboxMessage.setReadOnly(True) - self.textEditInboxMessage.setObjectName(_fromUtf8("textEditInboxMessage")) - self.verticalSplitter_7.addWidget(self.textEditInboxMessage) - self.verticalSplitter_7.setStretchFactor(0, 0) - self.verticalSplitter_7.setStretchFactor(1, 1) - self.verticalSplitter_7.setStretchFactor(2, 2) - self.verticalSplitter_7.setCollapsible(0, False) - self.verticalSplitter_7.setCollapsible(1, False) - self.verticalSplitter_7.setCollapsible(2, False) - self.verticalSplitter_7.handle(1).setEnabled(False) - self.horizontalSplitter_3.addWidget(self.verticalSplitter_7) - self.horizontalSplitter_3.setStretchFactor(0, 0) - self.horizontalSplitter_3.setStretchFactor(1, 1) - self.horizontalSplitter_3.setCollapsible(0, False) - self.horizontalSplitter_3.setCollapsible(1, False) - self.gridLayout.addWidget(self.horizontalSplitter_3) - icon2 = QtGui.QIcon() - icon2.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/inbox.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.tabWidget.addTab(self.inbox, icon2, _fromUtf8("")) - self.send = QtGui.QWidget() - self.send.setObjectName(_fromUtf8("send")) - self.gridLayout_7 = QtGui.QGridLayout(self.send) - self.gridLayout_7.setObjectName(_fromUtf8("gridLayout_7")) - self.horizontalSplitter = settingsmixin.SSplitter() - self.horizontalSplitter.setObjectName(_fromUtf8("horizontalSplitter")) - self.verticalSplitter_2 = settingsmixin.SSplitter() - self.verticalSplitter_2.setObjectName(_fromUtf8("verticalSplitter_2")) - self.verticalSplitter_2.setOrientation(QtCore.Qt.Vertical) - self.tableWidgetAddressBook = settingsmixin.STableWidget(self.send) - self.tableWidgetAddressBook.setAlternatingRowColors(True) - self.tableWidgetAddressBook.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) - self.tableWidgetAddressBook.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self.tableWidgetAddressBook.setObjectName(_fromUtf8("tableWidgetAddressBook")) - self.tableWidgetAddressBook.setColumnCount(2) - self.tableWidgetAddressBook.setRowCount(0) - self.tableWidgetAddressBook.resize(200, self.tableWidgetAddressBook.height()) - item = QtGui.QTableWidgetItem() - icon3 = QtGui.QIcon() - icon3.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/addressbook.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) - item.setIcon(icon3) - self.tableWidgetAddressBook.setHorizontalHeaderItem(0, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetAddressBook.setHorizontalHeaderItem(1, item) - self.tableWidgetAddressBook.horizontalHeader().setCascadingSectionResizes(True) - self.tableWidgetAddressBook.horizontalHeader().setDefaultSectionSize(200) - self.tableWidgetAddressBook.horizontalHeader().setHighlightSections(False) - self.tableWidgetAddressBook.horizontalHeader().setStretchLastSection(True) - self.tableWidgetAddressBook.verticalHeader().setVisible(False) - self.verticalSplitter_2.addWidget(self.tableWidgetAddressBook) - self.addressBookCompleter = AddressBookCompleter() - self.addressBookCompleter.setCompletionMode(QtGui.QCompleter.PopupCompletion) - self.addressBookCompleter.setCaseSensitivity(QtCore.Qt.CaseInsensitive) - self.addressBookCompleterModel = QtGui.QStringListModel() - self.addressBookCompleter.setModel(self.addressBookCompleterModel) - self.pushButtonAddAddressBook = QtGui.QPushButton(self.send) - self.pushButtonAddAddressBook.setObjectName(_fromUtf8("pushButtonAddAddressBook")) - self.pushButtonAddAddressBook.resize(200, self.pushButtonAddAddressBook.height()) - self.verticalSplitter_2.addWidget(self.pushButtonAddAddressBook) - self.pushButtonFetchNamecoinID = QtGui.QPushButton(self.send) - self.pushButtonFetchNamecoinID.resize(200, self.pushButtonFetchNamecoinID.height()) - self.pushButtonFetchNamecoinID.setObjectName(_fromUtf8("pushButtonFetchNamecoinID")) - self.verticalSplitter_2.addWidget(self.pushButtonFetchNamecoinID) - self.verticalSplitter_2.setStretchFactor(0, 1) - self.verticalSplitter_2.setStretchFactor(1, 0) - self.verticalSplitter_2.setStretchFactor(2, 0) - self.verticalSplitter_2.setCollapsible(0, False) - self.verticalSplitter_2.setCollapsible(1, False) - self.verticalSplitter_2.setCollapsible(2, False) - self.verticalSplitter_2.handle(1).setEnabled(False) - self.verticalSplitter_2.handle(2).setEnabled(False) - self.horizontalSplitter.addWidget(self.verticalSplitter_2) - self.verticalSplitter = settingsmixin.SSplitter() - self.verticalSplitter.setObjectName(_fromUtf8("verticalSplitter")) - self.verticalSplitter.setOrientation(QtCore.Qt.Vertical) - self.tabWidgetSend = QtGui.QTabWidget(self.send) - self.tabWidgetSend.setObjectName(_fromUtf8("tabWidgetSend")) - self.sendDirect = QtGui.QWidget() - self.sendDirect.setObjectName(_fromUtf8("sendDirect")) - self.gridLayout_8 = QtGui.QGridLayout(self.sendDirect) - self.gridLayout_8.setObjectName(_fromUtf8("gridLayout_8")) - self.verticalSplitter_5 = settingsmixin.SSplitter() - self.verticalSplitter_5.setObjectName(_fromUtf8("verticalSplitter_5")) - self.verticalSplitter_5.setOrientation(QtCore.Qt.Vertical) - self.gridLayout_2 = QtGui.QGridLayout() - self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) - self.label_3 = QtGui.QLabel(self.sendDirect) - self.label_3.setObjectName(_fromUtf8("label_3")) - self.gridLayout_2.addWidget(self.label_3, 2, 0, 1, 1) - self.label_2 = QtGui.QLabel(self.sendDirect) - self.label_2.setObjectName(_fromUtf8("label_2")) - self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 1) - self.lineEditSubject = QtGui.QLineEdit(self.sendDirect) - self.lineEditSubject.setText(_fromUtf8("")) - self.lineEditSubject.setObjectName(_fromUtf8("lineEditSubject")) - self.gridLayout_2.addWidget(self.lineEditSubject, 2, 1, 1, 1) - self.label = QtGui.QLabel(self.sendDirect) - self.label.setObjectName(_fromUtf8("label")) - self.gridLayout_2.addWidget(self.label, 1, 0, 1, 1) - self.comboBoxSendFrom = QtGui.QComboBox(self.sendDirect) - self.comboBoxSendFrom.setMinimumSize(QtCore.QSize(300, 0)) - self.comboBoxSendFrom.setObjectName(_fromUtf8("comboBoxSendFrom")) - self.gridLayout_2.addWidget(self.comboBoxSendFrom, 0, 1, 1, 1) - self.lineEditTo = QtGui.QLineEdit(self.sendDirect) - self.lineEditTo.setObjectName(_fromUtf8("lineEditTo")) - self.gridLayout_2.addWidget(self.lineEditTo, 1, 1, 1, 1) - self.lineEditTo.setCompleter(self.addressBookCompleter) - self.gridLayout_2_Widget = QtGui.QWidget() - self.gridLayout_2_Widget.setLayout(self.gridLayout_2) - self.verticalSplitter_5.addWidget(self.gridLayout_2_Widget) - self.textEditMessage = MessageCompose(self.sendDirect) - self.textEditMessage.setObjectName(_fromUtf8("textEditMessage")) - self.verticalSplitter_5.addWidget(self.textEditMessage) - self.verticalSplitter_5.setStretchFactor(0, 0) - self.verticalSplitter_5.setStretchFactor(1, 1) - self.verticalSplitter_5.setCollapsible(0, False) - self.verticalSplitter_5.setCollapsible(1, False) - self.verticalSplitter_5.handle(1).setEnabled(False) - self.gridLayout_8.addWidget(self.verticalSplitter_5, 0, 0, 1, 1) - self.tabWidgetSend.addTab(self.sendDirect, _fromUtf8("")) - self.sendBroadcast = QtGui.QWidget() - self.sendBroadcast.setObjectName(_fromUtf8("sendBroadcast")) - self.gridLayout_9 = QtGui.QGridLayout(self.sendBroadcast) - self.gridLayout_9.setObjectName(_fromUtf8("gridLayout_9")) - self.verticalSplitter_6 = settingsmixin.SSplitter() - self.verticalSplitter_6.setObjectName(_fromUtf8("verticalSplitter_6")) - self.verticalSplitter_6.setOrientation(QtCore.Qt.Vertical) - self.gridLayout_5 = QtGui.QGridLayout() - self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) - self.label_8 = QtGui.QLabel(self.sendBroadcast) - self.label_8.setObjectName(_fromUtf8("label_8")) - self.gridLayout_5.addWidget(self.label_8, 0, 0, 1, 1) - self.lineEditSubjectBroadcast = QtGui.QLineEdit(self.sendBroadcast) - self.lineEditSubjectBroadcast.setText(_fromUtf8("")) - self.lineEditSubjectBroadcast.setObjectName(_fromUtf8("lineEditSubjectBroadcast")) - self.gridLayout_5.addWidget(self.lineEditSubjectBroadcast, 1, 1, 1, 1) - self.label_7 = QtGui.QLabel(self.sendBroadcast) - self.label_7.setObjectName(_fromUtf8("label_7")) - self.gridLayout_5.addWidget(self.label_7, 1, 0, 1, 1) - self.comboBoxSendFromBroadcast = QtGui.QComboBox(self.sendBroadcast) - self.comboBoxSendFromBroadcast.setMinimumSize(QtCore.QSize(300, 0)) - self.comboBoxSendFromBroadcast.setObjectName(_fromUtf8("comboBoxSendFromBroadcast")) - self.gridLayout_5.addWidget(self.comboBoxSendFromBroadcast, 0, 1, 1, 1) - self.gridLayout_5_Widget = QtGui.QWidget() - self.gridLayout_5_Widget.setLayout(self.gridLayout_5) - self.verticalSplitter_6.addWidget(self.gridLayout_5_Widget) - self.textEditMessageBroadcast = MessageCompose(self.sendBroadcast) - self.textEditMessageBroadcast.setObjectName(_fromUtf8("textEditMessageBroadcast")) - self.verticalSplitter_6.addWidget(self.textEditMessageBroadcast) - self.verticalSplitter_6.setStretchFactor(0, 0) - self.verticalSplitter_6.setStretchFactor(1, 1) - self.verticalSplitter_6.setCollapsible(0, False) - self.verticalSplitter_6.setCollapsible(1, False) - self.verticalSplitter_6.handle(1).setEnabled(False) - self.gridLayout_9.addWidget(self.verticalSplitter_6, 0, 0, 1, 1) - self.tabWidgetSend.addTab(self.sendBroadcast, _fromUtf8("")) - self.verticalSplitter.addWidget(self.tabWidgetSend) - self.tTLContainer = QtGui.QWidget() - self.tTLContainer.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed) - self.horizontalLayout_5 = QtGui.QHBoxLayout() - self.tTLContainer.setLayout(self.horizontalLayout_5) - self.horizontalLayout_5.setObjectName(_fromUtf8("horizontalLayout_5")) - self.pushButtonTTL = QtGui.QPushButton(self.send) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.pushButtonTTL.sizePolicy().hasHeightForWidth()) - self.pushButtonTTL.setSizePolicy(sizePolicy) - palette = QtGui.QPalette() - brush = QtGui.QBrush(QtGui.QColor(0, 0, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush) - brush = QtGui.QBrush(QtGui.QColor(0, 0, 255)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush) - brush = QtGui.QBrush(QtGui.QColor(120, 120, 120)) - brush.setStyle(QtCore.Qt.SolidPattern) - palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush) - self.pushButtonTTL.setPalette(palette) - font = QtGui.QFont() - font.setUnderline(True) - self.pushButtonTTL.setFont(font) - self.pushButtonTTL.setFlat(True) - self.pushButtonTTL.setObjectName(_fromUtf8("pushButtonTTL")) - self.horizontalLayout_5.addWidget(self.pushButtonTTL, 0, QtCore.Qt.AlignRight) - self.horizontalSliderTTL = QtGui.QSlider(self.send) - self.horizontalSliderTTL.setMinimumSize(QtCore.QSize(70, 0)) - self.horizontalSliderTTL.setOrientation(QtCore.Qt.Horizontal) - self.horizontalSliderTTL.setInvertedAppearance(False) - self.horizontalSliderTTL.setInvertedControls(False) - self.horizontalSliderTTL.setObjectName(_fromUtf8("horizontalSliderTTL")) - self.horizontalLayout_5.addWidget(self.horizontalSliderTTL, 0, QtCore.Qt.AlignLeft) - self.labelHumanFriendlyTTLDescription = QtGui.QLabel(self.send) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.labelHumanFriendlyTTLDescription.sizePolicy().hasHeightForWidth()) - self.labelHumanFriendlyTTLDescription.setSizePolicy(sizePolicy) - self.labelHumanFriendlyTTLDescription.setMinimumSize(QtCore.QSize(45, 0)) - self.labelHumanFriendlyTTLDescription.setObjectName(_fromUtf8("labelHumanFriendlyTTLDescription")) - self.horizontalLayout_5.addWidget(self.labelHumanFriendlyTTLDescription, 1, QtCore.Qt.AlignLeft) - self.pushButtonClear = QtGui.QPushButton(self.send) - self.pushButtonClear.setObjectName(_fromUtf8("pushButtonClear")) - self.horizontalLayout_5.addWidget(self.pushButtonClear, 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) - self.horizontalSliderTTL.setMaximumSize(QtCore.QSize(105, self.pushButtonSend.height())) - self.verticalSplitter.addWidget(self.tTLContainer) - self.tTLContainer.adjustSize() - self.verticalSplitter.setStretchFactor(1, 0) - self.verticalSplitter.setStretchFactor(0, 1) - self.verticalSplitter.setCollapsible(0, False) - self.verticalSplitter.setCollapsible(1, False) - self.verticalSplitter.handle(1).setEnabled(False) - self.horizontalSplitter.addWidget(self.verticalSplitter) - self.horizontalSplitter.setStretchFactor(0, 0) - self.horizontalSplitter.setStretchFactor(1, 1) - self.horizontalSplitter.setCollapsible(0, False) - self.horizontalSplitter.setCollapsible(1, False) - self.gridLayout_7.addWidget(self.horizontalSplitter, 0, 0, 1, 1) - icon4 = QtGui.QIcon() - icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/send.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.tabWidget.addTab(self.send, icon4, _fromUtf8("")) - self.subscriptions = QtGui.QWidget() - self.subscriptions.setObjectName(_fromUtf8("subscriptions")) - self.gridLayout_3 = QtGui.QGridLayout(self.subscriptions) - self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) - self.horizontalSplitter_4 = settingsmixin.SSplitter() - self.horizontalSplitter_4.setObjectName(_fromUtf8("horizontalSplitter_4")) - self.verticalSplitter_3 = settingsmixin.SSplitter() - self.verticalSplitter_3.setObjectName(_fromUtf8("verticalSplitter_3")) - self.verticalSplitter_3.setOrientation(QtCore.Qt.Vertical) - self.treeWidgetSubscriptions = settingsmixin.STreeWidget(self.subscriptions) - self.treeWidgetSubscriptions.setAlternatingRowColors(True) - self.treeWidgetSubscriptions.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) - self.treeWidgetSubscriptions.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self.treeWidgetSubscriptions.setObjectName(_fromUtf8("treeWidgetSubscriptions")) - self.treeWidgetSubscriptions.resize(200, self.treeWidgetSubscriptions.height()) - icon5 = QtGui.QIcon() - icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/subscriptions.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) - self.treeWidgetSubscriptions.headerItem().setIcon(0, icon5) - self.verticalSplitter_3.addWidget(self.treeWidgetSubscriptions) - self.pushButtonAddSubscription = QtGui.QPushButton(self.subscriptions) - self.pushButtonAddSubscription.setObjectName(_fromUtf8("pushButtonAddSubscription")) - self.pushButtonAddSubscription.resize(200, self.pushButtonAddSubscription.height()) - self.verticalSplitter_3.addWidget(self.pushButtonAddSubscription) - self.verticalSplitter_3.setStretchFactor(0, 1) - self.verticalSplitter_3.setStretchFactor(1, 0) - self.verticalSplitter_3.setCollapsible(0, False) - self.verticalSplitter_3.setCollapsible(1, False) - self.verticalSplitter_3.handle(1).setEnabled(False) - self.horizontalSplitter_4.addWidget(self.verticalSplitter_3) - self.verticalSplitter_4 = settingsmixin.SSplitter() - self.verticalSplitter_4.setObjectName(_fromUtf8("verticalSplitter_4")) - self.verticalSplitter_4.setOrientation(QtCore.Qt.Vertical) - self.horizontalSplitter_2 = QtGui.QSplitter() - self.horizontalSplitter_2.setObjectName(_fromUtf8("horizontalSplitter_2")) - self.inboxSearchLineEditSubscriptions = QtGui.QLineEdit(self.subscriptions) - self.inboxSearchLineEditSubscriptions.setObjectName(_fromUtf8("inboxSearchLineEditSubscriptions")) - self.horizontalSplitter_2.addWidget(self.inboxSearchLineEditSubscriptions) - self.inboxSearchOptionSubscriptions = QtGui.QComboBox(self.subscriptions) - self.inboxSearchOptionSubscriptions.setObjectName(_fromUtf8("inboxSearchOptionSubscriptions")) - self.inboxSearchOptionSubscriptions.addItem(_fromUtf8("")) - self.inboxSearchOptionSubscriptions.addItem(_fromUtf8("")) - self.inboxSearchOptionSubscriptions.addItem(_fromUtf8("")) - self.inboxSearchOptionSubscriptions.addItem(_fromUtf8("")) - self.inboxSearchOptionSubscriptions.addItem(_fromUtf8("")) - self.inboxSearchOptionSubscriptions.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) - self.horizontalSplitter_2.addWidget(self.inboxSearchOptionSubscriptions) - self.horizontalSplitter_2.handle(1).setEnabled(False) - self.horizontalSplitter_2.setStretchFactor(0, 1) - self.horizontalSplitter_2.setStretchFactor(1, 0) - self.verticalSplitter_4.addWidget(self.horizontalSplitter_2) - self.tableWidgetInboxSubscriptions = settingsmixin.STableWidget(self.subscriptions) - self.tableWidgetInboxSubscriptions.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) - self.tableWidgetInboxSubscriptions.setAlternatingRowColors(True) - self.tableWidgetInboxSubscriptions.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) - self.tableWidgetInboxSubscriptions.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self.tableWidgetInboxSubscriptions.setWordWrap(False) - self.tableWidgetInboxSubscriptions.setObjectName(_fromUtf8("tableWidgetInboxSubscriptions")) - self.tableWidgetInboxSubscriptions.setColumnCount(4) - self.tableWidgetInboxSubscriptions.setRowCount(0) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxSubscriptions.setHorizontalHeaderItem(0, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxSubscriptions.setHorizontalHeaderItem(1, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxSubscriptions.setHorizontalHeaderItem(2, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxSubscriptions.setHorizontalHeaderItem(3, item) - self.tableWidgetInboxSubscriptions.horizontalHeader().setCascadingSectionResizes(True) - self.tableWidgetInboxSubscriptions.horizontalHeader().setDefaultSectionSize(200) - self.tableWidgetInboxSubscriptions.horizontalHeader().setHighlightSections(False) - self.tableWidgetInboxSubscriptions.horizontalHeader().setMinimumSectionSize(27) - self.tableWidgetInboxSubscriptions.horizontalHeader().setSortIndicatorShown(False) - self.tableWidgetInboxSubscriptions.horizontalHeader().setStretchLastSection(True) - self.tableWidgetInboxSubscriptions.verticalHeader().setVisible(False) - self.tableWidgetInboxSubscriptions.verticalHeader().setDefaultSectionSize(26) - self.verticalSplitter_4.addWidget(self.tableWidgetInboxSubscriptions) - self.textEditInboxMessageSubscriptions = MessageView(self.subscriptions) - self.textEditInboxMessageSubscriptions.setBaseSize(QtCore.QSize(0, 500)) - self.textEditInboxMessageSubscriptions.setReadOnly(True) - self.textEditInboxMessageSubscriptions.setObjectName(_fromUtf8("textEditInboxMessageSubscriptions")) - self.verticalSplitter_4.addWidget(self.textEditInboxMessageSubscriptions) - self.verticalSplitter_4.setStretchFactor(0, 0) - self.verticalSplitter_4.setStretchFactor(1, 1) - self.verticalSplitter_4.setStretchFactor(2, 2) - self.verticalSplitter_4.setCollapsible(0, False) - self.verticalSplitter_4.setCollapsible(1, False) - self.verticalSplitter_4.setCollapsible(2, False) - self.verticalSplitter_4.handle(1).setEnabled(False) - self.horizontalSplitter_4.addWidget(self.verticalSplitter_4) - self.horizontalSplitter_4.setStretchFactor(0, 0) - self.horizontalSplitter_4.setStretchFactor(1, 1) - self.horizontalSplitter_4.setCollapsible(0, False) - self.horizontalSplitter_4.setCollapsible(1, False) - self.gridLayout_3.addWidget(self.horizontalSplitter_4, 0, 0, 1, 1) - icon6 = QtGui.QIcon() - icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/subscriptions.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.tabWidget.addTab(self.subscriptions, icon6, _fromUtf8("")) - self.chans = QtGui.QWidget() - self.chans.setObjectName(_fromUtf8("chans")) - self.gridLayout_4 = QtGui.QGridLayout(self.chans) - self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) - self.horizontalSplitter_7 = settingsmixin.SSplitter() - self.horizontalSplitter_7.setObjectName(_fromUtf8("horizontalSplitter_7")) - self.verticalSplitter_17 = settingsmixin.SSplitter() - self.verticalSplitter_17.setObjectName(_fromUtf8("verticalSplitter_17")) - self.verticalSplitter_17.setOrientation(QtCore.Qt.Vertical) - self.treeWidgetChans = settingsmixin.STreeWidget(self.chans) - self.treeWidgetChans.setFrameShadow(QtGui.QFrame.Sunken) - self.treeWidgetChans.setLineWidth(1) - self.treeWidgetChans.setAlternatingRowColors(True) - self.treeWidgetChans.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) - self.treeWidgetChans.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self.treeWidgetChans.setObjectName(_fromUtf8("treeWidgetChans")) - self.treeWidgetChans.resize(200, self.treeWidgetChans.height()) - icon7 = QtGui.QIcon() - icon7.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-16px.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) - self.treeWidgetChans.headerItem().setIcon(0, icon7) - self.verticalSplitter_17.addWidget(self.treeWidgetChans) - self.pushButtonAddChan = QtGui.QPushButton(self.chans) - self.pushButtonAddChan.setObjectName(_fromUtf8("pushButtonAddChan")) - self.pushButtonAddChan.resize(200, self.pushButtonAddChan.height()) - self.verticalSplitter_17.addWidget(self.pushButtonAddChan) - self.verticalSplitter_17.setStretchFactor(0, 1) - self.verticalSplitter_17.setStretchFactor(1, 0) - self.verticalSplitter_17.setCollapsible(0, False) - self.verticalSplitter_17.setCollapsible(1, False) - self.verticalSplitter_17.handle(1).setEnabled(False) - self.horizontalSplitter_7.addWidget(self.verticalSplitter_17) - self.verticalSplitter_8 = settingsmixin.SSplitter() - self.verticalSplitter_8.setObjectName(_fromUtf8("verticalSplitter_8")) - self.verticalSplitter_8.setOrientation(QtCore.Qt.Vertical) - self.horizontalSplitter_6 = QtGui.QSplitter() - self.horizontalSplitter_6.setObjectName(_fromUtf8("horizontalSplitter_6")) - self.inboxSearchLineEditChans = QtGui.QLineEdit(self.chans) - self.inboxSearchLineEditChans.setObjectName(_fromUtf8("inboxSearchLineEditChans")) - self.horizontalSplitter_6.addWidget(self.inboxSearchLineEditChans) - self.inboxSearchOptionChans = QtGui.QComboBox(self.chans) - self.inboxSearchOptionChans.setObjectName(_fromUtf8("inboxSearchOptionChans")) - self.inboxSearchOptionChans.addItem(_fromUtf8("")) - self.inboxSearchOptionChans.addItem(_fromUtf8("")) - self.inboxSearchOptionChans.addItem(_fromUtf8("")) - self.inboxSearchOptionChans.addItem(_fromUtf8("")) - self.inboxSearchOptionChans.addItem(_fromUtf8("")) - self.inboxSearchOptionChans.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) - self.horizontalSplitter_6.addWidget(self.inboxSearchOptionChans) - self.horizontalSplitter_6.handle(1).setEnabled(False) - self.horizontalSplitter_6.setStretchFactor(0, 1) - self.horizontalSplitter_6.setStretchFactor(1, 0) - self.verticalSplitter_8.addWidget(self.horizontalSplitter_6) - self.tableWidgetInboxChans = settingsmixin.STableWidget(self.chans) - self.tableWidgetInboxChans.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) - self.tableWidgetInboxChans.setAlternatingRowColors(True) - self.tableWidgetInboxChans.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) - self.tableWidgetInboxChans.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self.tableWidgetInboxChans.setWordWrap(False) - self.tableWidgetInboxChans.setObjectName(_fromUtf8("tableWidgetInboxChans")) - self.tableWidgetInboxChans.setColumnCount(4) - self.tableWidgetInboxChans.setRowCount(0) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxChans.setHorizontalHeaderItem(0, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxChans.setHorizontalHeaderItem(1, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxChans.setHorizontalHeaderItem(2, item) - item = QtGui.QTableWidgetItem() - self.tableWidgetInboxChans.setHorizontalHeaderItem(3, item) - self.tableWidgetInboxChans.horizontalHeader().setCascadingSectionResizes(True) - self.tableWidgetInboxChans.horizontalHeader().setDefaultSectionSize(200) - self.tableWidgetInboxChans.horizontalHeader().setHighlightSections(False) - self.tableWidgetInboxChans.horizontalHeader().setMinimumSectionSize(27) - self.tableWidgetInboxChans.horizontalHeader().setSortIndicatorShown(False) - self.tableWidgetInboxChans.horizontalHeader().setStretchLastSection(True) - self.tableWidgetInboxChans.verticalHeader().setVisible(False) - self.tableWidgetInboxChans.verticalHeader().setDefaultSectionSize(26) - self.verticalSplitter_8.addWidget(self.tableWidgetInboxChans) - self.textEditInboxMessageChans = MessageView(self.chans) - self.textEditInboxMessageChans.setBaseSize(QtCore.QSize(0, 500)) - self.textEditInboxMessageChans.setReadOnly(True) - self.textEditInboxMessageChans.setObjectName(_fromUtf8("textEditInboxMessageChans")) - self.verticalSplitter_8.addWidget(self.textEditInboxMessageChans) - self.verticalSplitter_8.setStretchFactor(0, 0) - self.verticalSplitter_8.setStretchFactor(1, 1) - self.verticalSplitter_8.setStretchFactor(2, 2) - self.verticalSplitter_8.setCollapsible(0, False) - self.verticalSplitter_8.setCollapsible(1, False) - self.verticalSplitter_8.setCollapsible(2, False) - self.verticalSplitter_8.handle(1).setEnabled(False) - self.horizontalSplitter_7.addWidget(self.verticalSplitter_8) - self.horizontalSplitter_7.setStretchFactor(0, 0) - self.horizontalSplitter_7.setStretchFactor(1, 1) - self.horizontalSplitter_7.setCollapsible(0, False) - self.horizontalSplitter_7.setCollapsible(1, False) - self.gridLayout_4.addWidget(self.horizontalSplitter_7, 0, 0, 1, 1) - icon8 = QtGui.QIcon() - icon8.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-16px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.tabWidget.addTab(self.chans, icon8, _fromUtf8("")) - self.blackwhitelist = Blacklist() - self.tabWidget.addTab(self.blackwhitelist, QtGui.QIcon(":/newPrefix/images/blacklist.png"), "") - # Initialize the Blacklist or Whitelist - if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'white': - self.blackwhitelist.radioButtonWhitelist.click() - self.blackwhitelist.rerenderBlackWhiteList() - - self.networkstatus = NetworkStatus() - self.tabWidget.addTab(self.networkstatus, QtGui.QIcon(":/newPrefix/images/networkstatus.png"), "") - self.gridLayout_10.addWidget(self.tabWidget, 0, 0, 1, 1) - MainWindow.setCentralWidget(self.centralwidget) - self.menubar = QtGui.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 885, 27)) - self.menubar.setObjectName(_fromUtf8("menubar")) - self.menuFile = QtGui.QMenu(self.menubar) - self.menuFile.setObjectName(_fromUtf8("menuFile")) - self.menuSettings = QtGui.QMenu(self.menubar) - self.menuSettings.setObjectName(_fromUtf8("menuSettings")) - self.menuHelp = QtGui.QMenu(self.menubar) - self.menuHelp.setObjectName(_fromUtf8("menuHelp")) - MainWindow.setMenuBar(self.menubar) - self.statusbar = QtGui.QStatusBar(MainWindow) - self.statusbar.setMaximumSize(QtCore.QSize(16777215, 22)) - self.statusbar.setObjectName(_fromUtf8("statusbar")) - MainWindow.setStatusBar(self.statusbar) - self.actionImport_keys = QtGui.QAction(MainWindow) - self.actionImport_keys.setObjectName(_fromUtf8("actionImport_keys")) - self.actionManageKeys = QtGui.QAction(MainWindow) - self.actionManageKeys.setCheckable(False) - self.actionManageKeys.setEnabled(True) - icon = QtGui.QIcon.fromTheme(_fromUtf8("dialog-password")) - self.actionManageKeys.setIcon(icon) - self.actionManageKeys.setObjectName(_fromUtf8("actionManageKeys")) - self.actionNetworkSwitch = QtGui.QAction(MainWindow) - self.actionNetworkSwitch.setObjectName(_fromUtf8("actionNetworkSwitch")) - self.actionExit = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("application-exit")) - self.actionExit.setIcon(icon) - self.actionExit.setObjectName(_fromUtf8("actionExit")) - self.actionHelp = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("help-contents")) - self.actionHelp.setIcon(icon) - self.actionHelp.setObjectName(_fromUtf8("actionHelp")) - self.actionSupport = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("help-support")) - self.actionSupport.setIcon(icon) - self.actionSupport.setObjectName(_fromUtf8("actionSupport")) - self.actionAbout = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("help-about")) - self.actionAbout.setIcon(icon) - self.actionAbout.setObjectName(_fromUtf8("actionAbout")) - self.actionSettings = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("document-properties")) - self.actionSettings.setIcon(icon) - self.actionSettings.setObjectName(_fromUtf8("actionSettings")) - self.actionRegenerateDeterministicAddresses = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("view-refresh")) - self.actionRegenerateDeterministicAddresses.setIcon(icon) - self.actionRegenerateDeterministicAddresses.setObjectName(_fromUtf8("actionRegenerateDeterministicAddresses")) - self.actionDeleteAllTrashedMessages = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("user-trash")) - self.actionDeleteAllTrashedMessages.setIcon(icon) - self.actionDeleteAllTrashedMessages.setObjectName(_fromUtf8("actionDeleteAllTrashedMessages")) - self.actionJoinChan = QtGui.QAction(MainWindow) - icon = QtGui.QIcon.fromTheme(_fromUtf8("contact-new")) - self.actionJoinChan.setIcon(icon) - self.actionJoinChan.setObjectName(_fromUtf8("actionJoinChan")) - self.menuFile.addAction(self.actionManageKeys) - self.menuFile.addAction(self.actionDeleteAllTrashedMessages) - self.menuFile.addAction(self.actionRegenerateDeterministicAddresses) - self.menuFile.addAction(self.actionNetworkSwitch) - self.menuFile.addAction(self.actionExit) - self.menuSettings.addAction(self.actionSettings) - self.menuHelp.addAction(self.actionHelp) - self.menuHelp.addAction(self.actionSupport) - self.menuHelp.addAction(self.actionAbout) - self.menubar.addAction(self.menuFile.menuAction()) - self.menubar.addAction(self.menuSettings.menuAction()) - self.menubar.addAction(self.menuHelp.menuAction()) - - self.retranslateUi(MainWindow) - self.tabWidget.setCurrentIndex( - self.tabWidget.indexOf(self.inbox) - ) - self.tabWidgetSend.setCurrentIndex( - self.tabWidgetSend.indexOf(self.sendDirect) - ) - QtCore.QMetaObject.connectSlotsByName(MainWindow) - MainWindow.setTabOrder(self.tableWidgetInbox, self.textEditInboxMessage) - MainWindow.setTabOrder(self.textEditInboxMessage, self.comboBoxSendFrom) - MainWindow.setTabOrder(self.comboBoxSendFrom, self.lineEditTo) - MainWindow.setTabOrder(self.lineEditTo, self.lineEditSubject) - MainWindow.setTabOrder(self.lineEditSubject, self.textEditMessage) - MainWindow.setTabOrder(self.textEditMessage, self.pushButtonAddSubscription) - - def updateNetworkSwitchMenuLabel(self, dontconnect=None): - if dontconnect is None: - dontconnect = BMConfigParser().safeGetBoolean( - 'bitmessagesettings', 'dontconnect') - self.actionNetworkSwitch.setText( - _translate("MainWindow", "Go online", None) - if dontconnect else - _translate("MainWindow", "Go offline", None) - ) - - def retranslateUi(self, MainWindow): - MainWindow.setWindowTitle(_translate("MainWindow", "Bitmessage", None)) - self.treeWidgetYourIdentities.headerItem().setText(0, _translate("MainWindow", "Identities", None)) - self.pushButtonNewAddress.setText(_translate("MainWindow", "New Identity", None)) - self.inboxSearchLineEdit.setPlaceholderText(_translate("MainWindow", "Search", None)) - self.inboxSearchOption.setItemText(0, _translate("MainWindow", "All", None)) - self.inboxSearchOption.setItemText(1, _translate("MainWindow", "To", None)) - self.inboxSearchOption.setItemText(2, _translate("MainWindow", "From", None)) - self.inboxSearchOption.setItemText(3, _translate("MainWindow", "Subject", None)) - self.inboxSearchOption.setItemText(4, _translate("MainWindow", "Message", None)) - self.tableWidgetInbox.setSortingEnabled(True) - item = self.tableWidgetInbox.horizontalHeaderItem(0) - item.setText(_translate("MainWindow", "To", None)) - item = self.tableWidgetInbox.horizontalHeaderItem(1) - item.setText(_translate("MainWindow", "From", None)) - item = self.tableWidgetInbox.horizontalHeaderItem(2) - item.setText(_translate("MainWindow", "Subject", None)) - item = self.tableWidgetInbox.horizontalHeaderItem(3) - item.setText(_translate("MainWindow", "Received", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.inbox), _translate("MainWindow", "Messages", None)) - self.tableWidgetAddressBook.setSortingEnabled(True) - item = self.tableWidgetAddressBook.horizontalHeaderItem(0) - item.setText(_translate("MainWindow", "Address book", None)) - item = self.tableWidgetAddressBook.horizontalHeaderItem(1) - item.setText(_translate("MainWindow", "Address", None)) - self.pushButtonAddAddressBook.setText(_translate("MainWindow", "Add Contact", None)) - self.pushButtonFetchNamecoinID.setText(_translate("MainWindow", "Fetch Namecoin ID", None)) - self.label_3.setText(_translate("MainWindow", "Subject:", None)) - self.label_2.setText(_translate("MainWindow", "From:", None)) - self.label.setText(_translate("MainWindow", "To:", None)) - #self.textEditMessage.setHtml("") - self.tabWidgetSend.setTabText(self.tabWidgetSend.indexOf(self.sendDirect), _translate("MainWindow", "Send ordinary Message", None)) - self.label_8.setText(_translate("MainWindow", "From:", None)) - self.label_7.setText(_translate("MainWindow", "Subject:", None)) - #self.textEditMessageBroadcast.setHtml("") - self.tabWidgetSend.setTabText(self.tabWidgetSend.indexOf(self.sendBroadcast), _translate("MainWindow", "Send Message to your Subscribers", None)) - self.pushButtonTTL.setText(_translate("MainWindow", "TTL:", None)) - hours = 48 - try: - hours = int(BMConfigParser().getint('bitmessagesettings', 'ttl')/60/60) - except: - pass - self.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, hours)) - self.pushButtonClear.setText(_translate("MainWindow", "Clear", 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)) - self.pushButtonAddSubscription.setText(_translate("MainWindow", "Add new Subscription", None)) - self.inboxSearchLineEditSubscriptions.setPlaceholderText(_translate("MainWindow", "Search", None)) - self.inboxSearchOptionSubscriptions.setItemText(0, _translate("MainWindow", "All", None)) - self.inboxSearchOptionSubscriptions.setItemText(1, _translate("MainWindow", "To", None)) - self.inboxSearchOptionSubscriptions.setItemText(2, _translate("MainWindow", "From", None)) - self.inboxSearchOptionSubscriptions.setItemText(3, _translate("MainWindow", "Subject", None)) - self.inboxSearchOptionSubscriptions.setItemText(4, _translate("MainWindow", "Message", None)) - self.tableWidgetInboxSubscriptions.setSortingEnabled(True) - item = self.tableWidgetInboxSubscriptions.horizontalHeaderItem(0) - item.setText(_translate("MainWindow", "To", None)) - item = self.tableWidgetInboxSubscriptions.horizontalHeaderItem(1) - item.setText(_translate("MainWindow", "From", None)) - item = self.tableWidgetInboxSubscriptions.horizontalHeaderItem(2) - item.setText(_translate("MainWindow", "Subject", None)) - item = self.tableWidgetInboxSubscriptions.horizontalHeaderItem(3) - item.setText(_translate("MainWindow", "Received", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.subscriptions), _translate("MainWindow", "Subscriptions", None)) - self.treeWidgetChans.headerItem().setText(0, _translate("MainWindow", "Chans", None)) - self.pushButtonAddChan.setText(_translate("MainWindow", "Add Chan", None)) - self.inboxSearchLineEditChans.setPlaceholderText(_translate("MainWindow", "Search", None)) - self.inboxSearchOptionChans.setItemText(0, _translate("MainWindow", "All", None)) - self.inboxSearchOptionChans.setItemText(1, _translate("MainWindow", "To", None)) - self.inboxSearchOptionChans.setItemText(2, _translate("MainWindow", "From", None)) - self.inboxSearchOptionChans.setItemText(3, _translate("MainWindow", "Subject", None)) - self.inboxSearchOptionChans.setItemText(4, _translate("MainWindow", "Message", None)) - self.tableWidgetInboxChans.setSortingEnabled(True) - item = self.tableWidgetInboxChans.horizontalHeaderItem(0) - item.setText(_translate("MainWindow", "To", None)) - item = self.tableWidgetInboxChans.horizontalHeaderItem(1) - item.setText(_translate("MainWindow", "From", None)) - item = self.tableWidgetInboxChans.horizontalHeaderItem(2) - item.setText(_translate("MainWindow", "Subject", None)) - item = self.tableWidgetInboxChans.horizontalHeaderItem(3) - item.setText(_translate("MainWindow", "Received", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.chans), _translate("MainWindow", "Chans", None)) - self.blackwhitelist.retranslateUi() - self.tabWidget.setTabText(self.tabWidget.indexOf(self.blackwhitelist), _translate("blacklist", "Blacklist", None)) - self.networkstatus.retranslateUi() - self.tabWidget.setTabText(self.tabWidget.indexOf(self.networkstatus), _translate("networkstatus", "Network Status", None)) - self.menuFile.setTitle(_translate("MainWindow", "File", None)) - self.menuSettings.setTitle(_translate("MainWindow", "Settings", None)) - self.menuHelp.setTitle(_translate("MainWindow", "Help", None)) - self.actionImport_keys.setText(_translate("MainWindow", "Import keys", None)) - self.actionManageKeys.setText(_translate("MainWindow", "Manage keys", None)) - self.actionExit.setText(_translate("MainWindow", "Quit", None)) - self.actionExit.setShortcut(_translate("MainWindow", "Ctrl+Q", None)) - self.actionHelp.setText(_translate("MainWindow", "Help", None)) - self.actionHelp.setShortcut(_translate("MainWindow", "F1", None)) - self.actionSupport.setText(_translate("MainWindow", "Contact support", None)) - self.actionAbout.setText(_translate("MainWindow", "About", None)) - self.actionSettings.setText(_translate("MainWindow", "Settings", None)) - self.actionRegenerateDeterministicAddresses.setText(_translate("MainWindow", "Regenerate deterministic addresses", None)) - self.actionDeleteAllTrashedMessages.setText(_translate("MainWindow", "Delete all trashed messages", None)) - self.actionJoinChan.setText(_translate("MainWindow", "Join / Create chan", None)) - -import bitmessage_icons_rc - -if __name__ == "__main__": - import sys - - app = QtGui.QApplication(sys.argv) - MainWindow = settingsmixin.SMainWindow() - ui = Ui_MainWindow() - ui.setupUi(MainWindow) - MainWindow.show() - sys.exit(app.exec_()) - diff --git a/src/bitmessageqt/bitmessageui.ui b/src/bitmessageqt/bitmessageui.ui index fef40be6..f43fdd6a 100644 --- a/src/bitmessageqt/bitmessageui.ui +++ b/src/bitmessageqt/bitmessageui.ui @@ -14,7 +14,7 @@ Bitmessage - + :/newPrefix/images/can-icon-24px.png:/newPrefix/images/can-icon-24px.png @@ -22,6 +22,42 @@ + + + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + :/newPrefix/images/redicon.png:/newPrefix/images/redicon.png + + + true + + + + + + @@ -58,7 +94,7 @@ - + :/newPrefix/images/inbox.png:/newPrefix/images/inbox.png @@ -66,177 +102,198 @@ - - - - - - - - 200 - 16777215 - - - - - Identities - - - - :/newPrefix/images/identities.png - - - - - - - - - - 200 - 16777215 - - + + + Qt::Horizontal + + + false + + + + Qt::Vertical + + + false + + + + + 0 + 1 + + + + + Identities + + + + :/newPrefix/images/identities.png + + + + + + + New Identity + + + + + + + 1 + 0 + + + + Qt::Vertical + + + false + + + + Qt::Horizontal + + + + + 1 + 0 + + + + Search + + + + + QComboBox::AdjustToContents + + - New Indentitiy + All - - - - - - - - - - 0 + + + + To - - - - Search - - - - - - - - All - - - - - To - - - - - From - - - - - Subject - - - - - Message - - - - - - - - - - QAbstractItemView::NoEditTriggers + + + + From - - true + + + + Subject - - QAbstractItemView::ExtendedSelection + + + + Message - - QAbstractItemView::SelectRows - - - true - - - false - - - true - - - 200 - - - false - - - 27 - - - false - - - true - - - false - - - 26 - - - - To - - - - - From - - - - - Subject - - - - - Received - - - - - - - - - 0 - 500 - - - - true - - - - - - + + + + + + + 0 + 1 + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + true + + + 200 + + + false + + + 27 + + + false + + + true + + + false + + + 26 + + + + To + + + + + From + + + + + Subject + + + + + Received + + + + + + + 0 + 2 + + + + + 0 + 500 + + + + true + + + + - + :/newPrefix/images/send.png:/newPrefix/images/send.png @@ -244,368 +301,363 @@ - - - - - - - - 200 - 16777215 - - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - true - - - true - - - 200 - - - false - - - true - - - false - - - - Address book - - - - :/newPrefix/images/addressbook.png - - - - - - Address - - - - - - - - - 200 - 16777215 - - - - Add Contact - - - - - - - - 200 - 16777215 - - - - - 9 - - - - Fetch Namecoin ID - - - - - - - - - - - 0 - - - - Send ordinary Message - - - - - - - - - - Subject: - - - - - - - From: - - - - - - - - - - - - - - To: - - - - - - - - 300 - 0 - - - - - - - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Droid Sans'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2';"><br /></p></body></html> - - - - - - - - - - Send Message to your Subscribers - - - - - - - - - - From: - - - - - - - - - - - - - - Subject: - - - - - - - - 300 - 0 - - - - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Droid Sans'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2';"><br /></p></body></html> - - - - - - - - - - - - - - - - 0 - 0 - - - - - 32 - 16777215 - - - - - - - - - 0 - 0 - 255 - - - - - - - - - 0 - 0 - 255 - - - - - - - - - 120 - 120 - 120 - - - - - - - - - true - - - - TTL: - - - true - - - - - - - - 35 - 0 - - - - - 70 - 16777215 - - + + + Qt::Horizontal + + + false + + + + Qt::Vertical + + + false + + + + + 0 + 1 + + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + 200 + + + false + + + true + + + false + + + + Address book + + + + :/newPrefix/images/addressbook.png + + + + + + Address + + + + + + Add Contact + + + + + Fetch Namecoin ID + + + + + + + 1 + 0 + + + + Qt::Vertical + + + false + + + + + 0 + 1 + + + + 0 + + + + Send ordinary Message + + + + - Qt::Horizontal + Qt::Vertical - + false - - false - - - - - - - - 0 - 0 - - - - - 45 - 0 - - - - - 45 - 16777215 - - - - X days - - - - - - - - 16777215 - 16777215 - - - - Send - + + + + + + From: + + + + + + + + 300 + 0 + + + + + + + + To: + + + + + + + + + + Subject: + + + + + + + + + + + + + + + + 0 + 1 + + + - - - - + + + + Send Message to your Subscribers + + + + + + Qt::Vertical + + + false + + + + + + + From: + + + + + + + + + + + + + + Subject: + + + + + + + + 300 + 0 + + + + + + + + + + 0 + 1 + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + 255 + + + + + + + + + 0 + 0 + 255 + + + + + + + + + 120 + 120 + 120 + + + + + + + + + true + + + + TTL: + + + true + + + + + + + + 70 + 0 + + + + + 105 + 16777215 + + + + Qt::Horizontal + + + false + + + false + + + + + + + + 1 + 0 + + + + + 45 + 0 + + + + %n hour(s) + + + + + + + Clear + + + + + + + Send + + + + + + + - - + :/newPrefix/images/subscriptions.png:/newPrefix/images/subscriptions.png @@ -613,183 +665,207 @@ p, li { white-space: pre-wrap; } - - - - - - - - 200 - 16777215 - - - - true - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - - Subscriptions - - - - :/newPrefix/images/subscriptions.png - - - - - - - - - - 200 - 16777215 - - + + + Qt::Horizontal + + + false + + + + Qt::Vertical + + + false + + + + + 0 + 1 + + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + Subscriptions + + + + :/newPrefix/images/subscriptions.png + + + + + + + Add new Subscription + + + + + + + 1 + 0 + + + + Qt::Vertical + + + false + + + + Qt::Horizontal + + + + + 1 + 0 + + + + Search + + + + + QComboBox::AdjustToContents + + - Add new Subscription + All - - - - - - - - - - - - Search - - - - - - - - All - - - - - To - - - - - From - - - - - Subject - - - - - Message - - - - - - - - - - QAbstractItemView::NoEditTriggers + + + + To - - true + + + + From - - QAbstractItemView::ExtendedSelection + + + + Subject - - QAbstractItemView::SelectRows + + + + Message - - true - - - false - - - true - - - 200 - - - false - - - 27 - - - false - - - true - - - false - - - 26 - - - - To - - - - - From - - - - - Subject - - - - - Received - - - - - - - - - 0 - 500 - - - - true - - - - - - + + + + + + + 0 + 1 + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + true + + + 200 + + + false + + + 27 + + + false + + + true + + + false + + + 26 + + + + To + + + + + From + + + + + Subject + + + + + Received + + + + + + + 0 + 2 + + + + + 0 + 500 + + + + true + + + + - + - + :/newPrefix/images/can-icon-16px.png:/newPrefix/images/can-icon-16px.png @@ -797,494 +873,227 @@ p, li { white-space: pre-wrap; } - - - - - - - - 200 - 16777215 - - - - QFrame::Sunken - - - 1 - - - true - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - - Chans - - - - :/newPrefix/images/can-icon-16px.png - - - - - - - - - - 200 - 16777215 - - + + + Qt::Horizontal + + + false + + + + Qt::Vertical + + + false + + + + + 0 + 1 + + + + QFrame::Sunken + + + 1 + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + Chans + + + + :/newPrefix/images/can-icon-16px.png + + + + + + + Add Chan + + + + + + + 1 + 0 + + + + Qt::Vertical + + + false + + + + Qt::Horizontal + + + + + 1 + 0 + + + + Search + + + + + QComboBox::AdjustToContents + + - Add Chan + All - - - - - - - - - - - - Search - - - - - - - - All - - - - - To - - - - - From - - - - - Subject - - - - - Message - - - - - - - - - - QAbstractItemView::NoEditTriggers + + + + To - - true + + + + From - - QAbstractItemView::ExtendedSelection + + + + Subject - - QAbstractItemView::SelectRows + + + + Message - - true - - - false - - - true - - - 200 - - - false - - - 27 - - - false - - - true - - - false - - - 26 - - - - To - - - - - From - - - - - Subject - - - - - Received - - - - - - - - - 0 - 500 - - - - true - - - - - - + + + + + + + 0 + 1 + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + true + + + 200 + + + false + + + 27 + + + false + + + true + + + false + + + 26 + + + + To + + + + + From + + + + + Subject + + + + + Received + + + + + + + 0 + 2 + + + + + 0 + 500 + + + + true + + + + - + - + :/newPrefix/images/blacklist.png:/newPrefix/images/blacklist.png Blacklist - - - - - Use a Blacklist (Allow all incoming messages except those on the Blacklist) - - - true - - - - - - - Use a Whitelist (Block all incoming messages except those on the Whitelist) - - - - - - - Add new entry - - - - - - - Qt::Horizontal - - - - 689 - 20 - - - - - - - - true - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - true - - - true - - - 400 - - - false - - - false - - - true - - - false - - - - Name or Label - - - - - Address - - - - - - + - + :/newPrefix/images/networkstatus.png:/newPrefix/images/networkstatus.png Network Status - - - - 680 - 440 - 21 - 23 - - - - - - - - :/newPrefix/images/redicon.png:/newPrefix/images/redicon.png - - - true - - - - - - 20 - 70 - 241 - 241 - - - - - - - - - 212 - 208 - 200 - - - - - - - - - 212 - 208 - 200 - - - - - - - - - 212 - 208 - 200 - - - - - - - - QFrame::Box - - - QFrame::Plain - - - false - - - true - - - QAbstractItemView::NoSelection - - - true - - - false - - - true - - - false - - - - Stream # - - - - - Connections - - - - - - - 20 - 30 - 401 - 16 - - - - Total connections: - - - - - - 320 - 110 - 331 - 20 - - - - Since startup: - - - - - - 350 - 130 - 361 - 16 - - - - Processed 0 person-to-person messages. - - - - - - 350 - 170 - 331 - 16 - - - - Processed 0 public keys. - - - - - - 350 - 150 - 351 - 16 - - - - Processed 0 broadcasts. - - - - - - 320 - 250 - 291 - 16 - - - - Inventory lookups per second: 0 - - - - - - 350 - 210 - 251 - 16 - - - - Down: 0 KB/s - - - - - - 350 - 230 - 251 - 16 - - - - Up: 0 KB/s - - @@ -1296,7 +1105,7 @@ p, li { white-space: pre-wrap; } 0 0 885 - 27 + 30 @@ -1306,6 +1115,7 @@ p, li { white-space: pre-wrap; } + @@ -1319,20 +1129,14 @@ p, li { white-space: pre-wrap; } Help + - - - - 16777215 - 22 - - - + Import keys @@ -1354,6 +1158,11 @@ p, li { white-space: pre-wrap; } Manage keys + + + Go offline + + @@ -1380,6 +1189,16 @@ p, li { white-space: pre-wrap; } F1 + + + + + + + + Contact support + + @@ -1431,6 +1250,51 @@ p, li { white-space: pre-wrap; } + + + SSplitter + QSplitter +
bitmessageqt.settingsmixin.h
+ 1 +
+ + STreeWidget + QTreeWidget +
bitmessageqt.settingsmixin.h
+
+ + STableWidget + QTableWidget +
bitmessageqt.settingsmixin.h
+
+ + MessageView + QTextEdit +
bitmessageqt.messageview.h
+
+ + MessageCompose + QTextEdit +
bitmessageqt.messagecompose.h
+
+ + Blacklist + QWidget +
bitmessageqt.blacklist.h
+ 1 +
+ + NetworkStatus + QWidget +
bitmessageqt.networkstatus.h
+ 1 +
+ + BMStatusBar + QStatusBar +
bitmessageqt.statusbar.h
+
+
tableWidgetInbox textEditInboxMessage @@ -1438,17 +1302,8 @@ p, li { white-space: pre-wrap; } lineEditTo lineEditSubject textEditMessage - pushButtonSend pushButtonAddSubscription - radioButtonBlacklist - radioButtonWhitelist - pushButtonAddBlacklist - tableWidgetBlacklist - tableWidgetConnectionCount - pushButtonStatusIcon - - - + diff --git a/src/bitmessageqt/blacklist.py b/src/bitmessageqt/blacklist.py index 64413ebb..d9f5f9c8 100644 --- a/src/bitmessageqt/blacklist.py +++ b/src/bitmessageqt/blacklist.py @@ -240,3 +240,13 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): def on_action_BlacklistSetAvatar(self): self.window().on_action_SetAvatar(self.tableWidgetBlacklist) + def retranslateUi(self): + super(self.__class__, self).retranslateUi() + + tabs = self.parent().parent() + + if BMConfigParser().get("bitmessagesettings", "blackwhitelist") == "black": + tabs.setTabText(tabs.indexOf(self), _translate("blacklist", "Blacklist")) + else: + tabs.setTabText(tabs.indexOf(self), _translate("blacklist", "Whitelist")) + diff --git a/src/bitmessageqt/languagebox.py b/src/bitmessageqt/languagebox.py deleted file mode 100644 index 9032cc42..00000000 --- a/src/bitmessageqt/languagebox.py +++ /dev/null @@ -1,36 +0,0 @@ -import glob -import os -from PyQt4 import QtCore, QtGui - -from bmconfigparser import BMConfigParser -import paths - -class LanguageBox(QtGui.QComboBox): - languageName = {"system": "System Settings", "eo": "Esperanto", "en_pirate": "Pirate English"} - def __init__(self, parent = None): - super(QtGui.QComboBox, self).__init__(parent) - self.populate() - - def populate(self): - self.clear() - localesPath = os.path.join (paths.codePath(), 'translations') - self.addItem(QtGui.QApplication.translate("settingsDialog", "System Settings", "system"), "system") - self.setCurrentIndex(0) - self.setInsertPolicy(QtGui.QComboBox.InsertAlphabetically) - for translationFile in sorted(glob.glob(os.path.join(localesPath, "bitmessage_*.qm"))): - localeShort = os.path.split(translationFile)[1].split("_", 1)[1][:-3] - locale = QtCore.QLocale(QtCore.QString(localeShort)) - - if localeShort in LanguageBox.languageName: - self.addItem(LanguageBox.languageName[localeShort], localeShort) - elif locale.nativeLanguageName() == "": - self.addItem(localeShort, localeShort) - else: - self.addItem(locale.nativeLanguageName(), localeShort) - - configuredLocale = BMConfigParser().safeGet( - 'bitmessagesettings', 'userlocale', "system") - for i in range(self.count()): - if self.itemData(i) == configuredLocale: - self.setCurrentIndex(i) - break diff --git a/src/bitmessageqt/networkstatus.py b/src/bitmessageqt/networkstatus.py index 3691d5b3..256f8210 100644 --- a/src/bitmessageqt/networkstatus.py +++ b/src/bitmessageqt/networkstatus.py @@ -168,5 +168,9 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin): def retranslateUi(self): super(NetworkStatus, self).retranslateUi() + + tabs = self.parent().parent() + tabs.setTabText(tabs.indexOf(self), _translate("networkstatus", "Network Status")) + self.labelStartupTime.setText(_translate("networkstatus", "Since startup on %1").arg( l10n.formatTimestamp(self.startup))) diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py deleted file mode 100644 index 22c38a62..00000000 --- a/src/bitmessageqt/settings.py +++ /dev/null @@ -1,630 +0,0 @@ -# -*- coding: utf-8 -*- -# pylint: disable=too-many-instance-attributes,too-many-locals,too-many-statements,attribute-defined-outside-init -""" -Form implementation generated from reading ui file 'settings.ui' - -Created: Thu Dec 25 23:21:20 2014 - by: PyQt4 UI code generator 4.10.3 - -WARNING! All changes made in this file will be lost! -""" - -from __future__ import absolute_import - -from sys import platform - -from PyQt4 import QtCore, QtGui - -from . import bitmessage_icons_rc # pylint: disable=unused-import -from .languagebox import LanguageBox - - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s - -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig, _encoding) -except AttributeError: - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig) - - -class Ui_settingsDialog(object): - """Encapsulate a UI settings dialog object""" - - def setupUi(self, settingsDialog): - """Set up the UI""" - - settingsDialog.setObjectName(_fromUtf8("settingsDialog")) - settingsDialog.resize(521, 413) - self.gridLayout = QtGui.QGridLayout(settingsDialog) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.buttonBox = QtGui.QDialogButtonBox(settingsDialog) - self.buttonBox.setOrientation(QtCore.Qt.Horizontal) - self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok) - self.buttonBox.setObjectName(_fromUtf8("buttonBox")) - self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1) - self.tabWidgetSettings = QtGui.QTabWidget(settingsDialog) - self.tabWidgetSettings.setObjectName(_fromUtf8("tabWidgetSettings")) - self.tabUserInterface = QtGui.QWidget() - self.tabUserInterface.setEnabled(True) - self.tabUserInterface.setObjectName(_fromUtf8("tabUserInterface")) - self.formLayout = QtGui.QFormLayout(self.tabUserInterface) - self.formLayout.setObjectName(_fromUtf8("formLayout")) - self.checkBoxStartOnLogon = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxStartOnLogon.setObjectName(_fromUtf8("checkBoxStartOnLogon")) - self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.checkBoxStartOnLogon) - self.groupBoxTray = QtGui.QGroupBox(self.tabUserInterface) - self.groupBoxTray.setObjectName(_fromUtf8("groupBoxTray")) - self.formLayoutTray = QtGui.QFormLayout(self.groupBoxTray) - self.formLayoutTray.setObjectName(_fromUtf8("formLayoutTray")) - self.checkBoxStartInTray = QtGui.QCheckBox(self.groupBoxTray) - self.checkBoxStartInTray.setObjectName(_fromUtf8("checkBoxStartInTray")) - self.formLayoutTray.setWidget(0, QtGui.QFormLayout.SpanningRole, self.checkBoxStartInTray) - self.checkBoxMinimizeToTray = QtGui.QCheckBox(self.groupBoxTray) - self.checkBoxMinimizeToTray.setChecked(True) - self.checkBoxMinimizeToTray.setObjectName(_fromUtf8("checkBoxMinimizeToTray")) - self.formLayoutTray.setWidget(1, QtGui.QFormLayout.LabelRole, self.checkBoxMinimizeToTray) - self.checkBoxTrayOnClose = QtGui.QCheckBox(self.groupBoxTray) - self.checkBoxTrayOnClose.setChecked(True) - self.checkBoxTrayOnClose.setObjectName(_fromUtf8("checkBoxTrayOnClose")) - self.formLayoutTray.setWidget(2, QtGui.QFormLayout.LabelRole, self.checkBoxTrayOnClose) - self.formLayout.setWidget(1, QtGui.QFormLayout.SpanningRole, self.groupBoxTray) - self.checkBoxHideTrayConnectionNotifications = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxHideTrayConnectionNotifications.setChecked(False) - self.checkBoxHideTrayConnectionNotifications.setObjectName( - _fromUtf8("checkBoxHideTrayConnectionNotifications")) - self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, self.checkBoxHideTrayConnectionNotifications) - self.checkBoxShowTrayNotifications = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxShowTrayNotifications.setObjectName(_fromUtf8("checkBoxShowTrayNotifications")) - self.formLayout.setWidget(3, QtGui.QFormLayout.LabelRole, self.checkBoxShowTrayNotifications) - self.checkBoxPortableMode = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxPortableMode.setObjectName(_fromUtf8("checkBoxPortableMode")) - self.formLayout.setWidget(4, QtGui.QFormLayout.LabelRole, self.checkBoxPortableMode) - self.PortableModeDescription = QtGui.QLabel(self.tabUserInterface) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.PortableModeDescription.sizePolicy().hasHeightForWidth()) - self.PortableModeDescription.setSizePolicy(sizePolicy) - self.PortableModeDescription.setWordWrap(True) - self.PortableModeDescription.setObjectName(_fromUtf8("PortableModeDescription")) - self.formLayout.setWidget(5, QtGui.QFormLayout.SpanningRole, self.PortableModeDescription) - self.checkBoxWillinglySendToMobile = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxWillinglySendToMobile.setObjectName(_fromUtf8("checkBoxWillinglySendToMobile")) - self.formLayout.setWidget(6, QtGui.QFormLayout.SpanningRole, self.checkBoxWillinglySendToMobile) - self.checkBoxUseIdenticons = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxUseIdenticons.setObjectName(_fromUtf8("checkBoxUseIdenticons")) - self.formLayout.setWidget(7, QtGui.QFormLayout.LabelRole, self.checkBoxUseIdenticons) - self.checkBoxReplyBelow = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxReplyBelow.setObjectName(_fromUtf8("checkBoxReplyBelow")) - self.formLayout.setWidget(8, QtGui.QFormLayout.LabelRole, self.checkBoxReplyBelow) - self.groupBox = QtGui.QGroupBox(self.tabUserInterface) - self.groupBox.setObjectName(_fromUtf8("groupBox")) - self.formLayout_2 = QtGui.QFormLayout(self.groupBox) - self.formLayout_2.setObjectName(_fromUtf8("formLayout_2")) - self.languageComboBox = LanguageBox(self.groupBox) - self.languageComboBox.setMinimumSize(QtCore.QSize(100, 0)) - self.languageComboBox.setObjectName(_fromUtf8("languageComboBox")) # pylint: disable=not-callable - self.formLayout_2.setWidget(0, QtGui.QFormLayout.LabelRole, self.languageComboBox) - self.formLayout.setWidget(9, QtGui.QFormLayout.FieldRole, self.groupBox) - self.tabWidgetSettings.addTab(self.tabUserInterface, _fromUtf8("")) - self.tabNetworkSettings = QtGui.QWidget() - self.tabNetworkSettings.setObjectName(_fromUtf8("tabNetworkSettings")) - self.gridLayout_4 = QtGui.QGridLayout(self.tabNetworkSettings) - self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) - self.groupBox1 = QtGui.QGroupBox(self.tabNetworkSettings) - self.groupBox1.setObjectName(_fromUtf8("groupBox1")) - self.gridLayout_3 = QtGui.QGridLayout(self.groupBox1) - self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) - self.label = QtGui.QLabel(self.groupBox1) - self.label.setObjectName(_fromUtf8("label")) - self.gridLayout_3.addWidget(self.label, 0, 0, 1, 1, QtCore.Qt.AlignRight) - self.lineEditTCPPort = QtGui.QLineEdit(self.groupBox1) - self.lineEditTCPPort.setMaximumSize(QtCore.QSize(70, 16777215)) - self.lineEditTCPPort.setObjectName(_fromUtf8("lineEditTCPPort")) - self.gridLayout_3.addWidget(self.lineEditTCPPort, 0, 1, 1, 1, QtCore.Qt.AlignLeft) - self.labelUPnP = QtGui.QLabel(self.groupBox1) - self.labelUPnP.setObjectName(_fromUtf8("labelUPnP")) - self.gridLayout_3.addWidget(self.labelUPnP, 0, 2, 1, 1, QtCore.Qt.AlignRight) - self.checkBoxUPnP = QtGui.QCheckBox(self.groupBox1) - self.checkBoxUPnP.setObjectName(_fromUtf8("checkBoxUPnP")) - self.gridLayout_3.addWidget(self.checkBoxUPnP, 0, 3, 1, 1, QtCore.Qt.AlignLeft) - self.gridLayout_4.addWidget(self.groupBox1, 0, 0, 1, 1) - self.groupBox_3 = QtGui.QGroupBox(self.tabNetworkSettings) - self.groupBox_3.setObjectName(_fromUtf8("groupBox_3")) - self.gridLayout_9 = QtGui.QGridLayout(self.groupBox_3) - self.gridLayout_9.setObjectName(_fromUtf8("gridLayout_9")) - spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_9.addItem(spacerItem1, 0, 0, 2, 1) - self.label_24 = QtGui.QLabel(self.groupBox_3) - self.label_24.setObjectName(_fromUtf8("label_24")) - self.gridLayout_9.addWidget(self.label_24, 0, 1, 1, 1) - self.lineEditMaxDownloadRate = QtGui.QLineEdit(self.groupBox_3) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditMaxDownloadRate.sizePolicy().hasHeightForWidth()) - self.lineEditMaxDownloadRate.setSizePolicy(sizePolicy) - self.lineEditMaxDownloadRate.setMaximumSize(QtCore.QSize(60, 16777215)) - self.lineEditMaxDownloadRate.setObjectName(_fromUtf8("lineEditMaxDownloadRate")) - self.gridLayout_9.addWidget(self.lineEditMaxDownloadRate, 0, 2, 1, 1) - self.label_25 = QtGui.QLabel(self.groupBox_3) - self.label_25.setObjectName(_fromUtf8("label_25")) - self.gridLayout_9.addWidget(self.label_25, 1, 1, 1, 1) - self.lineEditMaxUploadRate = QtGui.QLineEdit(self.groupBox_3) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditMaxUploadRate.sizePolicy().hasHeightForWidth()) - self.lineEditMaxUploadRate.setSizePolicy(sizePolicy) - self.lineEditMaxUploadRate.setMaximumSize(QtCore.QSize(60, 16777215)) - self.lineEditMaxUploadRate.setObjectName(_fromUtf8("lineEditMaxUploadRate")) - self.gridLayout_9.addWidget(self.lineEditMaxUploadRate, 1, 2, 1, 1) - self.label_26 = QtGui.QLabel(self.groupBox_3) - self.label_26.setObjectName(_fromUtf8("label_26")) - self.gridLayout_9.addWidget(self.label_26, 2, 1, 1, 1) - self.lineEditMaxOutboundConnections = QtGui.QLineEdit(self.groupBox_3) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditMaxOutboundConnections.sizePolicy().hasHeightForWidth()) - self.lineEditMaxOutboundConnections.setSizePolicy(sizePolicy) - self.lineEditMaxOutboundConnections.setMaximumSize(QtCore.QSize(60, 16777215)) - self.lineEditMaxOutboundConnections.setObjectName(_fromUtf8("lineEditMaxOutboundConnections")) - self.lineEditMaxOutboundConnections.setValidator( - QtGui.QIntValidator(0, 8, self.lineEditMaxOutboundConnections)) - self.gridLayout_9.addWidget(self.lineEditMaxOutboundConnections, 2, 2, 1, 1) - self.gridLayout_4.addWidget(self.groupBox_3, 2, 0, 1, 1) - self.groupBox_2 = QtGui.QGroupBox(self.tabNetworkSettings) - self.groupBox_2.setObjectName(_fromUtf8("groupBox_2")) - self.gridLayout_2 = QtGui.QGridLayout(self.groupBox_2) - self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) - self.label_2 = QtGui.QLabel(self.groupBox_2) - self.label_2.setObjectName(_fromUtf8("label_2")) - self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 1) - self.label_3 = QtGui.QLabel(self.groupBox_2) - self.label_3.setObjectName(_fromUtf8("label_3")) - self.gridLayout_2.addWidget(self.label_3, 1, 1, 1, 1) - self.lineEditSocksHostname = QtGui.QLineEdit(self.groupBox_2) - self.lineEditSocksHostname.setObjectName(_fromUtf8("lineEditSocksHostname")) - self.lineEditSocksHostname.setPlaceholderText(_fromUtf8("127.0.0.1")) - self.gridLayout_2.addWidget(self.lineEditSocksHostname, 1, 2, 1, 2) - self.label_4 = QtGui.QLabel(self.groupBox_2) - self.label_4.setObjectName(_fromUtf8("label_4")) - self.gridLayout_2.addWidget(self.label_4, 1, 4, 1, 1) - self.lineEditSocksPort = QtGui.QLineEdit(self.groupBox_2) - self.lineEditSocksPort.setObjectName(_fromUtf8("lineEditSocksPort")) - if platform in ['darwin', 'win32', 'win64']: - self.lineEditSocksPort.setPlaceholderText(_fromUtf8("9150")) - else: - self.lineEditSocksPort.setPlaceholderText(_fromUtf8("9050")) - self.gridLayout_2.addWidget(self.lineEditSocksPort, 1, 5, 1, 1) - self.checkBoxAuthentication = QtGui.QCheckBox(self.groupBox_2) - self.checkBoxAuthentication.setObjectName(_fromUtf8("checkBoxAuthentication")) - self.gridLayout_2.addWidget(self.checkBoxAuthentication, 2, 1, 1, 1) - self.label_5 = QtGui.QLabel(self.groupBox_2) - self.label_5.setObjectName(_fromUtf8("label_5")) - self.gridLayout_2.addWidget(self.label_5, 2, 2, 1, 1) - self.lineEditSocksUsername = QtGui.QLineEdit(self.groupBox_2) - self.lineEditSocksUsername.setEnabled(False) - self.lineEditSocksUsername.setObjectName(_fromUtf8("lineEditSocksUsername")) - self.gridLayout_2.addWidget(self.lineEditSocksUsername, 2, 3, 1, 1) - self.label_6 = QtGui.QLabel(self.groupBox_2) - self.label_6.setObjectName(_fromUtf8("label_6")) - self.gridLayout_2.addWidget(self.label_6, 2, 4, 1, 1) - self.lineEditSocksPassword = QtGui.QLineEdit(self.groupBox_2) - self.lineEditSocksPassword.setEnabled(False) - self.lineEditSocksPassword.setInputMethodHints( - QtCore.Qt.ImhHiddenText | QtCore.Qt.ImhNoAutoUppercase | QtCore.Qt.ImhNoPredictiveText) - self.lineEditSocksPassword.setEchoMode(QtGui.QLineEdit.Password) - self.lineEditSocksPassword.setObjectName(_fromUtf8("lineEditSocksPassword")) - self.gridLayout_2.addWidget(self.lineEditSocksPassword, 2, 5, 1, 1) - self.checkBoxSocksListen = QtGui.QCheckBox(self.groupBox_2) - self.checkBoxSocksListen.setObjectName(_fromUtf8("checkBoxSocksListen")) - self.gridLayout_2.addWidget(self.checkBoxSocksListen, 3, 1, 1, 4) - self.comboBoxProxyType = QtGui.QComboBox(self.groupBox_2) - self.comboBoxProxyType.setObjectName(_fromUtf8("comboBoxProxyType")) # pylint: disable=not-callable - self.comboBoxProxyType.addItem(_fromUtf8("")) - self.comboBoxProxyType.addItem(_fromUtf8("")) - self.comboBoxProxyType.addItem(_fromUtf8("")) - self.gridLayout_2.addWidget(self.comboBoxProxyType, 0, 1, 1, 1) - self.gridLayout_4.addWidget(self.groupBox_2, 1, 0, 1, 1) - spacerItem2 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_4.addItem(spacerItem2, 3, 0, 1, 1) - self.tabWidgetSettings.addTab(self.tabNetworkSettings, _fromUtf8("")) - self.tabDemandedDifficulty = QtGui.QWidget() - self.tabDemandedDifficulty.setObjectName(_fromUtf8("tabDemandedDifficulty")) - self.gridLayout_6 = QtGui.QGridLayout(self.tabDemandedDifficulty) - self.gridLayout_6.setObjectName(_fromUtf8("gridLayout_6")) - self.label_9 = QtGui.QLabel(self.tabDemandedDifficulty) - self.label_9.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_9.setObjectName(_fromUtf8("label_9")) - self.gridLayout_6.addWidget(self.label_9, 1, 1, 1, 1) - self.label_10 = QtGui.QLabel(self.tabDemandedDifficulty) - self.label_10.setWordWrap(True) - self.label_10.setObjectName(_fromUtf8("label_10")) - self.gridLayout_6.addWidget(self.label_10, 2, 0, 1, 3) - self.label_11 = QtGui.QLabel(self.tabDemandedDifficulty) - self.label_11.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_11.setObjectName(_fromUtf8("label_11")) - self.gridLayout_6.addWidget(self.label_11, 3, 1, 1, 1) - self.label_8 = QtGui.QLabel(self.tabDemandedDifficulty) - self.label_8.setWordWrap(True) - self.label_8.setObjectName(_fromUtf8("label_8")) - self.gridLayout_6.addWidget(self.label_8, 0, 0, 1, 3) - spacerItem3 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_6.addItem(spacerItem3, 1, 0, 1, 1) - self.label_12 = QtGui.QLabel(self.tabDemandedDifficulty) - self.label_12.setWordWrap(True) - self.label_12.setObjectName(_fromUtf8("label_12")) - self.gridLayout_6.addWidget(self.label_12, 4, 0, 1, 3) - self.lineEditSmallMessageDifficulty = QtGui.QLineEdit(self.tabDemandedDifficulty) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditSmallMessageDifficulty.sizePolicy().hasHeightForWidth()) - self.lineEditSmallMessageDifficulty.setSizePolicy(sizePolicy) - self.lineEditSmallMessageDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) - self.lineEditSmallMessageDifficulty.setObjectName(_fromUtf8("lineEditSmallMessageDifficulty")) - self.gridLayout_6.addWidget(self.lineEditSmallMessageDifficulty, 3, 2, 1, 1) - self.lineEditTotalDifficulty = QtGui.QLineEdit(self.tabDemandedDifficulty) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditTotalDifficulty.sizePolicy().hasHeightForWidth()) - self.lineEditTotalDifficulty.setSizePolicy(sizePolicy) - self.lineEditTotalDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) - self.lineEditTotalDifficulty.setObjectName(_fromUtf8("lineEditTotalDifficulty")) - self.gridLayout_6.addWidget(self.lineEditTotalDifficulty, 1, 2, 1, 1) - spacerItem4 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_6.addItem(spacerItem4, 3, 0, 1, 1) - spacerItem5 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_6.addItem(spacerItem5, 5, 0, 1, 1) - self.tabWidgetSettings.addTab(self.tabDemandedDifficulty, _fromUtf8("")) - self.tabMaxAcceptableDifficulty = QtGui.QWidget() - self.tabMaxAcceptableDifficulty.setObjectName(_fromUtf8("tabMaxAcceptableDifficulty")) - self.gridLayout_7 = QtGui.QGridLayout(self.tabMaxAcceptableDifficulty) - self.gridLayout_7.setObjectName(_fromUtf8("gridLayout_7")) - self.label_15 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) - self.label_15.setWordWrap(True) - self.label_15.setObjectName(_fromUtf8("label_15")) - self.gridLayout_7.addWidget(self.label_15, 0, 0, 1, 3) - spacerItem6 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem6, 1, 0, 1, 1) - self.label_13 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) - self.label_13.setLayoutDirection(QtCore.Qt.LeftToRight) - self.label_13.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_13.setObjectName(_fromUtf8("label_13")) - self.gridLayout_7.addWidget(self.label_13, 1, 1, 1, 1) - self.lineEditMaxAcceptableTotalDifficulty = QtGui.QLineEdit(self.tabMaxAcceptableDifficulty) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditMaxAcceptableTotalDifficulty.sizePolicy().hasHeightForWidth()) - self.lineEditMaxAcceptableTotalDifficulty.setSizePolicy(sizePolicy) - self.lineEditMaxAcceptableTotalDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) - self.lineEditMaxAcceptableTotalDifficulty.setObjectName(_fromUtf8("lineEditMaxAcceptableTotalDifficulty")) - self.gridLayout_7.addWidget(self.lineEditMaxAcceptableTotalDifficulty, 1, 2, 1, 1) - spacerItem7 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem7, 2, 0, 1, 1) - self.label_14 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) - self.label_14.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_14.setObjectName(_fromUtf8("label_14")) - self.gridLayout_7.addWidget(self.label_14, 2, 1, 1, 1) - self.lineEditMaxAcceptableSmallMessageDifficulty = QtGui.QLineEdit(self.tabMaxAcceptableDifficulty) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditMaxAcceptableSmallMessageDifficulty.sizePolicy().hasHeightForWidth()) - self.lineEditMaxAcceptableSmallMessageDifficulty.setSizePolicy(sizePolicy) - self.lineEditMaxAcceptableSmallMessageDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) - self.lineEditMaxAcceptableSmallMessageDifficulty.setObjectName( - _fromUtf8("lineEditMaxAcceptableSmallMessageDifficulty")) - self.gridLayout_7.addWidget(self.lineEditMaxAcceptableSmallMessageDifficulty, 2, 2, 1, 1) - spacerItem8 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_7.addItem(spacerItem8, 3, 1, 1, 1) - self.labelOpenCL = QtGui.QLabel(self.tabMaxAcceptableDifficulty) - self.labelOpenCL.setObjectName(_fromUtf8("labelOpenCL")) - self.gridLayout_7.addWidget(self.labelOpenCL, 4, 0, 1, 1) - self.comboBoxOpenCL = QtGui.QComboBox(self.tabMaxAcceptableDifficulty) - self.comboBoxOpenCL.setObjectName = (_fromUtf8("comboBoxOpenCL")) - self.gridLayout_7.addWidget(self.comboBoxOpenCL, 4, 1, 1, 1) - self.tabWidgetSettings.addTab(self.tabMaxAcceptableDifficulty, _fromUtf8("")) - self.tabNamecoin = QtGui.QWidget() - self.tabNamecoin.setObjectName(_fromUtf8("tabNamecoin")) - self.gridLayout_8 = QtGui.QGridLayout(self.tabNamecoin) - self.gridLayout_8.setObjectName(_fromUtf8("gridLayout_8")) - spacerItem9 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem9, 2, 0, 1, 1) - self.label_16 = QtGui.QLabel(self.tabNamecoin) - self.label_16.setWordWrap(True) - self.label_16.setObjectName(_fromUtf8("label_16")) - self.gridLayout_8.addWidget(self.label_16, 0, 0, 1, 3) - self.label_17 = QtGui.QLabel(self.tabNamecoin) - self.label_17.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_17.setObjectName(_fromUtf8("label_17")) - self.gridLayout_8.addWidget(self.label_17, 2, 1, 1, 1) - self.lineEditNamecoinHost = QtGui.QLineEdit(self.tabNamecoin) - self.lineEditNamecoinHost.setObjectName(_fromUtf8("lineEditNamecoinHost")) - self.gridLayout_8.addWidget(self.lineEditNamecoinHost, 2, 2, 1, 1) - spacerItem10 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem10, 3, 0, 1, 1) - spacerItem11 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem11, 4, 0, 1, 1) - self.label_18 = QtGui.QLabel(self.tabNamecoin) - self.label_18.setEnabled(True) - self.label_18.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_18.setObjectName(_fromUtf8("label_18")) - self.gridLayout_8.addWidget(self.label_18, 3, 1, 1, 1) - self.lineEditNamecoinPort = QtGui.QLineEdit(self.tabNamecoin) - self.lineEditNamecoinPort.setObjectName(_fromUtf8("lineEditNamecoinPort")) - self.gridLayout_8.addWidget(self.lineEditNamecoinPort, 3, 2, 1, 1) - spacerItem12 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_8.addItem(spacerItem12, 8, 1, 1, 1) - self.labelNamecoinUser = QtGui.QLabel(self.tabNamecoin) - self.labelNamecoinUser.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.labelNamecoinUser.setObjectName(_fromUtf8("labelNamecoinUser")) - self.gridLayout_8.addWidget(self.labelNamecoinUser, 4, 1, 1, 1) - self.lineEditNamecoinUser = QtGui.QLineEdit(self.tabNamecoin) - self.lineEditNamecoinUser.setObjectName(_fromUtf8("lineEditNamecoinUser")) - self.gridLayout_8.addWidget(self.lineEditNamecoinUser, 4, 2, 1, 1) - spacerItem13 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem13, 5, 0, 1, 1) - self.labelNamecoinPassword = QtGui.QLabel(self.tabNamecoin) - self.labelNamecoinPassword.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.labelNamecoinPassword.setObjectName(_fromUtf8("labelNamecoinPassword")) - self.gridLayout_8.addWidget(self.labelNamecoinPassword, 5, 1, 1, 1) - self.lineEditNamecoinPassword = QtGui.QLineEdit(self.tabNamecoin) - self.lineEditNamecoinPassword.setInputMethodHints( - QtCore.Qt.ImhHiddenText | QtCore.Qt.ImhNoAutoUppercase | QtCore.Qt.ImhNoPredictiveText) - self.lineEditNamecoinPassword.setEchoMode(QtGui.QLineEdit.Password) - self.lineEditNamecoinPassword.setObjectName(_fromUtf8("lineEditNamecoinPassword")) - self.gridLayout_8.addWidget(self.lineEditNamecoinPassword, 5, 2, 1, 1) - self.labelNamecoinTestResult = QtGui.QLabel(self.tabNamecoin) - self.labelNamecoinTestResult.setText(_fromUtf8("")) - self.labelNamecoinTestResult.setObjectName(_fromUtf8("labelNamecoinTestResult")) - self.gridLayout_8.addWidget(self.labelNamecoinTestResult, 7, 0, 1, 2) - self.pushButtonNamecoinTest = QtGui.QPushButton(self.tabNamecoin) - self.pushButtonNamecoinTest.setObjectName(_fromUtf8("pushButtonNamecoinTest")) - self.gridLayout_8.addWidget(self.pushButtonNamecoinTest, 7, 2, 1, 1) - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.label_21 = QtGui.QLabel(self.tabNamecoin) - self.label_21.setObjectName(_fromUtf8("label_21")) - self.horizontalLayout.addWidget(self.label_21) - self.radioButtonNamecoinNamecoind = QtGui.QRadioButton(self.tabNamecoin) - self.radioButtonNamecoinNamecoind.setObjectName(_fromUtf8("radioButtonNamecoinNamecoind")) - self.horizontalLayout.addWidget(self.radioButtonNamecoinNamecoind) - self.radioButtonNamecoinNmcontrol = QtGui.QRadioButton(self.tabNamecoin) - self.radioButtonNamecoinNmcontrol.setObjectName(_fromUtf8("radioButtonNamecoinNmcontrol")) - self.horizontalLayout.addWidget(self.radioButtonNamecoinNmcontrol) - self.gridLayout_8.addLayout(self.horizontalLayout, 1, 0, 1, 3) - self.tabWidgetSettings.addTab(self.tabNamecoin, _fromUtf8("")) - self.tabResendsExpire = QtGui.QWidget() - self.tabResendsExpire.setObjectName(_fromUtf8("tabResendsExpire")) - self.gridLayout_5 = QtGui.QGridLayout(self.tabResendsExpire) - self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) - self.label_7 = QtGui.QLabel(self.tabResendsExpire) - self.label_7.setWordWrap(True) - self.label_7.setObjectName(_fromUtf8("label_7")) - self.gridLayout_5.addWidget(self.label_7, 0, 0, 1, 3) - spacerItem14 = QtGui.QSpacerItem(212, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_5.addItem(spacerItem14, 1, 0, 1, 1) - self.widget = QtGui.QWidget(self.tabResendsExpire) - self.widget.setMinimumSize(QtCore.QSize(231, 75)) - self.widget.setObjectName(_fromUtf8("widget")) - self.label_19 = QtGui.QLabel(self.widget) - self.label_19.setGeometry(QtCore.QRect(10, 20, 101, 20)) - self.label_19.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_19.setObjectName(_fromUtf8("label_19")) - self.label_20 = QtGui.QLabel(self.widget) - self.label_20.setGeometry(QtCore.QRect(30, 40, 80, 16)) - self.label_20.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) - self.label_20.setObjectName(_fromUtf8("label_20")) - self.lineEditDays = QtGui.QLineEdit(self.widget) - self.lineEditDays.setGeometry(QtCore.QRect(113, 20, 51, 20)) - self.lineEditDays.setObjectName(_fromUtf8("lineEditDays")) - self.lineEditMonths = QtGui.QLineEdit(self.widget) - self.lineEditMonths.setGeometry(QtCore.QRect(113, 40, 51, 20)) - self.lineEditMonths.setObjectName(_fromUtf8("lineEditMonths")) - self.label_22 = QtGui.QLabel(self.widget) - self.label_22.setGeometry(QtCore.QRect(169, 23, 61, 16)) - self.label_22.setObjectName(_fromUtf8("label_22")) - self.label_23 = QtGui.QLabel(self.widget) - self.label_23.setGeometry(QtCore.QRect(170, 41, 71, 16)) - self.label_23.setObjectName(_fromUtf8("label_23")) - self.gridLayout_5.addWidget(self.widget, 1, 2, 1, 1) - spacerItem15 = QtGui.QSpacerItem(20, 129, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_5.addItem(spacerItem15, 2, 1, 1, 1) - self.tabWidgetSettings.addTab(self.tabResendsExpire, _fromUtf8("")) - self.gridLayout.addWidget(self.tabWidgetSettings, 0, 0, 1, 1) - - self.retranslateUi(settingsDialog) - self.tabWidgetSettings.setCurrentIndex(0) - QtCore.QObject.connect( # pylint: disable=no-member - self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), settingsDialog.accept) - QtCore.QObject.connect( # pylint: disable=no-member - self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), settingsDialog.reject) - QtCore.QObject.connect( # pylint: disable=no-member - self.checkBoxAuthentication, - QtCore.SIGNAL( - _fromUtf8("toggled(bool)")), - self.lineEditSocksUsername.setEnabled) - QtCore.QObject.connect( # pylint: disable=no-member - self.checkBoxAuthentication, - QtCore.SIGNAL( - _fromUtf8("toggled(bool)")), - self.lineEditSocksPassword.setEnabled) - QtCore.QMetaObject.connectSlotsByName(settingsDialog) - settingsDialog.setTabOrder(self.tabWidgetSettings, self.checkBoxStartOnLogon) - settingsDialog.setTabOrder(self.checkBoxStartOnLogon, self.checkBoxStartInTray) - settingsDialog.setTabOrder(self.checkBoxStartInTray, self.checkBoxMinimizeToTray) - settingsDialog.setTabOrder(self.checkBoxMinimizeToTray, self.lineEditTCPPort) - settingsDialog.setTabOrder(self.lineEditTCPPort, self.comboBoxProxyType) - settingsDialog.setTabOrder(self.comboBoxProxyType, self.lineEditSocksHostname) - settingsDialog.setTabOrder(self.lineEditSocksHostname, self.lineEditSocksPort) - settingsDialog.setTabOrder(self.lineEditSocksPort, self.checkBoxAuthentication) - settingsDialog.setTabOrder(self.checkBoxAuthentication, self.lineEditSocksUsername) - settingsDialog.setTabOrder(self.lineEditSocksUsername, self.lineEditSocksPassword) - settingsDialog.setTabOrder(self.lineEditSocksPassword, self.checkBoxSocksListen) - settingsDialog.setTabOrder(self.checkBoxSocksListen, self.buttonBox) - - def retranslateUi(self, settingsDialog): - """Re-translate the UI into the supported languages""" - - settingsDialog.setWindowTitle(_translate("settingsDialog", "Settings", None)) - self.checkBoxStartOnLogon.setText(_translate("settingsDialog", "Start Bitmessage on user login", None)) - self.groupBoxTray.setTitle(_translate("settingsDialog", "Tray", None)) - self.checkBoxStartInTray.setText( - _translate( - "settingsDialog", - "Start Bitmessage in the tray (don\'t show main window)", - None)) - self.checkBoxMinimizeToTray.setText(_translate("settingsDialog", "Minimize to tray", None)) - self.checkBoxTrayOnClose.setText(_translate("settingsDialog", "Close to tray", None)) - self.checkBoxHideTrayConnectionNotifications.setText( - _translate("settingsDialog", "Hide connection notifications", None)) - self.checkBoxShowTrayNotifications.setText( - _translate( - "settingsDialog", - "Show notification when message received", - None)) - self.checkBoxPortableMode.setText(_translate("settingsDialog", "Run in Portable Mode", None)) - self.PortableModeDescription.setText( - _translate( - "settingsDialog", - "In Portable Mode, messages and config files are stored in the same directory as the" - " program rather than the normal application-data folder. This makes it convenient to" - " run Bitmessage from a USB thumb drive.", - None)) - self.checkBoxWillinglySendToMobile.setText( - _translate( - "settingsDialog", - "Willingly include unencrypted destination address when sending to a mobile device", - None)) - self.checkBoxUseIdenticons.setText(_translate("settingsDialog", "Use Identicons", None)) - self.checkBoxReplyBelow.setText(_translate("settingsDialog", "Reply below Quote", None)) - self.groupBox.setTitle(_translate("settingsDialog", "Interface Language", None)) - self.languageComboBox.setItemText(0, _translate("settingsDialog", "System Settings", "system")) - self.tabWidgetSettings.setTabText( - self.tabWidgetSettings.indexOf( - self.tabUserInterface), - _translate( - "settingsDialog", "User Interface", None)) - self.groupBox1.setTitle(_translate("settingsDialog", "Listening port", None)) - self.label.setText(_translate("settingsDialog", "Listen for connections on port:", None)) - self.labelUPnP.setText(_translate("settingsDialog", "UPnP:", None)) - self.groupBox_3.setTitle(_translate("settingsDialog", "Bandwidth limit", None)) - self.label_24.setText(_translate("settingsDialog", "Maximum download rate (kB/s): [0: unlimited]", None)) - self.label_25.setText(_translate("settingsDialog", "Maximum upload rate (kB/s): [0: unlimited]", None)) - self.label_26.setText(_translate("settingsDialog", "Maximum outbound connections: [0: none]", None)) - self.groupBox_2.setTitle(_translate("settingsDialog", "Proxy server / Tor", None)) - self.label_2.setText(_translate("settingsDialog", "Type:", None)) - self.label_3.setText(_translate("settingsDialog", "Server hostname:", None)) - self.label_4.setText(_translate("settingsDialog", "Port:", None)) - self.checkBoxAuthentication.setText(_translate("settingsDialog", "Authentication", None)) - self.label_5.setText(_translate("settingsDialog", "Username:", None)) - self.label_6.setText(_translate("settingsDialog", "Pass:", None)) - self.checkBoxSocksListen.setText( - _translate( - "settingsDialog", - "Listen for incoming connections when using proxy", - None)) - self.comboBoxProxyType.setItemText(0, _translate("settingsDialog", "none", None)) - self.comboBoxProxyType.setItemText(1, _translate("settingsDialog", "SOCKS4a", None)) - self.comboBoxProxyType.setItemText(2, _translate("settingsDialog", "SOCKS5", None)) - self.tabWidgetSettings.setTabText( - self.tabWidgetSettings.indexOf( - self.tabNetworkSettings), - _translate( - "settingsDialog", "Network Settings", None)) - self.label_9.setText(_translate("settingsDialog", "Total difficulty:", None)) - self.label_10.setText( - _translate( - "settingsDialog", - "The \'Total difficulty\' affects the absolute amount of work the sender must complete." - " Doubling this value doubles the amount of work.", - None)) - self.label_11.setText(_translate("settingsDialog", "Small message difficulty:", None)) - self.label_8.setText(_translate( - "settingsDialog", - "When someone sends you a message, their computer must first complete some work. The difficulty of this" - " work, by default, is 1. You may raise this default for new addresses you create by changing the values" - " here. Any new addresses you create will require senders to meet the higher difficulty. There is one" - " exception: if you add a friend or acquaintance to your address book, Bitmessage will automatically" - " notify them when you next send a message that they need only complete the minimum amount of" - " work: difficulty 1. ", - None)) - self.label_12.setText( - _translate( - "settingsDialog", - "The \'Small message difficulty\' mostly only affects the difficulty of sending small messages." - " Doubling this value makes it almost twice as difficult to send a small message but doesn\'t really" - " affect large messages.", - None)) - self.tabWidgetSettings.setTabText( - self.tabWidgetSettings.indexOf( - self.tabDemandedDifficulty), - _translate( - "settingsDialog", "Demanded difficulty", None)) - self.label_15.setText( - _translate( - "settingsDialog", - "Here you may set the maximum amount of work you are willing to do to send a message to another" - " person. Setting these values to 0 means that any value is acceptable.", - None)) - self.label_13.setText(_translate("settingsDialog", "Maximum acceptable total difficulty:", None)) - self.label_14.setText(_translate("settingsDialog", "Maximum acceptable small message difficulty:", None)) - self.tabWidgetSettings.setTabText( - self.tabWidgetSettings.indexOf( - self.tabMaxAcceptableDifficulty), - _translate( - "settingsDialog", "Max acceptable difficulty", None)) - self.labelOpenCL.setText(_translate("settingsDialog", "Hardware GPU acceleration (OpenCL):", None)) - self.label_16.setText(_translate( - "settingsDialog", - "

Bitmessage can utilize a different Bitcoin-based program called Namecoin to make" - " addresses human-friendly. For example, instead of having to tell your friend your long Bitmessage" - " address, you can simply tell him to send a message to test." - "

(Getting your own Bitmessage address into Namecoin is still rather difficult).

" - "

Bitmessage can use either namecoind directly or a running nmcontrol instance.

", - None)) - self.label_17.setText(_translate("settingsDialog", "Host:", None)) - self.label_18.setText(_translate("settingsDialog", "Port:", None)) - self.labelNamecoinUser.setText(_translate("settingsDialog", "Username:", None)) - self.labelNamecoinPassword.setText(_translate("settingsDialog", "Password:", None)) - self.pushButtonNamecoinTest.setText(_translate("settingsDialog", "Test", None)) - self.label_21.setText(_translate("settingsDialog", "Connect to:", None)) - self.radioButtonNamecoinNamecoind.setText(_translate("settingsDialog", "Namecoind", None)) - self.radioButtonNamecoinNmcontrol.setText(_translate("settingsDialog", "NMControl", None)) - self.tabWidgetSettings.setTabText( - self.tabWidgetSettings.indexOf( - self.tabNamecoin), - _translate( - "settingsDialog", "Namecoin integration", None)) - self.label_7.setText(_translate( - "settingsDialog", - "

By default, if you send a message to someone and he is offline for more than two" - " days, Bitmessage will send the message again after an additional two days. This will be continued with" - " exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver" - " acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain" - " number of days or months.

Leave these input fields blank for the default behavior." - "

", - None)) - self.label_19.setText(_translate("settingsDialog", "Give up after", None)) - self.label_20.setText(_translate("settingsDialog", "and", None)) - self.label_22.setText(_translate("settingsDialog", "days", None)) - self.label_23.setText(_translate("settingsDialog", "months.", None)) - self.tabWidgetSettings.setTabText( - self.tabWidgetSettings.indexOf( - self.tabResendsExpire), - _translate( - "settingsDialog", "Resends Expire", None)) diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index 4aeba3ce..0596dd2c 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -6,24 +6,14 @@ 0 0 - 521 - 413 + 616 + 592 Settings - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - @@ -44,23 +34,6 @@ - - - - Start Bitmessage in the tray (don't show main window) - - - - - - - Minimize to tray - - - true - - - @@ -126,81 +99,52 @@ 0 - - - System Settings - - - - - English - - - - - Esperanto - - - - - Français - - - - - Deutsch - - - - - Español - - - - - русский - - - - - Norsk - - - - - العربية - - - - - 简体中文 - - - - - 日本語 - - - - - Nederlands - - - - - Česky - - - - - Pirate English - - - - - Other (set in keys.dat) - - + + + + + + + + + Hide connection notifications + + + false + + + + + + + Tray + + + + + + Start Bitmessage in the tray (don't show main window) + + + + + + + Minimize to tray + + + true + + + + + + + Close to tray + + + true + @@ -213,33 +157,33 @@ Network Settings + + + + Qt::Vertical + + + + 20 + 40 + + + + Listening port - - - - Qt::Horizontal - - - - 125 - 20 - - - - - + Listen for connections on port: - + @@ -249,94 +193,13 @@ - - - - - - - Bandwidth limit - - - - - - Qt::Horizontal - - - - 40 - 20 - - - + + - - + + - Maximum download rate (kB/s): [0: unlimited] - - - - - - - - 0 - 0 - - - - - 60 - 16777215 - - - - - - - - Maximum upload rate (kB/s): [0: unlimited] - - - - - - - - 0 - 0 - - - - - 60 - 16777215 - - - - - - - - Maximum outbound connections: [0: none] - - - - - - - - 0 - 0 - - - - - 60 - 16777215 - + UPnP: @@ -364,7 +227,11 @@ - + + + 127.0.0.1 + + @@ -446,18 +313,96 @@ - - - - Qt::Vertical + + + + Bandwidth limit - - - 20 - 40 - - - + + + + + + 0 + 0 + + + + + 60 + 16777215 + + + + + + + + Maximum upload rate (kB/s): [0: unlimited] + + + + + + + + 0 + 0 + + + + + 60 + 16777215 + + + + + + + + + 0 + 0 + + + + + 60 + 16777215 + + + + + + + + Maximum outbound connections: [0: none] + + + + + + + Maximum download rate (kB/s): [0: unlimited] + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -594,13 +539,13 @@ Max acceptable difficulty - - + + - Here you may set the maximum amount of work you are willing to do to send a message to another person. Setting these values to 0 means that any value is acceptable. + Maximum acceptable small message difficulty: - - true + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -630,6 +575,32 @@ + + + + Here you may set the maximum amount of work you are willing to do to send a message to another person. Setting these values to 0 means that any value is acceptable. + + + true + + + + + + + + 0 + 0 + + + + + 70 + 16777215 + + + + @@ -659,33 +630,142 @@ - - - - Maximum acceptable small message difficulty: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + Proof of work solver + + + + + There are several worker modules to solve POW: + + + + + + + + + Forking solver using multiple processes: + + + + + + + 1 + + + 4096 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Fast solver in C with multiple threads: + + + + + + + 1 + + + 4096 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Dumb solver + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + GPU solver: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + - - - - - 0 - 0 - - - - - 70 - 16777215 - - - - - + Qt::Vertical @@ -741,7 +821,7 @@ - + Qt::Horizontal @@ -754,7 +834,7 @@ - + Qt::Horizontal @@ -1016,6 +1096,16 @@ + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + @@ -1033,9 +1123,7 @@ checkBoxSocksListen buttonBox - - - + buttonBox diff --git a/src/bitmessageqt/support.py b/src/bitmessageqt/support.py index 2a1ddb18..6f6d943a 100644 --- a/src/bitmessageqt/support.py +++ b/src/bitmessageqt/support.py @@ -11,14 +11,13 @@ import defaults from foldertree import AccountMixin from helper_sql import * from l10n import getTranslationLanguage -from openclpow import openclAvailable, openclEnabled import paths -import proofofwork from pyelliptic.openssl import OpenSSL import queues import network.stats import state from version import softwareVersion +import singleworker # this is BM support address going to Peter Surda OLD_SUPPORT_ADDRESS = 'BM-2cTkCtMYkrSPwFTpgcBrMrf5d8oZwvMZWK' @@ -113,10 +112,10 @@ def createSupportMessage(myapp): if paths.frozen: frozen = paths.frozen portablemode = "True" if state.appdata == paths.lookupExeFolder() else "False" - cpow = "True" if proofofwork.bmpow else "False" + cpow = "True" if "fast" in singleworker.workProver.availableSolvers else "False" openclpow = str( BMConfigParser().safeGet('bitmessagesettings', 'opencl') - ) if openclEnabled() else "None" + ) if BMConfigParser().safeGetBoolean("bitmessagesettings", "powsolver") == "gpu" else "None" locale = getTranslationLanguage() socks = BMConfigParser().safeGet( 'bitmessagesettings', 'socksproxytype', "N/A") diff --git a/src/bitmessageqt/uisignaler.py b/src/bitmessageqt/uisignaler.py index 055f9097..a3f590e9 100644 --- a/src/bitmessageqt/uisignaler.py +++ b/src/bitmessageqt/uisignaler.py @@ -26,14 +26,13 @@ class UISignaler(QThread): "writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), label, address, str(streamNumber)) elif command == 'updateStatusBar': self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"), data) - elif command == 'updateSentItemStatusByToAddress': - toAddress, message = data + elif command == "updateSentItemStatusByToAddress": self.emit(SIGNAL( - "updateSentItemStatusByToAddress(PyQt_PyObject,PyQt_PyObject)"), toAddress, message) - elif command == 'updateSentItemStatusByAckdata': - ackData, message = data + "updateSentItemStatusByToAddress(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), *data) + elif command == "updateSentItemStatusByAckdata": + status, address, message = data self.emit(SIGNAL( - "updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"), ackData, message) + "updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), *data) elif command == 'displayNewInboxMessage': inventoryHash, toAddress, fromAddress, subject, body = data self.emit(SIGNAL( @@ -44,6 +43,8 @@ class UISignaler(QThread): self.emit(SIGNAL( "displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), toAddress, fromLabel, fromAddress, subject, message, ackdata) + elif command == "deleteSentItemByAckData": + self.emit(SIGNAL("deleteSentItemByAckData(PyQt_PyObject)"), data) elif command == 'updateNetworkStatusTab': outbound, add, destination = data self.emit(SIGNAL("updateNetworkStatusTab(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), outbound, add, destination) @@ -74,6 +75,8 @@ class UISignaler(QThread): elif command == 'alert': title, text, exitAfterUserClicksOk = data self.emit(SIGNAL("displayAlert(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), title, text, exitAfterUserClicksOk) + elif command == "updateWorkProverStatus": + self.emit(SIGNAL("updateWorkProverStatus(PyQt_PyObject)"), data) else: sys.stderr.write( 'Command sent to UISignaler not recognized: %s\n' % command) diff --git a/src/bitmessageqt/widgets.py b/src/bitmessageqt/widgets.py index 8ef807f2..0620d2cb 100644 --- a/src/bitmessageqt/widgets.py +++ b/src/bitmessageqt/widgets.py @@ -11,3 +11,6 @@ def resource_path(resFile): def load(resFile, widget): uic.loadUi(resource_path(resFile), widget) + +def loadType(resFile): + return uic.loadUiType(resource_path(resFile)) diff --git a/src/bitmsghash/Makefile b/src/bitmsghash/Makefile deleted file mode 100644 index c4fb4ab5..00000000 --- a/src/bitmsghash/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -UNAME_S := $(shell uname -s) -ifeq ($(UNAME_S),Darwin) - CCFLAGS += -I/usr/local/Cellar/openssl/1.0.2d_1/include - LDFLAGS += -L/usr/local/Cellar/openssl/1.0.2d_1/lib -else ifeq ($(UNAME_S),MINGW32_NT-6.1) - CCFLAGS += -IC:\OpenSSL-1.0.2j-mingw\include -D_WIN32 -march=native - LDFLAGS += -static-libgcc -LC:\OpenSSL-1.0.2j-mingw\lib -lwsock32 -o bitmsghash32.dll -Wl,--out-implib,bitmsghash.a -else - LDFLAGS += -lpthread -o bitmsghash.so -endif - -all: bitmsghash.so - -powtest: - ./testpow.py - -bitmsghash.so: bitmsghash.o - ${CXX} bitmsghash.o -shared -fPIC -lcrypto $(LDFLAGS) - -bitmsghash.o: - ${CXX} -Wall -O3 -march=native -fPIC $(CCFLAGS) -c bitmsghash.cpp - -clean: - rm -f bitmsghash.o bitmsghash.so bitmsghash*.dll - diff --git a/src/bitmsghash/Makefile.bsd b/src/bitmsghash/Makefile.bsd deleted file mode 100644 index 6e680c86..00000000 --- a/src/bitmsghash/Makefile.bsd +++ /dev/null @@ -1,14 +0,0 @@ -all: bitmsghash.so - -powtest: - ./testpow.py - -bitmsghash.so: bitmsghash.o - ${CXX} bitmsghash.o -shared -fPIC -lpthread -lcrypto $(LDFLAGS) -o bitmsghash.so - -bitmsghash.o: - ${CXX} -Wall -O3 -march=native -fPIC $(CCFLAGS) -c bitmsghash.cpp - -clean: - rm -f bitmsghash.o bitmsghash.so - diff --git a/src/bitmsghash/Makefile.msvc b/src/bitmsghash/Makefile.msvc deleted file mode 100644 index 63482c34..00000000 --- a/src/bitmsghash/Makefile.msvc +++ /dev/null @@ -1,2 +0,0 @@ -all: - cl /I C:\OpenSSL-1.0.2j\include /INCREMENTAL bitmsghash.cpp /MT /link /DLL /OUT:bitmsghash32.dll /LIBPATH:C:\OpenSSL-1.0.2j\lib\ libeay32.lib ws2_32.lib diff --git a/src/bitmsghash/bitmsghash.cl b/src/bitmsghash/bitmsghash.cl deleted file mode 100644 index 3c8c21a5..00000000 --- a/src/bitmsghash/bitmsghash.cl +++ /dev/null @@ -1,276 +0,0 @@ -/* -* This is based on the John The Ripper SHA512 code, modified for double SHA512 and for use as a miner in Bitmessage. -* This software is originally Copyright (c) 2012 Myrice -* and it is hereby released to the general public under the following terms: -* Redistribution and use in source and binary forms, with or without modification, are permitted. -*/ - -#ifdef cl_khr_byte_addressable_store -#pragma OPENCL EXTENSION cl_khr_byte_addressable_store : disable -#endif - -#define uint8_t unsigned char -#define uint32_t unsigned int -#define uint64_t unsigned long -#define SALT_SIZE 0 - -#define BINARY_SIZE 8 -#define FULL_BINARY_SIZE 64 - - -#define PLAINTEXT_LENGTH 72 - -#define CIPHERTEXT_LENGTH 128 - - -/// Warning: This version of SWAP64(n) is slow and avoid bugs on AMD GPUs(7970) -// #define SWAP64(n) as_ulong(as_uchar8(n).s76543210) - -#define SWAP64(n) \ - (((n) << 56) \ - | (((n) & 0xff00) << 40) \ - | (((n) & 0xff0000) << 24) \ - | (((n) & 0xff000000) << 8) \ - | (((n) >> 8) & 0xff000000) \ - | (((n) >> 24) & 0xff0000) \ - | (((n) >> 40) & 0xff00) \ - | ((n) >> 56)) - - - -#define rol(x,n) ((x << n) | (x >> (64-n))) -#define ror(x,n) ((x >> n) | (x << (64-n))) -#define Ch(x,y,z) ((x & y) ^ ( (~x) & z)) -#define Maj(x,y,z) ((x & y) ^ (x & z) ^ (y & z)) -#define Sigma0(x) ((ror(x,28)) ^ (ror(x,34)) ^ (ror(x,39))) -#define Sigma1(x) ((ror(x,14)) ^ (ror(x,18)) ^ (ror(x,41))) -#define sigma0(x) ((ror(x,1)) ^ (ror(x,8)) ^(x>>7)) -#define sigma1(x) ((ror(x,19)) ^ (ror(x,61)) ^(x>>6)) - - - -typedef struct { // notice memory align problem - uint64_t H[8]; - uint32_t buffer[32]; //1024 bits - uint32_t buflen; -} sha512_ctx; - -typedef struct { - uint64_t target; - char v[PLAINTEXT_LENGTH+1]; -} sha512_key; - - -/* Macros for reading/writing chars from int32's */ -#define PUTCHAR(buf, index, val) (buf)[(index)>>2] = ((buf)[(index)>>2] & ~(0xffU << (((index) & 3) << 3))) + ((val) << (((index) & 3) << 3)) - - -__constant uint64_t k[] = { - 0x428a2f98d728ae22UL, 0x7137449123ef65cdUL, 0xb5c0fbcfec4d3b2fUL, - 0xe9b5dba58189dbbcUL, - 0x3956c25bf348b538UL, 0x59f111f1b605d019UL, 0x923f82a4af194f9bUL, - 0xab1c5ed5da6d8118UL, - 0xd807aa98a3030242UL, 0x12835b0145706fbeUL, 0x243185be4ee4b28cUL, - 0x550c7dc3d5ffb4e2UL, - 0x72be5d74f27b896fUL, 0x80deb1fe3b1696b1UL, 0x9bdc06a725c71235UL, - 0xc19bf174cf692694UL, - 0xe49b69c19ef14ad2UL, 0xefbe4786384f25e3UL, 0x0fc19dc68b8cd5b5UL, - 0x240ca1cc77ac9c65UL, - 0x2de92c6f592b0275UL, 0x4a7484aa6ea6e483UL, 0x5cb0a9dcbd41fbd4UL, - 0x76f988da831153b5UL, - 0x983e5152ee66dfabUL, 0xa831c66d2db43210UL, 0xb00327c898fb213fUL, - 0xbf597fc7beef0ee4UL, - 0xc6e00bf33da88fc2UL, 0xd5a79147930aa725UL, 0x06ca6351e003826fUL, - 0x142929670a0e6e70UL, - 0x27b70a8546d22ffcUL, 0x2e1b21385c26c926UL, 0x4d2c6dfc5ac42aedUL, - 0x53380d139d95b3dfUL, - 0x650a73548baf63deUL, 0x766a0abb3c77b2a8UL, 0x81c2c92e47edaee6UL, - 0x92722c851482353bUL, - 0xa2bfe8a14cf10364UL, 0xa81a664bbc423001UL, 0xc24b8b70d0f89791UL, - 0xc76c51a30654be30UL, - 0xd192e819d6ef5218UL, 0xd69906245565a910UL, 0xf40e35855771202aUL, - 0x106aa07032bbd1b8UL, - 0x19a4c116b8d2d0c8UL, 0x1e376c085141ab53UL, 0x2748774cdf8eeb99UL, - 0x34b0bcb5e19b48a8UL, - 0x391c0cb3c5c95a63UL, 0x4ed8aa4ae3418acbUL, 0x5b9cca4f7763e373UL, - 0x682e6ff3d6b2b8a3UL, - 0x748f82ee5defb2fcUL, 0x78a5636f43172f60UL, 0x84c87814a1f0ab72UL, - 0x8cc702081a6439ecUL, - 0x90befffa23631e28UL, 0xa4506cebde82bde9UL, 0xbef9a3f7b2c67915UL, - 0xc67178f2e372532bUL, - 0xca273eceea26619cUL, 0xd186b8c721c0c207UL, 0xeada7dd6cde0eb1eUL, - 0xf57d4f7fee6ed178UL, - 0x06f067aa72176fbaUL, 0x0a637dc5a2c898a6UL, 0x113f9804bef90daeUL, - 0x1b710b35131c471bUL, - 0x28db77f523047d84UL, 0x32caab7b40c72493UL, 0x3c9ebe0a15c9bebcUL, - 0x431d67c49c100d4cUL, - 0x4cc5d4becb3e42b6UL, 0x597f299cfc657e2aUL, 0x5fcb6fab3ad6faecUL, - 0x6c44198c4a475817UL, -}; - - - -static void setup_ctx(sha512_ctx* ctx, const char * password, uint8_t pass_len) -{ - uint32_t* b32 = ctx->buffer; - - //set password to buffer - for (uint32_t i = 0; i < pass_len; i++) { - PUTCHAR(b32,i,password[i]); - } - ctx->buflen = pass_len; - - //append 1 to ctx buffer - uint32_t length = ctx->buflen; - PUTCHAR(b32, length, 0x80); - while((++length & 3) != 0) { - PUTCHAR(b32, length, 0); - } - - uint32_t* buffer32 = b32+(length>>2); - for(uint32_t i = length; i < 128; i+=4) {// append 0 to 128 - *buffer32++=0; - } - - //append length to buffer - uint64_t *buffer64 = (uint64_t *)ctx->buffer; - buffer64[15] = SWAP64(((uint64_t) ctx->buflen) * 8); -} - -inline uint64_t sha512(char* password) -{ - __private sha512_ctx ctx; - setup_ctx(&ctx, password, 72); - // sha512 main` - int i; - - uint64_t a = 0x6a09e667f3bcc908UL; - uint64_t b = 0xbb67ae8584caa73bUL; - uint64_t c = 0x3c6ef372fe94f82bUL; - uint64_t d = 0xa54ff53a5f1d36f1UL; - uint64_t e = 0x510e527fade682d1UL; - uint64_t f = 0x9b05688c2b3e6c1fUL; - uint64_t g = 0x1f83d9abfb41bd6bUL; - uint64_t h = 0x5be0cd19137e2179UL; - - __private uint64_t w[16]; - - uint64_t *data = (uint64_t *) ctx.buffer; - - for (i = 0; i < 16; i++) - w[i] = SWAP64(data[i]); - - uint64_t t1, t2; - for (i = 0; i < 16; i++) { - t1 = k[i] + w[i] + h + Sigma1(e) + Ch(e, f, g); - t2 = Maj(a, b, c) + Sigma0(a); - - h = g; - g = f; - f = e; - e = d + t1; - d = c; - c = b; - b = a; - a = t1 + t2; - } - - for (i = 16; i < 80; i++) { - - w[i & 15] =sigma1(w[(i - 2) & 15]) + sigma0(w[(i - 15) & 15]) + w[(i -16) & 15] + w[(i - 7) & 15]; - t1 = k[i] + w[i & 15] + h + Sigma1(e) + Ch(e, f, g); - t2 = Maj(a, b, c) + Sigma0(a); - - h = g; - g = f; - f = e; - e = d + t1; - d = c; - c = b; - b = a; - a = t1 + t2; - } - - uint64_t finalhash[8]; - - finalhash[0] = SWAP64(a + 0x6a09e667f3bcc908UL); - finalhash[1] = SWAP64(b + 0xbb67ae8584caa73bUL); - finalhash[2] = SWAP64(c + 0x3c6ef372fe94f82bUL); - finalhash[3] = SWAP64(d + 0xa54ff53a5f1d36f1UL); - finalhash[4] = SWAP64(e + 0x510e527fade682d1UL); - finalhash[5] = SWAP64(f + 0x9b05688c2b3e6c1fUL); - finalhash[6] = SWAP64(g + 0x1f83d9abfb41bd6bUL); - finalhash[7] = SWAP64(h + 0x5be0cd19137e2179UL); - - setup_ctx(&ctx, (char*) finalhash, 64); - - a = 0x6a09e667f3bcc908UL; - b = 0xbb67ae8584caa73bUL; - c = 0x3c6ef372fe94f82bUL; - d = 0xa54ff53a5f1d36f1UL; - e = 0x510e527fade682d1UL; - f = 0x9b05688c2b3e6c1fUL; - g = 0x1f83d9abfb41bd6bUL; - h = 0x5be0cd19137e2179UL; - - data = (uint64_t *) ctx.buffer; - //((uint64_t*)ctx.buffer)[8] = SWAP64((uint64_t)0x80); - - for (i = 0; i < 16; i++) - w[i] = SWAP64(data[i]); - - for (i = 0; i < 16; i++) { - t1 = k[i] + w[i] + h + Sigma1(e) + Ch(e, f, g); - t2 = Maj(a, b, c) + Sigma0(a); - - h = g; - g = f; - f = e; - e = d + t1; - d = c; - c = b; - b = a; - a = t1 + t2; - } - - for (i = 16; i < 80; i++) { - - w[i & 15] =sigma1(w[(i - 2) & 15]) + sigma0(w[(i - 15) & 15]) + w[(i -16) & 15] + w[(i - 7) & 15]; - t1 = k[i] + w[i & 15] + h + Sigma1(e) + Ch(e, f, g); - t2 = Maj(a, b, c) + Sigma0(a); - - h = g; - g = f; - f = e; - e = d + t1; - d = c; - c = b; - b = a; - a = t1 + t2; - } - return SWAP64(a + 0x6a09e667f3bcc908UL); -} - -__kernel void kernel_sha512(__global const sha512_key *password,__global uint64_t *hash, uint64_t start) -{ - uint64_t idx = get_global_id(0); - if (idx == 0 && start == 0) { - *hash = 0; - } - uint64_t winval; - - uint64_t junk[9]; - - __global uint64_t * source = (__global uint64_t*) password->v; - for (int i = 1; i < 9; i++) { - junk[i] = source[i]; - } - - junk[0] = SWAP64(idx + (start)); - - winval = sha512((char*)junk); - if (SWAP64(winval) < password->target) { - *hash = SWAP64(junk[0]); - } -} - diff --git a/src/bitmsghash/bitmsghash.cpp b/src/bitmsghash/bitmsghash.cpp deleted file mode 100644 index 2d0d4b50..00000000 --- a/src/bitmsghash/bitmsghash.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// bitmessage cracker, build with g++ or MSVS to a shared library, use included python code for usage under bitmessage -#ifdef _WIN32 -#include "Winsock.h" -#include "Windows.h" -#define uint64_t unsigned __int64 -#else -#include -#include -#include -#endif -#include -#include -#include -#if defined(__APPLE__) || defined(__FreeBSD__) || defined (__DragonFly__) || defined (__OpenBSD__) || defined (__NetBSD__) -#include -#include -#endif - -#include "openssl/sha.h" - -#define HASH_SIZE 64 -#define BUFLEN 16384 - -#if defined(__GNUC__) - #define EXPORT __attribute__ ((__visibility__("default"))) -#elif defined(_WIN32) - #define EXPORT __declspec(dllexport) -#endif - -#ifndef __APPLE__ -#define ntohll(x) ( ( (uint64_t)(ntohl( (unsigned int)((x << 32) >> 32) )) << 32) | ntohl( ((unsigned int)(x >> 32)) ) ) -#endif - -unsigned long long max_val; -unsigned char *initialHash; -unsigned long long successval = 0; -unsigned int numthreads = 0; - -#ifdef _WIN32 -DWORD WINAPI threadfunc(LPVOID param) { -#else -void * threadfunc(void* param) { -#endif - unsigned int incamt = *((unsigned int*)param); - SHA512_CTX sha; - unsigned char buf[HASH_SIZE + sizeof(uint64_t)] = { 0 }; - unsigned char output[HASH_SIZE] = { 0 }; - - memcpy(buf + sizeof(uint64_t), initialHash, HASH_SIZE); - - unsigned long long tmpnonce = incamt; - unsigned long long * nonce = (unsigned long long *)buf; - unsigned long long * hash = (unsigned long long *)output; - while (successval == 0) { - tmpnonce += numthreads; - - (*nonce) = ntohll(tmpnonce); /* increment nonce */ - SHA512_Init(&sha); - SHA512_Update(&sha, buf, HASH_SIZE + sizeof(uint64_t)); - SHA512_Final(output, &sha); - SHA512_Init(&sha); - SHA512_Update(&sha, output, HASH_SIZE); - SHA512_Final(output, &sha); - - if (ntohll(*hash) < max_val) { - successval = tmpnonce; - } - } -#ifdef _WIN32 - return 0; -#else - return NULL; -#endif -} - -void getnumthreads() -{ -#ifdef _WIN32 - DWORD_PTR dwProcessAffinity, dwSystemAffinity; -#elif __linux__ - cpu_set_t dwProcessAffinity; -#elif __OpenBSD__ - int mib[2], core_count = 0; - int dwProcessAffinity = 0; - size_t len2; -#else - int dwProcessAffinity = 0; - int32_t core_count = 0; -#endif - size_t len = sizeof(dwProcessAffinity); - if (numthreads > 0) - return; -#ifdef _WIN32 - GetProcessAffinityMask(GetCurrentProcess(), &dwProcessAffinity, &dwSystemAffinity); -#elif __linux__ - sched_getaffinity(0, len, &dwProcessAffinity); -#elif __OpenBSD__ - len2 = sizeof(core_count); - mib[0] = CTL_HW; - mib[1] = HW_NCPU; - if (sysctl(mib, 2, &core_count, &len2, 0, 0) == 0) - numthreads = core_count; -#else - if (sysctlbyname("hw.logicalcpu", &core_count, &len, 0, 0) == 0) - numthreads = core_count; - else if (sysctlbyname("hw.ncpu", &core_count, &len, 0, 0) == 0) - numthreads = core_count; -#endif - for (unsigned int i = 0; i < len * 8; i++) -#if defined(_WIN32) -#if defined(_MSC_VER) - if (dwProcessAffinity & (1i64 << i)) -#else // CYGWIN/MINGW - if (dwProcessAffinity & (1ULL << i)) -#endif -#elif defined __linux__ - if (CPU_ISSET(i, &dwProcessAffinity)) -#else - if (dwProcessAffinity & (1 << i)) -#endif - numthreads++; - if (numthreads == 0) // something failed - numthreads = 1; - printf("Number of threads: %i\n", (int)numthreads); -} - -extern "C" EXPORT unsigned long long BitmessagePOW(unsigned char * starthash, unsigned long long target) -{ - successval = 0; - max_val = target; - getnumthreads(); - initialHash = (unsigned char *)starthash; -# ifdef _WIN32 - HANDLE* threads = (HANDLE*)calloc(sizeof(HANDLE), numthreads); -# else - pthread_t* threads = (pthread_t*)calloc(sizeof(pthread_t), numthreads); - struct sched_param schparam; - schparam.sched_priority = 0; -# endif - unsigned int *threaddata = (unsigned int *)calloc(sizeof(unsigned int), numthreads); - for (unsigned int i = 0; i < numthreads; i++) { - threaddata[i] = i; -# ifdef _WIN32 - threads[i] = CreateThread(NULL, 0, threadfunc, (LPVOID)&threaddata[i], 0, NULL); - SetThreadPriority(threads[i], THREAD_PRIORITY_IDLE); -# else - pthread_create(&threads[i], NULL, threadfunc, (void*)&threaddata[i]); -# ifdef __linux__ - pthread_setschedparam(threads[i], SCHED_IDLE, &schparam); -# else - pthread_setschedparam(threads[i], SCHED_RR, &schparam); -# endif -# endif - } -# ifdef _WIN32 - WaitForMultipleObjects(numthreads, threads, TRUE, INFINITE); -# else - for (unsigned int i = 0; i < numthreads; i++) { - pthread_join(threads[i], NULL); - } -# endif - free(threads); - free(threaddata); - return successval; -} diff --git a/src/build_osx.py b/src/build_osx.py index 1d8f470e..2dea94b9 100644 --- a/src/build_osx.py +++ b/src/build_osx.py @@ -9,7 +9,8 @@ mainscript = ["bitmessagemain.py"] DATA_FILES = [ ('', ['sslkeys', 'images']), - ('bitmsghash', ['bitmsghash/bitmsghash.cl', 'bitmsghash/bitmsghash.so']), + ("workprover", ["gpusolver.cl"]), + ("workprover/fastsolver", ["libfastsolver.so"]), ('translations', glob('translations/*.qm')), ('ui', glob('bitmessageqt/*.ui')), ('translations', glob(str(QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)) + '/qt_??.qm')), diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py index a5750813..134b876a 100644 --- a/src/class_addressGenerator.py +++ b/src/class_addressGenerator.py @@ -201,12 +201,10 @@ class addressGenerator(threading.Thread, StoppableThread): queues.UISignalQueue.put(('writeNewAddressToTable', ( label, address, streamNumber))) shared.reloadMyAddressHashes() - if addressVersionNumber == 3: - queues.workerQueue.put(( - 'sendOutOrStoreMyV3Pubkey', ripe.digest())) - elif addressVersionNumber == 4: - queues.workerQueue.put(( - 'sendOutOrStoreMyV4Pubkey', address)) + + # If this is a chan address, the worker thread won't send out the pubkey over the network + + queues.workerQueue.put(("sendMyPubkey", address)) elif command == 'createDeterministicAddresses' \ or command == 'getDeterministicAddress' \ @@ -366,15 +364,11 @@ class addressGenerator(threading.Thread, StoppableThread): encodeVarint(streamNumber) + ripe.digest() ).digest()).digest()[32:] shared.myAddressesByTag[tag] = address - if addressVersionNumber == 3: - # If this is a chan address, - # the worker thread won't send out - # the pubkey over the network. - queues.workerQueue.put(( - 'sendOutOrStoreMyV3Pubkey', ripe.digest())) - elif addressVersionNumber == 4: - queues.workerQueue.put(( - 'sendOutOrStoreMyV4Pubkey', address)) + + # If this is a chan address, the worker thread won't send out the pubkey over the network + + queues.workerQueue.put(("sendMyPubkey", address)) + queues.UISignalQueue.put(( 'updateStatusBar', tr._translate( diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index 9ae2093b..ba68aac8 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -25,6 +25,7 @@ import state import tr from debug import logger import l10n +import workprover.utils class objectProcessor(threading.Thread): @@ -114,30 +115,30 @@ class objectProcessor(threading.Thread): break def checkackdata(self, data): - # Let's check whether this is a message acknowledgement bound for us. - if len(data) < 32: + ackData = data[16: ] + + if len(ackData) < 16 or ackData not in state.watchedAckData: + logger.debug("This object is not an acknowledgement bound for us") + return - # bypass nonce and time, retain object type/version/stream + body - readPosition = 16 + logger.info("This object is an acknowledgement bound for us") - if data[readPosition:] in shared.ackdataForWhichImWatching: - logger.info('This object is an acknowledgement bound for me.') - del shared.ackdataForWhichImWatching[data[readPosition:]] - sqlExecute( - 'UPDATE sent SET status=?, lastactiontime=?' - ' WHERE ackdata=?', - 'ackreceived', int(time.time()), data[readPosition:]) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', - (data[readPosition:], - tr._translate( - "MainWindow", - "Acknowledgement of the message received %1" - ).arg(l10n.formatTimestamp())) - )) - else: - logger.debug('This object is not an acknowledgement bound for me.') + state.watchedAckData -= {ackData} + + sqlExecute(""" + UPDATE "sent" SET "status" = 'ackreceived', "lastactiontime" = ? + WHERE "status" IN ('doingmsgpow', 'msgsent') AND "ackdata" == ?; + """, int(time.time()), ackData) + + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "ackreceived", + ackData, + tr._translate( + "MainWindow", + "Acknowledgement of the message received %1" + ).arg(l10n.formatTimestamp()) + ))) def processgetpubkey(self, data): if len(data) > 200: @@ -237,12 +238,8 @@ class objectProcessor(threading.Thread): 'Found getpubkey-requested-hash in my list of EC hashes.' ' Telling Worker thread to do the POW for a pubkey message' ' and send it out.') - if requestedAddressVersionNumber == 2: - queues.workerQueue.put(('doPOWForMyV2Pubkey', requestedHash)) - elif requestedAddressVersionNumber == 3: - queues.workerQueue.put(('sendOutOrStoreMyV3Pubkey', requestedHash)) - elif requestedAddressVersionNumber == 4: - queues.workerQueue.put(('sendOutOrStoreMyV4Pubkey', myAddress)) + + queues.workerQueue.put(("sendMyPubkey", myAddress)) def processpubkey(self, data): pubkeyProcessingStartTime = time.time() @@ -396,20 +393,23 @@ class objectProcessor(threading.Thread): ' Sanity check failed.') return - tag = data[readPosition:readPosition + 32] - if tag not in state.neededPubkeys: - logger.info( - 'We don\'t need this v4 pubkey. We didn\'t ask for it.') - return + tag = data[readPosition: readPosition + 32] + attributes = state.neededPubkeys.get(tag, None) - # Let us try to decrypt the pubkey - toAddress, _ = state.neededPubkeys[tag] - if shared.decryptAndCheckPubkeyPayload(data, toAddress) == \ - 'successful': - # At this point we know that we have been waiting on this - # pubkey. This function will command the workerThread - # to start work on the messages that require it. - self.possibleNewPubkey(toAddress) + if attributes is None: + logger.info("We don't need this v4 pubkey. We didn't ask for it") + else: + address, cryptor = attributes + + storedData = protocol.decryptAndCheckV4Pubkey(data, address, cryptor) + + if storedData is not None: + sqlExecute(""" + INSERT INTO "pubkeys" ("address", "addressversion", "transmitdata", "time", "usedpersonally") + VALUES (?, 4, ?, ?, 'yes'); + """, address, storedData, int(time.time())) + + self.possibleNewPubkey(address) # Display timing data timeRequiredToProcessPubkey = time.time( @@ -608,16 +608,16 @@ class objectProcessor(threading.Thread): # If the toAddress version number is 3 or higher and not one of # my chan addresses: if decodeAddress(toAddress)[1] >= 3 \ - and not BMConfigParser().safeGetBoolean(toAddress, 'chan'): + and not BMConfigParser().has_section(toAddress): # If I'm not friendly with this person: if not shared.isAddressInMyAddressBookSubscriptionsListOrWhitelist(fromAddress): - requiredNonceTrialsPerByte = BMConfigParser().getint( - toAddress, 'noncetrialsperbyte') - requiredPayloadLengthExtraBytes = BMConfigParser().getint( - toAddress, 'payloadlengthextrabytes') - if not protocol.isProofOfWorkSufficient( - data, requiredNonceTrialsPerByte, - requiredPayloadLengthExtraBytes): + byteDifficulty = BMConfigParser().getint(toAddress, "noncetrialsperbyte") + lengthExtension = BMConfigParser().getint(toAddress, "payloadlengthextrabytes") + + byteDifficulty = max(defaults.networkDefaultProofOfWorkNonceTrialsPerByte, byteDifficulty) + lengthExtension = max(defaults.networkDefaultPayloadLengthExtraBytes, lengthExtension) + + if not workprover.utils.checkWorkSufficient(data, byteDifficulty, lengthExtension): logger.info( 'Proof of work in msg is insufficient only because' ' it does not meet our higher requirement.') @@ -748,7 +748,7 @@ class objectProcessor(threading.Thread): not BMConfigParser().safeGetBoolean(toAddress, 'dontsendack') and not BMConfigParser().safeGetBoolean(toAddress, 'chan') ): - shared.checkAndShareObjectWithPeers(ackData[24:]) + protocol.checkAndShareObjectWithPeers(ackData[24:]) # Display timing data timeRequiredToAttemptToDecryptMessage = time.time( @@ -1015,42 +1015,43 @@ class objectProcessor(threading.Thread): have been waiting for. Let's check. """ - # For address versions <= 3, we wait on a key with the correct - # address version, stream number and RIPE hash. - _, addressVersion, streamNumber, ripe = decodeAddress(address) - if addressVersion <= 3: - if address in state.neededPubkeys: - del state.neededPubkeys[address] - self.sendMessages(address) - else: - logger.debug( - 'We don\'t need this pub key. We didn\'t ask for it.' - ' For address: %s', address) - # For address versions >= 4, we wait on a pubkey with the correct tag. - # Let us create the tag from the address and see if we were waiting - # for it. - elif addressVersion >= 4: - tag = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersion) + encodeVarint(streamNumber) - + ripe).digest() - ).digest()[32:] - if tag in state.neededPubkeys: - del state.neededPubkeys[tag] - self.sendMessages(address) + status, version, stream, ripe = decodeAddress(address) - def sendMessages(self, address): - """ - This function is called by the possibleNewPubkey function when - that function sees that we now have the necessary pubkey - to send one or more messages. - """ - logger.info('We have been awaiting the arrival of this pubkey.') - sqlExecute( - "UPDATE sent SET status='doingmsgpow', retrynumber=0" - " WHERE toaddress=?" - " AND (status='awaitingpubkey' OR status='doingpubkeypow')" - " AND folder='sent'", address) - queues.workerQueue.put(('sendmessage', '')) + if version <= 3: + needed = address in state.neededPubkeys + state.neededPubkeys.pop(address, None) + elif version == 4: + secretEncryptionKey, tag = protocol.calculateAddressTag(version, stream, ripe) + + needed = tag in state.neededPubkeys + state.neededPubkeys.pop(tag, None) + + if needed: + logger.info("We have been awaiting the arrival of this pubkey") + + sqlExecute(""" + UPDATE "sent" SET "status" = 'msgqueued' + WHERE "status" IN ('doingpubkeypow', 'awaitingpubkey') AND "toaddress" == ? AND "folder" == 'sent'; + """, address) + + queues.workerQueue.put(("sendmessage", )) + + queued = sqlQuery(""" + SELECT "ackdata" FROM "sent" + WHERE "status" == 'msgqueued' AND "toaddress" == ? AND "folder" == 'sent'; + """, address) + + for i, in queued: + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "msgqueued", + i, + tr._translate( + "MainWindow", + "Queued." + ) + ))) + else: + logger.debug("We don't need this pubkey, we didn't ask for it: %s", address) def ackDataHasAValidHeader(self, ackData): if len(ackData) < protocol.Header.size: @@ -1075,7 +1076,7 @@ class objectProcessor(threading.Thread): # The largest message should be either an inv or a getdata # message at 1.6 MB in size. # That doesn't mean that the object may be that big. The - # shared.checkAndShareObjectWithPeers function will verify + # protocol.checkAndShareObjectWithPeers function will verify # that it is no larger than 2^18 bytes. return False # test the checksum in the message. diff --git a/src/class_singleCleaner.py b/src/class_singleCleaner.py index 1ba342b6..91a641af 100644 --- a/src/class_singleCleaner.py +++ b/src/class_singleCleaner.py @@ -35,6 +35,45 @@ import knownnodes import queues import state +def resendStaleMessages(): + staleMessages = sqlQuery(""" + SELECT "toaddress", "ackdata", "status" FROM "sent" + WHERE "status" IN ('awaitingpubkey', 'msgsent') AND "sleeptill" < ? AND "senttime" > ? AND "folder" == 'sent'; + """, int(time.time()), int(time.time()) - shared.maximumLengthOfTimeToBotherResendingMessages) + + resendMessages = False + + for destination, ackData, status in staleMessages: + if status == "awaitingpubkey": + logger.info("Retrying getpubkey request for %s", destination) + + sqlExecute(""" + UPDATE "sent" SET "status" = 'msgqueued' + WHERE "status" == 'awaitingpubkey' AND "ackdata" == ?; + """, ackData) + elif status == "msgsent": + state.watchedAckData -= {ackData} + + sqlExecute(""" + UPDATE "sent" SET "status" = 'msgqueued' + WHERE "status" == 'msgsent' AND "ackdata" == ?; + """, ackData) + + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "msgqueued", + ackData, + tr._translate( + "MainWindow", + "Queued." + ) + ))) + + resendMessages = True + + if resendMessages: + logger.info("Resending old messages with undelivered acks or unknown pubkeys") + + queues.workerQueue.put(("sendmessage", )) class singleCleaner(threading.Thread, StoppableThread): cycleLength = 300 @@ -92,28 +131,8 @@ class singleCleaner(threading.Thread, StoppableThread): # Let us resend getpubkey objects if we have not yet heard # a pubkey, and also msg objects if we have not yet heard # an acknowledgement - queryreturn = sqlQuery( - "SELECT toaddress, ackdata, status FROM sent" - " WHERE ((status='awaitingpubkey' OR status='msgsent')" - " AND folder='sent' AND sleeptill?)", - int(time.time()), - int(time.time()) - - shared.maximumLengthOfTimeToBotherResendingMessages - ) - for row in queryreturn: - if len(row) < 2: - logger.error( - 'Something went wrong in the singleCleaner thread:' - ' a query did not return the requested fields. %r', - row - ) - self.stop.wait(3) - break - toAddress, ackData, status = row - if status == 'awaitingpubkey': - resendPubkeyRequest(toAddress) - elif status == 'msgsent': - resendMsg(ackData) + + resendStaleMessages() # cleanup old nodes now = int(time.time()) @@ -189,41 +208,3 @@ class singleCleaner(threading.Thread, StoppableThread): if state.shutdown == 0: self.stop.wait(singleCleaner.cycleLength) - - -def resendPubkeyRequest(address): - logger.debug( - 'It has been a long time and we haven\'t heard a response to our' - ' getpubkey request. Sending again.' - ) - try: - # We need to take this entry out of the neededPubkeys structure - # because the queues.workerQueue checks to see whether the entry - # is already present and will not do the POW and send the message - # because it assumes that it has already done it recently. - del state.neededPubkeys[address] - except: - pass - - queues.UISignalQueue.put(( - 'updateStatusBar', - 'Doing work necessary to again attempt to request a public key...')) - sqlExecute( - '''UPDATE sent SET status='msgqueued' WHERE toaddress=?''', - address) - queues.workerQueue.put(('sendmessage', '')) - - -def resendMsg(ackdata): - logger.debug( - 'It has been a long time and we haven\'t heard an acknowledgement' - ' to our msg. Sending again.' - ) - sqlExecute( - '''UPDATE sent SET status='msgqueued' WHERE ackdata=?''', - ackdata) - queues.workerQueue.put(('sendmessage', '')) - queues.UISignalQueue.put(( - 'updateStatusBar', - 'Doing work necessary to again attempt to deliver a message...' - )) diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py deleted file mode 100644 index d51e124a..00000000 --- a/src/class_singleWorker.py +++ /dev/null @@ -1,1418 +0,0 @@ -from __future__ import division - -import time -import threading -import hashlib -from struct import pack -# used when the API must execute an outside program -from subprocess import call # nosec -from binascii import hexlify, unhexlify - -import tr -import l10n -import protocol -import queues -import state -import shared -import defaults -import highlevelcrypto -import proofofwork -import helper_inbox -import helper_random -import helper_msgcoding -from bmconfigparser import BMConfigParser -from debug import logger -from inventory import Inventory -from addresses import ( - decodeAddress, encodeVarint, decodeVarint, calculateInventoryHash -) -# from helper_generic import addDataPadding -from helper_threading import StoppableThread -from helper_sql import sqlQuery, sqlExecute - - -# This thread, of which there is only one, does the heavy lifting: -# calculating POWs. - -def sizeof_fmt(num, suffix='h/s'): - for unit in ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z']: - if abs(num) < 1000.0: - return "%3.1f%s%s" % (num, unit, suffix) - num /= 1024.0 - return "%.1f%s%s" % (num, 'Yi', suffix) - - -class singleWorker(threading.Thread, StoppableThread): - - def __init__(self): - # QThread.__init__(self, parent) - threading.Thread.__init__(self, name="singleWorker") - self.initStop() - proofofwork.init() - - def stopThread(self): - try: - queues.workerQueue.put(("stopThread", "data")) - except: - pass - super(singleWorker, self).stopThread() - - def run(self): - - while not state.sqlReady and state.shutdown == 0: - self.stop.wait(2) - if state.shutdown > 0: - return - - # Initialize the neededPubkeys dictionary. - queryreturn = sqlQuery( - '''SELECT DISTINCT toaddress FROM sent''' - ''' WHERE (status='awaitingpubkey' AND folder='sent')''') - for row in queryreturn: - toAddress, = row - # toStatus - _, toAddressVersionNumber, toStreamNumber, toRipe = \ - decodeAddress(toAddress) - if toAddressVersionNumber <= 3: - state.neededPubkeys[toAddress] = 0 - elif toAddressVersionNumber >= 4: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(toAddressVersionNumber) + - encodeVarint(toStreamNumber) + toRipe - ).digest()).digest() - # Note that this is the first half of the sha512 hash. - privEncryptionKey = doubleHashOfAddressData[:32] - tag = doubleHashOfAddressData[32:] - # We'll need this for when we receive a pubkey reply: - # it will be encrypted and we'll need to decrypt it. - state.neededPubkeys[tag] = ( - toAddress, - highlevelcrypto.makeCryptor( - hexlify(privEncryptionKey)) - ) - - # Initialize the shared.ackdataForWhichImWatching data structure - queryreturn = sqlQuery( - '''SELECT ackdata FROM sent WHERE status = 'msgsent' ''') - for row in queryreturn: - ackdata, = row - logger.info('Watching for ackdata ' + hexlify(ackdata)) - shared.ackdataForWhichImWatching[ackdata] = 0 - - # Fix legacy (headerless) watched ackdata to include header - for oldack in shared.ackdataForWhichImWatching.keys(): - if (len(oldack) == 32): - # attach legacy header, always constant (msg/1/1) - newack = '\x00\x00\x00\x02\x01\x01' + oldack - shared.ackdataForWhichImWatching[newack] = 0 - sqlExecute( - 'UPDATE sent SET ackdata=? WHERE ackdata=?', - newack, oldack - ) - del shared.ackdataForWhichImWatching[oldack] - - # give some time for the GUI to start - # before we start on existing POW tasks. - self.stop.wait(10) - - if state.shutdown == 0: - # just in case there are any pending tasks for msg - # messages that have yet to be sent. - queues.workerQueue.put(('sendmessage', '')) - # just in case there are any tasks for Broadcasts - # that have yet to be sent. - queues.workerQueue.put(('sendbroadcast', '')) - - while state.shutdown == 0: - self.busy = 0 - command, data = queues.workerQueue.get() - self.busy = 1 - if command == 'sendmessage': - try: - self.sendMsg() - except: - pass - elif command == 'sendbroadcast': - try: - self.sendBroadcast() - except: - pass - elif command == 'doPOWForMyV2Pubkey': - try: - self.doPOWForMyV2Pubkey(data) - except: - pass - elif command == 'sendOutOrStoreMyV3Pubkey': - try: - self.sendOutOrStoreMyV3Pubkey(data) - except: - pass - elif command == 'sendOutOrStoreMyV4Pubkey': - try: - self.sendOutOrStoreMyV4Pubkey(data) - except: - pass - elif command == 'resetPoW': - try: - proofofwork.resetPoW() - except: - pass - elif command == 'stopThread': - self.busy = 0 - return - else: - logger.error( - 'Probable programming error: The command sent' - ' to the workerThread is weird. It is: %s\n', - command - ) - - queues.workerQueue.task_done() - logger.info("Quitting...") - - def _getKeysForAddress(self, address): - privSigningKeyBase58 = BMConfigParser().get( - address, 'privsigningkey') - privEncryptionKeyBase58 = BMConfigParser().get( - address, 'privencryptionkey') - - privSigningKeyHex = hexlify(shared.decodeWalletImportFormat( - privSigningKeyBase58)) - privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( - privEncryptionKeyBase58)) - - # The \x04 on the beginning of the public keys are not sent. - # This way there is only one acceptable way to encode - # and send a public key. - pubSigningKey = unhexlify(highlevelcrypto.privToPub( - privSigningKeyHex))[1:] - pubEncryptionKey = unhexlify(highlevelcrypto.privToPub( - privEncryptionKeyHex))[1:] - - return privSigningKeyHex, privEncryptionKeyHex, \ - pubSigningKey, pubEncryptionKey - - def _doPOWDefaults(self, payload, TTL, - log_prefix='', - log_time=False): - target = 2 ** 64 / ( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte * ( - len(payload) + 8 + - defaults.networkDefaultPayloadLengthExtraBytes + (( - TTL * ( - len(payload) + 8 + - defaults.networkDefaultPayloadLengthExtraBytes - )) / (2 ** 16)) - )) - initialHash = hashlib.sha512(payload).digest() - logger.info( - '%s Doing proof of work... TTL set to %s', log_prefix, TTL) - if log_time: - start_time = time.time() - trialValue, nonce = proofofwork.run(target, initialHash) - logger.info( - '%s Found proof of work %s Nonce: %s', - log_prefix, trialValue, nonce - ) - try: - delta = time.time() - start_time - logger.info( - 'PoW took %.1f seconds, speed %s.', - delta, sizeof_fmt(nonce / delta) - ) - except: # NameError - pass - payload = pack('>Q', nonce) + payload - # inventoryHash = calculateInventoryHash(payload) - return payload - - # This function also broadcasts out the pubkey message - # once it is done with the POW - def doPOWForMyV2Pubkey(self, adressHash): - # Look up my stream number based on my address hash - """configSections = shared.config.addresses() - for addressInKeysFile in configSections: - if addressInKeysFile != 'bitmessagesettings': - status, addressVersionNumber, streamNumber, \ - hashFromThisParticularAddress = \ - decodeAddress(addressInKeysFile) - if hash == hashFromThisParticularAddress: - myAddress = addressInKeysFile - break""" - myAddress = shared.myAddressesByHash[adressHash] - # status - _, addressVersionNumber, streamNumber, adressHash = decodeAddress(myAddress) - - # 28 days from now plus or minus five minutes - TTL = int(28 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) - embeddedTime = int(time.time() + TTL) - payload = pack('>Q', (embeddedTime)) - payload += '\x00\x00\x00\x01' # object type: pubkey - payload += encodeVarint(addressVersionNumber) # Address version number - payload += encodeVarint(streamNumber) - # bitfield of features supported by me (see the wiki). - payload += protocol.getBitfield(myAddress) - - try: - # privSigningKeyHex, privEncryptionKeyHex - _, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(myAddress) - except Exception as err: - logger.error( - 'Error within doPOWForMyV2Pubkey. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - return - - payload += pubSigningKey + pubEncryptionKey - - # Do the POW for this pubkey message - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For pubkey message)') - - inventoryHash = calculateInventoryHash(payload) - objectType = 1 - Inventory()[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, '') - - logger.info('broadcasting inv with hash: %s', hexlify(inventoryHash)) - - queues.invQueue.put((streamNumber, inventoryHash)) - queues.UISignalQueue.put(('updateStatusBar', '')) - try: - BMConfigParser().set( - myAddress, 'lastpubkeysendtime', str(int(time.time()))) - BMConfigParser().save() - except: - # The user deleted the address out of the keys.dat file - # before this finished. - pass - - # If this isn't a chan address, this function assembles the pubkey data, - # does the necessary POW and sends it out. If it *is* a chan then it - # assembles the pubkey and stores is in the pubkey table so that we can - # send messages to "ourselves". - def sendOutOrStoreMyV3Pubkey(self, adressHash): - try: - myAddress = shared.myAddressesByHash[adressHash] - except: - # The address has been deleted. - return - if BMConfigParser().safeGetBoolean(myAddress, 'chan'): - logger.info('This is a chan address. Not sending pubkey.') - return - _, addressVersionNumber, streamNumber, adressHash = decodeAddress( - myAddress) - - # 28 days from now plus or minus five minutes - TTL = int(28 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) - embeddedTime = int(time.time() + TTL) - - # signedTimeForProtocolV2 = embeddedTime - TTL - # According to the protocol specification, the expiresTime - # along with the pubkey information is signed. But to be - # backwards compatible during the upgrade period, we shall sign - # not the expiresTime but rather the current time. There must be - # precisely a 28 day difference between the two. After the upgrade - # period we'll switch to signing the whole payload with the - # expiresTime time. - - payload = pack('>Q', (embeddedTime)) - payload += '\x00\x00\x00\x01' # object type: pubkey - payload += encodeVarint(addressVersionNumber) # Address version number - payload += encodeVarint(streamNumber) - # bitfield of features supported by me (see the wiki). - payload += protocol.getBitfield(myAddress) - - try: - # , privEncryptionKeyHex - privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(myAddress) - except Exception as err: - logger.error( - 'Error within sendOutOrStoreMyV3Pubkey. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - return - - payload += pubSigningKey + pubEncryptionKey - - payload += encodeVarint(BMConfigParser().getint( - myAddress, 'noncetrialsperbyte')) - payload += encodeVarint(BMConfigParser().getint( - myAddress, 'payloadlengthextrabytes')) - - signature = highlevelcrypto.sign(payload, privSigningKeyHex) - payload += encodeVarint(len(signature)) - payload += signature - - # Do the POW for this pubkey message - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For pubkey message)') - - inventoryHash = calculateInventoryHash(payload) - objectType = 1 - Inventory()[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, '') - - logger.info('broadcasting inv with hash: ' + hexlify(inventoryHash)) - - queues.invQueue.put((streamNumber, inventoryHash)) - queues.UISignalQueue.put(('updateStatusBar', '')) - try: - BMConfigParser().set( - myAddress, 'lastpubkeysendtime', str(int(time.time()))) - BMConfigParser().save() - except: - # The user deleted the address out of the keys.dat file - # before this finished. - pass - - # If this isn't a chan address, this function assembles - # the pubkey data, does the necessary POW and sends it out. - def sendOutOrStoreMyV4Pubkey(self, myAddress): - if not BMConfigParser().has_section(myAddress): - # The address has been deleted. - return - if shared.BMConfigParser().safeGetBoolean(myAddress, 'chan'): - logger.info('This is a chan address. Not sending pubkey.') - return - _, addressVersionNumber, streamNumber, addressHash = decodeAddress( - myAddress) - - # 28 days from now plus or minus five minutes - TTL = int(28 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) - embeddedTime = int(time.time() + TTL) - payload = pack('>Q', (embeddedTime)) - payload += '\x00\x00\x00\x01' # object type: pubkey - payload += encodeVarint(addressVersionNumber) # Address version number - payload += encodeVarint(streamNumber) - dataToEncrypt = protocol.getBitfield(myAddress) - - try: - # , privEncryptionKeyHex - privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(myAddress) - except Exception as err: - logger.error( - 'Error within sendOutOrStoreMyV4Pubkey. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - return - - dataToEncrypt += pubSigningKey + pubEncryptionKey - - dataToEncrypt += encodeVarint(BMConfigParser().getint( - myAddress, 'noncetrialsperbyte')) - dataToEncrypt += encodeVarint(BMConfigParser().getint( - myAddress, 'payloadlengthextrabytes')) - - # When we encrypt, we'll use a hash of the data - # contained in an address as a decryption key. This way - # in order to read the public keys in a pubkey message, - # a node must know the address first. We'll also tag, - # unencrypted, the pubkey with part of the hash so that nodes - # know which pubkey object to try to decrypt - # when they want to send a message. - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) + - encodeVarint(streamNumber) + addressHash - ).digest()).digest() - payload += doubleHashOfAddressData[32:] # the tag - signature = highlevelcrypto.sign( - payload + dataToEncrypt, privSigningKeyHex - ) - dataToEncrypt += encodeVarint(len(signature)) - dataToEncrypt += signature - - privEncryptionKey = doubleHashOfAddressData[:32] - pubEncryptionKey = highlevelcrypto.pointMult(privEncryptionKey) - payload += highlevelcrypto.encrypt( - dataToEncrypt, hexlify(pubEncryptionKey)) - - # Do the POW for this pubkey message - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For pubkey message)') - - inventoryHash = calculateInventoryHash(payload) - objectType = 1 - Inventory()[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, - doubleHashOfAddressData[32:] - ) - - logger.info('broadcasting inv with hash: ' + hexlify(inventoryHash)) - - queues.invQueue.put((streamNumber, inventoryHash)) - queues.UISignalQueue.put(('updateStatusBar', '')) - try: - BMConfigParser().set( - myAddress, 'lastpubkeysendtime', str(int(time.time()))) - BMConfigParser().save() - except Exception as err: - logger.error( - 'Error: Couldn\'t add the lastpubkeysendtime' - ' to the keys.dat file. Error message: %s', err - ) - - def sendBroadcast(self): - # Reset just in case - sqlExecute( - '''UPDATE sent SET status='broadcastqueued' ''' - - '''WHERE status = 'doingbroadcastpow' ''') - queryreturn = sqlQuery( - '''SELECT fromaddress, subject, message, ''' - ''' ackdata, ttl, encodingtype FROM sent ''' - ''' WHERE status=? and folder='sent' ''', 'broadcastqueued') - - for row in queryreturn: - fromaddress, subject, body, ackdata, TTL, encoding = row - # status - _, addressVersionNumber, streamNumber, ripe = \ - decodeAddress(fromaddress) - if addressVersionNumber <= 1: - logger.error( - 'Error: In the singleWorker thread, the ' - ' sendBroadcast function doesn\'t understand' - ' the address version.\n') - return - # We need to convert our private keys to public keys in order - # to include them. - try: - # , privEncryptionKeyHex - privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(fromaddress) - except: - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Error! Could not find sender address" - " (your address) in the keys.dat file.")) - )) - continue - - sqlExecute( - '''UPDATE sent SET status='doingbroadcastpow' ''' - ''' WHERE ackdata=? AND status='broadcastqueued' ''', - ackdata) - - # At this time these pubkeys are 65 bytes long - # because they include the encoding byte which we won't - # be sending in the broadcast message. - # pubSigningKey = \ - # highlevelcrypto.privToPub(privSigningKeyHex).decode('hex') - - if TTL > 28 * 24 * 60 * 60: - TTL = 28 * 24 * 60 * 60 - if TTL < 60 * 60: - TTL = 60 * 60 - # add some randomness to the TTL - TTL = int(TTL + helper_random.randomrandrange(-300, 300)) - embeddedTime = int(time.time() + TTL) - payload = pack('>Q', embeddedTime) - payload += '\x00\x00\x00\x03' # object type: broadcast - - if addressVersionNumber <= 3: - payload += encodeVarint(4) # broadcast version - else: - payload += encodeVarint(5) # broadcast version - - payload += encodeVarint(streamNumber) - if addressVersionNumber >= 4: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) + - encodeVarint(streamNumber) + ripe - ).digest()).digest() - tag = doubleHashOfAddressData[32:] - payload += tag - else: - tag = '' - - dataToEncrypt = encodeVarint(addressVersionNumber) - dataToEncrypt += encodeVarint(streamNumber) - # behavior bitfield - dataToEncrypt += protocol.getBitfield(fromaddress) - dataToEncrypt += pubSigningKey + pubEncryptionKey - if addressVersionNumber >= 3: - dataToEncrypt += encodeVarint(BMConfigParser().getint( - fromaddress, 'noncetrialsperbyte')) - dataToEncrypt += encodeVarint(BMConfigParser().getint( - fromaddress, 'payloadlengthextrabytes')) - # message encoding type - dataToEncrypt += encodeVarint(encoding) - encodedMessage = helper_msgcoding.MsgEncode( - {"subject": subject, "body": body}, encoding) - dataToEncrypt += encodeVarint(encodedMessage.length) - dataToEncrypt += encodedMessage.data - dataToSign = payload + dataToEncrypt - - signature = highlevelcrypto.sign( - dataToSign, privSigningKeyHex) - dataToEncrypt += encodeVarint(len(signature)) - dataToEncrypt += signature - - # Encrypt the broadcast with the information - # contained in the broadcaster's address. - # Anyone who knows the address can generate - # the private encryption key to decrypt the broadcast. - # This provides virtually no privacy; its purpose is to keep - # questionable and illegal content from flowing through the - # Internet connections and being stored on the disk of 3rd parties. - if addressVersionNumber <= 3: - privEncryptionKey = hashlib.sha512( - encodeVarint(addressVersionNumber) + - encodeVarint(streamNumber) + ripe - ).digest()[:32] - else: - privEncryptionKey = doubleHashOfAddressData[:32] - - pubEncryptionKey = highlevelcrypto.pointMult(privEncryptionKey) - payload += highlevelcrypto.encrypt( - dataToEncrypt, hexlify(pubEncryptionKey)) - - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Doing work necessary to send broadcast...")) - )) - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For broadcast message)') - - # Sanity check. The payload size should never be larger - # than 256 KiB. There should be checks elsewhere in the code - # to not let the user try to send a message this large - # until we implement message continuation. - if len(payload) > 2 ** 18: # 256 KiB - logger.critical( - 'This broadcast object is too large to send.' - ' This should never happen. Object size: %s', - len(payload) - ) - continue - - inventoryHash = calculateInventoryHash(payload) - objectType = 3 - Inventory()[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, tag) - logger.info( - 'sending inv (within sendBroadcast function)' - ' for object: %s', - hexlify(inventoryHash) - ) - queues.invQueue.put((streamNumber, inventoryHash)) - - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Broadcast sent on %1" - ).arg(l10n.formatTimestamp())) - )) - - # Update the status of the message in the 'sent' table to have - # a 'broadcastsent' status - sqlExecute( - 'UPDATE sent SET msgid=?, status=?, lastactiontime=?' - ' WHERE ackdata=?', - inventoryHash, 'broadcastsent', int(time.time()), ackdata - ) - - def sendMsg(self): - # Reset just in case - sqlExecute( - '''UPDATE sent SET status='msgqueued' ''' - ''' WHERE status IN ('doingpubkeypow', 'doingmsgpow')''') - queryreturn = sqlQuery( - '''SELECT toaddress, fromaddress, subject, message, ''' - ''' ackdata, status, ttl, retrynumber, encodingtype FROM ''' - ''' sent WHERE (status='msgqueued' or status='forcepow') ''' - ''' and folder='sent' ''') - # while we have a msg that needs some work - for row in queryreturn: - toaddress, fromaddress, subject, message, \ - ackdata, status, TTL, retryNumber, encoding = row - # toStatus - _, toAddressVersionNumber, toStreamNumber, toRipe = \ - decodeAddress(toaddress) - # fromStatus, , ,fromRipe - _, fromAddressVersionNumber, fromStreamNumber, _ = \ - decodeAddress(fromaddress) - - # We may or may not already have the pubkey - # for this toAddress. Let's check. - if status == 'forcepow': - # if the status of this msg is 'forcepow' - # then clearly we have the pubkey already - # because the user could not have overridden the message - # about the POW being too difficult without knowing - # the required difficulty. - pass - elif status == 'doingmsgpow': - # We wouldn't have set the status to doingmsgpow - # if we didn't already have the pubkey so let's assume - # that we have it. - pass - # If we are sending a message to ourselves or a chan - # then we won't need an entry in the pubkeys table; - # we can calculate the needed pubkey using the private keys - # in our keys.dat file. - elif BMConfigParser().has_section(toaddress): - sqlExecute( - '''UPDATE sent SET status='doingmsgpow' ''' - ''' WHERE toaddress=? AND status='msgqueued' ''', - toaddress - ) - status = 'doingmsgpow' - elif status == 'msgqueued': - # Let's see if we already have the pubkey in our pubkeys table - queryreturn = sqlQuery( - '''SELECT address FROM pubkeys WHERE address=?''', - toaddress - ) - # If we have the needed pubkey in the pubkey table already, - if queryreturn != []: - # set the status of this msg to doingmsgpow - sqlExecute( - '''UPDATE sent SET status='doingmsgpow' ''' - ''' WHERE toaddress=? AND status='msgqueued' ''', - toaddress - ) - status = 'doingmsgpow' - # mark the pubkey as 'usedpersonally' so that - # we don't delete it later. If the pubkey version - # is >= 4 then usedpersonally will already be set - # to yes because we'll only ever have - # usedpersonally v4 pubkeys in the pubkeys table. - sqlExecute( - '''UPDATE pubkeys SET usedpersonally='yes' ''' - ''' WHERE address=?''', - toaddress - ) - # We don't have the needed pubkey in the pubkeys table already. - else: - if toAddressVersionNumber <= 3: - toTag = '' - else: - toTag = hashlib.sha512(hashlib.sha512( - encodeVarint(toAddressVersionNumber) + - encodeVarint(toStreamNumber) + toRipe - ).digest()).digest()[32:] - if toaddress in state.neededPubkeys or \ - toTag in state.neededPubkeys: - # We already sent a request for the pubkey - sqlExecute( - '''UPDATE sent SET status='awaitingpubkey', ''' - ''' sleeptill=? WHERE toaddress=? ''' - ''' AND status='msgqueued' ''', - int(time.time()) + 2.5 * 24 * 60 * 60, - toaddress - ) - queues.UISignalQueue.put(( - 'updateSentItemStatusByToAddress', ( - toaddress, - tr._translate( - "MainWindow", - "Encryption key was requested earlier.")) - )) - # on with the next msg on which we can do some work - continue - else: - # We have not yet sent a request for the pubkey - needToRequestPubkey = True - # If we are trying to send to address - # version >= 4 then the needed pubkey might be - # encrypted in the inventory. - # If we have it we'll need to decrypt it - # and put it in the pubkeys table. - - # The decryptAndCheckPubkeyPayload function - # expects that the shared.neededPubkeys dictionary - # already contains the toAddress and cryptor - # object associated with the tag for this toAddress. - if toAddressVersionNumber >= 4: - doubleHashOfToAddressData = hashlib.sha512( - hashlib.sha512(encodeVarint( - toAddressVersionNumber) + - encodeVarint(toStreamNumber) + - toRipe - ).digest() - ).digest() - # The first half of the sha512 hash. - privEncryptionKey = doubleHashOfToAddressData[:32] - # The second half of the sha512 hash. - tag = doubleHashOfToAddressData[32:] - state.neededPubkeys[tag] = ( - toaddress, - highlevelcrypto.makeCryptor( - hexlify(privEncryptionKey)) - ) - - for value in Inventory().by_type_and_tag(1, toTag): - # if valid, this function also puts it - # in the pubkeys table. - if shared.decryptAndCheckPubkeyPayload( - value.payload, toaddress - ) == 'successful': - needToRequestPubkey = False - sqlExecute( - '''UPDATE sent SET ''' - ''' status='doingmsgpow', ''' - ''' retrynumber=0 WHERE ''' - ''' toaddress=? AND ''' - ''' (status='msgqueued' or ''' - ''' status='awaitingpubkey' or ''' - ''' status='doingpubkeypow')''', - toaddress) - del state.neededPubkeys[tag] - break - # else: - # There was something wrong with this - # pubkey object even though it had - # the correct tag- almost certainly - # because of malicious behavior or - # a badly programmed client. If there are - # any other pubkeys in our inventory - # with the correct tag then we'll try - # to decrypt those. - if needToRequestPubkey: - sqlExecute( - '''UPDATE sent SET ''' - ''' status='doingpubkeypow' WHERE ''' - ''' toaddress=? AND status='msgqueued' ''', - toaddress - ) - queues.UISignalQueue.put(( - 'updateSentItemStatusByToAddress', ( - toaddress, - tr._translate( - "MainWindow", - "Sending a request for the" - " recipient\'s encryption key.")) - )) - self.requestPubKey(toaddress) - # on with the next msg on which we can do some work - continue - - # At this point we know that we have the necessary pubkey - # in the pubkeys table. - - TTL *= 2**retryNumber - if TTL > 28 * 24 * 60 * 60: - TTL = 28 * 24 * 60 * 60 - # add some randomness to the TTL - TTL = int(TTL + helper_random.randomrandrange(-300, 300)) - embeddedTime = int(time.time() + TTL) - - # if we aren't sending this to ourselves or a chan - if not BMConfigParser().has_section(toaddress): - shared.ackdataForWhichImWatching[ackdata] = 0 - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Looking up the receiver\'s public key")) - )) - logger.info('Sending a message.') - logger.debug( - 'First 150 characters of message: %s', - repr(message[:150]) - ) - - # Let us fetch the recipient's public key out of - # our database. If the required proof of work difficulty - # is too hard then we'll abort. - queryreturn = sqlQuery( - 'SELECT transmitdata FROM pubkeys WHERE address=?', - toaddress) - for row in queryreturn: - pubkeyPayload, = row - - # The pubkey message is stored with the following items - # all appended: - # -address version - # -stream number - # -behavior bitfield - # -pub signing key - # -pub encryption key - # -nonce trials per byte (if address version is >= 3) - # -length extra bytes (if address version is >= 3) - - # to bypass the address version whose length is definitely 1 - readPosition = 1 - _, streamNumberLength = decodeVarint( - pubkeyPayload[readPosition:readPosition + 10]) - readPosition += streamNumberLength - behaviorBitfield = pubkeyPayload[readPosition:readPosition + 4] - # Mobile users may ask us to include their address's - # RIPE hash on a message unencrypted. Before we actually - # do it the sending human must check a box - # in the settings menu to allow it. - - # if receiver is a mobile device who expects that their - # address RIPE is included unencrypted on the front of - # the message.. - if shared.isBitSetWithinBitfield(behaviorBitfield, 30): - # if we are Not willing to include the receiver's - # RIPE hash on the message.. - if not shared.BMConfigParser().safeGetBoolean( - 'bitmessagesettings', 'willinglysendtomobile' - ): - logger.info( - 'The receiver is a mobile user but the' - ' sender (you) has not selected that you' - ' are willing to send to mobiles. Aborting' - ' send.' - ) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Problem: Destination is a mobile" - " device who requests that the" - " destination be included in the" - " message but this is disallowed in" - " your settings. %1" - ).arg(l10n.formatTimestamp())) - )) - # if the human changes their setting and then - # sends another message or restarts their client, - # this one will send at that time. - continue - readPosition += 4 # to bypass the bitfield of behaviors - # We don't use this key for anything here. - # pubSigningKeyBase256 = - # pubkeyPayload[readPosition:readPosition+64] - readPosition += 64 - pubEncryptionKeyBase256 = pubkeyPayload[ - readPosition:readPosition + 64] - readPosition += 64 - - # Let us fetch the amount of work required by the recipient. - if toAddressVersionNumber == 2: - requiredAverageProofOfWorkNonceTrialsPerByte = \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - requiredPayloadLengthExtraBytes = \ - defaults.networkDefaultPayloadLengthExtraBytes - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Doing work necessary to send message.\n" - "There is no required difficulty for" - " version 2 addresses like this.")) - )) - elif toAddressVersionNumber >= 3: - requiredAverageProofOfWorkNonceTrialsPerByte, \ - varintLength = decodeVarint( - pubkeyPayload[readPosition:readPosition + 10]) - readPosition += varintLength - requiredPayloadLengthExtraBytes, varintLength = \ - decodeVarint( - pubkeyPayload[readPosition:readPosition + 10]) - readPosition += varintLength - # We still have to meet a minimum POW difficulty - # regardless of what they say is allowed in order - # to get our message to propagate through the network. - if requiredAverageProofOfWorkNonceTrialsPerByte < \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte: - requiredAverageProofOfWorkNonceTrialsPerByte = \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - if requiredPayloadLengthExtraBytes < \ - defaults.networkDefaultPayloadLengthExtraBytes: - requiredPayloadLengthExtraBytes = \ - defaults.networkDefaultPayloadLengthExtraBytes - logger.debug( - 'Using averageProofOfWorkNonceTrialsPerByte: %s' - ' and payloadLengthExtraBytes: %s.', - requiredAverageProofOfWorkNonceTrialsPerByte, - requiredPayloadLengthExtraBytes - ) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Doing work necessary to send message.\n" - "Receiver\'s required difficulty: %1" - " and %2" - ).arg(str(float( - requiredAverageProofOfWorkNonceTrialsPerByte) / - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - )).arg(str(float( - requiredPayloadLengthExtraBytes) / - defaults.networkDefaultPayloadLengthExtraBytes - ))))) - if status != 'forcepow': - if (requiredAverageProofOfWorkNonceTrialsPerByte - > BMConfigParser().getint( - 'bitmessagesettings', - 'maxacceptablenoncetrialsperbyte' - ) and - BMConfigParser().getint( - 'bitmessagesettings', - 'maxacceptablenoncetrialsperbyte' - ) != 0) or ( - requiredPayloadLengthExtraBytes - > BMConfigParser().getint( - 'bitmessagesettings', - 'maxacceptablepayloadlengthextrabytes' - ) and - BMConfigParser().getint( - 'bitmessagesettings', - 'maxacceptablepayloadlengthextrabytes' - ) != 0): - # The demanded difficulty is more than - # we are willing to do. - sqlExecute( - '''UPDATE sent SET status='toodifficult' ''' - ''' WHERE ackdata=? ''', - ackdata) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Problem: The work demanded by" - " the recipient (%1 and %2) is" - " more difficult than you are" - " willing to do. %3" - ).arg(str(float( - requiredAverageProofOfWorkNonceTrialsPerByte) - / defaults.networkDefaultProofOfWorkNonceTrialsPerByte - )).arg(str(float( - requiredPayloadLengthExtraBytes) - / defaults.networkDefaultPayloadLengthExtraBytes - )).arg(l10n.formatTimestamp())) - )) - continue - else: # if we are sending a message to ourselves or a chan.. - logger.info('Sending a message.') - logger.debug( - 'First 150 characters of message: %r', message[:150]) - behaviorBitfield = protocol.getBitfield(fromaddress) - - try: - privEncryptionKeyBase58 = BMConfigParser().get( - toaddress, 'privencryptionkey') - except Exception as err: - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Problem: You are trying to send a" - " message to yourself or a chan but your" - " encryption key could not be found in" - " the keys.dat file. Could not encrypt" - " message. %1" - ).arg(l10n.formatTimestamp())) - )) - logger.error( - 'Error within sendMsg. Could not read the keys' - ' from the keys.dat file for our own address. %s\n', - err) - continue - privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( - privEncryptionKeyBase58)) - pubEncryptionKeyBase256 = unhexlify(highlevelcrypto.privToPub( - privEncryptionKeyHex))[1:] - requiredAverageProofOfWorkNonceTrialsPerByte = \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - requiredPayloadLengthExtraBytes = \ - defaults.networkDefaultPayloadLengthExtraBytes - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Doing work necessary to send message.")) - )) - - # Now we can start to assemble our message. - payload = encodeVarint(fromAddressVersionNumber) - payload += encodeVarint(fromStreamNumber) - # Bitfield of features and behaviors - # that can be expected from me. (See - # https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features) - payload += protocol.getBitfield(fromaddress) - - # We need to convert our private keys to public keys in order - # to include them. - try: - privSigningKeyHex, privEncryptionKeyHex, \ - pubSigningKey, pubEncryptionKey = self._getKeysForAddress( - fromaddress) - except: - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Error! Could not find sender address" - " (your address) in the keys.dat file.")) - )) - continue - - payload += pubSigningKey + pubEncryptionKey - - if fromAddressVersionNumber >= 3: - # If the receiver of our message is in our address book, - # subscriptions list, or whitelist then we will allow them to - # do the network-minimum proof of work. Let us check to see if - # the receiver is in any of those lists. - if shared.isAddressInMyAddressBookSubscriptionsListOrWhitelist( - toaddress): - payload += encodeVarint( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte) - payload += encodeVarint( - defaults.networkDefaultPayloadLengthExtraBytes) - else: - payload += encodeVarint(BMConfigParser().getint( - fromaddress, 'noncetrialsperbyte')) - payload += encodeVarint(BMConfigParser().getint( - fromaddress, 'payloadlengthextrabytes')) - - # This hash will be checked by the receiver of the message - # to verify that toRipe belongs to them. This prevents - # a Surreptitious Forwarding Attack. - payload += toRipe - payload += encodeVarint(encoding) # message encoding type - encodedMessage = helper_msgcoding.MsgEncode( - {"subject": subject, "body": message}, encoding - ) - payload += encodeVarint(encodedMessage.length) - payload += encodedMessage.data - if BMConfigParser().has_section(toaddress): - logger.info( - 'Not bothering to include ackdata because we are' - ' sending to ourselves or a chan.' - ) - fullAckPayload = '' - elif not protocol.checkBitfield( - behaviorBitfield, protocol.BITFIELD_DOESACK): - logger.info( - 'Not bothering to include ackdata because' - ' the receiver said that they won\'t relay it anyway.' - ) - fullAckPayload = '' - else: - # The fullAckPayload is a normal msg protocol message - # with the proof of work already completed that the - # receiver of this message can easily send out. - fullAckPayload = self.generateFullAckMessage( - ackdata, toStreamNumber, TTL) - payload += encodeVarint(len(fullAckPayload)) - payload += fullAckPayload - dataToSign = pack('>Q', embeddedTime) + '\x00\x00\x00\x02' + \ - encodeVarint(1) + encodeVarint(toStreamNumber) + payload - signature = highlevelcrypto.sign(dataToSign, privSigningKeyHex) - payload += encodeVarint(len(signature)) - payload += signature - - # We have assembled the data that will be encrypted. - try: - encrypted = highlevelcrypto.encrypt( - payload, "04" + hexlify(pubEncryptionKeyBase256) - ) - except: - sqlExecute( - '''UPDATE sent SET status='badkey' WHERE ackdata=?''', - ackdata - ) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Problem: The recipient\'s encryption key is" - " no good. Could not encrypt message. %1" - ).arg(l10n.formatTimestamp())) - )) - continue - - encryptedPayload = pack('>Q', embeddedTime) - encryptedPayload += '\x00\x00\x00\x02' # object type: msg - encryptedPayload += encodeVarint(1) # msg version - encryptedPayload += encodeVarint(toStreamNumber) + encrypted - target = 2 ** 64 / ( - requiredAverageProofOfWorkNonceTrialsPerByte * ( - len(encryptedPayload) + 8 + - requiredPayloadLengthExtraBytes + (( - TTL * ( - len(encryptedPayload) + 8 + - requiredPayloadLengthExtraBytes - )) / (2 ** 16)) - )) - logger.info( - '(For msg message) Doing proof of work. Total required' - ' difficulty: %f. Required small message difficulty: %f.', - float(requiredAverageProofOfWorkNonceTrialsPerByte) / - defaults.networkDefaultProofOfWorkNonceTrialsPerByte, - float(requiredPayloadLengthExtraBytes) / - defaults.networkDefaultPayloadLengthExtraBytes - ) - - powStartTime = time.time() - initialHash = hashlib.sha512(encryptedPayload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) - logger.info( - '(For msg message) Found proof of work %s Nonce: %s', - trialValue, nonce - ) - try: - logger.info( - 'PoW took %.1f seconds, speed %s.', - time.time() - powStartTime, - sizeof_fmt(nonce / (time.time() - powStartTime)) - ) - except: - pass - - encryptedPayload = pack('>Q', nonce) + encryptedPayload - - # Sanity check. The encryptedPayload size should never be - # larger than 256 KiB. There should be checks elsewhere - # in the code to not let the user try to send a message - # this large until we implement message continuation. - if len(encryptedPayload) > 2 ** 18: # 256 KiB - logger.critical( - 'This msg object is too large to send. This should' - ' never happen. Object size: %i', - len(encryptedPayload) - ) - continue - - inventoryHash = calculateInventoryHash(encryptedPayload) - objectType = 2 - Inventory()[inventoryHash] = ( - objectType, toStreamNumber, encryptedPayload, embeddedTime, '') - if BMConfigParser().has_section(toaddress) or \ - not protocol.checkBitfield( - behaviorBitfield, protocol.BITFIELD_DOESACK): - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Message sent. Sent at %1" - ).arg(l10n.formatTimestamp())) - )) - else: - # not sending to a chan or one of my addresses - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Message sent. Waiting for acknowledgement." - " Sent on %1" - ).arg(l10n.formatTimestamp())) - )) - logger.info( - 'Broadcasting inv for my msg(within sendmsg function): %s', - hexlify(inventoryHash) - ) - queues.invQueue.put((toStreamNumber, inventoryHash)) - - # Update the sent message in the sent table with the - # necessary information. - if BMConfigParser().has_section(toaddress) or \ - not protocol.checkBitfield( - behaviorBitfield, protocol.BITFIELD_DOESACK): - newStatus = 'msgsentnoackexpected' - else: - newStatus = 'msgsent' - # wait 10% past expiration - sleepTill = int(time.time() + TTL * 1.1) - sqlExecute( - '''UPDATE sent SET msgid=?, status=?, retrynumber=?, ''' - ''' sleeptill=?, lastactiontime=? WHERE ackdata=?''', - inventoryHash, newStatus, retryNumber + 1, - sleepTill, int(time.time()), ackdata - ) - - # If we are sending to ourselves or a chan, let's put - # the message in our own inbox. - if BMConfigParser().has_section(toaddress): - # Used to detect and ignore duplicate messages in our inbox - sigHash = hashlib.sha512(hashlib.sha512( - signature).digest()).digest()[32:] - t = (inventoryHash, toaddress, fromaddress, subject, int( - time.time()), message, 'inbox', encoding, 0, sigHash) - helper_inbox.insert(t) - - queues.UISignalQueue.put(('displayNewInboxMessage', ( - inventoryHash, toaddress, fromaddress, subject, message))) - - # If we are behaving as an API then we might need to run an - # outside command to let some program know that a new message - # has arrived. - if BMConfigParser().safeGetBoolean( - 'bitmessagesettings', 'apienabled'): - try: - apiNotifyPath = BMConfigParser().get( - 'bitmessagesettings', 'apinotifypath') - except: - apiNotifyPath = '' - if apiNotifyPath != '': - call([apiNotifyPath, "newMessage"]) - - def requestPubKey(self, toAddress): - toStatus, addressVersionNumber, streamNumber, ripe = decodeAddress( - toAddress) - if toStatus != 'success': - logger.error( - 'Very abnormal error occurred in requestPubKey.' - ' toAddress is: %r. Please report this error to Atheros.', - toAddress - ) - return - - queryReturn = sqlQuery( - '''SELECT retrynumber FROM sent WHERE toaddress=? ''' - ''' AND (status='doingpubkeypow' OR status='awaitingpubkey') ''' - ''' LIMIT 1''', - toAddress - ) - if len(queryReturn) == 0: - logger.critical( - 'BUG: Why are we requesting the pubkey for %s' - ' if there are no messages in the sent folder' - ' to that address?', toAddress - ) - return - retryNumber = queryReturn[0][0] - - if addressVersionNumber <= 3: - state.neededPubkeys[toAddress] = 0 - elif addressVersionNumber >= 4: - # If the user just clicked 'send' then the tag - # (and other information) will already be in the - # neededPubkeys dictionary. But if we are recovering - # from a restart of the client then we have to put it in now. - - # Note that this is the first half of the sha512 hash. - privEncryptionKey = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) + - encodeVarint(streamNumber) + ripe - ).digest()).digest()[:32] - # Note that this is the second half of the sha512 hash. - tag = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) + - encodeVarint(streamNumber) + ripe - ).digest()).digest()[32:] - if tag not in state.neededPubkeys: - # We'll need this for when we receive a pubkey reply: - # it will be encrypted and we'll need to decrypt it. - state.neededPubkeys[tag] = ( - toAddress, - highlevelcrypto.makeCryptor(hexlify(privEncryptionKey)) - ) - - # 2.5 days. This was chosen fairly arbitrarily. - TTL = 2.5 * 24 * 60 * 60 - TTL *= 2 ** retryNumber - if TTL > 28 * 24 * 60 * 60: - TTL = 28 * 24 * 60 * 60 - # add some randomness to the TTL - TTL = TTL + helper_random.randomrandrange(-300, 300) - embeddedTime = int(time.time() + TTL) - payload = pack('>Q', embeddedTime) - payload += '\x00\x00\x00\x00' # object type: getpubkey - payload += encodeVarint(addressVersionNumber) - payload += encodeVarint(streamNumber) - if addressVersionNumber <= 3: - payload += ripe - logger.info( - 'making request for pubkey with ripe: %s', hexlify(ripe)) - else: - payload += tag - logger.info( - 'making request for v4 pubkey with tag: %s', hexlify(tag)) - - # print 'trial value', trialValue - statusbar = 'Doing the computations necessary to request' +\ - ' the recipient\'s public key.' - queues.UISignalQueue.put(('updateStatusBar', statusbar)) - queues.UISignalQueue.put(( - 'updateSentItemStatusByToAddress', ( - toAddress, - tr._translate( - "MainWindow", - "Doing work necessary to request encryption key.")) - )) - - payload = self._doPOWDefaults(payload, TTL) - - inventoryHash = calculateInventoryHash(payload) - objectType = 1 - Inventory()[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, '') - logger.info('sending inv (for the getpubkey message)') - queues.invQueue.put((streamNumber, inventoryHash)) - - # wait 10% past expiration - sleeptill = int(time.time() + TTL * 1.1) - sqlExecute( - '''UPDATE sent SET lastactiontime=?, ''' - ''' status='awaitingpubkey', retrynumber=?, sleeptill=? ''' - ''' WHERE toaddress=? AND (status='doingpubkeypow' OR ''' - ''' status='awaitingpubkey') ''', - int(time.time()), retryNumber + 1, sleeptill, toAddress) - - queues.UISignalQueue.put(( - 'updateStatusBar', - tr._translate( - "MainWindow", - "Broadcasting the public key request. This program will" - " auto-retry if they are offline.") - )) - queues.UISignalQueue.put(( - 'updateSentItemStatusByToAddress', ( - toAddress, - tr._translate( - "MainWindow", - "Sending public key request. Waiting for reply." - " Requested at %1" - ).arg(l10n.formatTimestamp())) - )) - - def generateFullAckMessage(self, ackdata, toStreamNumber, TTL): - # It might be perfectly fine to just use the same TTL for - # the ackdata that we use for the message. But I would rather - # it be more difficult for attackers to associate ackData with - # the associated msg object. However, users would want the TTL - # of the acknowledgement to be about the same as they set - # for the message itself. So let's set the TTL of the - # acknowledgement to be in one of three 'buckets': 1 hour, 7 - # days, or 28 days, whichever is relatively close to what the - # user specified. - if TTL < 24 * 60 * 60: # 1 day - TTL = 24 * 60 * 60 # 1 day - elif TTL < 7 * 24 * 60 * 60: # 1 week - TTL = 7 * 24 * 60 * 60 # 1 week - else: - TTL = 28 * 24 * 60 * 60 # 4 weeks - # Add some randomness to the TTL - TTL = int(TTL + helper_random.randomrandrange(-300, 300)) - embeddedTime = int(time.time() + TTL) - - # type/version/stream already included - payload = pack('>Q', (embeddedTime)) + ackdata - - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For ack message)', log_time=True) - - return protocol.CreatePacket('object', payload) diff --git a/src/class_smtpDeliver.py b/src/class_smtpDeliver.py index f46a61b3..ff7e2a07 100644 --- a/src/class_smtpDeliver.py +++ b/src/class_smtpDeliver.py @@ -42,10 +42,10 @@ class smtpDeliver(threading.Thread, StoppableThread): elif command == 'updateStatusBar': pass elif command == 'updateSentItemStatusByToAddress': - toAddress, message = data + status, toAddress, message = data pass elif command == 'updateSentItemStatusByAckdata': - ackData, message = data + status, ackData, message = data pass elif command == 'displayNewInboxMessage': inventoryHash, toAddress, fromAddress, subject, body = data diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index a45571e0..d789998c 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -1,4 +1,6 @@ import threading +import addresses +import binascii from bmconfigparser import BMConfigParser import sqlite3 import time @@ -148,16 +150,55 @@ class sqlThread(threading.Thread): logger.debug('Vacuuming message.dat. You might notice that the file size gets much smaller.') self.cur.execute( ''' VACUUM ''') - # After code refactoring, the possible status values for sent messages - # have changed. - self.cur.execute( - '''update sent set status='doingmsgpow' where status='doingpow' ''') - self.cur.execute( - '''update sent set status='msgsent' where status='sentmessage' ''') - self.cur.execute( - '''update sent set status='doingpubkeypow' where status='findingpubkey' ''') - self.cur.execute( - '''update sent set status='broadcastqueued' where status='broadcastpending' ''') + # Retry number now has no meaning for getpubkey messages + + self.cur.execute(""" + UPDATE "sent" SET "retrynumber" = 0 + WHERE "status" IN ( + 'doingpubkeypow', 'awaitingpubkey', + 'findingpubkey' -- Old name + ); + """) + + # Reset temporary message status values + # TODO: stop writing them to the database, they should exist only in memory + + self.cur.execute(""" + UPDATE "sent" SET "status" = 'broadcastqueued' + WHERE "status" == 'doingbroadcastpow'; + """) + + self.cur.execute(""" + UPDATE "sent" SET "status" = 'msgqueued' + WHERE "status" IN ( + 'doingmsgpow', 'doingpubkeypow', 'awaitingpubkey', + 'doingpow', 'findingpubkey' -- Old names + ); + """) + + # Update status values inherited from old versions + + self.cur.execute("""UPDATE "sent" SET "status" = 'msgsent' WHERE "status" == 'sentmessage';""") + self.cur.execute("""UPDATE "sent" SET "status" = 'broadcastqueued' WHERE "status" == 'broadcastpending';""") + + # Add tags to all objects + + self.cur.execute("""SELECT "hash", "payload" FROM "inventory" WHERE "tag" == '';""") + updatingCursor = self.conn.cursor() + + for ID, payload in self.cur: + readPosition = 20 + + version, readLength = addresses.decodeVarint(payload[readPosition: readPosition + 9]) + readPosition += readLength + + stream, readLength = addresses.decodeVarint(payload[readPosition: readPosition + 9]) + readPosition += readLength + + tag = buffer(payload[readPosition: readPosition + 32]) # May be shorter than 32 bytes for getpubkeys + + updatingCursor.execute("""UPDATE "inventory" SET "tag" = ? WHERE "hash" == ?;""", (tag, ID)) + self.conn.commit() # Let's get rid of the first20bytesofencryptedmessage field in @@ -391,6 +432,24 @@ class sqlThread(threading.Thread): parameters = (int(time.time()),) self.cur.execute(item, parameters) + self.cur.execute("""SELECT "ackdata" FROM "sent" WHERE "status" == 'msgsent';""") + legacyAckData = [] + + for ackData, in self.cur: + logger.info("Watching for ack data: %s", binascii.hexlify(ackData)) + + if len(ackData) == 32: + legacyAckData.append(ackData) + else: + state.watchedAckData.add(ackData) + + for i in legacyAckData: + ackData = "\x00\x00\x00\x02\x01\x01" + i + + self.cur.execute("""UPDATE "sent" SET "ackdata" = ? WHERE "ackdata" == ?;""", (ackData, i)) + + state.watchedAckData.add(ackData) + state.sqlReady = True while True: diff --git a/src/helper_generic.py b/src/helper_generic.py index ce56a292..88494c5c 100644 --- a/src/helper_generic.py +++ b/src/helper_generic.py @@ -18,18 +18,6 @@ import queues import shutdown from debug import logger - -def powQueueSize(): - curWorkerQueue = queues.workerQueue.qsize() - for thread in threading.enumerate(): - try: - if thread.name == "singleWorker": - curWorkerQueue += thread.busy - except Exception as err: - logger.info('Thread error %s', err) - return curWorkerQueue - - def convertIntToString(n): a = __builtins__.hex(n) if a[-1:] == 'L': @@ -68,8 +56,8 @@ def signal_handler(signal, frame): # on Windows this isn't triggered, but it's fine, # it has its own process termination thing raise SystemExit - if "PoolWorker" in process.name: - raise SystemExit + if process.name == "ForkingSolver": + return if threading.current_thread().name not in ("PyBitmessage", "MainThread"): return logger.error("Got signal %i", signal) diff --git a/src/network/bmobject.py b/src/network/bmobject.py index 2e7dd092..b4c90c1a 100644 --- a/src/network/bmobject.py +++ b/src/network/bmobject.py @@ -7,6 +7,8 @@ from inventory import Inventory from network.dandelion import Dandelion import protocol import state +import workprover.utils +import defaults class BMObjectInsufficientPOWError(Exception): errorCodes = ("Insufficient proof of work") @@ -47,11 +49,16 @@ class BMObject(object): self.inventoryHash = calculateInventoryHash(data) # copy to avoid memory issues self.data = bytearray(data) + # Doesn't matter if the payload is shorter, old version "getpubkey" objects must have 20-byte tags self.tag = self.data[payloadOffset:payloadOffset+32] def checkProofOfWorkSufficient(self): # Let us check to make sure that the proof of work is sufficient. - if not protocol.isProofOfWorkSufficient(self.data): + if not workprover.utils.checkWorkSufficient( + self.data, + defaults.networkDefaultProofOfWorkNonceTrialsPerByte, + defaults.networkDefaultPayloadLengthExtraBytes + ): logger.info('Proof of work is insufficient.') raise BMObjectInsufficientPOWError() diff --git a/src/network/bmproto.py b/src/network/bmproto.py index 1f1c67bd..5af47041 100644 --- a/src/network/bmproto.py +++ b/src/network/bmproto.py @@ -353,7 +353,6 @@ class BMProto(AdvancedDispatcher, ObjectTracker): try: self.object.checkObjectByType() - objectProcessorQueue.put((self.object.objectType, buffer(self.object.data))) except BMObjectInvalidError as e: BMProto.stopDownloadingObject(self.object.inventoryHash, True) else: @@ -369,6 +368,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): self.object.objectType, self.object.streamNumber, buffer(self.payload[objectOffset:]), self.object.expiresTime, buffer(self.object.tag)) self.handleReceivedObject(self.object.streamNumber, self.object.inventoryHash) invQueue.put((self.object.streamNumber, self.object.inventoryHash, self.destination)) + objectProcessorQueue.put((self.object.objectType, buffer(self.object.data))) return True def _decode_addr(self): diff --git a/src/openclpow.py b/src/openclpow.py deleted file mode 100644 index b0be542c..00000000 --- a/src/openclpow.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python2.7 -from struct import pack, unpack -import time -import hashlib -import random -import os - -from bmconfigparser import BMConfigParser -import paths -from state import shutdown -from debug import logger - -libAvailable = True -ctx = False -queue = False -program = False -gpus = [] -enabledGpus = [] -vendors = [] -hash_dt = None - -try: - import pyopencl as cl - import numpy -except ImportError: - libAvailable = False - - -def initCL(): - global ctx, queue, program, hash_dt, libAvailable - if libAvailable is False: - return - del enabledGpus[:] - del vendors[:] - del gpus[:] - ctx = False - try: - hash_dt = numpy.dtype([('target', numpy.uint64), ('v', numpy.str_, 73)]) - try: - for platform in cl.get_platforms(): - gpus.extend(platform.get_devices(device_type=cl.device_type.GPU)) - if BMConfigParser().safeGet("bitmessagesettings", "opencl") == platform.vendor: - enabledGpus.extend(platform.get_devices(device_type=cl.device_type.GPU)) - if platform.vendor not in vendors: - vendors.append(platform.vendor) - except: - pass - if (len(enabledGpus) > 0): - ctx = cl.Context(devices=enabledGpus) - queue = cl.CommandQueue(ctx) - f = open(os.path.join(paths.codePath(), "bitmsghash", 'bitmsghash.cl'), 'r') - fstr = ''.join(f.readlines()) - program = cl.Program(ctx, fstr).build(options="") - logger.info("Loaded OpenCL kernel") - else: - logger.info("No OpenCL GPUs found") - del enabledGpus[:] - except Exception as e: - logger.error("OpenCL fail: ", exc_info=True) - del enabledGpus[:] - -def openclAvailable(): - return (len(gpus) > 0) - -def openclEnabled(): - return (len(enabledGpus) > 0) - -def do_opencl_pow(hash, target): - output = numpy.zeros(1, dtype=[('v', numpy.uint64, 1)]) - if (len(enabledGpus) == 0): - return output[0][0] - - data = numpy.zeros(1, dtype=hash_dt, order='C') - data[0]['v'] = ("0000000000000000" + hash).decode("hex") - data[0]['target'] = target - - hash_buf = cl.Buffer(ctx, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=data) - dest_buf = cl.Buffer(ctx, cl.mem_flags.WRITE_ONLY, output.nbytes) - - kernel = program.kernel_sha512 - worksize = kernel.get_work_group_info(cl.kernel_work_group_info.WORK_GROUP_SIZE, enabledGpus[0]) - - kernel.set_arg(0, hash_buf) - kernel.set_arg(1, dest_buf) - - start = time.time() - progress = 0 - globamt = worksize*2000 - - while output[0][0] == 0 and shutdown == 0: - kernel.set_arg(2, pack("Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) - print "{} - value {} < {}".format(nonce, trialValue, target) - diff --git a/src/proofofwork.py b/src/proofofwork.py deleted file mode 100644 index df6ed295..00000000 --- a/src/proofofwork.py +++ /dev/null @@ -1,295 +0,0 @@ -#import shared -#import time -#from multiprocessing import Pool, cpu_count -import hashlib -from struct import unpack, pack -from subprocess import call -import sys -import time -from bmconfigparser import BMConfigParser -from debug import logger -import paths -import openclpow -import queues -import tr -import os -import ctypes - -import state - -bitmsglib = 'bitmsghash.so' - -bmpow = None - -def _set_idle(): - if 'linux' in sys.platform: - os.nice(20) - else: - try: - sys.getwindowsversion() - import win32api,win32process,win32con # @UnresolvedImport - pid = win32api.GetCurrentProcessId() - handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, True, pid) - win32process.SetPriorityClass(handle, win32process.IDLE_PRIORITY_CLASS) - except: - #Windows 64-bit - pass - -def _pool_worker(nonce, initialHash, target, pool_size): - _set_idle() - trialValue = float('inf') - while trialValue > target: - nonce += pool_size - trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) - return [trialValue, nonce] - -def _doSafePoW(target, initialHash): - logger.debug("Safe PoW start") - nonce = 0 - trialValue = float('inf') - while trialValue > target and state.shutdown == 0: - nonce += 1 - trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) - if state.shutdown != 0: - raise StopIteration("Interrupted") - logger.debug("Safe PoW done") - return [trialValue, nonce] - -def _doFastPoW(target, initialHash): - logger.debug("Fast PoW start") - from multiprocessing import Pool, cpu_count - try: - pool_size = cpu_count() - except: - pool_size = 4 - try: - maxCores = BMConfigParser().getint('bitmessagesettings', 'maxcores') - except: - maxCores = 99999 - if pool_size > maxCores: - pool_size = maxCores - - pool = Pool(processes=pool_size) - result = [] - for i in range(pool_size): - result.append(pool.apply_async(_pool_worker, args=(i, initialHash, target, pool_size))) - - while True: - if state.shutdown > 0: - try: - pool.terminate() - pool.join() - except: - pass - raise StopIteration("Interrupted") - for i in range(pool_size): - if result[i].ready(): - try: - result[i].successful() - except AssertionError: - pool.terminate() - pool.join() - raise StopIteration("Interrupted") - result = result[i].get() - pool.terminate() - pool.join() - logger.debug("Fast PoW done") - return result[0], result[1] - time.sleep(0.2) - -def _doCPoW(target, initialHash): - h = initialHash - m = target - out_h = ctypes.pointer(ctypes.create_string_buffer(h, 64)) - out_m = ctypes.c_ulonglong(m) - logger.debug("C PoW start") - nonce = bmpow(out_h, out_m) - trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) - if state.shutdown != 0: - raise StopIteration("Interrupted") - logger.debug("C PoW done") - return [trialValue, nonce] - -def _doGPUPoW(target, initialHash): - logger.debug("GPU PoW start") - nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target) - trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) - #print "{} - value {} < {}".format(nonce, trialValue, target) - if trialValue > target: - deviceNames = ", ".join(gpu.name for gpu in openclpow.enabledGpus) - queues.UISignalQueue.put(('updateStatusBar', (tr._translate("MainWindow",'Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.'), 1))) - logger.error("Your GPUs (%s) did not calculate correctly, disabling OpenCL. Please report to the developers.", deviceNames) - openclpow.enabledGpus = [] - raise Exception("GPU did not calculate correctly.") - if state.shutdown != 0: - raise StopIteration("Interrupted") - logger.debug("GPU PoW done") - return [trialValue, nonce] - -def estimate(difficulty, format = False): - ret = difficulty / 10 - if ret < 1: - ret = 1 - if format: - out = str(int(ret)) + " seconds" - if ret > 60: - ret /= 60 - out = str(int(ret)) + " minutes" - if ret > 60: - ret /= 60 - out = str(int(ret)) + " hours" - if ret > 24: - ret /= 24 - out = str(int(ret)) + " days" - if ret > 7: - out = str(int(ret)) + " weeks" - if ret > 31: - out = str(int(ret)) + " months" - if ret > 366: - ret /= 366 - out = str(int(ret)) + " years" - else: - return ret - -def getPowType(): - if openclpow.openclEnabled(): - return "OpenCL" - if bmpow: - return "C" - return "python" - -def notifyBuild(tried=False): - if bmpow: - queues.UISignalQueue.put(('updateStatusBar', (tr._translate("proofofwork", "C PoW module built successfully."), 1))) - elif tried: - queues.UISignalQueue.put(('updateStatusBar', (tr._translate("proofofwork", "Failed to build C PoW module. Please build it manually."), 1))) - else: - queues.UISignalQueue.put(('updateStatusBar', (tr._translate("proofofwork", "C PoW module unavailable. Please build it."), 1))) - -def buildCPoW(): - if bmpow is not None: - return - if paths.frozen is not None: - notifyBuild(False) - return - if sys.platform in ["win32", "win64"]: - notifyBuild(False) - return - try: - if "bsd" in sys.platform: - # BSD make - call(["make", "-C", os.path.join(paths.codePath(), "bitmsghash"), '-f', 'Makefile.bsd']) - else: - # GNU make - call(["make", "-C", os.path.join(paths.codePath(), "bitmsghash")]) - if os.path.exists(os.path.join(paths.codePath(), "bitmsghash", "bitmsghash.so")): - init() - notifyBuild(True) - else: - notifyBuild(True) - except: - notifyBuild(True) - -def run(target, initialHash): - if state.shutdown != 0: - raise - target = int(target) - if openclpow.openclEnabled(): -# trialvalue1, nonce1 = _doGPUPoW(target, initialHash) -# trialvalue, nonce = _doFastPoW(target, initialHash) -# print "GPU: %s, %s" % (trialvalue1, nonce1) -# print "Fast: %s, %s" % (trialvalue, nonce) -# return [trialvalue, nonce] - try: - return _doGPUPoW(target, initialHash) - except StopIteration: - raise - except: - pass # fallback - if bmpow: - try: - return _doCPoW(target, initialHash) - except StopIteration: - raise - except: - pass # fallback - if paths.frozen == "macosx_app" or not paths.frozen: - # on my (Peter Surda) Windows 10, Windows Defender - # does not like this and fights with PyBitmessage - # over CPU, resulting in very slow PoW - # added on 2015-11-29: multiprocesing.freeze_support() doesn't help - try: - return _doFastPoW(target, initialHash) - except StopIteration: - logger.error("Fast PoW got StopIteration") - raise - except: - logger.error("Fast PoW got exception:", exc_info=True) - pass #fallback - try: - return _doSafePoW(target, initialHash) - except StopIteration: - raise - except: - pass #fallback - -def resetPoW(): - openclpow.initCL() - -# init -def init(): - global bitmsglib, bso, bmpow - - openclpow.initCL() - - if "win32" == sys.platform: - if ctypes.sizeof(ctypes.c_voidp) == 4: - bitmsglib = 'bitmsghash32.dll' - else: - bitmsglib = 'bitmsghash64.dll' - try: - # MSVS - bso = ctypes.WinDLL(os.path.join(paths.codePath(), "bitmsghash", bitmsglib)) - logger.info("Loaded C PoW DLL (stdcall) %s", bitmsglib) - bmpow = bso.BitmessagePOW - bmpow.restype = ctypes.c_ulonglong - _doCPoW(2**63, "") - logger.info("Successfully tested C PoW DLL (stdcall) %s", bitmsglib) - except: - logger.error("C PoW test fail.", exc_info=True) - try: - # MinGW - bso = ctypes.CDLL(os.path.join(paths.codePath(), "bitmsghash", bitmsglib)) - logger.info("Loaded C PoW DLL (cdecl) %s", bitmsglib) - bmpow = bso.BitmessagePOW - bmpow.restype = ctypes.c_ulonglong - _doCPoW(2**63, "") - logger.info("Successfully tested C PoW DLL (cdecl) %s", bitmsglib) - except: - logger.error("C PoW test fail.", exc_info=True) - bso = None - else: - try: - bso = ctypes.CDLL(os.path.join(paths.codePath(), "bitmsghash", bitmsglib)) - except OSError: - import glob - try: - bso = ctypes.CDLL(glob.glob(os.path.join( - paths.codePath(), "bitmsghash", "bitmsghash*.so" - ))[0]) - except (OSError, IndexError): - bso = None - except: - bso = None - else: - logger.info("Loaded C PoW DLL %s", bitmsglib) - if bso: - try: - bmpow = bso.BitmessagePOW - bmpow.restype = ctypes.c_ulonglong - except: - bmpow = None - else: - bmpow = None - if bmpow is None: - buildCPoW() diff --git a/src/protocol.py b/src/protocol.py index 913c8e5f..f6b62406 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -27,9 +27,10 @@ from addresses import calculateInventoryHash, encodeVarint, decodeVarint, decode from bmconfigparser import BMConfigParser from debug import logger from helper_sql import sqlExecute -from inventory import Inventory -from queues import objectProcessorQueue from version import softwareVersion +import inventory +import queues +import workprover.utils # Service flags @@ -62,32 +63,20 @@ Header = Struct('!L12sL4s') VersionPacket = Struct('>LqQ20s4s36sH') -# Bitfield +def calculateDoubleHash(data): + return hashlib.sha512(hashlib.sha512(data).digest()).digest() +def calculateRipeHash(data): + return hashlib.new("ripemd160", hashlib.sha512(data).digest()).digest() -def getBitfield(address): - """Get a bitfield from an address""" - # bitfield of features supported by me (see the wiki). - bitfield = 0 - # send ack - if not BMConfigParser().safeGetBoolean(address, 'dontsendack'): - bitfield |= BITFIELD_DOESACK - return pack('>I', bitfield) - - -def checkBitfield(bitfieldBinary, flags): - """Check if a bitfield matches the given flags""" - bitfield, = unpack('>I', bitfieldBinary) - return (bitfield & flags) == flags - - -def isBitSetWithinBitfield(fourByteString, n): - """Check if a particular bit is set in a bitfeld""" - # Uses MSB 0 bit numbering across 4 bytes of data - n = 31 - n - x, = unpack('>L', fourByteString) - return x & 2**n != 0 +def calculateAddressTag(version, stream, ripe): + doubleHash = calculateDoubleHash( + encodeVarint(version) + + encodeVarint(stream) + + ripe + ) + return doubleHash[: 32], doubleHash[32: ] # ip addresses @@ -200,36 +189,6 @@ def checkSocksIP(host): return state.socksIP == host -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 - Arguments: - int nonceTrialsPerByte (default: from default.py) - int payloadLengthExtraBytes (default: from default.py) - float recvTime (optional) UNIX epoch time when object was - received from the network (default: current system time) - Returns: - True if PoW valid and sufficient, False in all other cases - """ - if nonceTrialsPerByte < defaults.networkDefaultProofOfWorkNonceTrialsPerByte: - nonceTrialsPerByte = defaults.networkDefaultProofOfWorkNonceTrialsPerByte - if payloadLengthExtraBytes < defaults.networkDefaultPayloadLengthExtraBytes: - payloadLengthExtraBytes = defaults.networkDefaultPayloadLengthExtraBytes - endOfLifeTime, = unpack('>Q', data[8:16]) - 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)))) - - # Packet creation @@ -320,344 +279,186 @@ def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''): # Packet decoding +def decryptAndCheckV4Pubkey(payload, address, cryptor): + status, version, stream, ripe = decodeAddress(address) + + readPosition = 20 -def decryptAndCheckPubkeyPayload(data, address): - """ - Version 4 pubkeys are encrypted. This function is run when we already have the - address to which we want to try to send a message. The 'data' may come either - off of the wire or we might have had it already in our inventory when we tried - to send a msg to this particular address. - """ - # pylint: disable=unused-variable try: - status, addressVersion, streamNumber, ripe = decodeAddress(address) + embeddedVersion, readLength = decodeVarint(payload[readPosition: readPosition + 9]) + readPosition += readLength - readPosition = 20 # bypass the nonce, time, and object type - embeddedAddressVersion, varintLength = decodeVarint(data[readPosition:readPosition + 10]) - readPosition += varintLength - embeddedStreamNumber, varintLength = decodeVarint(data[readPosition:readPosition + 10]) - readPosition += varintLength - # We'll store the address version and stream number (and some more) in the pubkeys table. - storedData = data[20:readPosition] + embeddedStream, readLength = decodeVarint(payload[readPosition: readPosition + 9]) + readPosition += readLength + except: + return None - if addressVersion != embeddedAddressVersion: - logger.info('Pubkey decryption was UNsuccessful due to address version mismatch.') - return 'failed' - if streamNumber != embeddedStreamNumber: - logger.info('Pubkey decryption was UNsuccessful due to stream number mismatch.') - return 'failed' + if embeddedVersion != 4: + logger.info("Pubkey decryption failed due to address version mismatch") - tag = data[readPosition:readPosition + 32] - readPosition += 32 - # the time through the tag. More data is appended onto signedData below after the decryption. - signedData = data[8:readPosition] - encryptedData = data[readPosition:] + return None - # Let us try to decrypt the pubkey - toAddress, cryptorObject = state.neededPubkeys[tag] - if toAddress != address: - logger.critical( - 'decryptAndCheckPubkeyPayload failed due to toAddress mismatch.' - ' This is very peculiar. toAddress: %s, address %s', - toAddress, - address) - # the only way I can think that this could happen is if someone encodes their address data two different - # ways. That sort of address-malleability should have been caught by the UI or API and an error given to - # the user. - return 'failed' - try: - decryptedData = cryptorObject.decrypt(encryptedData) - except: - # Someone must have encrypted some data with a different key - # but tagged it with a tag for which we are watching. - logger.info('Pubkey decryption was unsuccessful.') - return 'failed' + if embeddedStream != stream: + logger.info("Pubkey decryption failed due to stream number mismatch") - readPosition = 0 - bitfieldBehaviors = decryptedData[readPosition:readPosition + 4] + return None + + result = payload[20: readPosition] + + tag = payload[readPosition: readPosition + 32] + readPosition += 32 + + if len(tag) < 32: + return None + + signedData = payload[8: readPosition] + ciphertext = payload[readPosition: ] + + try: + plaintext = cryptor.decrypt(ciphertext) + except: + logger.info("Pubkey decryption failed") + + return None + + readPosition = 0 + + try: + bitfield = unpack(">I", plaintext[readPosition: readPosition + 4]) readPosition += 4 - publicSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64] - readPosition += 64 - publicEncryptionKey = '\x04' + decryptedData[readPosition:readPosition + 64] - readPosition += 64 - specifiedNonceTrialsPerByte, specifiedNonceTrialsPerByteLength = decodeVarint( - decryptedData[readPosition:readPosition + 10]) - readPosition += specifiedNonceTrialsPerByteLength - specifiedPayloadLengthExtraBytes, specifiedPayloadLengthExtraBytesLength = decodeVarint( - decryptedData[readPosition:readPosition + 10]) - readPosition += specifiedPayloadLengthExtraBytesLength - storedData += decryptedData[:readPosition] - signedData += decryptedData[:readPosition] - signatureLength, signatureLengthLength = decodeVarint( - decryptedData[readPosition:readPosition + 10]) - readPosition += signatureLengthLength - signature = decryptedData[readPosition:readPosition + signatureLength] + except: + return None - if highlevelcrypto.verify(signedData, signature, hexlify(publicSigningKey)): - logger.info('ECDSA verify passed (within decryptAndCheckPubkeyPayload)') - else: - logger.info('ECDSA verify failed (within decryptAndCheckPubkeyPayload)') - return 'failed' + publicSigningKey = "\x04" + plaintext[readPosition: readPosition + 64] + readPosition += 64 - sha = hashlib.new('sha512') - sha.update(publicSigningKey + publicEncryptionKey) - ripeHasher = hashlib.new('ripemd160') - ripeHasher.update(sha.digest()) - embeddedRipe = ripeHasher.digest() + publicEncryptionKey = "\x04" + plaintext[readPosition: readPosition + 64] + readPosition += 64 - if embeddedRipe != ripe: - # Although this pubkey object had the tag were were looking for and was - # encrypted with the correct encryption key, it doesn't contain the - # correct pubkeys. Someone is either being malicious or using buggy software. - logger.info('Pubkey decryption was UNsuccessful due to RIPE mismatch.') - return 'failed' + if len(publicSigningKey) != 65 or len(publicEncryptionKey) != 65: + return None - # Everything checked out. Insert it into the pubkeys table. + embeddedRipe = calculateRipeHash(publicSigningKey + publicEncryptionKey) - logger.info( - os.linesep.join([ - 'within decryptAndCheckPubkeyPayload,' - ' addressVersion: %s, streamNumber: %s' % addressVersion, streamNumber, - 'ripe %s' % hexlify(ripe), - 'publicSigningKey in hex: %s' % hexlify(publicSigningKey), - 'publicEncryptionKey in hex: %s' % hexlify(publicEncryptionKey), - ]) - ) + if embeddedRipe != ripe: + logger.info("Pubkey decryption failed due to RIPE mismatch") - t = (address, addressVersion, storedData, int(time.time()), 'yes') - sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) - return 'successful' - except varintDecodeError: - logger.info('Pubkey decryption was UNsuccessful due to a malformed varint.') - return 'failed' - except Exception: - logger.critical( - 'Pubkey decryption was UNsuccessful because of an unhandled exception! This is definitely a bug! \n%s', - traceback.format_exc()) - return 'failed' + return None - -def checkAndShareObjectWithPeers(data): - """ - This function is called after either receiving an object off of the wire - or after receiving one as ackdata. - Returns the length of time that we should reserve to process this message - if we are receiving it off of the wire. - """ - if len(data) > 2 ** 18: - logger.info('The payload length of this object is too large (%s bytes). Ignoring it.', len(data)) - return 0 - # Let us check to make sure that the proof of work is sufficient. - if not isProofOfWorkSufficient(data): - logger.info('Proof of work is insufficient.') - return 0 - - endOfLifeTime, = unpack('>Q', data[8:16]) - # The TTL may not be larger than 28 days + 3 hours of wiggle room - if endOfLifeTime - int(time.time()) > 28 * 24 * 60 * 60 + 10800: - logger.info('This object\'s End of Life time is too far in the future. Ignoring it. Time is %s', endOfLifeTime) - return 0 - if endOfLifeTime - int(time.time()) < - 3600: # The EOL time was more than an hour ago. That's too much. - logger.info( - 'This object\'s End of Life time was more than an hour ago. Ignoring the object. Time is %s', - endOfLifeTime) - return 0 - intObjectType, = unpack('>I', data[16:20]) try: - if intObjectType == 0: - _checkAndShareGetpubkeyWithPeers(data) - return 0.1 - elif intObjectType == 1: - _checkAndSharePubkeyWithPeers(data) - return 0.1 - elif intObjectType == 2: - _checkAndShareMsgWithPeers(data) - return 0.6 - elif intObjectType == 3: - _checkAndShareBroadcastWithPeers(data) - return 0.6 - _checkAndShareUndefinedObjectWithPeers(data) - return 0.6 - except varintDecodeError as err: - logger.debug( - "There was a problem with a varint while checking to see whether it was appropriate to share an object" - " with peers. Some details: %s", err - ) - except Exception: - logger.critical( - 'There was a problem while checking to see whether it was appropriate to share an object with peers.' - ' This is definitely a bug! %s%s' % os.linesep, traceback.format_exc() - ) - return 0 + byteDifficulty, readLength = decodeVarint(plaintext[readPosition: readPosition + 9]) + readPosition += readLength + lengthExtension, readLength = decodeVarint(plaintext[readPosition: readPosition + 9]) + readPosition += readLength + except: + return None -def _checkAndShareUndefinedObjectWithPeers(data): - # pylint: disable=unused-variable - embeddedTime, = unpack('>Q', data[8:16]) - readPosition = 20 # bypass nonce, time, and object type - objectVersion, objectVersionLength = decodeVarint( - data[readPosition:readPosition + 9]) - readPosition += objectVersionLength - streamNumber, streamNumberLength = decodeVarint( - data[readPosition:readPosition + 9]) - if streamNumber not in state.streamsInWhichIAmParticipating: - logger.debug('The streamNumber %s isn\'t one we are interested in.', streamNumber) - return + result += plaintext[: readPosition] + signedData += plaintext[: readPosition] - inventoryHash = calculateInventoryHash(data) - if inventoryHash in Inventory(): - logger.debug('We have already received this undefined object. Ignoring.') - return - objectType, = unpack('>I', data[16:20]) - Inventory()[inventoryHash] = ( - objectType, streamNumber, data, embeddedTime, '') - logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) - broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + signatureLength, readLength = decodeVarint(plaintext[readPosition: readPosition + 9]) + readPosition += readLength + signature = plaintext[readPosition: readPosition + signatureLength] -def _checkAndShareMsgWithPeers(data): - embeddedTime, = unpack('>Q', data[8:16]) - readPosition = 20 # bypass nonce, time, and object type - objectVersion, objectVersionLength = decodeVarint( # pylint: disable=unused-variable - data[readPosition:readPosition + 9]) - readPosition += objectVersionLength - streamNumber, streamNumberLength = decodeVarint( - data[readPosition:readPosition + 9]) - if streamNumber not in state.streamsInWhichIAmParticipating: - logger.debug('The streamNumber %s isn\'t one we are interested in.', streamNumber) - return - readPosition += streamNumberLength - inventoryHash = calculateInventoryHash(data) - if inventoryHash in Inventory(): - logger.debug('We have already received this msg message. Ignoring.') - return - # This msg message is valid. Let's let our peers know about it. - objectType = 2 - Inventory()[inventoryHash] = ( - objectType, streamNumber, data, embeddedTime, '') - logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) - broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + if len(signature) != signatureLength: + return None - # Now let's enqueue it to be processed ourselves. - objectProcessorQueue.put((objectType, data)) + if not highlevelcrypto.verify(signedData, signature, hexlify(publicSigningKey)): + logger.info("Invalid signature on a pubkey") + return None -def _checkAndShareGetpubkeyWithPeers(data): - # pylint: disable=unused-variable - if len(data) < 42: - logger.info('getpubkey message doesn\'t contain enough data. Ignoring.') - return - if len(data) > 200: - logger.info('getpubkey is abnormally long. Sanity check failed. Ignoring object.') - embeddedTime, = unpack('>Q', data[8:16]) - readPosition = 20 # bypass the nonce, time, and object type - requestedAddressVersionNumber, addressVersionLength = decodeVarint( - data[readPosition:readPosition + 10]) - readPosition += addressVersionLength - streamNumber, streamNumberLength = decodeVarint( - data[readPosition:readPosition + 10]) - if streamNumber not in state.streamsInWhichIAmParticipating: - logger.debug('The streamNumber %s isn\'t one we are interested in.', streamNumber) - return - readPosition += streamNumberLength + return result - inventoryHash = calculateInventoryHash(data) - if inventoryHash in Inventory(): - logger.debug('We have already received this getpubkey request. Ignoring it.') - return +def checkAndShareObjectWithPeers(payload): + if len(payload) > 2 ** 18: + logger.info("The payload length of this object is too large (%i bytes)", len(payload)) - objectType = 0 - Inventory()[inventoryHash] = ( - objectType, streamNumber, data, embeddedTime, '') - # This getpubkey request is valid. Forward to peers. - logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) - broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + return None - # Now let's queue it to be processed ourselves. - objectProcessorQueue.put((objectType, data)) + if not workprover.utils.checkWorkSufficient( + payload, + defaults.networkDefaultProofOfWorkNonceTrialsPerByte, + defaults.networkDefaultPayloadLengthExtraBytes + ): + logger.info("Proof of work is insufficient") + return None -def _checkAndSharePubkeyWithPeers(data): - if len(data) < 146 or len(data) > 440: # sanity check - return - embeddedTime, = unpack('>Q', data[8:16]) - readPosition = 20 # bypass the nonce, time, and object type - addressVersion, varintLength = decodeVarint( - data[readPosition:readPosition + 10]) - readPosition += varintLength - streamNumber, varintLength = decodeVarint( - data[readPosition:readPosition + 10]) - readPosition += varintLength - if streamNumber not in state.streamsInWhichIAmParticipating: - logger.debug('The streamNumber %s isn\'t one we are interested in.', streamNumber) - return - if addressVersion >= 4: - tag = data[readPosition:readPosition + 32] - logger.debug('tag in received pubkey is: %s', hexlify(tag)) + readPosition = 8 + + try: + expiryTime, objectType = unpack(">QI", payload[readPosition: readPosition + 12]) + readPosition += 12 + + version, readLength = decodeVarint(payload[readPosition: readPosition + 9]) + readPosition += readLength + + stream, readLength = decodeVarint(payload[readPosition: readPosition + 9]) + readPosition += readLength + except: + logger.info("Error parsing object header") + + return None + + tag = payload[readPosition: readPosition + 32] + + TTL = expiryTime - int(time.time()) + + # TTL may not be lesser than -1 hour or larger than 28 days + 3 hours of wiggle room + + if TTL < -3600: + logger.info("This object\'s expiry time was more than an hour ago: %s", expiryTime) + + return None + elif TTL > 28 * 24 * 60 * 60 + 10800: + logger.info("This object\'s expiry time is too far in the future: %s", expiryTime) + + return None + + if stream not in state.streamsInWhichIAmParticipating: + logger.info("The stream number %i isn\'t one we are interested in", stream) + + return None + + if objectType == 0: + if len(payload) < 42: + logger.info("Too short \"getpubkey\" message") + + return None + elif objectType == 1: + if len(payload) < 146 or len(payload) > 440: + logger.info("Invalid length \"pubkey\"") + + return None + elif objectType == 3: + if len(payload) < 180: + logger.info("Too short \"broadcast\" message") + + return None + + if version == 1: + logger.info("Obsolete \"broadcast\" message version") + + return None + + inventoryHash = calculateDoubleHash(payload)[: 32] + + if inventoryHash in inventory.Inventory(): + logger.info("We already have this object") + + return inventoryHash else: - tag = '' + inventory.Inventory()[inventoryHash] = objectType, stream, payload, expiryTime, buffer(tag) + queues.invQueue.put((stream, inventoryHash)) - inventoryHash = calculateInventoryHash(data) - if inventoryHash in Inventory(): - logger.debug('We have already received this pubkey. Ignoring it.') - return - objectType = 1 - Inventory()[inventoryHash] = ( - objectType, streamNumber, data, embeddedTime, tag) - # This object is valid. Forward it to peers. - logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) - broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + logger.info("Broadcasting inventory object with hash: %s", hexlify(inventoryHash)) - # Now let's queue it to be processed ourselves. - objectProcessorQueue.put((objectType, data)) - - -def _checkAndShareBroadcastWithPeers(data): - if len(data) < 180: - logger.debug( - 'The payload length of this broadcast packet is unreasonably low. ' - 'Someone is probably trying funny business. Ignoring message.') - return - embeddedTime, = unpack('>Q', data[8:16]) - readPosition = 20 # bypass the nonce, time, and object type - broadcastVersion, broadcastVersionLength = decodeVarint( - data[readPosition:readPosition + 10]) - readPosition += broadcastVersionLength - if broadcastVersion >= 2: - streamNumber, streamNumberLength = decodeVarint(data[readPosition:readPosition + 10]) - readPosition += streamNumberLength - if streamNumber not in state.streamsInWhichIAmParticipating: - logger.debug('The streamNumber %s isn\'t one we are interested in.', streamNumber) - return - if broadcastVersion >= 3: - tag = data[readPosition:readPosition + 32] - else: - tag = '' - inventoryHash = calculateInventoryHash(data) - if inventoryHash in Inventory(): - logger.debug('We have already received this broadcast object. Ignoring.') - return - # It is valid. Let's let our peers know about it. - objectType = 3 - Inventory()[inventoryHash] = ( - objectType, streamNumber, data, embeddedTime, tag) - # This object is valid. Forward it to peers. - logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) - broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) - - # Now let's queue it to be processed ourselves. - objectProcessorQueue.put((objectType, data)) - - -def broadcastToSendDataQueues(data): - """ - If you want to command all of the sendDataThreads to do something, like shutdown or send some data, this - function puts your data into the queues for each of the sendDataThreads. The sendDataThreads are - responsible for putting their queue into (and out of) the sendDataQueues list. - """ - for q in state.sendDataQueues: - q.put(data) + queues.objectProcessorQueue.put((objectType, payload)) + return inventoryHash # sslProtocolVersion if sys.version_info >= (2, 7, 13): diff --git a/src/queues.py b/src/queues.py index e8923dbd..ad8a1252 100644 --- a/src/queues.py +++ b/src/queues.py @@ -14,3 +14,4 @@ portCheckerQueue = Queue.Queue() receiveDataQueue = Queue.Queue() apiAddressGeneratorReturnQueue = Queue.Queue( ) # The address generator thread uses this queue to get information back to the API thread. +processedRawObjectsQueue = Queue.Queue() diff --git a/src/shared.py b/src/shared.py index caf24769..438c058a 100644 --- a/src/shared.py +++ b/src/shared.py @@ -24,8 +24,6 @@ from addresses import ( calculateInventoryHash ) from helper_sql import sqlQuery, sqlExecute -from inventory import Inventory -from queues import objectProcessorQueue verbose = 1 @@ -68,7 +66,6 @@ alreadyAttemptedConnectionsListLock = threading.Lock() alreadyAttemptedConnectionsListResetTime = int(time.time()) # A list of the amounts of time it took to successfully decrypt msg messages successfullyDecryptMessageTimings = [] -ackdataForWhichImWatching = {} # used by API command clientStatus clientHasReceivedIncomingConnections = False numberOfMessagesProcessed = 0 @@ -287,392 +284,6 @@ def fixSensitiveFilePermissions(filename, hasEnabledKeys): logger.exception('Keyfile permissions could not be fixed.') raise - -def isBitSetWithinBitfield(fourByteString, n): - # Uses MSB 0 bit numbering across 4 bytes of data - n = 31 - n - x, = unpack('>L', fourByteString) - return x & 2**n != 0 - - -def decryptAndCheckPubkeyPayload(data, address): - """ - Version 4 pubkeys are encrypted. This function is run when we - already have the address to which we want to try to send a message. - The 'data' may come either off of the wire or we might have had it - already in our inventory when we tried to send a msg to this - particular address. - """ - try: - # status - _, addressVersion, streamNumber, ripe = decodeAddress(address) - - readPosition = 20 # bypass the nonce, time, and object type - embeddedAddressVersion, varintLength = \ - decodeVarint(data[readPosition:readPosition + 10]) - readPosition += varintLength - embeddedStreamNumber, varintLength = \ - decodeVarint(data[readPosition:readPosition + 10]) - readPosition += varintLength - # We'll store the address version and stream number - # (and some more) in the pubkeys table. - storedData = data[20:readPosition] - - if addressVersion != embeddedAddressVersion: - logger.info( - 'Pubkey decryption was UNsuccessful' - ' due to address version mismatch.') - return 'failed' - if streamNumber != embeddedStreamNumber: - logger.info( - 'Pubkey decryption was UNsuccessful' - ' due to stream number mismatch.') - return 'failed' - - tag = data[readPosition:readPosition + 32] - readPosition += 32 - # the time through the tag. More data is appended onto - # signedData below after the decryption. - signedData = data[8:readPosition] - encryptedData = data[readPosition:] - - # Let us try to decrypt the pubkey - toAddress, cryptorObject = state.neededPubkeys[tag] - if toAddress != address: - logger.critical( - 'decryptAndCheckPubkeyPayload failed due to toAddress' - ' mismatch. This is very peculiar.' - ' toAddress: %s, address %s', - toAddress, address - ) - # the only way I can think that this could happen - # is if someone encodes their address data two different ways. - # That sort of address-malleability should have been caught - # by the UI or API and an error given to the user. - return 'failed' - try: - decryptedData = cryptorObject.decrypt(encryptedData) - except: - # Someone must have encrypted some data with a different key - # but tagged it with a tag for which we are watching. - logger.info('Pubkey decryption was unsuccessful.') - return 'failed' - - readPosition = 0 - # bitfieldBehaviors = decryptedData[readPosition:readPosition + 4] - readPosition += 4 - publicSigningKey = \ - '\x04' + decryptedData[readPosition:readPosition + 64] - readPosition += 64 - publicEncryptionKey = \ - '\x04' + decryptedData[readPosition:readPosition + 64] - readPosition += 64 - specifiedNonceTrialsPerByte, specifiedNonceTrialsPerByteLength = \ - decodeVarint(decryptedData[readPosition:readPosition + 10]) - readPosition += specifiedNonceTrialsPerByteLength - specifiedPayloadLengthExtraBytes, \ - specifiedPayloadLengthExtraBytesLength = \ - decodeVarint(decryptedData[readPosition:readPosition + 10]) - readPosition += specifiedPayloadLengthExtraBytesLength - storedData += decryptedData[:readPosition] - signedData += decryptedData[:readPosition] - signatureLength, signatureLengthLength = \ - decodeVarint(decryptedData[readPosition:readPosition + 10]) - readPosition += signatureLengthLength - signature = decryptedData[readPosition:readPosition + signatureLength] - - if not highlevelcrypto.verify( - signedData, signature, hexlify(publicSigningKey)): - logger.info( - 'ECDSA verify failed (within decryptAndCheckPubkeyPayload)') - return 'failed' - - logger.info( - 'ECDSA verify passed (within decryptAndCheckPubkeyPayload)') - - sha = hashlib.new('sha512') - sha.update(publicSigningKey + publicEncryptionKey) - ripeHasher = hashlib.new('ripemd160') - ripeHasher.update(sha.digest()) - embeddedRipe = ripeHasher.digest() - - if embeddedRipe != ripe: - # Although this pubkey object had the tag were were looking for - # and was encrypted with the correct encryption key, - # it doesn't contain the correct pubkeys. Someone is - # either being malicious or using buggy software. - logger.info( - 'Pubkey decryption was UNsuccessful due to RIPE mismatch.') - return 'failed' - - # Everything checked out. Insert it into the pubkeys table. - - logger.info( - 'within decryptAndCheckPubkeyPayload, ' - 'addressVersion: %s, streamNumber: %s\nripe %s\n' - 'publicSigningKey in hex: %s\npublicEncryptionKey in hex: %s', - addressVersion, streamNumber, hexlify(ripe), - hexlify(publicSigningKey), hexlify(publicEncryptionKey) - ) - - t = (address, addressVersion, storedData, int(time.time()), 'yes') - sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) - return 'successful' - except varintDecodeError: - logger.info( - 'Pubkey decryption was UNsuccessful due to a malformed varint.') - return 'failed' - except Exception: - logger.critical( - 'Pubkey decryption was UNsuccessful because of' - ' an unhandled exception! This is definitely a bug! \n%s' % - traceback.format_exc() - ) - return 'failed' - - -def checkAndShareObjectWithPeers(data): - """ - This function is called after either receiving an object - off of the wire or after receiving one as ackdata. - Returns the length of time that we should reserve to process - this message if we are receiving it off of the wire. - """ - if len(data) > 2 ** 18: - logger.info( - 'The payload length of this object is too large (%i bytes).' - ' Ignoring it.', len(data) - ) - return 0 - # Let us check to make sure that the proof of work is sufficient. - if not protocol.isProofOfWorkSufficient(data): - logger.info('Proof of work is insufficient.') - return 0 - - endOfLifeTime, = unpack('>Q', data[8:16]) - # The TTL may not be larger than 28 days + 3 hours of wiggle room - if endOfLifeTime - int(time.time()) > 28 * 24 * 60 * 60 + 10800: - logger.info( - 'This object\'s End of Life time is too far in the future.' - ' Ignoring it. Time is %s', endOfLifeTime - ) - return 0 - # The EOL time was more than an hour ago. That's too much. - if endOfLifeTime - int(time.time()) < -3600: - logger.info( - 'This object\'s End of Life time was more than an hour ago.' - ' Ignoring the object. Time is %s' % endOfLifeTime - ) - return 0 - intObjectType, = unpack('>I', data[16:20]) - try: - if intObjectType == 0: - _checkAndShareGetpubkeyWithPeers(data) - return 0.1 - elif intObjectType == 1: - _checkAndSharePubkeyWithPeers(data) - return 0.1 - elif intObjectType == 2: - _checkAndShareMsgWithPeers(data) - return 0.6 - elif intObjectType == 3: - _checkAndShareBroadcastWithPeers(data) - return 0.6 - else: - _checkAndShareUndefinedObjectWithPeers(data) - return 0.6 - except varintDecodeError as e: - logger.debug( - 'There was a problem with a varint while checking' - ' to see whether it was appropriate to share an object' - ' with peers. Some details: %s' % e) - except Exception: - logger.critical( - 'There was a problem while checking to see whether it was' - ' appropriate to share an object with peers. This is' - ' definitely a bug! \n%s' % traceback.format_exc()) - return 0 - - -def _checkAndShareUndefinedObjectWithPeers(data): - embeddedTime, = unpack('>Q', data[8:16]) - readPosition = 20 # bypass nonce, time, and object type - objectVersion, objectVersionLength = decodeVarint( - data[readPosition:readPosition + 9]) - readPosition += objectVersionLength - streamNumber, streamNumberLength = decodeVarint( - data[readPosition:readPosition + 9]) - if streamNumber not in state.streamsInWhichIAmParticipating: - logger.debug( - 'The streamNumber %i isn\'t one we are interested in.', - streamNumber - ) - return - - inventoryHash = calculateInventoryHash(data) - if inventoryHash in Inventory(): - logger.debug( - 'We have already received this undefined object. Ignoring.') - return - objectType, = unpack('>I', data[16:20]) - Inventory()[inventoryHash] = ( - objectType, streamNumber, data, embeddedTime, '') - logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) - protocol.broadcastToSendDataQueues( - (streamNumber, 'advertiseobject', inventoryHash)) - - -def _checkAndShareMsgWithPeers(data): - embeddedTime, = unpack('>Q', data[8:16]) - readPosition = 20 # bypass nonce, time, and object type - objectVersion, objectVersionLength = \ - decodeVarint(data[readPosition:readPosition + 9]) - readPosition += objectVersionLength - streamNumber, streamNumberLength = \ - decodeVarint(data[readPosition:readPosition + 9]) - if streamNumber not in state.streamsInWhichIAmParticipating: - logger.debug( - 'The streamNumber %i isn\'t one we are interested in.', - streamNumber - ) - return - readPosition += streamNumberLength - inventoryHash = calculateInventoryHash(data) - if inventoryHash in Inventory(): - logger.debug('We have already received this msg message. Ignoring.') - return - # This msg message is valid. Let's let our peers know about it. - objectType = 2 - Inventory()[inventoryHash] = ( - objectType, streamNumber, data, embeddedTime, '') - logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) - protocol.broadcastToSendDataQueues( - (streamNumber, 'advertiseobject', inventoryHash)) - - # Now let's enqueue it to be processed ourselves. - objectProcessorQueue.put((objectType, data)) - - -def _checkAndShareGetpubkeyWithPeers(data): - if len(data) < 42: - logger.info( - 'getpubkey message doesn\'t contain enough data. Ignoring.') - return - embeddedTime, = unpack('>Q', data[8:16]) - readPosition = 20 # bypass the nonce, time, and object type - requestedAddressVersionNumber, addressVersionLength = \ - decodeVarint(data[readPosition:readPosition + 10]) - readPosition += addressVersionLength - streamNumber, streamNumberLength = \ - decodeVarint(data[readPosition:readPosition + 10]) - if streamNumber not in state.streamsInWhichIAmParticipating: - logger.debug( - 'The streamNumber %i isn\'t one we are interested in.', - streamNumber - ) - return - readPosition += streamNumberLength - - inventoryHash = calculateInventoryHash(data) - if inventoryHash in Inventory(): - logger.debug( - 'We have already received this getpubkey request. Ignoring it.') - return - - objectType = 0 - Inventory()[inventoryHash] = ( - objectType, streamNumber, data, embeddedTime, '') - # This getpubkey request is valid. Forward to peers. - logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) - protocol.broadcastToSendDataQueues( - (streamNumber, 'advertiseobject', inventoryHash)) - - # Now let's queue it to be processed ourselves. - objectProcessorQueue.put((objectType, data)) - - -def _checkAndSharePubkeyWithPeers(data): - if len(data) < 146 or len(data) > 440: # sanity check - return - embeddedTime, = unpack('>Q', data[8:16]) - readPosition = 20 # bypass the nonce, time, and object type - addressVersion, varintLength = \ - decodeVarint(data[readPosition:readPosition + 10]) - readPosition += varintLength - streamNumber, varintLength = \ - decodeVarint(data[readPosition:readPosition + 10]) - readPosition += varintLength - if streamNumber not in state.streamsInWhichIAmParticipating: - logger.debug( - 'The streamNumber %i isn\'t one we are interested in.', - streamNumber - ) - return - if addressVersion >= 4: - tag = data[readPosition:readPosition + 32] - logger.debug('tag in received pubkey is: %s', hexlify(tag)) - else: - tag = '' - - inventoryHash = calculateInventoryHash(data) - if inventoryHash in Inventory(): - logger.debug('We have already received this pubkey. Ignoring it.') - return - objectType = 1 - Inventory()[inventoryHash] = ( - objectType, streamNumber, data, embeddedTime, tag) - # This object is valid. Forward it to peers. - logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) - protocol.broadcastToSendDataQueues( - (streamNumber, 'advertiseobject', inventoryHash)) - - # Now let's queue it to be processed ourselves. - objectProcessorQueue.put((objectType, data)) - - -def _checkAndShareBroadcastWithPeers(data): - if len(data) < 180: - logger.debug( - 'The payload length of this broadcast packet is unreasonably low.' - ' Someone is probably trying funny business. Ignoring message.') - return - embeddedTime, = unpack('>Q', data[8:16]) - readPosition = 20 # bypass the nonce, time, and object type - broadcastVersion, broadcastVersionLength = \ - decodeVarint(data[readPosition:readPosition + 10]) - readPosition += broadcastVersionLength - if broadcastVersion >= 2: - streamNumber, streamNumberLength = \ - decodeVarint(data[readPosition:readPosition + 10]) - readPosition += streamNumberLength - if streamNumber not in state.streamsInWhichIAmParticipating: - logger.debug( - 'The streamNumber %i isn\'t one we are interested in.', - streamNumber - ) - return - if broadcastVersion >= 3: - tag = data[readPosition:readPosition+32] - else: - tag = '' - inventoryHash = calculateInventoryHash(data) - if inventoryHash in Inventory(): - logger.debug( - 'We have already received this broadcast object. Ignoring.') - return - # It is valid. Let's let our peers know about it. - objectType = 3 - Inventory()[inventoryHash] = ( - objectType, streamNumber, data, embeddedTime, tag) - # This object is valid. Forward it to peers. - logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) - protocol.broadcastToSendDataQueues( - (streamNumber, 'advertiseobject', inventoryHash)) - - # Now let's queue it to be processed ourselves. - objectProcessorQueue.put((objectType, data)) - - def openKeysFile(): if 'linux' in sys.platform: subprocess.call(["xdg-open", state.appdata + 'keys.dat']) diff --git a/src/singleworker.py b/src/singleworker.py new file mode 100644 index 00000000..b8dc8b60 --- /dev/null +++ b/src/singleworker.py @@ -0,0 +1,1099 @@ +import binascii +import collections +import hashlib +import os.path +import struct +import threading +import time + +import addresses +import bmconfigparser +import debug +import defaults +import helper_msgcoding +import helper_sql +import helper_random +import helper_threading +import highlevelcrypto +import inventory +import l10n +import paths +import protocol +import shared +import state +import tr +import queues +import workprover + +# Message status flow: +# +# +----------------------------------------------------------------------------------------+ +# v | +# +-> msgqueued -+---------------------------------------->+-+-> doingmsgpow -+-> msgsent -+-> ackreceived +# ^ | ^ | | +# | +-> awaitingpubkey -+-> doingpubkeypow -+ | | +-> msgsentnoackexpected +# | ^ v | | | | +# +--------------+-------------------+-------------------+ | | +-> badkey +# | | +# | +-> toodifficult --> forcepow -+ +# | | +# +--------------------------------+ +# +# Can also be "msgcanceled" + +# Broadcast status flow: +# +# broadcastqueued --> doingbroadcastpow --> broadcastsent +# +# Can also be "broadcastcanceled" + +# TODO: queued pubkey messages are not saved to the database, they disappear when the client is closed + +AddressProperties = collections.namedtuple("AddressProperties", [ + "version", "stream", "ripe", + "own", "chan", "bitfield", "byteDifficulty", "lengthExtension", + "secretSigningKey", "secretEncryptionKey", "publicSigningKey", "publicEncryptionKey" +]) + +def getMyAddressProperties(address, defaultDifficulty = False): + status, version, stream, ripe = addresses.decodeAddress(address) + + if defaultDifficulty: + byteDifficulty = defaults.networkDefaultProofOfWorkNonceTrialsPerByte + lengthExtension = defaults.networkDefaultPayloadLengthExtraBytes + else: + byteDifficulty = bmconfigparser.BMConfigParser().safeGetInt(address, "noncetrialsperbyte", None) + lengthExtension = bmconfigparser.BMConfigParser().safeGetInt(address, "payloadlengthextrabytes", None) + + chan = bmconfigparser.BMConfigParser().safeGetBoolean(address, "chan") + bitfield = 0 + + if not bmconfigparser.BMConfigParser().safeGetBoolean(address, "dontsendack"): + bitfield |= protocol.BITFIELD_DOESACK + + secretSigningKeyBase58 = bmconfigparser.BMConfigParser().get(address, "privsigningkey") + secretEncryptionKeyBase58 = bmconfigparser.BMConfigParser().get(address, "privencryptionkey") + + secretSigningKey = shared.decodeWalletImportFormat(secretSigningKeyBase58) + secretEncryptionKey = shared.decodeWalletImportFormat(secretEncryptionKeyBase58) + + publicSigningKey = binascii.unhexlify(highlevelcrypto.privToPub(binascii.hexlify(secretSigningKey))) + publicEncryptionKey = binascii.unhexlify(highlevelcrypto.privToPub(binascii.hexlify(secretEncryptionKey))) + + return AddressProperties( + version, stream, ripe, + True, chan, bitfield, byteDifficulty, lengthExtension, + secretSigningKey, secretEncryptionKey, publicSigningKey, publicEncryptionKey + ) + +def parsePubkeyMessage(encoded): + readPosition = 0 + + version, readLength = addresses.decodeVarint(encoded[readPosition: readPosition + 9]) + readPosition += readLength + + stream, readLength = addresses.decodeVarint(encoded[readPosition: readPosition + 9]) + readPosition += readLength + + bitfield, = struct.unpack(">I", encoded[readPosition: readPosition + 4]) + readPosition += 4 + + publicSigningKey = "\x04" + encoded[readPosition: readPosition + 64] + readPosition += 64 + + publicEncryptionKey = "\x04" + encoded[readPosition: readPosition + 64] + readPosition += 64 + + ripe = protocol.calculateRipeHash(publicSigningKey + publicEncryptionKey) + + if version < 3: + byteDifficulty = defaults.networkDefaultProofOfWorkNonceTrialsPerByte + lengthExtension = defaults.networkDefaultPayloadLengthExtraBytes + else: + byteDifficulty, readLength = addresses.decodeVarint(encoded[readPosition: readPosition + 9]) + readPosition += readLength + + lengthExtension, readLength = addresses.decodeVarint(encoded[readPosition: readPosition + 9]) + readPosition += readLength + + byteDifficulty = max(defaults.networkDefaultProofOfWorkNonceTrialsPerByte, byteDifficulty) + lengthExtension = max(defaults.networkDefaultPayloadLengthExtraBytes, lengthExtension) + + return AddressProperties( + version, stream, ripe, + False, False, bitfield, byteDifficulty, lengthExtension, + None, None, publicSigningKey, publicEncryptionKey + ) + +def getDestinationAddressProperties(address): + # Search own and chan addresses + + try: + return getMyAddressProperties(address, True) + except: + pass + + # Search the "pubkeys" table in the database + + status, version, stream, ripe = addresses.decodeAddress(address) + + if version == 4: + secretEncryptionKey, tag = protocol.calculateAddressTag(version, stream, ripe) + + cryptor = highlevelcrypto.makeCryptor(binascii.hexlify(secretEncryptionKey)) + + alreadyNeeded = tag in state.neededPubkeys + state.neededPubkeys[tag] = address, cryptor + else: + alreadyNeeded = address in state.neededPubkeys + state.neededPubkeys[address] = None + + helper_sql.sqlExecute("""UPDATE "pubkeys" SET "usedpersonally" = 'yes' WHERE "address" == ?;""", address) + encodedPubkeys = helper_sql.sqlQuery("""SELECT "transmitdata" FROM "pubkeys" WHERE "address" == ?;""", address) + + result = None + + if len(encodedPubkeys) != 0: + result = parsePubkeyMessage(encodedPubkeys[-1][0]) + + # Search the inventory for encrypted keys + + if result is None and version == 4: + for i in inventory.Inventory().by_type_and_tag(1, tag): + encodedPubkey = protocol.decryptAndCheckV4Pubkey(i.payload, address, cryptor) + + if encodedPubkey is None: + continue + + helper_sql.sqlExecute(""" + INSERT INTO "pubkeys" ("address", "addressversion", "transmitdata", "time", "usedpersonally") + VALUES (?, 4, ?, ?, 'yes'); + """, address, encodedPubkey, int(time.time())) + + result = parsePubkeyMessage(encodedPubkey) + + break + + if result is not None: + if version == 4: + state.neededPubkeys.pop(tag, None) + else: + state.neededPubkeys.pop(address, None) + + helper_sql.sqlExecute(""" + UPDATE "sent" SET "status" = 'msgqueued' + WHERE "status" IN ('doingpubkeypow', 'awaitingpubkey') AND "toaddress" == ? AND "folder" == 'sent'; + """, address) + + if alreadyNeeded: + queues.workerQueue.put(("sendmessage", )) + + queued = helper_sql.sqlQuery(""" + SELECT "ackdata" FROM "sent" + WHERE "status" == 'msgqueued' AND "toaddress" == ? AND "folder" == 'sent'; + """, address) + + for i, in queued: + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "msgqueued", + i, + tr._translate( + "MainWindow", + "Queued." + ) + ))) + + return result + + return None + +def randomizeTTL(TTL): + return TTL + helper_random.randomrandrange(-300, 300) + +workProver = workprover.WorkProver( + os.path.join(paths.codePath(), "workprover"), + helper_random.randomBytes(32), + lambda status: queues.UISignalQueue.put(("updateWorkProverStatus", status)), + queues.workerQueue +) + +debug.logger.info("Availabe solvers: %s", str(workProver.availableSolvers.keys())) + +if "fast" not in workProver.availableSolvers: + queues.UISignalQueue.put(("updateStatusBar", ( + tr._translate( + "proofofwork", + "C PoW module unavailable. Please build it." + ), 1 + ))) + +def setBestSolver(): + solverName = bmconfigparser.BMConfigParser().safeGet("bitmessagesettings", "powsolver", "gpu") + forkingSolverParallelism = bmconfigparser.BMConfigParser().safeGetInt("bitmessagesettings", "processes") + fastSolverParallelism = bmconfigparser.BMConfigParser().safeGetInt("bitmessagesettings", "threads") + GPUVendor = bmconfigparser.BMConfigParser().safeGet("bitmessagesettings", "opencl") + + if forkingSolverParallelism < 1: + forkingSolverParallelism = workProver.defaultParallelism + + if fastSolverParallelism < 1: + fastSolverParallelism = workProver.defaultParallelism + + maxcores = bmconfigparser.BMConfigParser().safeGetInt("bitmessagesettings", "maxcores", None) + + if maxcores is not None: + forkingSolverParallelism = min(maxcores, forkingSolverParallelism) + fastSolverParallelism = min(maxcores, fastSolverParallelism) + + if solverName == "gpu" and GPUVendor is None: + solverName = "fast" + + while solverName not in workProver.availableSolvers: + if solverName == "gpu": + solverName = "fast" + elif solverName == "fast": + solverName = "forking" + elif solverName == "forking": + solverName = "dumb" + + bmconfigparser.BMConfigParser().set("bitmessagesettings", "powsolver", solverName) + bmconfigparser.BMConfigParser().set("bitmessagesettings", "processes", str(forkingSolverParallelism)) + bmconfigparser.BMConfigParser().set("bitmessagesettings", "threads", str(fastSolverParallelism)) + bmconfigparser.BMConfigParser().save() + + if solverName in ["dumb", "gpu"]: + workProver.commandsQueue.put(("setSolver", solverName, None)) + elif solverName == "forking": + workProver.commandsQueue.put(("setSolver", "forking", forkingSolverParallelism)) + elif solverName == "fast": + workProver.commandsQueue.put(("setSolver", "fast", fastSolverParallelism)) + +setBestSolver() + +class singleWorker(threading.Thread, helper_threading.StoppableThread): + def __init__(self): + super(self.__class__, self).__init__(name = "singleWorker") + + self.initStop() + + def stopThread(self): + queues.workerQueue.put(("stopThread", "data")) + workProver.commandsQueue.put(("shutdown", )) + + super(self.__class__, self).stopThread() + + def run(self): + workProver.start() + + self.startedWorks = {} + + # Give some time for the GUI to start + # TODO: use a condition variable + + self.stop.wait(10) + + queues.workerQueue.put(("sendmessage", )) + queues.workerQueue.put(("sendbroadcast", )) + + while state.shutdown == 0: + queueItem = queues.workerQueue.get() + command, arguments = queueItem[0], queueItem[1: ] + + if command == "sendmessage": + self.sendMessages() + elif command == "cancelMessage": + self.cancelMessage(*arguments) + elif command == "sendbroadcast": + self.sendBroadcasts() + elif command == "cancelBroadcast": + self.cancelBroadcast(*arguments) + elif command == "sendMyPubkey": + self.sendMyPubkey(*arguments) + elif command == "requestPubkey": + self.requestPubkey(*arguments) + elif command == "sendRawObject": + self.sendRawObject(*arguments) + elif command == "cancelRawObject": + self.cancelRawObject(*arguments) + elif command == "GPUError": + self.handleGPUError(*arguments) + elif command == "taskDone": + self.workDone(*arguments) + elif command == "stopThread": + workProver.commandsQueue.put(("shutdown", )) + workProver.join() + + break + + debug.logger.info("Quitting...") + + def handleGPUError(self): + bmconfigparser.BMConfigParser().set("bitmessagesettings", "powsolver", "dumb") + + workProver.commandsQueue.put(("setSolver", "dumb", None)) + + debug.logger.error( + "Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers" + ) + + queues.UISignalQueue.put(("updateStatusBar", ( + tr._translate( + "MainWindow", + "Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers." + ), 1 + ))) + + def startWork(self, ID, headlessPayload, TTL, expiryTime, byteDifficulty, lengthExtension, logPrefix, callback): + debug.logger.info( + "%s Starting work %s, payload length = %s, TTL = %s", + logPrefix, ID, 8 + 8 + len(headlessPayload), TTL + ) + + self.startedWorks[ID] = callback + + workProver.commandsQueue.put(( + "addTask", ID, headlessPayload, TTL, expiryTime, + byteDifficulty, lengthExtension + )) + + def workDone(self, ID, nonce, expiryTime): + debug.logger.info("Found proof of work %s", ID) + + if ID in self.startedWorks: + self.startedWorks[ID](nonce + struct.pack(">Q", expiryTime)) + + del self.startedWorks[ID] + + def sendMyPubkey(self, address): + ID = "pubkey", address + + if ID in self.startedWorks: + return + + try: + addressProperties = getMyAddressProperties(address) + except Exception as exception: + debug.logger.error("Could not get the properties of a requested address %s\n", exception) + + return + + if addressProperties.chan: + debug.logger.info("This is a chan address. Not sending pubkey") + + return + + if addressProperties.version == 4: + secretEncryptionKey, tag = protocol.calculateAddressTag( + addressProperties.version, + addressProperties.stream, + addressProperties.ripe + ) + + publicEncryptionKey = highlevelcrypto.pointMult(secretEncryptionKey) + else: + tag = "" + + debug.logger.info("Sending pubkey of %s", address) + + TTL = randomizeTTL(28 * 24 * 60 * 60) + expiryTime = int(time.time() + TTL) + + headlessPayload = struct.pack(">I", 1) + headlessPayload += addresses.encodeVarint(addressProperties.version) + headlessPayload += addresses.encodeVarint(addressProperties.stream) + + headlessPayload += tag + + if addressProperties.version == 4: + plaintext = struct.pack(">I", addressProperties.bitfield) + plaintext += addressProperties.publicSigningKey[1: ] + plaintext += addressProperties.publicEncryptionKey[1: ] + plaintext += addresses.encodeVarint(addressProperties.byteDifficulty) + plaintext += addresses.encodeVarint(addressProperties.lengthExtension) + + signature = highlevelcrypto.sign( + struct.pack(">Q", expiryTime) + headlessPayload + plaintext, + binascii.hexlify(addressProperties.secretSigningKey) + ) + + plaintext += addresses.encodeVarint(len(signature)) + plaintext += signature + + headlessPayload += highlevelcrypto.encrypt(plaintext, binascii.hexlify(publicEncryptionKey)) + else: + headlessPayload += struct.pack(">I", addressProperties.bitfield) + headlessPayload += addressProperties.publicSigningKey[1: ] + headlessPayload += addressProperties.publicEncryptionKey[1: ] + + if addressProperties.version == 3: + headlessPayload += addresses.encodeVarint(addressProperties.byteDifficulty) + headlessPayload += addresses.encodeVarint(addressProperties.lengthExtension) + + signature = highlevelcrypto.sign( + struct.pack(">Q", expiryTime) + headlessPayload, + binascii.hexlify(addressProperties.secretSigningKey) + ) + + headlessPayload += addresses.encodeVarint(len(signature)) + headlessPayload += signature + + def workDone(head): + protocol.checkAndShareObjectWithPeers(head + headlessPayload) + + # TODO: not atomic with the addition to the inventory, the "lastpubkeysendtime" property should be removed + # Instead check if the pubkey is present in the inventory + + try: + bmconfigparser.BMConfigParser().set(address, "lastpubkeysendtime", str(int(time.time()))) + bmconfigparser.BMConfigParser().save() + except: + pass + + queues.UISignalQueue.put(("updateStatusBar", "")) + + self.startWork( + ID, headlessPayload, TTL, expiryTime, + defaults.networkDefaultProofOfWorkNonceTrialsPerByte, + defaults.networkDefaultPayloadLengthExtraBytes, + "(For pubkey version {} message)".format(addressProperties.version), + workDone + ) + + def processBroadcast(self, address, subject, body, ackData, TTL, encoding): + ID = "broadcast", ackData + + try: + addressProperties = getMyAddressProperties(address) + except: + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "broadcastqueued", + ackData, + tr._translate( + "MainWindow", + "Error! Could not find sender address (your address) in the keys.dat file." + ) + ))) + + return + + if addressProperties.version < 2: + debug.logger.error("Address version unsupported for broadcasts") + + return + + debug.logger.info("Sending broadcast from %s", address) + + if addressProperties.version == 4: + secretEncryptionKey, tag = protocol.calculateAddressTag( + addressProperties.version, + addressProperties.stream, + addressProperties.ripe + ) + else: + secretEncryptionKey = hashlib.sha512( + addresses.encodeVarint(addressProperties.version) + + addresses.encodeVarint(addressProperties.stream) + + addressProperties.ripe + ).digest()[: 32] + + tag = "" + + publicEncryptionKey = highlevelcrypto.pointMult(secretEncryptionKey) + + TTL = min(28 * 24 * 60 * 60, TTL) + TTL = max(60 * 60, TTL) + TTL = randomizeTTL(TTL) + expiryTime = int(time.time() + TTL) + + headlessPayload = struct.pack(">I", 3) + + if addressProperties.version == 4: + headlessPayload += addresses.encodeVarint(5) + else: + headlessPayload += addresses.encodeVarint(4) + + headlessPayload += addresses.encodeVarint(addressProperties.stream) + + headlessPayload += tag + + plaintext = addresses.encodeVarint(addressProperties.version) + plaintext += addresses.encodeVarint(addressProperties.stream) + plaintext += struct.pack(">I", addressProperties.bitfield) + plaintext += addressProperties.publicSigningKey[1: ] + plaintext += addressProperties.publicEncryptionKey[1: ] + + if addressProperties.version >= 3: + plaintext += addresses.encodeVarint(addressProperties.byteDifficulty) + plaintext += addresses.encodeVarint(addressProperties.lengthExtension) + + encodedMessage = helper_msgcoding.MsgEncode({"subject": subject, "body": body}, encoding) + + plaintext += addresses.encodeVarint(encoding) + plaintext += addresses.encodeVarint(encodedMessage.length) + plaintext += encodedMessage.data + + signature = highlevelcrypto.sign( + struct.pack(">Q", expiryTime) + headlessPayload + plaintext, + binascii.hexlify(addressProperties.secretSigningKey) + ) + + plaintext += addresses.encodeVarint(len(signature)) + plaintext += signature + + headlessPayload += highlevelcrypto.encrypt(plaintext, binascii.hexlify(publicEncryptionKey)) + + if len(headlessPayload) > 2 ** 18 - (8 + 8): # 256 kiB + debug.logger.critical( + "This broadcast object is too large to send. This should never happen. Object size: %s", + len(headlessPayload) + ) + + return + + def workDone(head): + # TODO: adding to the inventory, adding to inbox and setting the sent status should be within a single SQL transaction + + inventoryHash = protocol.checkAndShareObjectWithPeers(head + headlessPayload) + + helper_sql.sqlExecute(""" + UPDATE "sent" SET "msgid" = ?, "status" = 'broadcastsent', "lastactiontime" = ? + WHERE "ackdata" == ?; + """, inventoryHash, int(time.time()), ackData) + + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "broadcastsent", + ackData, + tr._translate("MainWindow", "Broadcast sent on %1").arg(l10n.formatTimestamp()) + ))) + + helper_sql.sqlExecute("""UPDATE "sent" SET "status" = 'doingbroadcastpow' WHERE "ackdata" == ?;""", ackData) + + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "doingbroadcastpow", + ackData, + tr._translate( + "MainWindow", + "Doing work necessary to send broadcast." + ) + ))) + + self.startWork( + ID, headlessPayload, TTL, expiryTime, + defaults.networkDefaultProofOfWorkNonceTrialsPerByte, + defaults.networkDefaultPayloadLengthExtraBytes, + "(For broadcast message)", + workDone + ) + + def sendBroadcasts(self): + queued = helper_sql.sqlQuery(""" + SELECT "fromaddress", "subject", "message", "ackdata", "ttl", "encodingtype" FROM "sent" + WHERE "status" == 'broadcastqueued' AND "folder" == 'sent'; + """) + + for i in queued: + # Must be in a separate function because of the nested callback + + self.processBroadcast(*i) + + def cancelBroadcast(self, ackData, delete, trash): + ID = "broadcast", ackData + + if ID in self.startedWorks: + del self.startedWorks[ID] + + workProver.commandsQueue.put(("cancelTask", ID)) + + helper_sql.sqlExecute(""" + UPDATE "sent" SET "status" = 'broadcastcanceled' + WHERE "ackdata" == ? AND "status" != 'broadcastsent'; + """, ackData) + + if delete: + if trash: + helper_sql.sqlExecute("""UPDATE "sent" SET "folder" = 'trash' WHERE "ackdata" == ?;""", ackData) + else: + helper_sql.sqlExecute("""DELETE FROM "sent" WHERE "ackdata" == ?""", ackData) + + queues.UISignalQueue.put(("deleteSentItemByAckData", ackData)) + else: + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "broadcastcanceled", + ackData, + tr._translate( + "MainWindow", + "Broadcast canceled." + ) + ))) + + def generateAckMessage(self, ackData, stream, TTL, callback): + ID = "ack", ackData + + # It might be perfectly fine to just use the same TTL for + # the ackdata that we use for the message. But I would rather + # it be more difficult for attackers to associate ackData with + # the associated msg object. However, users would want the TTL + # of the acknowledgement to be about the same as they set + # for the message itself. So let's set the TTL of the + # acknowledgement to be in one of three 'buckets': 1 hour, 7 + # days, or 28 days, whichever is relatively close to what the + # user specified. + + if TTL < 24 * 60 * 60: + TTL = 24 * 60 * 60 + elif TTL < 7 * 24 * 60 * 60: + TTL = 7 * 24 * 60 * 60 + else: + TTL = 28 * 24 * 60 * 60 + + TTL = randomizeTTL(TTL) + expiryTime = int(time.time() + TTL) + + def workDone(head): + callback(protocol.CreatePacket("object", head + ackData)) + + self.startWork( + ID, ackData, TTL, expiryTime, + defaults.networkDefaultProofOfWorkNonceTrialsPerByte, + defaults.networkDefaultPayloadLengthExtraBytes, + "(For ack message)", + workDone + ) + + def processMessage(self, status, destination, source, subject, body, ackData, TTL, retryNumber, encoding): + ID = "message", ackData + + helper_sql.sqlExecute("""UPDATE "sent" SET "status" = 'awaitingpubkey' WHERE "ackdata" == ?;""", ackData) + + destinationProperties = getDestinationAddressProperties(destination) + + if destinationProperties is None: + queues.workerQueue.put(("requestPubkey", destination)) + + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "awaitingpubkey", + ackData, + tr._translate( + "MainWindow", + "Waiting for their encryption key. Will request it again soon." + ) + ))) + + return + + try: + defaultDifficulty = shared.isAddressInMyAddressBookSubscriptionsListOrWhitelist(destination) + + if destinationProperties.own: + defaultDifficulty = True + + sourceProperties = getMyAddressProperties(source, defaultDifficulty) + except: + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "msgqueued", + ackData, + tr._translate( + "MainWindow", + "Error! Could not find sender address (your address) in the keys.dat file." + ) + ))) + + return + + relativeByteDifficulty = ( + float(destinationProperties.byteDifficulty) / + defaults.networkDefaultProofOfWorkNonceTrialsPerByte + ) + + relativeLengthExtension = ( + float(destinationProperties.lengthExtension) / + defaults.networkDefaultPayloadLengthExtraBytes + ) + + if status != "forcepow": + maximumByteDifficulty = bmconfigparser.BMConfigParser().getint( + "bitmessagesettings", "maxacceptablenoncetrialsperbyte" + ) + + maximumLengthExtension = bmconfigparser.BMConfigParser().getint( + "bitmessagesettings", "maxacceptablepayloadlengthextrabytes" + ) + + if ( + maximumByteDifficulty != 0 and destinationProperties.byteDifficulty > maximumLengthExtension or + maximumLengthExtension != 0 and destinationProperties.lengthExtension > maximumLengthExtension + ): + helper_sql.sqlExecute("""UPDATE "sent" SET "status" = 'toodifficult' WHERE "ackdata" == ?;""", ackData) + + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "toodifficult", + ackData, + tr._translate( + "MainWindow", + "Problem: The work demanded by the recipient (%1 and %2) is " + "more difficult than you are willing to do. %3" + ).arg(str(relativeByteDifficulty)).arg(str(relativeLengthExtension)).arg(l10n.formatTimestamp()) + ))) + + return + + debug.logger.info("Sending message from %s to %s", source, destination) + + TTL *= 2 ** retryNumber + TTL = min(28 * 24 * 60 * 60, TTL) + TTL = max(60 * 60, TTL) + TTL = randomizeTTL(TTL) + expiryTime = int(time.time() + TTL) + + def ackMessageGenerated(ackMessage): + headlessPayload = struct.pack(">I", 2) + headlessPayload += addresses.encodeVarint(1) + headlessPayload += addresses.encodeVarint(destinationProperties.stream) + + plaintext = addresses.encodeVarint(sourceProperties.version) + plaintext += addresses.encodeVarint(sourceProperties.stream) + plaintext += struct.pack(">I", sourceProperties.bitfield) + plaintext += sourceProperties.publicSigningKey[1: ] + plaintext += sourceProperties.publicEncryptionKey[1: ] + + if sourceProperties.version >= 3: + plaintext += addresses.encodeVarint(sourceProperties.byteDifficulty) + plaintext += addresses.encodeVarint(sourceProperties.lengthExtension) + + plaintext += destinationProperties.ripe # To prevent resending a signed message to a different reciever + + encodedMessage = helper_msgcoding.MsgEncode({"subject": subject, "body": body}, encoding) + + plaintext += addresses.encodeVarint(encoding) + plaintext += addresses.encodeVarint(encodedMessage.length) + plaintext += encodedMessage.data + + if ackMessage is None: + plaintext += addresses.encodeVarint(0) + else: + plaintext += addresses.encodeVarint(len(ackMessage)) + plaintext += ackMessage + + signature = highlevelcrypto.sign( + struct.pack(">Q", expiryTime) + headlessPayload + plaintext, + binascii.hexlify(sourceProperties.secretSigningKey) + ) + + plaintext += addresses.encodeVarint(len(signature)) + plaintext += signature + + try: + ciphertext = highlevelcrypto.encrypt( + plaintext, + binascii.hexlify(destinationProperties.publicEncryptionKey) + ) + except: + helper_sql.sqlExecute("""UPDATE "sent" SET "status" = 'badkey' WHERE "ackdata" == ?;""", ackData) + + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "badkey", + ackData, + tr._translate( + "MainWindow", + "Problem: The recipient's encryption key is no good. Could not encrypt message. %1" + ).arg(l10n.formatTimestamp()) + ))) + + return + + headlessPayload += ciphertext + + if len(headlessPayload) > 2 ** 18 - (8 + 8): # 256 kiB + debug.logger.critical( + "This message object is too large to send. This should never happen. Object size: %s", + len(headlessPayload) + ) + + return + + def workDone(head): + if ackMessage is not None: + state.watchedAckData.add(ackData) + + #TODO: adding to the inventory, adding to inbox and setting the sent status should be within a single SQL transaction + + inventoryHash = protocol.checkAndShareObjectWithPeers(head + headlessPayload) + + if ackMessage is None: + newStatus = "msgsentnoackexpected" + sleepTill = 0 + else: + newStatus = "msgsent" + sleepTill = int(time.time() + TTL * 1.1) + + helper_sql.sqlExecute(""" + UPDATE "sent" SET "msgid" = ?, "status" = ?, "retrynumber" = ?, + "sleeptill" = ?, "lastactiontime" = ? + WHERE "status" == 'doingmsgpow' AND "ackdata" == ?; + """, inventoryHash, newStatus, retryNumber + 1, sleepTill, int(time.time()), ackData) + + if ackMessage is None: + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "msgsentnoackexpected", + ackData, + tr._translate( + "MainWindow", + "Message sent. Sent at %1" + ).arg(l10n.formatTimestamp()) + ))) + else: + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "msgsent", + ackData, + tr._translate( + "MainWindow", + "Message sent. Waiting for acknowledgement. Sent on %1" + ).arg(l10n.formatTimestamp()) + ))) + + self.startWork( + ID, headlessPayload, TTL, expiryTime, + destinationProperties.byteDifficulty, + destinationProperties.lengthExtension, + "(For message)", + workDone + ) + + helper_sql.sqlExecute("""UPDATE "sent" SET "status" = 'doingmsgpow' WHERE "ackdata" == ?;""", ackData) + + if relativeByteDifficulty != 1 or relativeLengthExtension != 1: + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "doingmsgpow", + ackData, + tr._translate( + "MainWindow", + "Doing work necessary to send message.\nReceiver's required difficulty: %1 and %2" + ).arg(str(relativeByteDifficulty)).arg(str(relativeLengthExtension)) + ))) + else: + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "doingmsgpow", + ackData, + tr._translate( + "MainWindow", + "Doing work necessary to send message." + ) + ))) + + if destinationProperties.own: + debug.logger.info("Not bothering to include ack data because we are sending to ourselves or a chan") + + ackMessageGenerated(None) + elif destinationProperties.bitfield & protocol.BITFIELD_DOESACK == 0: + debug.logger.info("Not bothering to include ack data because the receiver said that they won't relay it anyway") + + ackMessageGenerated(None) + else: + self.generateAckMessage(ackData, destinationProperties.stream, TTL, ackMessageGenerated) + + def sendMessages(self): + queued = helper_sql.sqlQuery(""" + SELECT "status", "toaddress", "fromaddress", "subject", "message", + "ackdata", "ttl", "retrynumber", "encodingtype" FROM "sent" + WHERE "status" IN ('msgqueued', 'forcepow') AND "folder" == 'sent'; + """) + + for i in queued: + # Must be in a separate function because of the nested callback + + self.processMessage(*i) + + def cancelMessage(self, ackData, delete, trash): + ID = "ack", ackData + + if ID in self.startedWorks: + del self.startedWorks[ID] + + workProver.commandsQueue.put(("cancelTask", ID)) + + ID = "message", ackData + + if ID in self.startedWorks: + del self.startedWorks[ID] + + workProver.commandsQueue.put(("cancelTask", ID)) + + state.watchedAckData -= {ackData} + + queryReturn = helper_sql.sqlQuery("""SELECT "toaddress" FROM "sent" WHERE "ackdata" == ?;""", ackData) + + if len(queryReturn) != 0: + destination = queryReturn[0][0] + + count = helper_sql.sqlQuery(""" + SELECT COUNT(*) FROM "sent" + WHERE "status" IN ('doingpubkeypow', 'awaitingpubkey') AND "toaddress" == ? AND "ackdata" != ?; + """, destination, ackData)[0][0] + + if count == 0: + ID = "getpubkey", destination + + if ID in self.startedWorks: + del self.startedWorks[ID] + + workProver.commandsQueue.put(("cancelTask", ID)) + + status, version, stream, ripe = addresses.decodeAddress(destination) + + if version == 4: + secretEncryptionKey, tag = protocol.calculateAddressTag(version, stream, ripe) + + state.neededPubkeys.pop(tag, None) + else: + state.neededPubkeys.pop(destination, None) + + helper_sql.sqlExecute(""" + UPDATE "sent" SET "status" = 'msgcanceled' + WHERE "ackdata" == ? AND "status" NOT IN ('ackreceived', 'msgsentnoackexpected', 'badkey'); + """, ackData) + + if delete: + if trash: + helper_sql.sqlExecute("""UPDATE "sent" SET "folder" = 'trash' WHERE "ackdata" == ?;""", ackData) + else: + helper_sql.sqlExecute("""DELETE FROM "sent" WHERE "ackdata" == ?""", ackData) + + queues.UISignalQueue.put(("deleteSentItemByAckData", ackData)) + else: + queues.UISignalQueue.put(("updateSentItemStatusByAckdata", ( + "msgcanceled", + ackData, + tr._translate( + "MainWindow", + "Message canceled." + ) + ))) + + def requestPubkey(self, address): + ID = "getpubkey", address + + if ID in self.startedWorks: + return + + status, version, stream, ripe = addresses.decodeAddress(address) + + # Check if a request is already in the inventory + + if version == 4: + secretEncryptionKey, tag = protocol.calculateAddressTag(version, stream, ripe) + else: + tag = ripe + + currentExpiryTime = None + + for i in inventory.Inventory().by_type_and_tag(0, tag): + if currentExpiryTime is None: + currentExpiryTime = i.expires + else: + currentExpiryTime = max(currentExpiryTime, i.expires) + + if currentExpiryTime is not None: + helper_sql.sqlExecute(""" + UPDATE "sent" SET "status" = 'awaitingpubkey', "sleeptill" = ? + WHERE "status" IN ('doingpubkeypow', 'awaitingpubkey') AND "toaddress" == ? AND "folder" == 'sent'; + """, currentExpiryTime, address) + + queues.UISignalQueue.put(("updateSentItemStatusByToAddress", ( + "awaitingpubkey", + address, + tr._translate( + "MainWindow", + "Waiting for their encryption key. Will request it again soon." + ) + ))) + + return + + debug.logger.info("Making request for version %s pubkey with tag: %s", version, binascii.hexlify(tag)) + + TTL = randomizeTTL(28 * 24 * 60 * 60) + expiryTime = int(time.time() + TTL) + + headlessPayload = struct.pack(">I", 0) + headlessPayload += addresses.encodeVarint(version) + headlessPayload += addresses.encodeVarint(stream) + + headlessPayload += tag + + def workDone(head): + # TODO: adding to the inventory and setting the sent status should be within a single SQL transaction + + protocol.checkAndShareObjectWithPeers(head + headlessPayload) + + sleepTill = int(time.time() + TTL * 1.1) + + helper_sql.sqlExecute(""" + UPDATE "sent" SET "status" = 'awaitingpubkey', "sleeptill" = ?, "lastactiontime" = ? + WHERE "status" IN ('doingpubkeypow', 'awaitingpubkey') AND "toaddress" == ? AND "folder" == 'sent'; + """, sleepTill, int(time.time()), address) + + queues.UISignalQueue.put(("updateSentItemStatusByToAddress", ( + "awaitingpubkey", + address, + tr._translate( + "MainWindow", + "Sending public key request. Waiting for reply. Requested at %1" + ).arg(l10n.formatTimestamp()) + ))) + + helper_sql.sqlExecute(""" + UPDATE "sent" SET "status" = 'doingpubkeypow' + WHERE "status" == 'awaitingpubkey' AND "toaddress" == ? AND "folder" == 'sent'; + """, address) + + queues.UISignalQueue.put(("updateSentItemStatusByToAddress", ( + "doingpubkeypow", + address, + tr._translate( + "MainWindow", + "Doing work necessary to request encryption key." + ) + ))) + + self.startWork( + ID, headlessPayload, TTL, expiryTime, + defaults.networkDefaultProofOfWorkNonceTrialsPerByte, + defaults.networkDefaultPayloadLengthExtraBytes, + "(For getpubkey message)".format(version), + workDone + ) + + def sendRawObject(self, randomID, TTL, headlessPayload): + ID = "raw", randomID + + debug.logger.info("Sending raw object") + + expiryTime = int(time.time() + TTL) + + def workDone(head): + inventoryHash = protocol.checkAndShareObjectWithPeers(head + headlessPayload) + + if inventoryHash is None: + queues.processedRawObjectsQueue.put(("failed", randomID)) + else: + queues.processedRawObjectsQueue.put(("sent", randomID, inventoryHash)) + + queues.processedRawObjectsQueue.put(("doingwork", randomID)) + + self.startWork( + ID, headlessPayload, TTL, expiryTime, + defaults.networkDefaultProofOfWorkNonceTrialsPerByte, + defaults.networkDefaultPayloadLengthExtraBytes, + "(For raw object)", + workDone + ) + + def cancelRawObject(self, randomID): + ID = "raw", randomID + + if ID in self.startedWorks: + del self.startedWorks[ID] + + workProver.commandsQueue.put(("cancelTask", ID)) + + queues.processedRawObjectsQueue.put(("canceled", randomID)) diff --git a/src/state.py b/src/state.py index 834b60be..450af6e7 100644 --- a/src/state.py +++ b/src/state.py @@ -1,8 +1,12 @@ import collections +# Single worker assumes, that object processor checks this dict only after a pubkey is added to the inventory or the "pubkeys" table +# TODO: add locking? + neededPubkeys = {} +watchedAckData = set() + streamsInWhichIAmParticipating = [] -sendDataQueues = [] # each sendData thread puts its queue in this list. # For UPnP extPort = None diff --git a/src/workprover/Readme.md b/src/workprover/Readme.md new file mode 100644 index 00000000..105346be --- /dev/null +++ b/src/workprover/Readme.md @@ -0,0 +1,42 @@ +Please keep this module independent from the outside code, so that it can be reused in other applications. + +If you are going to use it, you should wrap your program's main file in this: + +```python +import workprover.dumbsolver + +workprover.dumbsolver.libcrypto = ... + +if __name__ == "__main__": + import multiprocessing + + multiprocessing.freeze_support() + + ... +``` + +See the `multiprocessing` module documentation for explaination. + +Build fast solver +----------------- + +On Linux, BSDs or MacOS: `make -C fastsolver`. + +On Windows: + +- Install OpenSSL. Build it yourself or install [third-party](https://wiki.openssl.org/index.php/Binaries) prebuilt binaries. + +- Install MSVC as part of Visual Studio or standalone. Official offline installer: https://aka.ms/vcpython27. + +- Open its command line and go to the `fastsolver` directory. + +- Add OpenSSL paths to environment variables: + +```bat +set INCLUDE=C:\OpenSSL-Win64\include;%INCLUDE% +set LIB=C:\OpenSSL-Win64\lib;%LIB% +``` + +- Do `cl @options.txt`. + +- Append the `-32` or `-64` suffix to the DLL file name. diff --git a/src/workprover/__init__.py b/src/workprover/__init__.py new file mode 100644 index 00000000..1aac3a65 --- /dev/null +++ b/src/workprover/__init__.py @@ -0,0 +1,262 @@ +import Queue +import collections +import multiprocessing +import struct +import sys +import threading +import time + +import dumbsolver +import fastsolver +import forkingsolver +import gpusolver +import utils + +timeout = .5 + +class Stop(Exception): + pass + +class Task(object): + previous = None + next = None + + def __init__(self, headlessPayload, TTL, expiryTime, target, difficulty): + self.headlessPayload = headlessPayload + self.TTL = TTL + self.expiryTime = expiryTime + self.target = target + self.difficulty = difficulty + +Status = collections.namedtuple("Status", ["solverName", "solverStatus", "speed", "tasksCount", "difficulty"]) + +# Only one instance allowed + +class WorkProver(threading.Thread): + # Seed must be 32 bytes + + def __init__(self, codePath, seed, statusUpdated, resultsQueue): + super(self.__class__, self).__init__() + + self.availableSolvers = { + "dumb": dumbsolver.DumbSolver(codePath) + } + + # Comment from the previous version: + + # on my (Peter Surda) Windows 10, Windows Defender + # does not like this and fights with PyBitmessage + # over CPU, resulting in very slow PoW + # added on 2015-11-29: multiprocesing.freeze_support() doesn't help + + if not hasattr(sys, "frozen") or sys.frozen == "macosx_app": + self.availableSolvers["forking"] = forkingsolver.ForkingSolver(codePath) + + try: + self.availableSolvers["fast"] = fastsolver.FastSolver(codePath) + except fastsolver.FastSolverError: + pass + + try: + self.availableSolvers["gpu"] = gpusolver.GPUSolver(codePath) + except gpusolver.GPUSolverError: + pass + + try: + self.defaultParallelism = multiprocessing.cpu_count() + except NotImplementedError: + self.defaultParallelism = 1 + + self.seed = seed + self.roundsCounter = 0 + self.statusUpdated = statusUpdated + + self.commandsQueue = Queue.Queue() + + if resultsQueue is None: + self.resultsQueue = Queue.Queue() + else: + self.resultsQueue = resultsQueue + + self.solverName = None + self.solver = None + + self.lastTime = utils.getTimePoint() + self.timedIntervals = collections.deque() + self.speed = 0 + self.totalDifficulty = 0 + + self.tasks = {} + self.currentTaskID = None + + def notifyStatus(self): + if self.statusUpdated is None: + return + + status = None + + if self.solver is not None: + status = self.solver.status + + self.statusUpdated(Status(self.solverName, status, self.speed, len(self.tasks), self.totalDifficulty)) + + def setSolver(self, name, configuration): + try: + if name is None and self.solverName is None: + pass + elif name == self.solverName: + self.solver.setConfiguration(configuration) + else: + if self.solver is not None: + self.solver.setConfiguration(None) + self.solverName = None + self.solver = None + + if name is not None: + if name not in self.availableSolvers: + name, configuration = "dumb", None + + self.solverName = name + self.solver = self.availableSolvers[name] + self.solver.setConfiguration(configuration) + except gpusolver.GPUSolverError: + self.solverName = None + self.solver = None + + self.resultsQueue.put(("GPUError", )) + + self.notifyStatus() + + def updateSpeed(self, iterationsCount): + currentTime = utils.getTimePoint() + duration = currentTime - self.lastTime + self.lastTime = currentTime + + self.timedIntervals.append((currentTime, iterationsCount, duration)) + + for i in xrange(len(self.timedIntervals)): + time, iterationsCount, duration = self.timedIntervals[0] + + if time + duration < currentTime - 3: + self.timedIntervals.popleft() + + totalDuration = 0 + totalIterationsCount = 0 + + for time, iterationsCount, duration in self.timedIntervals: + totalIterationsCount += iterationsCount + totalDuration += duration + + if totalDuration < .25: + self.speed = 0 + else: + self.speed = totalIterationsCount / totalDuration + + self.notifyStatus() + + def addTask(self, ID, headlessPayload, TTL, expiryTime, byteDifficulty, lengthExtension): + target, difficulty = utils.calculateTarget(8 + 8 + len(headlessPayload), TTL, byteDifficulty, lengthExtension) + + task = Task(headlessPayload, TTL, expiryTime, target, difficulty) + + self.tasks[ID] = task + self.totalDifficulty += difficulty + + if self.currentTaskID is None: + task.previous = ID + task.next = ID + + self.currentTaskID = ID + else: + task.previous = self.currentTaskID + task.next = self.tasks[self.currentTaskID].next + + self.tasks[task.previous].next = ID + self.tasks[task.next].previous = ID + + self.notifyStatus() + + def cancelTask(self, ID): + if ID not in self.tasks: + return + + task = self.tasks.pop(ID) + self.totalDifficulty -= task.difficulty + + if len(self.tasks) == 0: + self.currentTaskID = None + else: + self.tasks[task.previous].next = task.next + self.tasks[task.next].previous = task.previous + + if self.currentTaskID == ID: + self.currentTaskID = task.next + + self.notifyStatus() + + def nextTask(self): + self.currentTaskID = self.tasks[self.currentTaskID].next + + def shutdown(self): + self.setSolver(None, None) + + for i in self.tasks.keys(): + self.cancelTask(i) + + raise Stop() + + def processCommand(self, command, *arguments): + getattr(self, command)(*arguments) + + def round(self): + while True: + try: + self.processCommand(*self.commandsQueue.get_nowait()) + except Queue.Empty: + break + + while self.solver is None or self.currentTaskID is None: + try: + self.processCommand(*self.commandsQueue.get(True, timeout)) + except Queue.Empty: + self.updateSpeed(0) + + task = self.tasks[self.currentTaskID] + + if task.expiryTime is None: + expiryTime = int(time.time() + task.TTL) + else: + expiryTime = task.expiryTime + + initialPayload = struct.pack(">Q", expiryTime) + task.headlessPayload + initialHash = utils.calculateInitialHash(initialPayload) + + appendedSeed = self.seed + struct.pack(">Q", self.roundsCounter) + self.roundsCounter += 1 + + try: + nonce, iterationsCount = self.solver.search(initialHash, task.target, appendedSeed, timeout) + except gpusolver.GPUSolverError: + self.setSolver(None, None) + + self.resultsQueue.put(("GPUError", )) + + nonce, iterationsCount = None, 0 + + self.updateSpeed(iterationsCount) + + if nonce is None: + self.nextTask() + else: + self.resultsQueue.put(("taskDone", self.currentTaskID, nonce, expiryTime)) + + self.cancelTask(self.currentTaskID) + + def run(self): + try: + while True: + self.round() + except Stop: + return + except Exception as exception: + self.resultsQueue.put(exception) diff --git a/src/workprover/dumbsolver.py b/src/workprover/dumbsolver.py new file mode 100644 index 00000000..8dfbbf9c --- /dev/null +++ b/src/workprover/dumbsolver.py @@ -0,0 +1,70 @@ +import ctypes +import hashlib +import struct + +import utils + +libcrypto = None + +class DumbSolver(object): + def __init__(self, codePath): + libcrypto.SHA512.restype = ctypes.c_void_p + + self.prefixes = [chr(i) for i in xrange(256)] + + if ctypes.c_size_t is ctypes.c_uint: + self.proofLength = 8 + 64 + self.hashLength = 64 + else: + # Using the wrapper instead of a clear number slows the work down, but otherwise seems to be unsafe + + self.proofLength = ctypes.c_size_t(8 + 64) + self.hashLength = ctypes.c_size_t(64) + + self.firstHash = ctypes.create_string_buffer(64) + self.secondHash = ctypes.create_string_buffer(64) + + self.status = None + + def search(self, initialHash, target, seed, timeout): + startTime = utils.getTimePoint() + + sha512 = libcrypto.SHA512 + + prefixes = self.prefixes + proofLength = self.proofLength + hashLength = self.hashLength + firstHash = self.firstHash + secondHash = self.secondHash + + encodedTarget = struct.pack(">Q", target) + + solutions = [] + i = 0 + + while True: + randomness = hashlib.sha512(seed + struct.pack(">Q", i)).digest() + i += 1 + + suffix = randomness[: 7] + initialHash + + for j in prefixes: + proof = j + suffix + + sha512(j + suffix, proofLength, firstHash) + sha512(firstHash, hashLength, secondHash) + + if secondHash[: 8] <= encodedTarget: + solutions.append(proof[: 8]) + + if len(solutions) != 0: + index, = struct.unpack(">Q", randomness[7: 15]) + nonce = solutions[index % len(solutions)] + + return nonce, 256 * i + + if utils.getTimePoint() - startTime >= timeout: + return None, 256 * i + + def setConfiguration(self, configuration): + pass diff --git a/src/workprover/fastsolver.py b/src/workprover/fastsolver.py new file mode 100644 index 00000000..4fe26fc7 --- /dev/null +++ b/src/workprover/fastsolver.py @@ -0,0 +1,88 @@ +import ctypes +import os.path +import platform +import subprocess +import sys + +class FastSolverError(Exception): + pass + +def loadFastSolver(codePath): + if hasattr(sys, "winver"): + suffix = "-32" + + if platform.architecture()[0] == "64bit": + suffix = "-64" + + path = os.path.join(codePath, "fastsolver/libfastsolver{}.dll".format(suffix)) + + try: + return ctypes.WinDLL(path) + except: + raise FastSolverError() + + makePath = os.path.join(codePath, "fastsolver") + path = os.path.join(codePath, "fastsolver/libfastsolver.so") + + try: + return ctypes.CDLL(path) + except: + if hasattr(sys, "frozen"): + raise FastSolverError() + + try: + subprocess.call(["make", "-C", makePath]) + + return ctypes.CDLL(path) + except: + raise FastSolverError() + +# Only one instance allowed + +class FastSolver(object): + def __init__(self, codePath): + self.libfastsolver = loadFastSolver(codePath) + + self.libfastsolver.fastsolver_add.restype = ctypes.c_size_t + self.libfastsolver.fastsolver_add.argtypes = [] + + self.libfastsolver.fastsolver_remove.restype = ctypes.c_size_t + self.libfastsolver.fastsolver_remove.argtypes = [ctypes.c_size_t] + + self.libfastsolver.fastsolver_search.restype = ctypes.c_int + + self.libfastsolver.fastsolver_search.argtypes = [ + ctypes.c_void_p, ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_ulonglong, ctypes.c_void_p, ctypes.c_ulonglong + ] + + self.nonce = ctypes.create_string_buffer(8) + self.iterationsCount = ctypes.c_ulonglong() + + self.parallelism = 0 + self.status = 0 + + def search(self, initialHash, target, seed, timeout): + found = self.libfastsolver.fastsolver_search( + self.nonce, ctypes.byref(self.iterationsCount), + initialHash, target, seed, long(1000000000 * timeout) + ) + + if found == 1: + return self.nonce.raw, self.iterationsCount.value + else: + return None, self.iterationsCount.value + + def setConfiguration(self, configuration): + if configuration is None: + parallelism = 0 + else: + parallelism = min(4096, configuration) + + for i in xrange(self.parallelism, parallelism): + self.parallelism = self.libfastsolver.fastsolver_add() + + if parallelism < self.parallelism: + self.parallelism = self.libfastsolver.fastsolver_remove(self.parallelism - parallelism) + + self.status = parallelism diff --git a/src/workprover/fastsolver/common.c b/src/workprover/fastsolver/common.c new file mode 100644 index 00000000..5467b854 --- /dev/null +++ b/src/workprover/fastsolver/common.c @@ -0,0 +1,100 @@ +#include + +#include + +#include "common.h" + +volatile int run; + +const char *initial_hash; +unsigned long long target; +const char *seed; + +static void encode_big_endian(char *result, unsigned long long number) { + result[0] = number >> 56; + result[1] = number >> 48 & 0xff; + result[2] = number >> 40 & 0xff; + result[3] = number >> 32 & 0xff; + result[4] = number >> 24 & 0xff; + result[5] = number >> 16 & 0xff; + result[6] = number >> 8 & 0xff; + result[7] = number & 0xff; +} + +static unsigned long long decode_big_endian(const char *encoded) { + return ( + (encoded[0] & 0xffull) << 56 | + (encoded[1] & 0xffull) << 48 | + (encoded[2] & 0xffull) << 40 | + (encoded[3] & 0xffull) << 32 | + (encoded[4] & 0xffull) << 24 | + (encoded[5] & 0xffull) << 16 | + (encoded[6] & 0xffull) << 8 | + (encoded[7] & 0xffull) + ); +} + +int work(char *nonce, unsigned long long *iterations_count, size_t thread_number) { + unsigned long long i; + + char proof[8 + 64]; + char appended_seed[SEED_LENGTH + 8 + 8]; + + memcpy(proof + 8, initial_hash, 64); + memcpy(appended_seed, seed, SEED_LENGTH); + encode_big_endian(appended_seed + SEED_LENGTH, thread_number); + + for (i = 0; run; ++i) { + char randomness[64]; + + size_t solutions_count = 0; + char solutions[256]; + + size_t j; + + encode_big_endian(appended_seed + SEED_LENGTH + 8, i); + + SHA512((unsigned char *) appended_seed, SEED_LENGTH + 8 + 8, (unsigned char *) randomness); + + memcpy(proof + 1, randomness, 7); + + for (j = 0; j < 256; ++j) { + unsigned long long trial; + + SHA512_CTX context; + + char first_hash[64]; + char second_hash[64]; + + proof[0] = j; + + SHA512_Init(&context); + SHA512_Update(&context, (unsigned char *) proof, 8 + 64); + SHA512_Final((unsigned char *) first_hash, &context); + + SHA512_Init(&context); + SHA512_Update(&context, (unsigned char *) first_hash, 64); + SHA512_Final((unsigned char *) second_hash, &context); + + trial = decode_big_endian(second_hash); + + if (trial <= target) { + solutions[solutions_count] = j; + ++solutions_count; + } + + ++*iterations_count; + } + + if (solutions_count != 0) { + unsigned long long index = decode_big_endian(randomness + 7); + + nonce[0] = solutions[index % solutions_count]; + memcpy(nonce + 1, proof + 1, 7); + + return 1; + } + } + + return 0; +} diff --git a/src/workprover/fastsolver/common.h b/src/workprover/fastsolver/common.h new file mode 100644 index 00000000..a4ef1b7d --- /dev/null +++ b/src/workprover/fastsolver/common.h @@ -0,0 +1,19 @@ +#ifndef COMMON_H + #define COMMON_H + + #ifdef _WIN32 + #define EXPORT __declspec(dllexport) + #else + #define EXPORT __attribute__ ((visibility("default"))) + #endif + + extern volatile int run; + + #define SEED_LENGTH (32 + 8) + + extern const char *initial_hash; + extern unsigned long long target; + extern const char *seed; + + int work(char *nonce, unsigned long long *iterations_count, size_t thread_number); +#endif diff --git a/src/workprover/fastsolver/makefile b/src/workprover/fastsolver/makefile new file mode 100644 index 00000000..17be4224 --- /dev/null +++ b/src/workprover/fastsolver/makefile @@ -0,0 +1,12 @@ +CFLAGS += -std=gnu99 -Wall -Wextra -pedantic -O3 -fPIC -fvisibility=hidden +LDFLAGS += -shared +LDLIBS = -lpthread -lcrypto + +libfastsolver.so: common.o pthread.o + $(CC) $(LDFLAGS) -o $@ common.o pthread.o $(LDLIBS) + +common.o: common.h common.c +pthread.o: common.h pthread.c + +clean: + rm -f common.o pthread.o libfastsolver.so diff --git a/src/workprover/fastsolver/options.txt b/src/workprover/fastsolver/options.txt new file mode 100644 index 00000000..8db700c0 --- /dev/null +++ b/src/workprover/fastsolver/options.txt @@ -0,0 +1 @@ +/Ox /MD common.c winapi.c /link /DLL /OUT:libfastsolver.dll libeay32.lib diff --git a/src/workprover/fastsolver/pthread.c b/src/workprover/fastsolver/pthread.c new file mode 100644 index 00000000..a9f47eb5 --- /dev/null +++ b/src/workprover/fastsolver/pthread.c @@ -0,0 +1,214 @@ +#include +#include + +#include + +#include "common.h" + +static int initialized; + +#define MAXIMUM_THREADS_COUNT 4096 + +static size_t threads_count; +static pthread_t threads[MAXIMUM_THREADS_COUNT]; + +static pthread_mutex_t lock; +static pthread_cond_t start; +static pthread_cond_t done; + +static size_t running_threads_count; + +static int found; +static char best_nonce[8]; +static unsigned long long total_iterations_count; + +static void *thread_function(void *argument) { + size_t thread_number = (pthread_t *) argument - threads; + + while (1) { + char nonce[8]; + unsigned long long iterations_count = 0; + int result; + + pthread_mutex_lock(&lock); + + while (!run && threads_count > thread_number) { + pthread_cond_wait(&start, &lock); + } + + if (threads_count <= thread_number) { + pthread_mutex_unlock(&lock); + + return NULL; + } + + ++running_threads_count; + + pthread_mutex_unlock(&lock); + + result = work(nonce, &iterations_count, thread_number); + + pthread_mutex_lock(&lock); + + if (result == 1) { + found = 1; + memcpy(best_nonce, nonce, 8); + } + + total_iterations_count += iterations_count; + + run = 0; + --running_threads_count; + + pthread_cond_signal(&done); + pthread_mutex_unlock(&lock); + } +} + +static int initialize(void) { + pthread_condattr_t done_attributes; + + if (initialized == 1) { + return 1; + } + + if (pthread_mutex_init(&lock, NULL) != 0) { + goto error_lock; + } + + if (pthread_cond_init(&start, NULL) != 0) { + goto error_start; + } + + if (pthread_condattr_init(&done_attributes) != 0) { + goto error_done_attributes; + } + + #ifndef __APPLE__ + pthread_condattr_setclock(&done_attributes, CLOCK_MONOTONIC); + #endif + + if (pthread_cond_init(&done, &done_attributes) != 0) { + goto error_done; + } + + pthread_condattr_destroy(&done_attributes); + + initialized = 1; + + return 1; + + error_done: pthread_condattr_destroy(&done_attributes); + error_done_attributes: pthread_cond_destroy(&start); + error_start: pthread_mutex_destroy(&lock); + error_lock: return 0; +} + +EXPORT size_t fastsolver_add(void) { + #ifdef SCHED_IDLE + int policy = SCHED_IDLE; + #else + int policy = SCHED_OTHER; + #endif + + struct sched_param parameters; + + if (initialize() == 0) { + return threads_count; + } + + pthread_mutex_lock(&lock); + + if (pthread_create(&threads[threads_count], NULL, thread_function, &threads[threads_count]) != 0) { + pthread_mutex_unlock(&lock); + + return threads_count; + } + + parameters.sched_priority = sched_get_priority_min(policy); + pthread_setschedparam(threads[threads_count], policy, ¶meters); + + ++threads_count; + + pthread_mutex_unlock(&lock); + + return threads_count; +} + +EXPORT size_t fastsolver_remove(size_t count) { + size_t i; + + pthread_mutex_lock(&lock); + + threads_count -= count; + + pthread_cond_broadcast(&start); + pthread_mutex_unlock(&lock); + + for (i = 0; i < count; ++i) { + void *result; + + pthread_join(threads[threads_count + i], &result); + } + + return threads_count; +} + +EXPORT int fastsolver_search( + char *local_nonce, + unsigned long long *local_iterations_count, + const char *local_initial_hash, + unsigned long long local_target, + const char *local_seed, + unsigned long long timeout +) { + struct timespec wait_time; + unsigned long long nanoseconds; + + initial_hash = local_initial_hash; + target = local_target; + seed = local_seed; + + found = 0; + total_iterations_count = 0; + + #ifdef __APPLE__ + wait_time.tv_sec = 0; + wait_time.tv_nsec = 0; + #else + clock_gettime(CLOCK_MONOTONIC, &wait_time); + #endif + + nanoseconds = wait_time.tv_nsec + timeout; + + wait_time.tv_sec += nanoseconds / 1000000000; + wait_time.tv_nsec = nanoseconds % 1000000000; + + pthread_mutex_lock(&lock); + + run = 1; + + pthread_cond_broadcast(&start); + + #ifdef __APPLE__ + pthread_cond_timedwait_relative_np(&done, &lock, &wait_time); + #else + pthread_cond_timedwait(&done, &lock, &wait_time); + #endif + + run = 0; + + while (running_threads_count != 0) { + pthread_cond_wait(&done, &lock); + } + + pthread_mutex_unlock(&lock); + + if (found) { + memcpy(local_nonce, best_nonce, 8); + } + + *local_iterations_count = total_iterations_count; + + return found; +} diff --git a/src/workprover/fastsolver/winapi.c b/src/workprover/fastsolver/winapi.c new file mode 100644 index 00000000..1f8fe045 --- /dev/null +++ b/src/workprover/fastsolver/winapi.c @@ -0,0 +1,160 @@ +#include + +#include + +#include "common.h" + +static int initialized; + +#define MAXIMUM_THREADS_COUNT 4096 + +static size_t threads_count; +static HANDLE threads[MAXIMUM_THREADS_COUNT]; + +static CRITICAL_SECTION lock; +static CONDITION_VARIABLE start = CONDITION_VARIABLE_INIT; +static CONDITION_VARIABLE done = CONDITION_VARIABLE_INIT; + +static size_t running_threads_count; + +static int found; +static char best_nonce[8]; +static unsigned long long total_iterations_count; + +DWORD WINAPI thread_function(LPVOID argument) { + size_t thread_number = (HANDLE *) argument - threads; + + while (1) { + char nonce[8]; + unsigned long long iterations_count = 0; + int result; + + EnterCriticalSection(&lock); + + while (!run && threads_count > thread_number) { + SleepConditionVariableCS(&start, &lock, INFINITE); + } + + if (threads_count <= thread_number) { + LeaveCriticalSection(&lock); + + return 0; + } + + ++running_threads_count; + + LeaveCriticalSection(&lock); + + result = work(nonce, &iterations_count, thread_number); + + EnterCriticalSection(&lock); + + if (result == 1) { + found = 1; + memcpy(best_nonce, nonce, 8); + } + + total_iterations_count += iterations_count; + + run = 0; + --running_threads_count; + + WakeConditionVariable(&done); + LeaveCriticalSection(&lock); + } +} + +static int initialize(void) { + if (initialized == 1) { + return 1; + } + + InitializeCriticalSection(&lock); + + initialized = 1; + + return 1; +} + +EXPORT size_t fastsolver_add(void) { + if (initialize() == 0) { + return threads_count; + } + + EnterCriticalSection(&lock); + + threads[threads_count] = CreateThread(NULL, 0, thread_function, &threads[threads_count], 0, NULL); + + if (threads[threads_count] == NULL) { + LeaveCriticalSection(&lock); + + return threads_count; + } + + SetThreadPriority(threads[threads_count], THREAD_PRIORITY_IDLE); + + ++threads_count; + + LeaveCriticalSection(&lock); + + return threads_count; +} + +EXPORT size_t fastsolver_remove(size_t count) { + size_t i; + + EnterCriticalSection(&lock); + + threads_count -= count; + + WakeAllConditionVariable(&start); + LeaveCriticalSection(&lock); + + WaitForMultipleObjects(count, threads + threads_count, TRUE, INFINITE); + + for (i = 0; i < count; ++i) { + CloseHandle(threads[threads_count + i]); + } + + return threads_count; +} + +EXPORT int fastsolver_search( + char *local_nonce, + unsigned long long *local_iterations_count, + const char *local_initial_hash, + unsigned long long local_target, + const char *local_seed, + unsigned long long timeout +) { + initial_hash = local_initial_hash; + target = local_target; + seed = local_seed; + + found = 0; + total_iterations_count = 0; + + EnterCriticalSection(&lock); + + run = 1; + + WakeAllConditionVariable(&start); + + SleepConditionVariableCS(&done, &lock, timeout / 1000); + + run = 0; + + while (running_threads_count != 0) { + SleepConditionVariableCS(&done, &lock, INFINITE); + } + + LeaveCriticalSection(&lock); + + if (found) { + memcpy(local_nonce, best_nonce, 8); + } + + *local_iterations_count = total_iterations_count; + + return found; +} diff --git a/src/workprover/forkingsolver.py b/src/workprover/forkingsolver.py new file mode 100644 index 00000000..5ab84da7 --- /dev/null +++ b/src/workprover/forkingsolver.py @@ -0,0 +1,117 @@ +import multiprocessing +import os +import struct + +import dumbsolver + +def setIdle(): + if hasattr(os, "nice"): + os.nice(40) + + return + + try: + import psutil + + psutil.Process().nice(psutil.IDLE_PRIORITY_CLASS) + + return + except: + pass + + try: + import win32api + import win32con + import win32process + + PID = win32api.GetCurrentProcessId() + handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, True, PID) + + win32process.SetPriorityClass(handle, win32process.IDLE_PRIORITY_CLASS) + except: + pass + +def threadFunction(local, remote, codePath, threadNumber): + remote.close() + setIdle() + + solver = dumbsolver.DumbSolver(codePath) + + while True: + try: + received = local.recv() + + command = received[0] + arguments = received[1: ] + + if command == "search": + initialHash, target, seed, timeout = arguments + appendedSeed = seed + struct.pack(">Q", threadNumber) + + nonce, iterationsCount = solver.search(initialHash, target, appendedSeed, timeout) + + local.send(("done", nonce, iterationsCount)) + elif command == "shutdown": + local.close() + + return + except (EOFError, IOError): + return + +class ForkingSolver(object): + def __init__(self, codePath): + self.pipes = [] + self.processes = [] + + self.status = 0 + + self.codePath = codePath + + def search(self, initialHash, target, seed, timeout): + for i in self.pipes: + i.send(("search", initialHash, target, seed, timeout)) + + bestNonce, totalIterationsCount = None, 0 + + for i in self.pipes: + event, nonce, iterationsCount = i.recv() + + if nonce is not None: + bestNonce = nonce + + totalIterationsCount += iterationsCount + + return bestNonce, totalIterationsCount + + def setConfiguration(self, configuration): + if configuration is None: + parallelism = 0 + else: + parallelism = min(4096, configuration) + + for i in xrange(len(self.processes), parallelism): + local, remote = multiprocessing.Pipe() + + process = multiprocessing.Process( + target = threadFunction, + args = (remote, local, self.codePath, i), + name = "ForkingSolver" + ) + + process.start() + + remote.close() + + self.pipes.append(local) + self.processes.append(process) + + for i in xrange(parallelism, len(self.processes)): + pipe = self.pipes.pop() + + pipe.send(("shutdown", )) + pipe.close() + + for i in xrange(parallelism, len(self.processes)): + self.processes.pop().join() + + self.status = parallelism diff --git a/src/workprover/gpusolver.cl b/src/workprover/gpusolver.cl new file mode 100644 index 00000000..6bd6d010 --- /dev/null +++ b/src/workprover/gpusolver.cl @@ -0,0 +1,136 @@ +constant ulong k[80] = { + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, + 0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, + 0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694, + 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, + 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70, + 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, + 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, + 0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, + 0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, + 0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, + 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 +}; + +constant ulong h[8] = { + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179 +}; + +#define ROTATE(x, n) ((x) >> (n) | (x) << 64 - (n)) + +#define C(x, y, z) ((x) & (y) ^ ~(x) & (z)) +#define M(x, y, z) ((x) & (y) ^ (x) & (z) ^ (y) & (z)) +#define S0(x) (ROTATE((x), 28) ^ ROTATE((x), 34) ^ ROTATE((x), 39)) +#define S1(x) (ROTATE((x), 14) ^ ROTATE((x), 18) ^ ROTATE((x), 41)) +#define s0(x) (ROTATE((x), 1) ^ ROTATE((x), 8) ^ (x) >> 7) +#define s1(x) (ROTATE((x), 19) ^ ROTATE((x), 61) ^ (x) >> 6) + +void sha512_process_block(ulong *state, ulong *block) { + ulong a = state[0]; + ulong b = state[1]; + ulong c = state[2]; + ulong d = state[3]; + ulong e = state[4]; + ulong f = state[5]; + ulong g = state[6]; + ulong h = state[7]; + + ulong *w = block; + + #pragma unroll + + for (size_t i = 0; i < 16; i++) { + ulong t = k[i] + w[i & 15] + h + S1(e) + C(e, f, g); + + h = g; + g = f; + f = e; + e = d + t; + t += M(a, b, c) + S0(a); + d = c; + c = b; + b = a; + a = t; + } + + #pragma unroll 16 + + for (size_t i = 16; i < 80; i++) { + w[i & 15] += s0(w[i + 1 & 15]) + s1(w[i + 14 & 15]) + w[i + 9 & 15]; + + ulong t = k[i] + w[i & 15] + h + S1(e) + C(e, f, g); + + h = g; + g = f; + f = e; + e = d + t; + t += M(a, b, c) + S0(a); + d = c; + c = b; + b = a; + a = t; + } + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + state[5] += f; + state[6] += g; + state[7] += h; +} + +ulong compute_trial(ulong nonce, global const ulong *initial_hash) { + ulong fisrt_block[16] = { + nonce, + initial_hash[0], initial_hash[1], initial_hash[2], initial_hash[3], + initial_hash[4], initial_hash[5], initial_hash[6], initial_hash[7], + 0x8000000000000000, 0, 0, 0, 0, 0, 8 * (8 + 64) + }; + + ulong second_block[16] = { + h[0], h[1], h[2], h[3], + h[4], h[5], h[6], h[7], + 0x8000000000000000, 0, 0, 0, 0, 0, 0, 8 * 64 + }; + + ulong double_hash[8] = { + h[0], h[1], h[2], h[3], + h[4], h[5], h[6], h[7] + }; + + sha512_process_block(second_block, fisrt_block); + sha512_process_block(double_hash, second_block); + + return double_hash[0]; +} + +kernel void search(global unsigned int *output, global ulong *input) { + size_t thread_number = get_global_id(0); + + global unsigned int *solutions_count = output; + global unsigned int *solutions = output + 1; + + global ulong *nonce = input; + global ulong *initial_hash = input + 1; + global ulong *target = input + 9; + + ulong trial = compute_trial(*nonce + thread_number, initial_hash); + + if (trial <= *target) { + unsigned int index = atom_inc(solutions_count); + + solutions[index] = thread_number; + } +} diff --git a/src/workprover/gpusolver.py b/src/workprover/gpusolver.py new file mode 100644 index 00000000..7b5d09fd --- /dev/null +++ b/src/workprover/gpusolver.py @@ -0,0 +1,116 @@ +import hashlib +import os.path +import struct + +import utils + +pyopencl = None +numpy = None + +class GPUSolverError(Exception): + pass + +class GPUSolver(object): + def __init__(self, codePath): + global pyopencl + + try: + import pyopencl + except ImportError: + raise GPUSolverError() + + self.vendors = {} + + for i in pyopencl.get_platforms(): + devices = i.get_devices(device_type = pyopencl.device_type.GPU) + + if len(devices) != 0: + self.vendors[i.vendor] = devices[0] + + if len(self.vendors) == 0: + raise GPUSolverError() + + with open(os.path.join(codePath, "gpusolver.cl")) as file: + self.source = file.read() + + self.status = None + + def search(self, initialHash, target, seed, timeout): + startTime = utils.getTimePoint() + + self.hostOutput[0] = 0 + + for i in xrange(8): + self.hostInput[1 + i], = struct.unpack(">Q", initialHash[8 * i: 8 * (i + 1)]) + + self.hostInput[9] = target + + pyopencl.enqueue_copy(self.queue, self.output, self.hostOutput[: 1]) + + i = 0 + + while True: + randomness = hashlib.sha512(seed + struct.pack(">Q", i)).digest() + i += 1 + + self.hostInput[0], = struct.unpack(">Q", randomness[: 8]) + + pyopencl.enqueue_copy(self.queue, self.input, self.hostInput) + pyopencl.enqueue_nd_range_kernel(self.queue, self.kernel, (self.batchSize, ), None) + self.queue.finish() + pyopencl.enqueue_copy(self.queue, self.hostOutput[: 1], self.output) + + solutionsCount = long(self.hostOutput[0]) + + if solutionsCount != 0: + pyopencl.enqueue_copy(self.queue, self.hostOutput[0: 1 + solutionsCount], self.output) + + index, = struct.unpack(">Q", randomness[8: 16]) + threadNumber = self.hostOutput[1 + index % solutionsCount] + + nonce = struct.pack(">Q", long(self.hostInput[0]) + threadNumber) + + if not utils.checkProof(nonce, initialHash, target): + raise GPUSolverError() + + return nonce, self.batchSize * i + + if utils.getTimePoint() - startTime >= timeout: + return None, self.batchSize * i + + def setConfiguration(self, configuration): + global numpy + + if numpy is not None: + return + + import numpy + + if configuration is None: + configuration = self.vendors.keys()[0] + + if configuration not in self.vendors: + raise GPUSolverError() + + device = self.vendors[configuration] + context = pyopencl.Context(devices = [device]) + + computeUnitsCount = device.get_info(pyopencl.device_info.MAX_COMPUTE_UNITS) + workGroupSize = device.get_info(pyopencl.device_info.MAX_WORK_GROUP_SIZE) + + self.batchSize = workGroupSize * computeUnitsCount * 256 + + self.queue = pyopencl.CommandQueue(context, device) + + program = pyopencl.Program(context, self.source).build() + + self.hostOutput = numpy.zeros(1 + self.batchSize, numpy.uint32) + self.hostInput = numpy.zeros(1 + 8 + 1, numpy.uint64) + + self.output = pyopencl.Buffer(context, pyopencl.mem_flags.READ_WRITE, 4 * (1 + self.batchSize)) + self.input = pyopencl.Buffer(context, pyopencl.mem_flags.READ_ONLY, 8 * (1 + 8 + 1)) + + self.kernel = program.search + self.kernel.set_args(self.output, self.input) + + self.status = self.batchSize diff --git a/src/workprover/test.py b/src/workprover/test.py new file mode 100755 index 00000000..535f50ce --- /dev/null +++ b/src/workprover/test.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python2.7 + +import binascii +import ctypes +import ctypes.util +import os.path +import struct +import sys +import unittest + +import __init__ +import dumbsolver +import fastsolver +import forkingsolver +import gpusolver +import utils + +codePath = os.path.dirname(__file__) + +if hasattr(sys, "winver"): + dumbsolver.libcrypto = ctypes.WinDLL("libeay32.dll") +else: + dumbsolver.libcrypto = ctypes.CDLL(ctypes.util.find_library("crypto")) + +nonce = binascii.unhexlify("9ca6790a249679f8") +expiryTime = 1525845600 + +headlessPayload = binascii.unhexlify("000000000001") +initialPayload = struct.pack(">Q", expiryTime) + headlessPayload +payload = nonce + initialPayload + +initialHash = binascii.unhexlify( + "1e87a288a10454dea0d3a9b606cc538db1b8b47fe8a21a37b8e57da3db6928eb" + "d854fd22aed3e1849c4a1c596fe0bfec266c05900a862c5b356a6b7e51a4b510" +) + +doubleHash = binascii.unhexlify( + "16cdf04b739412bea1bf58d6c5a53ec92e7d4aab180213405bf10d615354d417" + "00f8b1510d0844a4b7c7b7434e6c115b52fcec5c591e96c31f4b8769ee683552" +) + +TTL = 3600 +byteDifficulty = 1000 +lengthExtension = 1000 + +target = 0x00000f903320b7f6 +difficulty = 1078000 + +seed = binascii.unhexlify("3941c24a1256660a8f65d962954c406dab7bc449317fa087c4a3f1a3ca7d95fd") +timeout = .5 + +class TestUtils(unittest.TestCase): + def testCalculateInitialHash(self): + self.assertEqual(utils.calculateInitialHash(initialPayload), initialHash) + + def testCalculateDoubleHash(self): + self.assertEqual(utils.calculateDoubleHash(payload), doubleHash) + + def testCalculateTarget(self): + self.assertEqual(utils.calculateTarget(1000, 1015, 1000, 1000), (0x00000843bf57fed2, 2030000)) + self.assertEqual(utils.calculateTarget(1000, 1016, 1000, 1000), (0x00000842b4a960c2, 2031000)) + + def testCheckProof(self): + self.assertFalse(utils.checkProof(nonce, initialHash, 0x000002fe91eba355)) + self.assertTrue(utils.checkProof(nonce, initialHash, 0x000002fe91eba356)) + + def testCheckWorkSufficient(self): + self.assertFalse(utils.checkWorkSufficient(payload, byteDifficulty, lengthExtension, expiryTime - 293757.5)) + self.assertTrue(utils.checkWorkSufficient(payload, byteDifficulty, lengthExtension, expiryTime - 293757)) + + originalTime = utils.time.time + + utils.time.time = lambda: expiryTime - 293757.5 + self.assertFalse(utils.checkWorkSufficient(payload, byteDifficulty, lengthExtension)) + + utils.time.time = lambda: expiryTime - 293757 + self.assertTrue(utils.checkWorkSufficient(payload, byteDifficulty, lengthExtension)) + + utils.time.time = originalTime + + def testEstimateMaximumIterationsCount(self): + self.assertEqual(utils.estimateMaximumIterationsCount(4096, .1), 512) + self.assertEqual(utils.estimateMaximumIterationsCount(difficulty, .8), 1735168) + +class TestSolver(unittest.TestCase): + def setUp(self): + try: + self.solver = self.Solver(codePath) + except gpusolver.GPUSolverError: + self.skipTest("OpenCL unavailable") + + self.solver.setConfiguration(self.configuration) + + def testSearch(self): + nonce = None + + i = 0 + + while nonce is None: + appendedSeed = seed + struct.pack(">Q", i) + i += 1 + + nonce, iterationsCount = self.solver.search(initialHash, target, appendedSeed, timeout) + + self.assertTrue(utils.checkProof(nonce, initialHash, target)) + + def tearDown(self): + self.solver.setConfiguration(None) + +class TestDumbSolver(TestSolver): + Solver = dumbsolver.DumbSolver + configuration = None + +class TestForkingSolver(TestSolver): + Solver = forkingsolver.ForkingSolver + configuration = 3 + +class TestFastSolver(TestSolver): + Solver = fastsolver.FastSolver + configuration = 3 + +class TestGPUSolver(TestSolver): + Solver = gpusolver.GPUSolver + configuration = None + +class TestWorkProver(unittest.TestCase): + def setUp(self): + self.thread = __init__.WorkProver(codePath, seed, None, None) + self.thread.start() + + def checkTaskLinks(self): + IDs = set(self.thread.tasks.keys()) + + if len(IDs) == 0: + return + + self.assertIn(self.thread.currentTaskID, IDs) + + linkID = next(iter(IDs)) + + for i in xrange(len(IDs)): + self.assertGreaterEqual(self.thread.totalDifficulty, 0) + + self.assertIn(linkID, IDs) + + IDs.remove(linkID) + + nextLinkID = self.thread.tasks[linkID].next + + self.assertEqual(self.thread.tasks[nextLinkID].previous, linkID) + + linkID = nextLinkID + + def testTasks(self): + self.thread.addTask(0, headlessPayload, TTL, None, byteDifficulty, lengthExtension) + + self.checkTaskLinks() + + self.thread.addTask(1, headlessPayload, TTL, None, byteDifficulty, lengthExtension) + self.thread.addTask(2, headlessPayload, TTL, None, byteDifficulty, lengthExtension) + + self.checkTaskLinks() + + self.thread.cancelTask(self.thread.currentTaskID) + self.thread.nextTask() + self.thread.nextTask() + self.thread.nextTask() + self.thread.addTask(3, headlessPayload, TTL, None, byteDifficulty, lengthExtension) + + self.checkTaskLinks() + + def testSearch(self): + self.thread.commandsQueue.put(( + "addTask", 0, + headlessPayload, TTL, None, byteDifficulty, lengthExtension + )) + + self.thread.commandsQueue.put(( + "addTask", 1, + headlessPayload, TTL, None, byteDifficulty, lengthExtension + )) + + self.thread.commandsQueue.put(( + "addTask", 2, + headlessPayload, TTL * 100, expiryTime, byteDifficulty, lengthExtension + )) + + self.thread.commandsQueue.put(("setSolver", "dumb", 1)) + + for i in xrange(3): + event, ID, nonce, localExpiryTime = self.thread.resultsQueue.get() + + initialPayload = struct.pack(">Q", localExpiryTime) + headlessPayload + initialHash = utils.calculateInitialHash(initialPayload) + + self.assertTrue(utils.checkProof(nonce, initialHash, target)) + + def tearDown(self): + self.thread.commandsQueue.put(("shutdown", )) + self.thread.join() + +def load_tests(loader, tests, pattern): + return unittest.TestSuite([ + loader.loadTestsFromTestCase(TestUtils), + loader.loadTestsFromTestCase(TestDumbSolver), + loader.loadTestsFromTestCase(TestForkingSolver), + loader.loadTestsFromTestCase(TestFastSolver), + loader.loadTestsFromTestCase(TestGPUSolver), + loader.loadTestsFromTestCase(TestWorkProver) + ]) + +if __name__ == "__main__": + import multiprocessing + + multiprocessing.freeze_support() + + loader = unittest.TestLoader() + runner = unittest.TextTestRunner() + + runner.run(load_tests(loader, [], None)) diff --git a/src/workprover/utils.py b/src/workprover/utils.py new file mode 100644 index 00000000..9d3b793f --- /dev/null +++ b/src/workprover/utils.py @@ -0,0 +1,53 @@ +import hashlib +import math +import os +import struct +import sys +import time + +def calculateInitialHash(initialPayload): + return hashlib.sha512(initialPayload).digest() + +def calculateDoubleHash(data): + return hashlib.sha512(hashlib.sha512(data).digest()).digest() + +# Length including nonce + +def calculateTarget(length, TTL, byteDifficulty, lengthExtension): + adjustedLength = length + lengthExtension + timeEquivalent = TTL * adjustedLength / 2 ** 16 + + difficulty = byteDifficulty * (adjustedLength + timeEquivalent) + + return 2 ** 64 / difficulty, difficulty + +def checkProof(nonce, initialHash, target): + proof = nonce + initialHash + trial, = struct.unpack(">Q", calculateDoubleHash(proof)[: 8]) + + return trial <= target + +def checkWorkSufficient(payload, byteDifficulty, lengthExtension, receivedTime = None): + if receivedTime is None: + receivedTime = int(time.time()) + + expiryTime, = struct.unpack(">Q", payload[8: 16]) + minimumTTL = max(300, expiryTime - receivedTime) + + nonce = payload[: 8] + initialHash = calculateInitialHash(payload[8: ]) + + target, difficulty = calculateTarget(len(payload), minimumTTL, byteDifficulty, lengthExtension) + + return checkProof(nonce, initialHash, target) + +def estimateMaximumIterationsCount(difficulty, probability): + coefficient = -math.log(1 - probability) + + return int(coefficient * difficulty + 255) / 256 * 256 + +if hasattr(sys, "winver"): + getTimePoint = time.clock +else: + def getTimePoint(): + return os.times()[4]