This repository has been archived on 2025-02-28. You can view files and clone it, but cannot push or open issues or pull requests.
PyBitmessage-2025-02-28/src/proofofwork.py

406 lines
12 KiB
Python
Raw Normal View History

"""
2019-12-23 15:19:03 +05:30
Proof of work calculation
"""
2023-10-02 02:11:06 +03:00
# pylint: disable=import-outside-toplevel
import ctypes
2023-10-02 02:11:06 +03:00
import hashlib
import os
import subprocess # nosec B404
import sys
import tempfile
import time
from struct import pack, unpack
2021-07-29 22:16:37 +03:00
import highlevelcrypto
import openclpow
import paths
import queues
import state
from bmconfigparser import config
from debug import logger
from defaults import (
networkDefaultProofOfWorkNonceTrialsPerByte,
networkDefaultPayloadLengthExtraBytes)
2023-10-02 02:11:06 +03:00
from tr import _translate
bitmsglib = 'bitmsghash.so'
2017-08-15 14:09:19 +02:00
bmpow = None
class LogOutput(object): # pylint: disable=too-few-public-methods
"""
A context manager that block stdout for its scope
and appends it's content to log before exit. Usage::
with LogOutput():
os.system('ls -l')
https://stackoverflow.com/questions/5081657
"""
def __init__(self, prefix='PoW'):
self.prefix = prefix
try:
sys.stdout.flush()
self._stdout = sys.stdout
self._stdout_fno = os.dup(sys.stdout.fileno())
except AttributeError:
# NullWriter instance has no attribute 'fileno' on Windows
self._stdout = None
else:
self._dst, self._filepath = tempfile.mkstemp()
def __enter__(self):
if not self._stdout:
return
stdout = os.dup(1)
os.dup2(self._dst, 1)
os.close(self._dst)
sys.stdout = os.fdopen(stdout, 'w')
def __exit__(self, exc_type, exc_val, exc_tb):
if not self._stdout:
return
sys.stdout.close()
sys.stdout = self._stdout
sys.stdout.flush()
os.dup2(self._stdout_fno, 1)
with open(self._filepath) as out:
for line in out:
logger.info('%s: %s', self.prefix, line)
os.remove(self._filepath)
2013-06-03 01:04:22 -04:00
def _set_idle():
if 'linux' in sys.platform:
os.nice(20)
else:
2013-06-18 12:56:03 -04:00
try:
# pylint: disable=no-member,import-error
2013-06-18 12:56:03 -04:00
sys.getwindowsversion()
import win32api
import win32process
import win32con
2023-10-02 02:11:06 +03:00
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
2013-06-18 12:56:03 -04:00
pass
2013-05-30 16:25:42 -04:00
2021-07-29 22:16:37 +03:00
def trial_value(nonce, initialHash):
"""Calculate PoW trial value"""
trialValue, = unpack(
'>Q', highlevelcrypto.double_sha512(
pack('>Q', nonce) + initialHash)[0:8])
return trialValue
def _pool_worker(nonce, initialHash, target, pool_size):
2013-06-03 01:04:22 -04:00
_set_idle()
trialValue = float('inf')
while trialValue > target:
2013-05-30 16:25:42 -04:00
nonce += pool_size
2021-07-29 22:16:37 +03:00
trialValue = trial_value(nonce, initialHash)
2023-10-02 02:11:06 +03:00
return trialValue, nonce
def _doSafePoW(target, initialHash):
2023-10-02 02:11:06 +03:00
logger.debug('Safe PoW start')
2013-06-03 23:14:24 -04:00
nonce = 0
trialValue = float('inf')
while trialValue > target and state.shutdown == 0:
2013-06-03 23:14:24 -04:00
nonce += 1
2021-07-29 22:16:37 +03:00
trialValue = trial_value(nonce, initialHash)
if state.shutdown != 0:
2021-07-29 22:16:37 +03:00
raise StopIteration("Interrupted")
2023-10-02 02:11:06 +03:00
logger.debug('Safe PoW done')
return trialValue, nonce
def _doFastPoW(target, initialHash):
2023-10-02 02:11:06 +03:00
# pylint:disable=bare-except
logger.debug('Fast PoW start')
from multiprocessing import Pool, cpu_count
try:
2013-05-30 16:25:42 -04:00
pool_size = cpu_count()
except: # noqa:E722
2013-05-30 16:25:42 -04:00
pool_size = 4
2023-10-02 02:11:06 +03:00
maxCores = config.safeGetInt('bitmessagesettings', 'maxcores', 99999)
pool_size = min(pool_size, maxCores)
pool = Pool(processes=pool_size)
result = []
for i in range(pool_size):
2023-10-02 02:11:06 +03:00
result.append(pool.apply_async(
_pool_worker, args=(i, initialHash, target, pool_size)))
while True:
2023-10-02 02:11:06 +03:00
if state.shutdown != 0:
try:
pool.terminate()
pool.join()
2023-10-02 02:11:06 +03:00
except: # nosec B110 # noqa:E722
pass
raise StopIteration("Interrupted")
2013-05-30 16:25:42 -04:00
for i in range(pool_size):
if result[i].ready():
try:
result[i].successful()
except AssertionError:
pool.terminate()
pool.join()
raise StopIteration("Interrupted")
2013-05-30 16:25:42 -04:00
result = result[i].get()
pool.terminate()
pool.join()
2023-10-02 02:11:06 +03:00
logger.debug('Fast PoW done')
2013-05-30 16:25:42 -04:00
return result[0], result[1]
time.sleep(0.2)
def _doCPoW(target, initialHash):
with LogOutput():
h = initialHash
m = target
out_h = ctypes.pointer(ctypes.create_string_buffer(h, 64))
out_m = ctypes.c_ulonglong(m)
2023-10-02 02:11:06 +03:00
logger.debug('C PoW start')
nonce = bmpow(out_h, out_m)
2021-07-29 22:16:37 +03:00
trialValue = trial_value(nonce, initialHash)
if state.shutdown != 0:
raise StopIteration("Interrupted")
2023-10-02 02:11:06 +03:00
logger.debug('C PoW done')
return trialValue, nonce
def _doGPUPoW(target, initialHash):
2023-10-02 02:11:06 +03:00
logger.debug('GPU PoW start')
nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target)
2021-07-29 22:16:37 +03:00
trialValue = trial_value(nonce, initialHash)
if trialValue > target:
deviceNames = ", ".join(gpu.name for gpu in openclpow.enabledGpus)
queues.UISignalQueue.put((
'updateStatusBar', (
2023-10-02 02:11:06 +03:00
_translate(
"MainWindow",
2023-10-02 02:11:06 +03:00
"Your GPU(s) did not calculate correctly,"
" disabling OpenCL. Please report to the developers."
), 1)
))
logger.error(
2023-10-02 02:11:06 +03:00
'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")
2023-10-02 02:11:06 +03:00
logger.debug('GPU PoW done')
return trialValue, nonce
# 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():
"""Get the proof of work implementation"""
if openclpow.openclEnabled():
return "OpenCL"
if bmpow:
return "C"
return "python"
def notifyBuild(tried=False):
2023-10-02 02:11:06 +03:00
"""
Notify the user of the success or otherwise of building the PoW C module
"""
if bmpow:
2023-10-02 02:11:06 +03:00
queues.UISignalQueue.put(('updateStatusBar', (_translate(
"proofofwork", "C PoW module built successfully."), 1)))
elif tried:
2023-10-02 02:11:06 +03:00
queues.UISignalQueue.put(('updateStatusBar', (_translate(
"proofofwork",
"Failed to build C PoW module. Please build it manually."), 1)))
else:
2023-10-02 02:11:06 +03:00
queues.UISignalQueue.put(('updateStatusBar', (_translate(
"proofofwork", "C PoW module unavailable. Please build it."), 1)))
def buildCPoW():
"""Attempt to build the PoW C module"""
if bmpow is not None:
return
2023-10-02 02:11:06 +03:00
if paths.frozen or sys.platform.startswith('win'):
notifyBuild(False)
return
2023-10-02 02:11:06 +03:00
try:
2023-10-02 02:11:06 +03:00
# GNU make
make_cmd = ['make', '-C', os.path.join(paths.codePath(), 'bitmsghash')]
if "bsd" in sys.platform:
# BSD make
2023-10-02 02:11:06 +03:00
make_cmd += ['-f', 'Makefile.bsd']
subprocess.check_call(make_cmd) # nosec B603
if os.path.exists(
2023-10-02 02:11:06 +03:00
os.path.join(paths.codePath(), 'bitmsghash', 'bitmsghash.so')
):
init()
except (OSError, subprocess.CalledProcessError):
2023-10-02 02:11:06 +03:00
pass
except: # noqa:E722
logger.warning(
'Unexpected exception rised when tried to build bitmsghash lib',
exc_info=True)
2023-10-02 02:11:06 +03:00
notifyBuild(True)
def run(target, initialHash):
2023-10-02 02:11:06 +03:00
"""Run the proof of work calculation"""
if state.shutdown != 0:
2023-10-02 02:11:06 +03:00
raise StopIteration("Interrupted")
2015-09-30 10:22:41 +02:00
target = int(target)
if openclpow.openclEnabled():
2023-10-02 02:11:06 +03:00
return _doGPUPoW(target, initialHash)
if bmpow:
2023-10-02 02:11:06 +03:00
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
2023-10-02 02:11:06 +03:00
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():
"""Initialise the OpenCL PoW"""
openclpow.initCL()
# init
def init():
"""Initialise PoW"""
# pylint: disable=broad-exception-caught,global-statement
global bitmsglib, bmpow
openclpow.initCL()
2023-10-02 02:11:06 +03:00
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
2023-10-02 02:11:06 +03:00
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, "")
2023-10-02 02:11:06 +03:00
logger.info(
'Successfully tested C PoW DLL (stdcall) %s', bitmsglib)
except ValueError:
try:
# MinGW
2023-10-02 02:11:06 +03:00
bso = ctypes.CDLL(libfile)
logger.info('Loaded C PoW DLL (cdecl) %s', bitmsglib)
bmpow = bso.BitmessagePOW
bmpow.restype = ctypes.c_ulonglong
_doCPoW(2**63, "")
2023-10-02 02:11:06 +03:00
logger.info(
'Successfully tested C PoW DLL (cdecl) %s', bitmsglib)
except Exception as e:
2023-10-02 02:11:06 +03:00
logger.error('Error: %s', e, exc_info=True)
2021-01-12 23:10:03 +05:30
except Exception as e:
2023-10-02 02:11:06 +03:00
logger.error('Error: %s', e, exc_info=True)
else:
try:
2023-10-02 02:11:06 +03:00
bso = ctypes.CDLL(
os.path.join(paths.codePath(), 'bitmsghash', bitmsglib))
except OSError:
import glob
try:
bso = ctypes.CDLL(glob.glob(os.path.join(
2023-10-02 02:11:06 +03:00
paths.codePath(), 'bitmsghash', 'bitmsghash*.so'
))[0])
except (OSError, IndexError):
bso = None
2023-10-02 02:11:06 +03:00
except Exception:
bso = None
else:
2023-10-02 02:11:06 +03:00
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()