diff --git a/setup.cfg b/setup.cfg
index 28ceaede..c735e0a8 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -20,6 +20,8 @@ ignore = E722,F841,W503
 disable=invalid-name,bare-except,broad-except
 # invalid-name: needs fixing during a large, project-wide refactor
 # bare-except,broad-except: Need fixing once thorough testing is easier
+max-args = 8
+max-attributes = 8
 
 [MASTER]
 init-hook = import sys;sys.path.append('src')
diff --git a/src/api.py b/src/api.py
index afd21779..9f61f40b 100644
--- a/src/api.py
+++ b/src/api.py
@@ -1410,15 +1410,10 @@ class BMRPCDispatcher(object):
                 / networkDefaultPayloadLengthExtraBytes,
             )
             powStartTime = time.time()
-            target = 2**64 / (
-                nonceTrialsPerByte * (
-                    len(encryptedPayload) + 8 + payloadLengthExtraBytes + ((
-                        TTL * (
-                            len(encryptedPayload) + 8 + payloadLengthExtraBytes
-                        )) / (2 ** 16))
-                ))
-            initialHash = hashlib.sha512(encryptedPayload).digest()
-            trialValue, nonce = proofofwork.run(target, initialHash)
+            trialValue, nonce = proofofwork.calculate(
+                encryptedPayload, TTL,
+                nonceTrialsPerByte, payloadLengthExtraBytes
+            )
             logger.info(
                 '(For msg message via API) Found proof of work %s\nNonce: %s\n'
                 'POW took %s seconds. %s nonce trials per second.',
@@ -1429,9 +1424,7 @@ class BMRPCDispatcher(object):
 
         inventoryHash = calculateInventoryHash(encryptedPayload)
         state.Inventory[inventoryHash] = (
-            objectType, toStreamNumber, encryptedPayload,
-            expiresTime, b''
-        )
+            objectType, toStreamNumber, encryptedPayload, expiresTime, b'')
         logger.info(
             'Broadcasting inv for msg(API disseminatePreEncryptedMsg'
             ' command): %s', hexlify(inventoryHash))
diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py
index f79d9240..83009c37 100644
--- a/src/class_singleWorker.py
+++ b/src/class_singleWorker.py
@@ -217,36 +217,36 @@ class singleWorker(StoppableThread):
         return privSigningKeyHex, privEncryptionKeyHex, \
             pubSigningKey, pubEncryptionKey
 
-    def _doPOWDefaults(self, payload, TTL,
-                       log_prefix='',
-                       log_time=False):
-        target = 2 ** 64 / (
-            defaults.networkDefaultProofOfWorkNonceTrialsPerByte * (
-                len(payload) + 8
-                + defaults.networkDefaultPayloadLengthExtraBytes + ((
-                    TTL * (
-                        len(payload) + 8
-                        + defaults.networkDefaultPayloadLengthExtraBytes
-                    )) / (2 ** 16))
-            ))
-        initialHash = hashlib.sha512(payload).digest()
-        self.logger.info(
+    @classmethod
+    def _doPOWDefaults(
+        cls, payload, TTL,
+        nonceTrialsPerByte=None, payloadLengthExtraBytes=None,
+        log_prefix='', log_time=False
+    ):
+        if not nonceTrialsPerByte:
+            nonceTrialsPerByte = \
+                defaults.networkDefaultProofOfWorkNonceTrialsPerByte
+        if not payloadLengthExtraBytes:
+            payloadLengthExtraBytes = \
+                defaults.networkDefaultPayloadLengthExtraBytes
+        cls.logger.info(
             '%s Doing proof of work... TTL set to %s', log_prefix, TTL)
         if log_time:
             start_time = time.time()
-        trialValue, nonce = proofofwork.run(target, initialHash)
-        self.logger.info(
+        trialValue, nonce = proofofwork.calculate(
+            payload, TTL, nonceTrialsPerByte, payloadLengthExtraBytes)
+        cls.logger.info(
             '%s Found proof of work %s Nonce: %s',
             log_prefix, trialValue, nonce
         )
         try:
             delta = time.time() - start_time
-            self.logger.info(
+            cls.logger.info(
                 'PoW took %.1f seconds, speed %s.',
                 delta, sizeof_fmt(nonce / delta)
             )
-        except:  # noqa:E722  # NameError
-            self.logger.warning("Proof of Work exception")
+        except NameError:  # no start_time - no logging
+            pass
         payload = pack('>Q', nonce) + payload
         return payload
 
@@ -1250,41 +1250,13 @@ class singleWorker(StoppableThread):
             encryptedPayload += '\x00\x00\x00\x02'  # object type: msg
             encryptedPayload += encodeVarint(1)  # msg version
             encryptedPayload += encodeVarint(toStreamNumber) + encrypted
-            target = 2 ** 64 / (
-                requiredAverageProofOfWorkNonceTrialsPerByte * (
-                    len(encryptedPayload) + 8
-                    + requiredPayloadLengthExtraBytes + ((
-                        TTL * (
-                            len(encryptedPayload) + 8
-                            + requiredPayloadLengthExtraBytes
-                        )) / (2 ** 16))
-                ))
-            self.logger.info(
-                '(For msg message) Doing proof of work. Total required'
-                ' difficulty: %f. Required small message difficulty: %f.',
-                float(requiredAverageProofOfWorkNonceTrialsPerByte)
-                / defaults.networkDefaultProofOfWorkNonceTrialsPerByte,
-                float(requiredPayloadLengthExtraBytes)
-                / defaults.networkDefaultPayloadLengthExtraBytes
-            )
 
-            powStartTime = time.time()
-            initialHash = hashlib.sha512(encryptedPayload).digest()
-            trialValue, nonce = proofofwork.run(target, initialHash)
-            self.logger.info(
-                '(For msg message) Found proof of work %s Nonce: %s',
-                trialValue, nonce
+            encryptedPayload = self._doPOWDefaults(
+                encryptedPayload, TTL,
+                requiredAverageProofOfWorkNonceTrialsPerByte,
+                requiredPayloadLengthExtraBytes,
+                log_prefix='(For msg message)', log_time=True
             )
-            try:
-                self.logger.info(
-                    'PoW took %.1f seconds, speed %s.',
-                    time.time() - powStartTime,
-                    sizeof_fmt(nonce / (time.time() - powStartTime))
-                )
-            except:  # noqa:E722
-                self.logger.warning("Proof of Work exception")
-
-            encryptedPayload = pack('>Q', nonce) + encryptedPayload
 
             # Sanity check. The encryptedPayload size should never be
             # larger than 256 KiB. There should be checks elsewhere
diff --git a/src/proofofwork.py b/src/proofofwork.py
index 5e157db9..539db710 100644
--- a/src/proofofwork.py
+++ b/src/proofofwork.py
@@ -1,9 +1,10 @@
-# pylint: disable=too-many-branches,too-many-statements,protected-access
 """
 Proof of work calculation
 """
+# pylint: disable=import-outside-toplevel
 
 import ctypes
+import hashlib
 import os
 import subprocess  # nosec B404
 import sys
@@ -16,9 +17,13 @@ import openclpow
 import paths
 import queues
 import state
-import tr
 from bmconfigparser import config
 from debug import logger
+from defaults import (
+    networkDefaultProofOfWorkNonceTrialsPerByte,
+    networkDefaultPayloadLengthExtraBytes)
+from tr import _translate
+
 
 bitmsglib = 'bitmsghash.so'
 bmpow = None
@@ -79,10 +84,13 @@ def _set_idle():
             import win32api
             import win32process
             import win32con
-            pid = win32api.GetCurrentProcessId()
-            handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, True, pid)
-            win32process.SetPriorityClass(handle, win32process.IDLE_PRIORITY_CLASS)
-        except:  # nosec B110 # noqa:E722 # pylint:disable=bare-except
+
+            handle = win32api.OpenProcess(
+                win32con.PROCESS_ALL_ACCESS, True,
+                win32api.GetCurrentProcessId())
+            win32process.SetPriorityClass(
+                handle, win32process.IDLE_PRIORITY_CLASS)
+        except:  # nosec B110 # noqa:E722 pylint:disable=bare-except
             # Windows 64-bit
             pass
 
@@ -101,11 +109,11 @@ def _pool_worker(nonce, initialHash, target, pool_size):
     while trialValue > target:
         nonce += pool_size
         trialValue = trial_value(nonce, initialHash)
-    return [trialValue, nonce]
+    return trialValue, nonce
 
 
 def _doSafePoW(target, initialHash):
-    logger.debug("Safe PoW start")
+    logger.debug('Safe PoW start')
     nonce = 0
     trialValue = float('inf')
     while trialValue > target and state.shutdown == 0:
@@ -113,35 +121,33 @@ def _doSafePoW(target, initialHash):
         trialValue = trial_value(nonce, initialHash)
     if state.shutdown != 0:
         raise StopIteration("Interrupted")
-    logger.debug("Safe PoW done")
-    return [trialValue, nonce]
+    logger.debug('Safe PoW done')
+    return trialValue, nonce
 
 
 def _doFastPoW(target, initialHash):
-    logger.debug("Fast PoW start")
+    # pylint:disable=bare-except
+    logger.debug('Fast PoW start')
     from multiprocessing import Pool, cpu_count
     try:
         pool_size = cpu_count()
     except:  # noqa:E722
         pool_size = 4
-    try:
-        maxCores = config.getint('bitmessagesettings', 'maxcores')
-    except:  # noqa:E722
-        maxCores = 99999
-    if pool_size > maxCores:
-        pool_size = maxCores
+    maxCores = config.safeGetInt('bitmessagesettings', 'maxcores', 99999)
+    pool_size = min(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)))
+        result.append(pool.apply_async(
+            _pool_worker, args=(i, initialHash, target, pool_size)))
 
     while True:
-        if state.shutdown > 0:
+        if state.shutdown != 0:
             try:
                 pool.terminate()
                 pool.join()
-            except:  # nosec B110 # noqa:E722 # pylint:disable=bare-except
+            except:  # nosec B110 # noqa:E722
                 pass
             raise StopIteration("Interrupted")
         for i in range(pool_size):
@@ -155,7 +161,7 @@ def _doFastPoW(target, initialHash):
                 result = result[i].get()
                 pool.terminate()
                 pool.join()
-                logger.debug("Fast PoW done")
+                logger.debug('Fast PoW done')
                 return result[0], result[1]
         time.sleep(0.2)
 
@@ -166,70 +172,67 @@ def _doCPoW(target, initialHash):
         m = target
         out_h = ctypes.pointer(ctypes.create_string_buffer(h, 64))
         out_m = ctypes.c_ulonglong(m)
-        logger.debug("C PoW start")
+        logger.debug('C PoW start')
         nonce = bmpow(out_h, out_m)
 
     trialValue = trial_value(nonce, initialHash)
     if state.shutdown != 0:
         raise StopIteration("Interrupted")
-    logger.debug("C PoW done")
-    return [trialValue, nonce]
+    logger.debug('C PoW done')
+    return trialValue, nonce
 
 
 def _doGPUPoW(target, initialHash):
-    logger.debug("GPU PoW start")
+    logger.debug('GPU PoW start')
     nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target)
     trialValue = trial_value(nonce, initialHash)
     if trialValue > target:
         deviceNames = ", ".join(gpu.name for gpu in openclpow.enabledGpus)
         queues.UISignalQueue.put((
             'updateStatusBar', (
-                tr._translate(
+                _translate(
                     "MainWindow",
-                    'Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.'
-                ),
-                1)))
+                    "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)
+            '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]
+    logger.debug('GPU PoW done')
+    return trialValue, nonce
 
 
-def estimate(difficulty, format=False):  # pylint: disable=redefined-builtin
-    """
-    .. todo: fix unused variable
-    """
-    ret = difficulty / 10
-    if ret < 1:
-        ret = 1
-
-    if format:
-        # pylint: disable=unused-variable
-        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"
-        ret = None  # Ensure legacy behaviour
-
-    return ret
+# def estimate(difficulty, fmt=False):
+#     ret = difficulty / 10
+#     if ret < 1:
+#         ret = 1
+#
+#     if fmt:
+#         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"
+#         ret = None  # Ensure legacy behaviour
+#
+#     return ret
 
 
 def getPowType():
@@ -243,25 +246,19 @@ def getPowType():
 
 
 def notifyBuild(tried=False):
-    """Notify the user of the success or otherwise of building the PoW C module"""
+    """
+    Notify the user of the success or otherwise of building the PoW C module
+    """
 
     if bmpow:
-        queues.UISignalQueue.put(('updateStatusBar', (tr._translate(
+        queues.UISignalQueue.put(('updateStatusBar', (_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
-                )
-            )
-        )
+        queues.UISignalQueue.put(('updateStatusBar', (_translate(
+            "proofofwork",
+            "Failed to build C PoW module. Please build it manually."), 1)))
     else:
-        queues.UISignalQueue.put(('updateStatusBar', (tr._translate(
+        queues.UISignalQueue.put(('updateStatusBar', (_translate(
             "proofofwork", "C PoW module unavailable. Please build it."), 1)))
 
 
@@ -269,76 +266,71 @@ def buildCPoW():
     """Attempt to build the PoW C module"""
     if bmpow is not None:
         return
-    if paths.frozen is not None:
-        notifyBuild(False)
-        return
-    if sys.platform in ["win32", "win64"]:
+    if paths.frozen or sys.platform.startswith('win'):
         notifyBuild(False)
         return
+
     try:
+        # GNU make
+        make_cmd = ['make', '-C', os.path.join(paths.codePath(), 'bitmsghash')]
         if "bsd" in sys.platform:
             # BSD make
-            subprocess.check_call([  # nosec B607, B603
-                "make", "-C", os.path.join(paths.codePath(), "bitmsghash"),
-                '-f', 'Makefile.bsd'])
-        else:
-            # GNU make
-            subprocess.check_call([  # nosec B607, B603
-                "make", "-C", os.path.join(paths.codePath(), "bitmsghash")])
+            make_cmd += ['-f', 'Makefile.bsd']
+
+        subprocess.check_call(make_cmd)  # nosec B603
         if os.path.exists(
-            os.path.join(paths.codePath(), "bitmsghash", "bitmsghash.so")
+            os.path.join(paths.codePath(), 'bitmsghash', 'bitmsghash.so')
         ):
             init()
-            notifyBuild(True)
-        else:
-            notifyBuild(True)
     except (OSError, subprocess.CalledProcessError):
-        notifyBuild(True)
+        pass
     except:  # noqa:E722
         logger.warning(
             'Unexpected exception rised when tried to build bitmsghash lib',
             exc_info=True)
-        notifyBuild(True)
+    notifyBuild(True)
 
 
 def run(target, initialHash):
-    """Run the proof of work thread"""
+    """Run the proof of work calculation"""
 
     if state.shutdown != 0:
-        raise  # pylint: disable=misplaced-bare-raise
+        raise StopIteration("Interrupted")
     target = int(target)
     if openclpow.openclEnabled():
-        try:
-            return _doGPUPoW(target, initialHash)
-        except StopIteration:
-            raise
-        except:  # nosec B110 # noqa:E722 # pylint:disable=bare-except
-            pass  # fallback
+        return _doGPUPoW(target, initialHash)
     if bmpow:
-        try:
-            return _doCPoW(target, initialHash)
-        except StopIteration:
-            raise
-        except:  # nosec B110 # noqa:E722 # pylint:disable=bare-except
-            pass  # fallback
+        return _doCPoW(target, initialHash)
     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:  # noqa:E722 # pylint:disable=bare-except
-            logger.error("Fast PoW got exception:", exc_info=True)
-    try:
-        return _doSafePoW(target, initialHash)
-    except StopIteration:
-        raise
-    except:  # nosec B110 # noqa:E722 # pylint:disable=bare-except
-        pass  # fallback
+        return _doFastPoW(target, initialHash)
+
+    return _doSafePoW(target, initialHash)
+
+
+def getTarget(payloadLength, ttl, nonceTrialsPerByte, payloadLengthExtraBytes):
+    """Get PoW target for given length, ttl and difficulty params"""
+    return 2 ** 64 / (
+        nonceTrialsPerByte * (
+            payloadLength + 8 + payloadLengthExtraBytes + ((
+                ttl * (
+                    payloadLength + 8 + payloadLengthExtraBytes
+                )) / (2 ** 16))
+        ))
+
+
+def calculate(
+    payload, ttl,
+    nonceTrialsPerByte=networkDefaultProofOfWorkNonceTrialsPerByte,
+    payloadLengthExtraBytes=networkDefaultPayloadLengthExtraBytes
+):
+    """Do the PoW for the payload and TTL with optional difficulty params"""
+    return run(getTarget(
+        len(payload), ttl, nonceTrialsPerByte, payloadLengthExtraBytes),
+        hashlib.sha512(payload).digest())
 
 
 def resetPoW():
@@ -351,60 +343,63 @@ def resetPoW():
 
 def init():
     """Initialise PoW"""
-    # pylint: disable=global-statement
+    # pylint: disable=broad-exception-caught,global-statement
     global bitmsglib, bmpow
 
     openclpow.initCL()
-    if sys.platform == "win32":
-        if ctypes.sizeof(ctypes.c_voidp) == 4:
-            bitmsglib = 'bitmsghash32.dll'
-        else:
-            bitmsglib = 'bitmsghash64.dll'
+    if sys.platform.startswith('win'):
+        bitmsglib = (
+            'bitmsghash32.dll' if ctypes.sizeof(ctypes.c_voidp) == 4 else
+            'bitmsghash64.dll')
+        libfile = os.path.join(paths.codePath(), 'bitmsghash', bitmsglib)
         try:
             # MSVS
-            bso = ctypes.WinDLL(os.path.join(paths.codePath(), "bitmsghash", bitmsglib))
-            logger.info("Loaded C PoW DLL (stdcall) %s", bitmsglib)
+            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)
+            logger.info(
+                'Successfully tested C PoW DLL (stdcall) %s', bitmsglib)
         except ValueError:
             try:
                 # MinGW
-                bso = ctypes.CDLL(os.path.join(paths.codePath(), "bitmsghash", bitmsglib))
-                logger.info("Loaded C PoW DLL (cdecl) %s", bitmsglib)
+                bso = ctypes.CDLL(libfile)
+                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)
+                logger.info(
+                    'Successfully tested C PoW DLL (cdecl) %s', bitmsglib)
             except Exception as e:
-                logger.error("Error: %s", e, exc_info=True)
-                bso = None
+                logger.error('Error: %s', e, exc_info=True)
         except Exception as e:
-            logger.error("Error: %s", e, exc_info=True)
-            bso = None
+            logger.error('Error: %s', e, exc_info=True)
     else:
         try:
-            bso = ctypes.CDLL(os.path.join(paths.codePath(), "bitmsghash", bitmsglib))
+            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"
+                    paths.codePath(), 'bitmsghash', 'bitmsghash*.so'
                 ))[0])
             except (OSError, IndexError):
                 bso = None
-        except:  # noqa:E722
+        except Exception:
             bso = None
         else:
-            logger.info("Loaded C PoW DLL %s", bitmsglib)
-    if bso:
-        try:
-            bmpow = bso.BitmessagePOW
-            bmpow.restype = ctypes.c_ulonglong
-        except:  # noqa:E722
-            bmpow = None
-    else:
-        bmpow = None
+            logger.info('Loaded C PoW DLL %s', bitmsglib)
+        if bso:
+            try:
+                bmpow = bso.BitmessagePOW
+                bmpow.restype = ctypes.c_ulonglong
+            except Exception:
+                logger.warning(
+                    'Failed to setup bmpow lib %s', bso, exc_info=True)
+                return
+
     if bmpow is None:
         buildCPoW()
diff --git a/src/tests/partial.py b/src/tests/partial.py
index 870f6626..f97dc414 100644
--- a/src/tests/partial.py
+++ b/src/tests/partial.py
@@ -2,6 +2,7 @@
 
 import os
 import sys
+import time
 import unittest
 
 from pybitmessage import pathmagic
@@ -39,3 +40,5 @@ class TestPartialRun(unittest.TestCase):
         # deactivate pathmagic
         os.chdir(cls.dirs[0])
         sys.path.remove(cls.dirs[1])
+        time.sleep(5)
+        cls.state.shutdown = 0
diff --git a/src/tests/samples.py b/src/tests/samples.py
index dd862318..55debea3 100644
--- a/src/tests/samples.py
+++ b/src/tests/samples.py
@@ -91,3 +91,12 @@ sample_privsigningkey_wif = \
     b'5K42shDERM5g7Kbi3JT5vsAWpXMqRhWZpX835M2pdSoqQQpJMYm'
 sample_privencryptionkey_wif = \
     b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHqpA'
+
+
+# PoW
+
+sample_pow_target = 54227212183
+sample_pow_initial_hash = unhexlify(
+    '3758f55b5a8d902fd3597e4ce6a2d3f23daff735f65d9698c270987f4e67ad590'
+    'b93f3ffeba0ef2fd08a8dc2f87b68ae5a0dc819ab57f22ad2c4c9c8618a43b3'
+)
diff --git a/src/tests/test_openclpow.py b/src/tests/test_openclpow.py
index 4770072e..d9ccbe2e 100644
--- a/src/tests/test_openclpow.py
+++ b/src/tests/test_openclpow.py
@@ -3,9 +3,12 @@ Tests for openclpow module
 """
 
 import unittest
+from binascii import hexlify
 
 from pybitmessage import openclpow, proofofwork
 
+from .samples import sample_pow_target, sample_pow_initial_hash
+
 
 class TestOpenClPow(unittest.TestCase):
     """
@@ -19,11 +22,8 @@ class TestOpenClPow(unittest.TestCase):
     @unittest.skipUnless(openclpow.enabledGpus, "No GPUs found / enabled")
     def test_openclpow(self):
         """Check the working of openclpow module"""
-        target_ = 54227212183
-        initialHash = (
-            "3758f55b5a8d902fd3597e4ce6a2d3f23daff735f65d9698c270987f4e67ad590"
-            "b93f3ffeba0ef2fd08a8dc2f87b68ae5a0dc819ab57f22ad2c4c9c8618a43b3"
-        ).decode("hex")
-        nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target_)
+        nonce = openclpow.do_opencl_pow(
+            hexlify(sample_pow_initial_hash), sample_pow_target)
         self.assertLess(
-            nonce - proofofwork.trial_value(nonce, initialHash), target_)
+            nonce - proofofwork.trial_value(nonce, sample_pow_initial_hash),
+            sample_pow_target)
diff --git a/src/tests/test_proofofwork.py b/src/tests/test_proofofwork.py
new file mode 100644
index 00000000..16ff649d
--- /dev/null
+++ b/src/tests/test_proofofwork.py
@@ -0,0 +1,120 @@
+"""
+Tests for proofofwork module
+"""
+# pylint: disable=protected-access
+
+import hashlib
+import os
+import time
+import unittest
+from struct import pack, unpack
+
+from pybitmessage import proofofwork, protocol
+from pybitmessage.defaults import (
+    networkDefaultProofOfWorkNonceTrialsPerByte,
+    networkDefaultPayloadLengthExtraBytes)
+
+from .partial import TestPartialRun
+from .samples import sample_pow_target, sample_pow_initial_hash
+
+default_ttl = 7200
+
+
+class TestProofofworkBase(TestPartialRun):
+    """Basic test case for proofofwork"""
+
+    @classmethod
+    def setUpClass(cls):
+        proofofwork.init()
+        super(TestProofofworkBase, cls).setUpClass()
+
+    def setUp(self):
+        self.state.shutdown = 0
+
+    @staticmethod
+    def _make_sample_payload(TTL=default_ttl):
+        return pack('>Q', int(time.time() + TTL)) + os.urandom(166)
+
+    def test_calculate(self):
+        """Ensure a calculated nonce has sufficient work for the protocol"""
+        payload = self._make_sample_payload()
+        nonce = proofofwork.calculate(payload, default_ttl)[1]
+        self.assertTrue(
+            protocol.isProofOfWorkSufficient(pack('>Q', nonce) + payload))
+
+        # pylint: disable=import-outside-toplevel
+        from class_singleWorker import singleWorker
+
+        self.assertTrue(protocol.isProofOfWorkSufficient(
+            singleWorker._doPOWDefaults(payload, default_ttl)))
+
+
+@unittest.skipUnless(
+    os.getenv('BITMESSAGE_TEST_POW'), "BITMESSAGE_TEST_POW is not set")
+class TestProofofwork(TestProofofworkBase):
+    """The main test case for proofofwork"""
+
+    def _make_sample_data(self):
+        payload = self._make_sample_payload()
+        return payload, proofofwork.getTarget(
+            len(payload), default_ttl,
+            networkDefaultProofOfWorkNonceTrialsPerByte,
+            networkDefaultPayloadLengthExtraBytes
+        ), hashlib.sha512(payload).digest()
+
+    def test_calculate(self):
+        """Extended test for the main proofofwork call"""
+        # raise difficulty and TTL
+        TTL = 24 * 60 * 60
+        payload = self._make_sample_payload(TTL)
+        nonce = proofofwork.calculate(payload, TTL, 2000, 2000)[1]
+        self.assertTrue(
+            protocol.isProofOfWorkSufficient(
+                pack('>Q', nonce) + payload, 2000, 2000,
+                int(time.time()) + TTL - 3600))
+
+        # pylint: disable=import-outside-toplevel
+        from class_singleWorker import singleWorker
+
+        # pylint: disable=no-member
+        with self.assertLogs('default') as cm:
+            self.assertTrue(protocol.isProofOfWorkSufficient(
+                singleWorker._doPOWDefaults(payload, TTL, log_prefix='+')))
+        self.assertEqual(
+            cm.output[0],
+            'INFO:default:+ Doing proof of work... TTL set to %s' % TTL)
+        self.assertEqual(
+            cm.output[1][:34], 'INFO:default:+ Found proof of work')
+
+        with self.assertLogs('default') as cm:
+            self.assertTrue(protocol.isProofOfWorkSufficient(
+                singleWorker._doPOWDefaults(payload, TTL, log_time=True)))
+        self.assertEqual(cm.output[2][:22], 'INFO:default:PoW took ')
+
+        with self.assertRaises(StopIteration):
+            self.state.shutdown = 1
+            proofofwork.calculate(payload, TTL)
+
+    def test_CPoW(self):
+        """Do PoW with parameters from test_openclpow and check the result"""
+        nonce = proofofwork._doCPoW(
+            sample_pow_target, sample_pow_initial_hash)[0]
+        trial_value, = unpack(
+            '>Q', hashlib.sha512(hashlib.sha512(
+                pack('>Q', nonce) + sample_pow_initial_hash
+            ).digest()).digest()[0:8])
+        self.assertLess((nonce - trial_value), sample_pow_target)
+
+    def test_SafePoW(self):
+        """Do python PoW for a sample payload and check by protocol"""
+        payload, target, initial_hash = self._make_sample_data()
+        nonce = proofofwork._doSafePoW(target, initial_hash)[1]
+        self.assertTrue(
+            protocol.isProofOfWorkSufficient(pack('>Q', nonce) + payload))
+
+    def test_FastPoW(self):
+        """Do python multiprocessing PoW for a sample payload and check"""
+        payload, target, initial_hash = self._make_sample_data()
+        nonce = proofofwork._doFastPoW(target, initial_hash)[1]
+        self.assertTrue(
+            protocol.isProofOfWorkSufficient(pack('>Q', nonce) + payload))
diff --git a/tox.ini b/tox.ini
index 3524bb57..2acf7a86 100644
--- a/tox.ini
+++ b/tox.ini
@@ -50,6 +50,11 @@ commands = python pybitmessage/bitmessagemain.py -t
 [testenv:py35]
 skip_install = true
 
+[testenv:py36]
+setenv =
+    BITMESSAGE_TEST_POW = true
+    {[testenv]setenv}
+
 [testenv:reset]
 skip_install = true
 deps = coverage