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 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 @@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]