MiNode/minode/proofofwork.py

132 lines
3.9 KiB
Python

"""Doing proof of work"""
import base64
import ctypes
import glob
import hashlib
import importlib
import logging
import multiprocessing
import os
import struct
import threading
import time
from . import message, shared, structure
def _pow_worker(target, initial_hash, q):
nonce = 0
logging.debug(
'target: %s, initial_hash: %s',
target, base64.b16encode(initial_hash).decode())
trial_value = target + 1
while trial_value > target:
nonce += 1
trial_value = struct.unpack('>Q', hashlib.sha512(hashlib.sha512(
struct.pack('>Q', nonce) + initial_hash).digest()).digest()[:8])[0]
q.put(struct.pack('>Q', nonce))
class Worker(threading.Thread):
"""The thread doind PoW for objects and publishing them"""
def __init__(self, obj=None):
self.obj = obj
super().__init__(
name='Worker' if not self.obj
else 'Worker-%s' % (base64.b16encode(obj.vector).decode())
)
self.bmpow = bso = None
try:
bitmsglib = importlib.util.find_spec('minode.bitmsghash').origin
if not bitmsglib:
bitmsglib = glob.glob(os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'bitmsghash*.so'
))[0]
except (AttributeError, IndexError):
return
try:
bso = ctypes.CDLL(bitmsglib)
except Exception: # pylint: disable=broad-exception-caught
logging.warning('Error loading bitmsghash', exc_info=True)
try:
# MSVS on Windows
bso = ctypes.WinDLL(bitmsglib)
except (AttributeError, TypeError, ValueError):
return
self.bmpow = bso.BitmessagePOW
self.bmpow.restype = ctypes.c_ulonglong
@staticmethod
def _worker_result(obj):
q = multiprocessing.Queue()
p = multiprocessing.Process(target=_pow_worker, args=(
obj.pow_target(), obj.pow_initial_hash(), q))
p.start()
nonce = q.get()
p.join()
return nonce
def add_pow(self, obj):
"""
Do PoW for the given object and return a new `.structure.Object`
- with the proper nonce and vector.
"""
t = time.time()
if not self.bmpow:
logging.debug('Starting PoW process')
nonce = self._worker_result(obj)
else:
logging.debug('Calling PoW extension')
nonce = struct.pack('>Q', self.bmpow(
ctypes.pointer(
ctypes.create_string_buffer(obj.pow_initial_hash(), 64)),
ctypes.c_ulonglong(obj.pow_target())
))
logging.debug(
'Finished doing PoW, nonce: %s, time: %ss', nonce, time.time() - t)
obj = structure.Object(
nonce, obj.expires_time, obj.object_type, obj.version,
obj.stream_number, obj.object_payload)
logging.debug(
'Object vector is %s', base64.b16encode(obj.vector).decode())
return obj
@staticmethod
def _publish(obj):
with shared.objects_lock:
shared.objects[obj.vector] = obj
shared.vector_advertise_queue.put(obj.vector)
def run(self):
if self.obj:
self._publish(self.add_pow(self.obj))
return
while not shared.shutting_down:
data = shared.objects_queue.get()
obj = structure.Object.from_message(
message.Message.from_bytes(data))
if int.from_bytes(obj.nonce, 'big') == 0:
obj = self.add_pow(obj)
self._publish(obj)
def do_pow_and_publish(obj):
"""
Start a worker thread doing PoW for the given object
and putting a new object and its vector into appropriate places in `shared`
to advertize to the network.
"""
Worker(obj).start()