From 7864677498705cd8b6daea64201824e00ae38f7a Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sat, 16 Jun 2018 15:54:56 -0500 Subject: [PATCH] + added secrets.py * work on communicator2, syncing blocks * modify insertblock to use new pow metadata (may need more work for new spec) --- onionr/communicator2.py | 63 +++++-- onionr/core.py | 5 +- onionr/dependencies/secrets.py | 331 +++++++++++++++++++++++++++++++++ onionr/onionr.py | 2 +- onionr/onionrcrypto.py | 23 ++- 5 files changed, 391 insertions(+), 33 deletions(-) create mode 100644 onionr/dependencies/secrets.py diff --git a/onionr/communicator2.py b/onionr/communicator2.py index 24529a2f..67a61e19 100755 --- a/onionr/communicator2.py +++ b/onionr/communicator2.py @@ -35,6 +35,7 @@ class OnionrCommunicatorDaemon: self.proxyPort = sys.argv[2] self.onlinePeers = [] + self.offlinePeers = [] self.threadCounts = {} @@ -60,8 +61,8 @@ class OnionrCommunicatorDaemon: OnionrCommunicatorTimers(self, self.daemonCommands, 5) OnionrCommunicatorTimers(self, self.detectAPICrash, 5) OnionrCommunicatorTimers(self, self.getOnlinePeers, 60) - #OnionrCommunicatorTimers(self, self.lookupBlocks, 120) - #OnionrCommunicatorTimers(self, self.getBlocks, 30) + OnionrCommunicatorTimers(self, self.lookupBlocks, 7) + OnionrCommunicatorTimers(self, self.getBlocks, 10) # Main daemon loop, mainly for calling timers, do not do any complex operations here while not self.shutdown: @@ -72,37 +73,53 @@ class OnionrCommunicatorDaemon: def lookupBlocks(self): '''Lookup new blocks''' + logger.info('LOOKING UP NEW BLOCKS') tryAmount = 2 newBlocks = '' for i in range(tryAmount): - newBlocks = self.peerAction(pickOnlinePeer(), 'getBlockHashes') - if newBlocks != False: - # if request was a success - for i in newBlocks.split('\n'): - if self._core.utils.validateHash(i): - # if newline seperated string is valid hash - if not os.path.exists('data/blocks/' + i + '.db'): - # if block does not exist on disk and is not already in block queue - if i not in self.blockQueue: - self.blockQueue.append(i) + peer = self.pickOnlinePeer() + newDBHash = self.peerAction(peer, 'getDBHash') + if newDBHash == False: + continue + if newDBHash != self._core.getAddressInfo(peer, 'DBHash'): + self._core.setAddressInfo(peer, 'DBHash', newDBHash) + newBlocks = self.peerAction(peer, 'getBlockHashes') + if newBlocks != False: + # if request was a success + for i in newBlocks.split('\n'): + if self._core._utils.validateHash(i): + # if newline seperated string is valid hash + if not os.path.exists('data/blocks/' + i + '.db'): + # if block does not exist on disk and is not already in block queue + if i not in self.blockQueue: + self.blockQueue.append(i) self.decrementThreadCount('lookupBlocks') return def getBlocks(self): '''download new blocks''' for blockHash in self.blockQueue: + logger.info("ATTEMPTING TO DOWNLOAD " + blockHash) content = self.peerAction(self.pickOnlinePeer(), 'getData', data=blockHash) if content != False: try: content = content.encode() except AttributeError: pass - content = base64.b64decode(content).decode() + content = base64.b64decode(content) if self._core._crypto.sha3Hash(content) == blockHash: + content = content.decode() # decode here because sha3Hash needs bytes above metas = self._core._utils.getBlockMetadataFromData(content) metadata = metas[0] meta = metas[1] - #if self._core._crypto.verifyPow(metas[2], metas[1]) + if self._core._crypto.verifyPow(metas[2], metadata['meta']): + logger.info('Block passed proof, saving.') + self._core.setData(content) + self.blockQueue.remove(blockHash) + else: + logger.warn('POW failed for block' + blockHash) + else: + logger.warn('Block hash validation failed for ' + blockHash) return def pickOnlinePeer(self): @@ -110,6 +127,8 @@ class OnionrCommunicatorDaemon: retData = '' while True: peerLength = len(self.onlinePeers) + if peerLength <= 0: + break try: # get a random online peer, securely. May get stuck in loop if network is lost or if all peers in pool magically disconnect at once retData = self.onlinePeers[self._core._crypto.secrets.randbelow(peerLength)] @@ -121,8 +140,11 @@ class OnionrCommunicatorDaemon: def decrementThreadCount(self, threadName): '''Decrement amount of a thread name if more than zero, called when a function meant to be run in a thread ends''' - if self.threadCounts[threadName] > 0: - self.threadCounts[threadName] -= 1 + try: + if self.threadCounts[threadName] > 0: + self.threadCounts[threadName] -= 1 + except KeyError: + pass def getOnlinePeers(self): '''Manages the self.onlinePeers attribute list''' @@ -137,6 +159,7 @@ class OnionrCommunicatorDaemon: def connectNewPeer(self, peer=''): '''Adds a new random online peer to self.onlinePeers''' retData = False + tried = self.offlinePeers if peer != '': if self._core._utils.validateID(peer): peerList = [peer] @@ -149,12 +172,15 @@ class OnionrCommunicatorDaemon: peerList.extend(self._core.bootstrapList) for address in peerList: + if len(address) == 0 or address in tried or address in self.onlinePeers: + continue if self.peerAction(address, 'ping') == 'pong!': logger.info('connected to ' + address) self.onlinePeers.append(address) retData = address break else: + tried.append(address) logger.debug('failed to connect to ' + address) else: logger.warn('Could not connect to any peer') @@ -171,7 +197,10 @@ class OnionrCommunicatorDaemon: def peerAction(self, peer, action, data=''): '''Perform a get request to a peer''' logger.info('Performing ' + action + ' with ' + peer + ' on port ' + str(self.proxyPort)) - retData = self._core._utils.doGetRequest('http://' + peer + '/public/?action=' + action + '&data=' + data, port=self.proxyPort) + url = 'http://' + peer + '/public/?action=' + action + if len(data) > 0: + url += '&data=' + data + retData = self._core._utils.doGetRequest(url, port=self.proxyPort) if retData == False: try: self.onlinePeers.remove(peer) diff --git a/onionr/core.py b/onionr/core.py index 141c0480..0d9aafde 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -486,6 +486,7 @@ class Core: trust int 6 pubkeyExchanged int 7 hashID text 8 + pow text 9 ''' conn = sqlite3.connect(self.peerDB) c = conn.cursor() @@ -672,7 +673,6 @@ class Core: if powToken == False: time.sleep(0.3) continue - powHash = powToken[0] powToken = base64.b64encode(powToken[1]) try: powToken = powToken.decode() @@ -693,8 +693,7 @@ class Core: retData = '' metadata['type'] = header - metadata['powHash'] = powHash - metadata['powToken'] = powToken + metadata['powRandomToken'] = powToken sig = {} diff --git a/onionr/dependencies/secrets.py b/onionr/dependencies/secrets.py new file mode 100644 index 00000000..9b2eb61b --- /dev/null +++ b/onionr/dependencies/secrets.py @@ -0,0 +1,331 @@ +"""Generate cryptographically strong pseudo-random numbers suitable for +managing secrets such as account authentication, tokens, and similar. + +See PEP 506 for more information. +https://www.python.org/dev/peps/pep-0506/ + + +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Python Software Foundation; All +Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +""" + +__all__ = ['choice', 'randbelow', 'randbits', 'SystemRandom', + 'token_bytes', 'token_hex', 'token_urlsafe', + 'compare_digest', + ] + + +import base64 +import binascii +import os + +from hmac import compare_digest +from random import SystemRandom + +_sysrand = SystemRandom() + +randbits = _sysrand.getrandbits +choice = _sysrand.choice + +def randbelow(exclusive_upper_bound): + """Return a random int in the range [0, n).""" + if exclusive_upper_bound <= 0: + raise ValueError("Upper bound must be positive.") + return _sysrand._randbelow(exclusive_upper_bound) + +DEFAULT_ENTROPY = 32 # number of bytes to return by default + +def token_bytes(nbytes=None): + """Return a random byte string containing *nbytes* bytes. + + If *nbytes* is ``None`` or not supplied, a reasonable + default is used. + + >>> token_bytes(16) #doctest:+SKIP + b'\\xebr\\x17D*t\\xae\\xd4\\xe3S\\xb6\\xe2\\xebP1\\x8b' + + """ + if nbytes is None: + nbytes = DEFAULT_ENTROPY + return os.urandom(nbytes) + +def token_hex(nbytes=None): + """Return a random text string, in hexadecimal. + + The string has *nbytes* random bytes, each byte converted to two + hex digits. If *nbytes* is ``None`` or not supplied, a reasonable + default is used. + + >>> token_hex(16) #doctest:+SKIP + 'f9bf78b9a18ce6d46a0cd2b0b86df9da' + + """ + return binascii.hexlify(token_bytes(nbytes)).decode('ascii') + +def token_urlsafe(nbytes=None): + """Return a random URL-safe text string, in Base64 encoding. + + The string has *nbytes* random bytes. If *nbytes* is ``None`` + or not supplied, a reasonable default is used. + + >>> token_urlsafe(16) #doctest:+SKIP + 'Drmhze6EPcv0fN_81Bj-nA' + + """ + tok = token_bytes(nbytes) + return base64.urlsafe_b64encode(tok).rstrip(b'=').decode('ascii') + diff --git a/onionr/onionr.py b/onionr/onionr.py index 0b818670..d008d138 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -42,7 +42,7 @@ except ImportError: ONIONR_TAGLINE = 'Anonymous P2P Platform - GPLv3 - https://Onionr.VoidNet.Tech' ONIONR_VERSION = '0.1.0' # for debugging and stuff ONIONR_VERSION_TUPLE = tuple(ONIONR_VERSION.split('.')) # (MAJOR, MINOR, VERSION) -API_VERSION = '2' # increments of 1; only change when something fundemental about how the API works changes. This way other nodes knows how to communicate without learning too much information about you. +API_VERSION = '3' # increments of 1; only change when something fundemental about how the API works changes. This way other nodes knows how to communicate without learning too much information about you. class Onionr: def __init__(self): diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py index ef6ff58f..1b1d3192 100644 --- a/onionr/onionrcrypto.py +++ b/onionr/onionrcrypto.py @@ -255,29 +255,28 @@ class OnionrCrypto: ''' retData = False - if not (('powToken' in metadata) and ('powHash' in metadata)): + if not 'powRandomToken' in metadata: + logger.warn('No powRandomToken') return False dataLen = len(blockContent) - expectedHash = self.blake2bHash(base64.b64decode(metadata['powToken']) + self.blake2bHash(blockContent.encode())) + expectedHash = self.blake2bHash(base64.b64decode(metadata['powRandomToken']) + self.blake2bHash(blockContent.encode())) difficulty = 0 try: expectedHash = expectedHash.decode() except AttributeError: pass - if metadata['powHash'] == expectedHash: - difficulty = math.floor(dataLen / 1000000) - mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode() - puzzle = mainHash[:difficulty] + difficulty = math.floor(dataLen / 1000000) - if metadata['powHash'][:difficulty] == puzzle: - # logger.debug('Validated block pow') - retData = True - else: - logger.debug("Invalid token (#1)") + mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode() + puzzle = mainHash[:difficulty] + + if metadata['powHash'][:difficulty] == puzzle: + # logger.debug('Validated block pow') + retData = True else: - logger.debug('Invalid token (#2): Expected hash %s, got hash %s...' % (metadata['powHash'], expectedHash)) + logger.debug("Invalid token, bad proof") return retData