Onionr/onionr/subprocesspow.py

113 lines
4.4 KiB
Python
Raw Permalink Normal View History

2019-03-12 18:23:46 +00:00
#!/usr/bin/env python3
'''
Onionr - Private P2P Communication
Multiprocess proof of work
'''
'''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
import subprocess, os
import multiprocessing, threading, time, json
2019-03-12 18:23:46 +00:00
from multiprocessing import Pipe, Process
2019-07-20 15:52:03 +00:00
import onionrblockapi, config, onionrutils, logger, onionrproofs, onionrcrypto as crypto
from onionrutils import bytesconverter
2019-03-12 18:23:46 +00:00
class SubprocessPOW:
2019-07-19 19:49:56 +00:00
def __init__(self, data, metadata, subproc_count=None):
'''
Onionr proof of work using multiple processes
2019-07-19 19:49:56 +00:00
Accepts block data, block metadata
if subproc_count is not set, os.cpu_count() is used to determine the number of processes
Do to Python GIL multiprocessing or use of external libraries is necessary to accelerate CPU bound tasks
'''
# No known benefit to using more processes than there are cores.
# Note: os.cpu_count perhaps not always accurate
if subproc_count is None:
subproc_count = os.cpu_count()
self.subproc_count = subproc_count
2019-03-12 18:23:46 +00:00
self.result = ''
self.shutdown = False
self.data = data
self.metadata = metadata
# dump dict to measure bytes of json metadata. Cannot reuse later because the pow token must be added
json_metadata = json.dumps(metadata).encode()
2019-03-12 18:23:46 +00:00
self.data = bytesconverter.str_to_bytes(data)
# Calculate difficulty. Dumb for now, may use good algorithm in the future.
2019-07-20 00:01:16 +00:00
self.difficulty = onionrproofs.getDifficultyForNewBlock(bytes(json_metadata + b'\n' + self.data))
2019-03-12 18:23:46 +00:00
2019-07-20 00:01:16 +00:00
logger.info('Computing POW (difficulty: %s)...' % (self.difficulty,))
2019-03-12 18:23:46 +00:00
self.mainHash = '0' * 64
self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))]
self.shutdown = False
self.payload = None
def start(self):
# Create a new thread for each subprocess
for x in range(self.subproc_count):
2019-03-12 18:23:46 +00:00
threading.Thread(target=self._spawn_proc).start()
# Monitor the processes for a payload, shut them down when its found
2019-03-12 18:23:46 +00:00
while True:
if self.payload is None:
time.sleep(0.1)
else:
self.shutdown = True
return self.payload
def _spawn_proc(self):
# Create a child proof of work process, wait for data and send shutdown signal when its found
2019-03-12 18:23:46 +00:00
parent_conn, child_conn = Pipe()
p = Process(target=self.do_pow, args=(child_conn,))
p.start()
p.join()
payload = None
try:
while True:
data = parent_conn.recv()
if len(data) >= 1:
payload = data
break
except KeyboardInterrupt:
pass
finally:
parent_conn.send('shutdown')
self.payload = payload
def do_pow(self, pipe):
nonce = -10000000 # Start nonce at negative 10 million so that the chosen nonce is likely to be small in length
2019-03-12 18:23:46 +00:00
nonceStart = nonce
data = self.data
metadata = self.metadata
puzzle = self.puzzle
difficulty = self.difficulty
while True:
# Break if shutdown received
if pipe.poll() and pipe.recv() == 'shutdown':
break
# Load nonce into block metadata
2019-04-04 21:56:18 +00:00
metadata['pow'] = nonce
# Serialize metadata, combine with block data
2019-03-12 18:23:46 +00:00
payload = json.dumps(metadata).encode() + b'\n' + data
# Check sha3_256 hash of block, compare to puzzle. Send payload if puzzle finished
2019-07-20 15:52:03 +00:00
token = crypto.hashers.sha3_hash(payload)
token = bytesconverter.bytes_to_str(token) # ensure token is string
2019-03-12 18:23:46 +00:00
if puzzle == token[0:difficulty]:
pipe.send(payload)
break
nonce += 1