PyBitmessage-2021-04-27/src/workprover/__init__.py

263 lines
7.5 KiB
Python
Raw Normal View History

2018-06-23 11:00:24 +00:00
import Queue
import collections
2018-06-23 08:57:34 +00:00
import multiprocessing
import struct
2018-06-23 11:00:24 +00:00
import sys
2018-06-23 08:57:34 +00:00
import threading
2018-06-23 11:00:24 +00:00
import time
2018-06-23 08:57:34 +00:00
import dumbsolver
import fastsolver
2018-06-23 11:00:24 +00:00
import forkingsolver
2018-06-23 08:57:34 +00:00
import gpusolver
2018-06-23 11:00:24 +00:00
import utils
2018-06-23 08:57:34 +00:00
timeout = .5
class Stop(Exception):
pass
class Task(object):
previous = None
next = None
2018-07-23 08:01:22 +00:00
def __init__(self, headlessPayload, TTL, expiryTime, target, difficulty):
2018-06-23 08:57:34 +00:00
self.headlessPayload = headlessPayload
self.TTL = TTL
self.expiryTime = expiryTime
self.target = target
2018-07-23 08:01:22 +00:00
self.difficulty = difficulty
2018-06-23 08:57:34 +00:00
2018-07-23 08:01:22 +00:00
Status = collections.namedtuple("Status", ["solverName", "solverStatus", "speed", "tasksCount", "difficulty"])
2018-07-29 04:38:05 +00:00
# Only one instance allowed
2018-06-23 08:57:34 +00:00
class WorkProver(threading.Thread):
2018-07-15 18:37:23 +00:00
# Seed must be 32 bytes
2018-07-29 04:38:05 +00:00
def __init__(self, codePath, seed, statusUpdated, resultsQueue):
2018-06-23 08:57:34 +00:00
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)
2018-06-23 10:32:05 +00:00
except fastsolver.FastSolverError:
2018-06-23 08:57:34 +00:00
pass
try:
2018-07-29 04:38:05 +00:00
self.availableSolvers["gpu"] = gpusolver.GPUSolver(codePath)
2018-06-23 10:32:05 +00:00
except gpusolver.GPUSolverError:
2018-06-23 08:57:34 +00:00
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()
2018-07-15 18:37:23 +00:00
if resultsQueue is None:
self.resultsQueue = Queue.Queue()
else:
self.resultsQueue = resultsQueue
2018-06-23 08:57:34 +00:00
self.solverName = None
self.solver = None
self.lastTime = utils.getTimePoint()
self.timedIntervals = collections.deque()
self.speed = 0
2018-07-23 08:01:22 +00:00
self.totalDifficulty = 0
2018-06-23 08:57:34 +00:00
self.tasks = {}
self.currentTaskID = None
def notifyStatus(self):
if self.statusUpdated is None:
return
2018-06-28 14:19:50 +00:00
status = None
if self.solver is not None:
status = self.solver.status
2018-06-23 08:57:34 +00:00
2018-07-23 08:01:22 +00:00
self.statusUpdated(Status(self.solverName, status, self.speed, len(self.tasks), self.totalDifficulty))
2018-06-23 08:57:34 +00:00
2018-06-28 14:19:50 +00:00
def setSolver(self, name, configuration):
2018-07-29 04:38:05 +00:00
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
2018-06-23 08:57:34 +00:00
2018-07-29 04:38:05 +00:00
if name is not None:
if name not in self.availableSolvers:
name, configuration = "dumb", None
2018-06-23 08:57:34 +00:00
2018-07-29 04:38:05 +00:00
self.solverName = name
self.solver = self.availableSolvers[name]
self.solver.setConfiguration(configuration)
2018-08-02 15:00:23 +00:00
except gpusolver.GPUSolverError:
2018-07-29 04:38:05 +00:00
self.solverName = None
self.solver = None
self.resultsQueue.put(("GPUError", ))
2018-06-23 08:57:34 +00:00
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):
2018-07-23 08:01:22 +00:00
target, difficulty = utils.calculateTarget(8 + 8 + len(headlessPayload), TTL, byteDifficulty, lengthExtension)
2018-06-23 08:57:34 +00:00
2018-07-23 08:01:22 +00:00
task = Task(headlessPayload, TTL, expiryTime, target, difficulty)
2018-06-23 08:57:34 +00:00
self.tasks[ID] = task
2018-07-23 08:01:22 +00:00
self.totalDifficulty += difficulty
2018-06-23 08:57:34 +00:00
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
2018-07-23 08:01:22 +00:00
self.notifyStatus()
2018-06-23 08:57:34 +00:00
def cancelTask(self, ID):
if ID not in self.tasks:
return
task = self.tasks.pop(ID)
2018-07-23 08:01:22 +00:00
self.totalDifficulty -= task.difficulty
2018-06-23 08:57:34 +00:00
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
2018-07-23 08:01:22 +00:00
self.notifyStatus()
2018-06-23 08:57:34 +00:00
def nextTask(self):
self.currentTaskID = self.tasks[self.currentTaskID].next
def shutdown(self):
2018-06-28 14:19:50 +00:00
self.setSolver(None, None)
2018-06-23 08:57:34 +00:00
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:
2018-07-29 04:38:05 +00:00
self.setSolver(None, None)
self.resultsQueue.put(("GPUError", ))
2018-06-23 08:57:34 +00:00
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)