From b3b5e5bb50b291b296d8bb88b6aa1141f339de15 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Mon, 9 Jul 2018 02:02:33 -0500 Subject: [PATCH] + re-added old pow system as a different class for things like ed25519 keys * no longer run communicator threads if they need peers and we have none connected --- onionr/communicator2.py | 61 +++++++++++++++---------- onionr/onionr.py | 8 +++- onionr/onionrcrypto.py | 2 +- onionr/onionrexceptions.py | 4 ++ onionr/onionrproofs.py | 93 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 140 insertions(+), 28 deletions(-) diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 4003cb79..99244985 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -67,23 +67,22 @@ class OnionrCommunicatorDaemon: if debug or developmentMode: OnionrCommunicatorTimers(self, self.heartbeat, 10) - # Initalize peer online list - logger.warn('Onionr is starting up and is not yet ready to recieve commands.') - self.getOnlinePeers() - # Print nice header thing :) - if config.get('general.display_header', True): + if config.get('general.display_header', True) and not self.shutdown: self.header() # Set timers, function reference, seconds OnionrCommunicatorTimers(self, self.daemonCommands, 5) OnionrCommunicatorTimers(self, self.detectAPICrash, 5) - OnionrCommunicatorTimers(self, self.getOnlinePeers, 60) - OnionrCommunicatorTimers(self, self.lookupBlocks, 7) - OnionrCommunicatorTimers(self, self.getBlocks, 10) + peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60) + OnionrCommunicatorTimers(self, self.lookupBlocks, 7, requiresPeer=True) + OnionrCommunicatorTimers(self, self.getBlocks, 10, requiresPeer=True) OnionrCommunicatorTimers(self, self.clearOfflinePeer, 120) - OnionrCommunicatorTimers(self, self.lookupKeys, 125) - OnionrCommunicatorTimers(self, self.lookupAdders, 600) + OnionrCommunicatorTimers(self, self.lookupKeys, 125, requiresPeer=True) + OnionrCommunicatorTimers(self, self.lookupAdders, 600, requiresPeer=True) + + # set loop to execute instantly to load up peer pool (replaced old pool init wait) + peerPoolTimer.count = (peerPoolTimer.frequency - 1) # Main daemon loop, mainly for calling timers, don't do any complex operations here to avoid locking try: @@ -227,8 +226,11 @@ class OnionrCommunicatorDaemon: for i in range(needed): if len(self.onlinePeers) == 0: self.connectNewPeer(useBootstrap=True) - if len(self.onlinePeers) == 0: - logger.warn('Could not connect to any peer.') + if self.shutdown: + break + else: + if len(self.onlinePeers) == 0: + logger.warn('Could not connect to any peer.') self.decrementThreadCount('getOnlinePeers') def addBootstrapListToPeerList(self, peerList): @@ -358,11 +360,12 @@ class OnionrCommunicatorDaemon: logger.info(logger.colors.fg.lightgreen + '-> ' + str(message) + logger.colors.reset + logger.colors.fg.lightgreen + ' <-\n') class OnionrCommunicatorTimers: - def __init__(self, daemonInstance, timerFunction, frequency, makeThread=True, threadAmount=1, maxThreads=5): + def __init__(self, daemonInstance, timerFunction, frequency, makeThread=True, threadAmount=1, maxThreads=5, requiresPeer=False): self.timerFunction = timerFunction self.frequency = frequency self.threadAmount = threadAmount self.makeThread = makeThread + self.requiresPeer = requiresPeer self.daemonInstance = daemonInstance self.maxThreads = maxThreads self._core = self.daemonInstance._core @@ -371,25 +374,33 @@ class OnionrCommunicatorTimers: self.count = 0 def processTimer(self): + # mark how many instances of a thread we have (decremented at thread end) - self.count += 1 try: self.daemonInstance.threadCounts[self.timerFunction.__name__] except KeyError: self.daemonInstance.threadCounts[self.timerFunction.__name__] = 0 - # execute thread if it is time + + # execute thread if it is time, and we are not missing *required* online peer if self.count == self.frequency: - if self.makeThread: - for i in range(self.threadAmount): - if self.daemonInstance.threadCounts[self.timerFunction.__name__] >= self.maxThreads: - logger.warn(self.timerFunction.__name__ + ' has too many current threads to start anymore.') - else: - self.daemonInstance.threadCounts[self.timerFunction.__name__] += 1 - newThread = threading.Thread(target=self.timerFunction) - newThread.start() + try: + if self.requiresPeer and len(self.daemonInstance.onlinePeers) == 0: + raise onionrexceptions.OnlinePeerNeeded + except onionrexceptions.OnlinePeerNeeded: + pass else: - self.timerFunction() - self.count = 0 + if self.makeThread: + for i in range(self.threadAmount): + if self.daemonInstance.threadCounts[self.timerFunction.__name__] >= self.maxThreads: + logger.warn(self.timerFunction.__name__ + ' has too many current threads to start anymore.') + else: + self.daemonInstance.threadCounts[self.timerFunction.__name__] += 1 + newThread = threading.Thread(target=self.timerFunction) + newThread.start() + else: + self.timerFunction() + self.count = -1 # negative 1 because its incremented at bottom + self.count += 1 shouldRun = False debug = True diff --git a/onionr/onionr.py b/onionr/onionr.py index 5f91b576..5cb78de1 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -25,7 +25,7 @@ import sys if sys.version_info[0] == 2 or sys.version_info[1] < 5: print('Error, Onionr requires Python 3.4+') sys.exit(1) -import os, base64, random, getpass, shutil, subprocess, requests, time, platform, datetime, re, json, getpass +import os, base64, random, getpass, shutil, subprocess, requests, time, platform, datetime, re, json, getpass, sqlite3 from threading import Thread import api, core, config, logger, onionrplugins as plugins, onionrevents as events import onionrutils @@ -604,7 +604,11 @@ class Onionr: try: events.event('daemon_stop', onionr = self) net = NetController(config.get('client.port', 59496)) - self.onionrCore.daemonQueueAdd('shutdown') + try: + self.onionrCore.daemonQueueAdd('shutdown') + except sqlite3.OperationalError: + pass + net.killTor() except Exception as e: logger.error('Failed to shutdown daemon.', error = e, timestamp = False) diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index 394bea7f..4a751140 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -59,7 +59,7 @@ class OnionrCrypto: with open(self._keyFile, 'w') as keyfile: keyfile.write(self.pubKey + ',' + self.privKey) with open(self.keyPowFile, 'w') as keyPowFile: - proof = onionrproofs.POW(self.pubKey) + proof = onionrproofs.DataPOW(self.pubKey) logger.info('Doing necessary work to insert our public key') while True: time.sleep(0.2) diff --git a/onionr/onionrexceptions.py b/onionr/onionrexceptions.py index dc6485a1..e1227305 100644 --- a/onionr/onionrexceptions.py +++ b/onionr/onionrexceptions.py @@ -26,6 +26,10 @@ class Unknown(Exception): class Invalid(Exception): pass +# communicator exceptions +class OnlinePeerNeeded(Exception): + pass + # crypto exceptions class InvalidPubkey(Exception): pass diff --git a/onionr/onionrproofs.py b/onionr/onionrproofs.py index 89278a65..b6a63d8a 100644 --- a/onionr/onionrproofs.py +++ b/onionr/onionrproofs.py @@ -21,6 +21,99 @@ import nacl.encoding, nacl.hash, nacl.utils, time, math, threading, binascii, logger, sys, base64, json import core +class DataPOW: + def __init__(self, data, threadCount = 5): + self.foundHash = False + self.difficulty = 0 + self.data = data + self.threadCount = threadCount + + dataLen = sys.getsizeof(data) + self.difficulty = math.floor(dataLen / 1000000) + if self.difficulty <= 2: + self.difficulty = 4 + + try: + self.data = self.data.encode() + except AttributeError: + pass + + self.data = nacl.hash.blake2b(self.data) + + logger.info('Computing POW (difficulty: %s)...' % self.difficulty) + + self.mainHash = '0' * 70 + self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))] + + myCore = core.Core() + for i in range(max(1, threadCount)): + t = threading.Thread(name = 'thread%s' % i, target = self.pow, args = (True,myCore)) + t.start() + + return + + def pow(self, reporting = False, myCore = None): + startTime = math.floor(time.time()) + self.hashing = True + self.reporting = reporting + iFound = False # if current thread is the one that found the answer + answer = '' + heartbeat = 200000 + hbCount = 0 + + while self.hashing: + rand = nacl.utils.random() + token = nacl.hash.blake2b(rand + self.data).decode() + #print(token) + if self.puzzle == token[0:self.difficulty]: + self.hashing = False + iFound = True + break + + if iFound: + endTime = math.floor(time.time()) + if self.reporting: + logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True) + logger.debug('Random value was: %s' % base64.b64encode(rand).decode()) + self.result = (token, rand) + + def shutdown(self): + self.hashing = False + self.puzzle = '' + + def changeDifficulty(self, newDiff): + self.difficulty = newDiff + + def getResult(self): + ''' + Returns the result then sets to false, useful to automatically clear the result + ''' + + try: + retVal = self.result + except AttributeError: + retVal = False + + self.result = False + return retVal + + def waitForResult(self): + ''' + Returns the result only when it has been found, False if not running and not found + ''' + result = False + try: + while True: + result = self.getResult() + if not self.hashing: + break + else: + time.sleep(2) + except KeyboardInterrupt: + self.shutdown() + logger.warn('Got keyboard interrupt while waiting for POW result, stopping') + return result + class POW: def __init__(self, metadata, data, threadCount = 5): self.foundHash = False