132 lines
3.9 KiB
Python
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()
|