Merge pull request #10 from beardog108/communicator-multithreading
Communicator multithreadingmaster
commit
e69971bc27
|
@ -11,3 +11,4 @@ onionr/gnupg/*
|
||||||
run.sh
|
run.sh
|
||||||
onionr/data-encrypted.dat
|
onionr/data-encrypted.dat
|
||||||
onionr/.onionr-lock
|
onionr/.onionr-lock
|
||||||
|
core
|
||||||
|
|
7
Makefile
7
Makefile
|
@ -16,6 +16,8 @@ uninstall:
|
||||||
sudo rm -f /usr/bin/onionr
|
sudo rm -f /usr/bin/onionr
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
@./RUN-LINUX.sh stop
|
||||||
|
@sleep 1
|
||||||
@rm -rf onionr/data-backup
|
@rm -rf onionr/data-backup
|
||||||
@mv onionr/data onionr/data-backup | true > /dev/null 2>&1
|
@mv onionr/data onionr/data-backup | true > /dev/null 2>&1
|
||||||
-@cd onionr; ./tests.py; ./cryptotests.py;
|
-@cd onionr; ./tests.py; ./cryptotests.py;
|
||||||
|
@ -30,4 +32,9 @@ soft-reset:
|
||||||
reset:
|
reset:
|
||||||
@echo "Hard-resetting Onionr..."
|
@echo "Hard-resetting Onionr..."
|
||||||
rm -rf onionr/data/ | true > /dev/null 2>&1
|
rm -rf onionr/data/ | true > /dev/null 2>&1
|
||||||
|
#@./RUN-LINUX.sh version | grep -v "Failed" --color=always
|
||||||
|
|
||||||
|
plugins-reset:
|
||||||
|
@echo "Resetting plugins..."
|
||||||
|
rm -rf onionr/data/plugins/ | true > /dev/null 2>&1
|
||||||
@./RUN-LINUX.sh version | grep -v "Failed" --color=always
|
@./RUN-LINUX.sh version | grep -v "Failed" --color=always
|
||||||
|
|
|
@ -227,8 +227,8 @@ class API:
|
||||||
response = 'none'
|
response = 'none'
|
||||||
resp = Response(response)
|
resp = Response(response)
|
||||||
elif action == 'kex':
|
elif action == 'kex':
|
||||||
peers = self._core.listPeers()
|
peers = self._core.listPeers(getPow=True)
|
||||||
response = ','.join(self._core.listPeers())
|
response = ','.join(peers)
|
||||||
resp = Response(response)
|
resp = Response(response)
|
||||||
else:
|
else:
|
||||||
resp = Response("")
|
resp = Response("")
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit a74e826e9c69e643ead7950f9f76a05ab8664ddc
|
|
|
@ -1,44 +0,0 @@
|
||||||
'''
|
|
||||||
Onionr - P2P Microblogging Platform & Social network
|
|
||||||
|
|
||||||
Handle bitcoin operations
|
|
||||||
'''
|
|
||||||
'''
|
|
||||||
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/>.
|
|
||||||
'''
|
|
||||||
from bitpeer.node import *
|
|
||||||
from bitpeer.storage.shelve import ShelveStorage
|
|
||||||
import logging, time
|
|
||||||
import socks, sys
|
|
||||||
class OnionrBTC:
|
|
||||||
def __init__(self, lastBlock='00000000000000000021ee6242d08e3797764c9258e54e686bc2afff51baf599', lastHeight=510613, torP=9050):
|
|
||||||
stream = logging.StreamHandler()
|
|
||||||
logger = logging.getLogger('halfnode')
|
|
||||||
logger.addHandler(stream)
|
|
||||||
logger.setLevel (10)
|
|
||||||
|
|
||||||
LASTBLOCK = lastBlock
|
|
||||||
LASTBLOCKINDEX = lastHeight
|
|
||||||
self.node = Node ('BTC', ShelveStorage ('data/btc-blocks.db'), lastblockhash=LASTBLOCK, lastblockheight=LASTBLOCKINDEX, torPort=torP)
|
|
||||||
|
|
||||||
self.node.bootstrap ()
|
|
||||||
self.node.connect ()
|
|
||||||
self.node.loop ()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
torPort = int(sys.argv[1])
|
|
||||||
bitcoin = OnionrBTC(torPort)
|
|
||||||
while True:
|
|
||||||
print(bitcoin.node.getBlockHash(bitcoin.node.getLastBlockHeight())) # Using print on purpose, do not change to logger
|
|
||||||
time.sleep(5)
|
|
|
@ -19,8 +19,8 @@ and code to operate as a daemon, getting commands from the command queue databas
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
'''
|
'''
|
||||||
import sqlite3, requests, hmac, hashlib, time, sys, os, math, logger, urllib.parse, base64, binascii, random, json
|
import sqlite3, requests, hmac, hashlib, time, sys, os, math, logger, urllib.parse, base64, binascii, random, json, threading
|
||||||
import core, onionrutils, onionrcrypto, netcontroller, onionrproofs, btc, config, onionrplugins as plugins
|
import core, onionrutils, onionrcrypto, netcontroller, onionrproofs, config, onionrplugins as plugins
|
||||||
|
|
||||||
class OnionrCommunicate:
|
class OnionrCommunicate:
|
||||||
def __init__(self, debug, developmentMode):
|
def __init__(self, debug, developmentMode):
|
||||||
|
@ -40,28 +40,27 @@ class OnionrCommunicate:
|
||||||
self.ignoredHashes = []
|
self.ignoredHashes = []
|
||||||
|
|
||||||
self.highFailureAmount = 7
|
self.highFailureAmount = 7
|
||||||
'''
|
|
||||||
logger.info('Starting Bitcoin Node... with Tor socks port:' + str(sys.argv[2]), timestamp=True)
|
|
||||||
try:
|
|
||||||
self.bitcoin = btc.OnionrBTC(torP=int(sys.argv[2]))
|
|
||||||
except _gdbm.error:
|
|
||||||
pass
|
|
||||||
logger.info('Bitcoin Node started, on block: ' + self.bitcoin.node.getBlockHash(self.bitcoin.node.getLastBlockHeight()), timestamp=True)
|
|
||||||
'''
|
|
||||||
#except:
|
|
||||||
#logger.fatal('Failed to start Bitcoin Node, exiting...')
|
|
||||||
#exit(1)
|
|
||||||
|
|
||||||
blockProcessTimer = 0
|
self.communicatorThreads = 0
|
||||||
blockProcessAmount = 5
|
self.maxThreads = 75
|
||||||
highFailureTimer = 0
|
self.processBlocksThreads = 0
|
||||||
highFailureRate = 10
|
self.lookupBlocksThreads = 0
|
||||||
heartBeatTimer = 0
|
|
||||||
heartBeatRate = 0
|
self.blocksProcessing = [] # list of blocks currently processing, to avoid trying a block twice at once in 2 seperate threads
|
||||||
pexTimer = 25 # How often we should check for new peers
|
self.peerStatus = {} # network actions (active requests) for peers used mainly to prevent conflicting actions in threads
|
||||||
pexCount = 0
|
|
||||||
|
self.communicatorTimers = {} # communicator timers, name: rate (in seconds)
|
||||||
|
self.communicatorTimerCounts = {}
|
||||||
|
self.communicatorTimerFuncs = {}
|
||||||
|
|
||||||
|
self.registerTimer('blockProcess', 20)
|
||||||
|
self.registerTimer('highFailure', 10)
|
||||||
|
self.registerTimer('heartBeat', 10)
|
||||||
|
self.registerTimer('pex', 120)
|
||||||
logger.debug('Communicator debugging enabled.')
|
logger.debug('Communicator debugging enabled.')
|
||||||
torID = open('data/hs/hostname').read()
|
|
||||||
|
with open('data/hs/hostname', 'r') as torID:
|
||||||
|
todID = torID.read()
|
||||||
|
|
||||||
apiRunningCheckRate = 10
|
apiRunningCheckRate = 10
|
||||||
apiRunningCheckCount = 0
|
apiRunningCheckCount = 0
|
||||||
|
@ -77,24 +76,44 @@ class OnionrCommunicate:
|
||||||
while True:
|
while True:
|
||||||
command = self._core.daemonQueue()
|
command = self._core.daemonQueue()
|
||||||
# Process blocks based on a timer
|
# Process blocks based on a timer
|
||||||
blockProcessTimer += 1
|
self.timerTick()
|
||||||
heartBeatTimer += 1
|
# TODO: migrate below if statements to be own functions which are called in the above timerTick() function
|
||||||
pexCount += 1
|
if self.communicatorTimers['highFailure'] == self.communicatorTimerCounts['highFailure']:
|
||||||
if highFailureTimer == highFailureRate:
|
self.communicatorTimerCounts['highFailure'] = 0
|
||||||
highFailureTimer = 0
|
|
||||||
for i in self.peerData:
|
for i in self.peerData:
|
||||||
if self.peerData[i]['failCount'] >= self.highFailureAmount:
|
if self.peerData[i]['failCount'] >= self.highFailureAmount:
|
||||||
self.peerData[i]['failCount'] -= 1
|
self.peerData[i]['failCount'] -= 1
|
||||||
if pexTimer == pexCount:
|
if self.communicatorTimers['pex'] == self.communicatorTimerCounts['pex']:
|
||||||
self.getNewPeers()
|
pT1 = threading.Thread(target=self.getNewPeers, name="pT1")
|
||||||
pexCount = 0 # TODO: do not reset timer if low peer count
|
pT1.start()
|
||||||
if heartBeatRate == heartBeatTimer:
|
pT2 = threading.Thread(target=self.getNewPeers, name="pT2")
|
||||||
|
pT2.start()
|
||||||
|
self.communicatorTimerCounts['pex'] = 0# TODO: do not reset timer if low peer count
|
||||||
|
if self.communicatorTimers['heartBeat'] == self.communicatorTimerCounts['heartBeat']:
|
||||||
logger.debug('Communicator heartbeat')
|
logger.debug('Communicator heartbeat')
|
||||||
heartBeatTimer = 0
|
self.communicatorTimerCounts['heartBeat'] = 0
|
||||||
if blockProcessTimer == blockProcessAmount:
|
if self.communicatorTimers['blockProcess'] == self.communicatorTimerCounts['blockProcess']:
|
||||||
self.lookupBlocks()
|
lT1 = threading.Thread(target=self.lookupBlocks, name="lt1", args=(True,))
|
||||||
self.processBlocks()
|
lT2 = threading.Thread(target=self.lookupBlocks, name="lt2", args=(True,))
|
||||||
blockProcessTimer = 0
|
lT3 = threading.Thread(target=self.lookupBlocks, name="lt3", args=(True,))
|
||||||
|
lT4 = threading.Thread(target=self.lookupBlocks, name="lt4", args=(True,))
|
||||||
|
pbT1 = threading.Thread(target=self.processBlocks, name='pbT1', args=(True,))
|
||||||
|
pbT2 = threading.Thread(target=self.processBlocks, name='pbT2', args=(True,))
|
||||||
|
pbT3 = threading.Thread(target=self.processBlocks, name='pbT3', args=(True,))
|
||||||
|
pbT4 = threading.Thread(target=self.processBlocks, name='pbT4', args=(True,))
|
||||||
|
if (self.maxThreads - 8) >= threading.active_count():
|
||||||
|
lT1.start()
|
||||||
|
lT2.start()
|
||||||
|
lT3.start()
|
||||||
|
lT4.start()
|
||||||
|
pbT1.start()
|
||||||
|
pbT2.start()
|
||||||
|
pbT3.start()
|
||||||
|
pbT4.start()
|
||||||
|
self.communicatorTimerCounts['blockProcess'] = 0
|
||||||
|
else:
|
||||||
|
logger.debug(threading.active_count())
|
||||||
|
logger.debug('Too many threads.')
|
||||||
if command != False:
|
if command != False:
|
||||||
if command[0] == 'shutdown':
|
if command[0] == 'shutdown':
|
||||||
logger.info('Daemon received exit command.', timestamp=True)
|
logger.info('Daemon received exit command.', timestamp=True)
|
||||||
|
@ -114,6 +133,8 @@ class OnionrCommunicate:
|
||||||
elif command[0] == 'runCheck':
|
elif command[0] == 'runCheck':
|
||||||
logger.info('Status check; looks good.')
|
logger.info('Status check; looks good.')
|
||||||
open('data/.runcheck', 'w+').close()
|
open('data/.runcheck', 'w+').close()
|
||||||
|
elif command[0] == 'kex':
|
||||||
|
self.pexCount = pexTimer - 1
|
||||||
elif command[0] == 'event':
|
elif command[0] == 'event':
|
||||||
# todo
|
# todo
|
||||||
pass
|
pass
|
||||||
|
@ -165,6 +186,28 @@ class OnionrCommunicate:
|
||||||
connection_handlers = {}
|
connection_handlers = {}
|
||||||
id_peer_cache = {}
|
id_peer_cache = {}
|
||||||
|
|
||||||
|
def registerTimer(self, timerName, rate, timerFunc=None):
|
||||||
|
'''Register a communicator timer'''
|
||||||
|
self.communicatorTimers[timerName] = rate
|
||||||
|
self.communicatorTimerCounts[timerName] = 0
|
||||||
|
self.communicatorTimerFuncs[timerName] = timerFunc
|
||||||
|
|
||||||
|
def timerTick(self):
|
||||||
|
'''Increments timers "ticks" and calls funcs if applicable'''
|
||||||
|
tName = ''
|
||||||
|
for i in self.communicatorTimers.items():
|
||||||
|
tName = i[0]
|
||||||
|
self.communicatorTimerCounts[tName] += 1
|
||||||
|
|
||||||
|
if self.communicatorTimerCounts[tName] == self.communicatorTimers[tName]:
|
||||||
|
try:
|
||||||
|
self.communicatorTimerFuncs[tName]()
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.communicatorTimerCounts[tName] = 0
|
||||||
|
|
||||||
|
|
||||||
def get_connection_handlers(self, name = None):
|
def get_connection_handlers(self, name = None):
|
||||||
'''
|
'''
|
||||||
Returns a list of callback handlers by name, or, if name is None, it returns all handlers.
|
Returns a list of callback handlers by name, or, if name is None, it returns all handlers.
|
||||||
|
@ -174,7 +217,7 @@ class OnionrCommunicate:
|
||||||
return self.connection_handlers
|
return self.connection_handlers
|
||||||
elif name in self.connection_handlers:
|
elif name in self.connection_handlers:
|
||||||
return self.connection_handlers[name]
|
return self.connection_handlers[name]
|
||||||
else
|
else:
|
||||||
return list()
|
return list()
|
||||||
|
|
||||||
def add_connection_handler(self, name, handler):
|
def add_connection_handler(self, name, handler):
|
||||||
|
@ -274,7 +317,7 @@ class OnionrCommunicate:
|
||||||
|
|
||||||
events.event('outgoing_direct_connection', data = {'callback' : True, 'communicator' : self, 'data' : data, 'id' : identifier, 'token' : token, 'peer' : peer, 'callback' : callback, 'log' : log})
|
events.event('outgoing_direct_connection', data = {'callback' : True, 'communicator' : self, 'data' : data, 'id' : identifier, 'token' : token, 'peer' : peer, 'callback' : callback, 'log' : log})
|
||||||
|
|
||||||
logger.debug('Direct connection (identifier: "%s"): %s' + (identifier, data_str))
|
logger.debug('Direct connection (identifier: "%s"): %s' % (identifier, data_str))
|
||||||
try:
|
try:
|
||||||
self.performGet('directMessage', peer, data_str)
|
self.performGet('directMessage', peer, data_str)
|
||||||
except:
|
except:
|
||||||
|
@ -360,10 +403,10 @@ class OnionrCommunicate:
|
||||||
|
|
||||||
def getNewPeers(self):
|
def getNewPeers(self):
|
||||||
'''
|
'''
|
||||||
Get new peers and keys
|
Get new peers and ed25519 keys
|
||||||
'''
|
'''
|
||||||
|
|
||||||
peersCheck = 5 # Amount of peers to ask for new peers + keys
|
peersCheck = 1 # Amount of peers to ask for new peers + keys
|
||||||
peersChecked = 0
|
peersChecked = 0
|
||||||
peerList = list(self._core.listAdders()) # random ordered list of peers
|
peerList = list(self._core.listAdders()) # random ordered list of peers
|
||||||
newKeys = []
|
newKeys = []
|
||||||
|
@ -380,40 +423,49 @@ class OnionrCommunicate:
|
||||||
while peersCheck > peersChecked:
|
while peersCheck > peersChecked:
|
||||||
#i = secrets.randbelow(maxN) # cant use prior to 3.6
|
#i = secrets.randbelow(maxN) # cant use prior to 3.6
|
||||||
i = random.randint(0, maxN)
|
i = random.randint(0, maxN)
|
||||||
logger.info('Using ' + peerList[i] + ' to find new peers', timestamp=True)
|
|
||||||
|
try:
|
||||||
|
if self.peerStatusTaken(peerList[i], 'pex') or self.peerStatusTaken(peerList[i], 'kex'):
|
||||||
|
continue
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logger.info('Using %s to find new peers...' % peerList[i], timestamp=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
newAdders = self.performGet('pex', peerList[i], skipHighFailureAddress=True)
|
newAdders = self.performGet('pex', peerList[i], skipHighFailureAddress=True)
|
||||||
logger.debug('Attempting to merge address: ')
|
if not newAdders is False: # keep the is False thing in there, it might not be bool
|
||||||
logger.debug(newAdders)
|
logger.debug('Attempting to merge address: %s' % str(newAdders))
|
||||||
self._utils.mergeAdders(newAdders)
|
self._utils.mergeAdders(newAdders)
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
logger.info(peerList[i] + ' connection failed', timestamp=True)
|
logger.info('%s connection failed' % peerList[i], timestamp=True)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
logger.info('Using ' + peerList[i] + ' to find new keys')
|
logger.info('Using %s to find new keys...' % peerList[i])
|
||||||
newKeys = self.performGet('kex', peerList[i], skipHighFailureAddress=True)
|
newKeys = self.performGet('kex', peerList[i], skipHighFailureAddress=True)
|
||||||
logger.debug('Attempting to merge pubkey: ')
|
logger.debug('Attempting to merge pubkey: %s' % str(newKeys))
|
||||||
logger.debug(newKeys)
|
|
||||||
# TODO: Require keys to come with POW token (very large amount of POW)
|
# TODO: Require keys to come with POW token (very large amount of POW)
|
||||||
self._utils.mergeKeys(newKeys)
|
self._utils.mergeKeys(newKeys)
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
logger.info(peerList[i] + ' connection failed', timestamp=True)
|
logger.info('%s connection failed' % peerList[i], timestamp=True)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
peersChecked += 1
|
peersChecked += 1
|
||||||
return
|
return
|
||||||
|
|
||||||
def lookupBlocks(self):
|
def lookupBlocks(self, isThread=False):
|
||||||
'''
|
'''
|
||||||
Lookup blocks and merge new ones
|
Lookup blocks and merge new ones
|
||||||
'''
|
'''
|
||||||
|
if isThread:
|
||||||
|
self.lookupBlocksThreads += 1
|
||||||
peerList = self._core.listAdders()
|
peerList = self._core.listAdders()
|
||||||
blocks = ''
|
blockList = list()
|
||||||
|
|
||||||
for i in peerList:
|
for i in peerList:
|
||||||
|
if self.peerStatusTaken(i, 'getBlockHashes') or self.peerStatusTaken(i, 'getDBHash'):
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
if self.peerData[i]['failCount'] >= self.highFailureAmount:
|
if self.peerData[i]['failCount'] >= self.highFailureAmount:
|
||||||
continue
|
continue
|
||||||
|
@ -423,66 +475,70 @@ class OnionrCommunicate:
|
||||||
lastDB = self._core.getAddressInfo(i, 'DBHash')
|
lastDB = self._core.getAddressInfo(i, 'DBHash')
|
||||||
|
|
||||||
if lastDB == None:
|
if lastDB == None:
|
||||||
logger.debug('Fetching hash from ' + str(i) + ', no previous known.')
|
logger.debug('Fetching hash from %s, no previous known.' % str(i))
|
||||||
else:
|
else:
|
||||||
logger.debug('Fetching hash from ' + str(i) + ', ' + str(lastDB) + ' last known')
|
logger.debug('Fetching hash from %s, %s last known' % (str(i), str(lastDB)))
|
||||||
|
|
||||||
currentDB = self.performGet('getDBHash', i)
|
currentDB = self.performGet('getDBHash', i)
|
||||||
|
|
||||||
if currentDB != False:
|
if currentDB != False:
|
||||||
logger.debug(i + " hash db (from request): " + currentDB)
|
logger.debug('%s hash db (from request): %s' % (str(i), str(currentDB)))
|
||||||
else:
|
else:
|
||||||
logger.warn("Error getting hash db status for " + i)
|
logger.warn('Failed to get hash db status for %s' % str(i))
|
||||||
|
|
||||||
if currentDB != False:
|
if currentDB != False:
|
||||||
if lastDB != currentDB:
|
if lastDB != currentDB:
|
||||||
logger.debug('Fetching hash from ' + i + ' - ' + currentDB + ' current hash.')
|
logger.debug('Fetching hash from %s - %s current hash.' % (str(i), currentDB))
|
||||||
try:
|
try:
|
||||||
blocks += self.performGet('getBlockHashes', i)
|
blockList.append(self.performGet('getBlockHashes', i))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
logger.warn('Failed to get data hash from ' + i)
|
logger.warn('Failed to get data hash from %s' % str(i))
|
||||||
self.peerData[i]['failCount'] -= 1
|
self.peerData[i]['failCount'] -= 1
|
||||||
if self._utils.validateHash(currentDB):
|
if self._utils.validateHash(currentDB):
|
||||||
self._core.setAddressInfo(i, "DBHash", currentDB)
|
self._core.setAddressInfo(i, "DBHash", currentDB)
|
||||||
|
|
||||||
if len(blocks.strip()) != 0:
|
if len(blockList) != 0:
|
||||||
pass
|
pass
|
||||||
#logger.debug('BLOCKS:' + blocks)
|
|
||||||
|
|
||||||
blockList = blocks.split('\n')
|
|
||||||
|
|
||||||
for i in blockList:
|
for i in blockList:
|
||||||
if len(i.strip()) == 0:
|
if len(i.strip()) == 0:
|
||||||
continue
|
continue
|
||||||
|
try:
|
||||||
if self._utils.hasBlock(i):
|
if self._utils.hasBlock(i):
|
||||||
continue
|
continue
|
||||||
|
except:
|
||||||
|
logger.warn('Invalid hash') # TODO: move below validate hash check below
|
||||||
|
pass
|
||||||
if i in self.ignoredHashes:
|
if i in self.ignoredHashes:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
#logger.debug('Exchanged block (blockList): ' + i)
|
#logger.debug('Exchanged block (blockList): ' + i)
|
||||||
if not self._utils.validateHash(i):
|
if not self._utils.validateHash(i):
|
||||||
# skip hash if it isn't valid
|
# skip hash if it isn't valid
|
||||||
logger.warn('Hash ' + i + ' is not valid')
|
logger.warn('Hash %s is not valid' % str(i))
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
self.newHashes[i] = 0
|
self.newHashes[i] = 0
|
||||||
logger.debug('Adding ' + i + ' to hash database...')
|
logger.debug('Adding %s to hash database...' % str(i))
|
||||||
self._core.addToBlockDB(i)
|
self._core.addToBlockDB(i)
|
||||||
|
self.lookupBlocksThreads -= 1
|
||||||
return
|
return
|
||||||
|
|
||||||
def processBlocks(self):
|
def processBlocks(self, isThread=False):
|
||||||
'''
|
'''
|
||||||
Work with the block database and download any missing blocks
|
Work with the block database and download any missing blocks
|
||||||
|
|
||||||
This is meant to be called from the communicator daemon on its timer.
|
This is meant to be called from the communicator daemon on its timer.
|
||||||
'''
|
'''
|
||||||
|
if isThread:
|
||||||
for i in self._core.getBlockList(unsaved=True).split("\n"):
|
self.processBlocksThreads += 1
|
||||||
|
for i in self._core.getBlockList(unsaved = True):
|
||||||
if i != "":
|
if i != "":
|
||||||
if i in self.ignoredHashes:
|
if i in self.blocksProcessing or i in self.ignoredHashes:
|
||||||
|
#logger.debug('already processing ' + i)
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
|
self.blocksProcessing.append(i)
|
||||||
try:
|
try:
|
||||||
self.newHashes[i]
|
self.newHashes[i]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -490,61 +546,78 @@ class OnionrCommunicate:
|
||||||
|
|
||||||
# check if a new hash has been around too long, delete it from database and add it to ignore list
|
# check if a new hash has been around too long, delete it from database and add it to ignore list
|
||||||
if self.newHashes[i] >= self.keepNewHash:
|
if self.newHashes[i] >= self.keepNewHash:
|
||||||
logger.warn('Ignoring block ' + i + ' because it took to long to get valid data.')
|
logger.warn('Ignoring block %s because it took to long to get valid data.' % str(i))
|
||||||
del self.newHashes[i]
|
del self.newHashes[i]
|
||||||
self._core.removeBlock(i)
|
self._core.removeBlock(i)
|
||||||
self.ignoredHashes.append(i)
|
self.ignoredHashes.append(i)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.newHashes[i] += 1
|
self.newHashes[i] += 1
|
||||||
logger.warn('UNSAVED BLOCK: ' + i)
|
logger.warn('Block is unsaved: %s' % str(i))
|
||||||
data = self.downloadBlock(i)
|
data = self.downloadBlock(i)
|
||||||
|
|
||||||
# if block was successfull gotten (hash already verified)
|
# if block was successfully gotten (hash already verified)
|
||||||
if data:
|
if data:
|
||||||
del self.newHashes[i] # remove from probation list
|
del self.newHashes[i] # remove from probation list
|
||||||
|
|
||||||
# deal with block metadata
|
# deal with block metadata
|
||||||
blockContent = self._core.getData(i)
|
blockContent = self._core.getData(i)
|
||||||
try:
|
try:
|
||||||
#blockMetadata = json.loads(self._core.getData(i)).split('}')[0] + '}'
|
blockContent = blockContent.encode()
|
||||||
blockMetadata = self._core.getData(i).split(b'}')[0]
|
except AttributeError:
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
blockMetadata = blockMetadata.decode()
|
#blockMetadata = json.loads(self._core.getData(i)).split('}')[0] + '}'
|
||||||
|
blockMetadata = json.loads(blockContent[:blockContent.find(b'\n')].decode())
|
||||||
|
try:
|
||||||
|
blockMeta2 = json.loads(blockMetadata['meta'])
|
||||||
|
except KeyError:
|
||||||
|
blockMeta2 = {'type': ''}
|
||||||
|
pass
|
||||||
|
blockContent = blockContent[blockContent.find(b'\n') + 1:]
|
||||||
|
try:
|
||||||
|
blockContent = blockContent.decode()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
blockMetadata = json.loads(blockMetadata + '}')
|
if not self._crypto.verifyPow(blockContent, blockMeta2):
|
||||||
|
logger.warn("%s has invalid or insufficient proof of work token, deleting..." % str(i))
|
||||||
try:
|
self._core.removeBlock(i)
|
||||||
blockMetadata['sig']
|
continue
|
||||||
blockMetadata['id']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
creator = self._utils.getPeerByHashId(blockMetadata['id'])
|
if (('sig' in blockMetadata) and ('id' in blockMeta2)): # id doesn't exist in blockMeta2, so this won't workin the first place
|
||||||
|
|
||||||
|
#blockData = json.dumps(blockMetadata['meta']) + blockMetadata[blockMetadata.rfind(b'}') + 1:]
|
||||||
|
|
||||||
|
creator = self._utils.getPeerByHashId(blockMeta2['id'])
|
||||||
try:
|
try:
|
||||||
creator = creator.decode()
|
creator = creator.decode()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if self._core._crypto.edVerify(blockContent.split(b'}')[1], creator, blockMetadata['sig'], encodedData=True):
|
if self._core._crypto.edVerify(blockMetadata['meta'] + blockContent, creator, blockMetadata['sig'], encodedData=True):
|
||||||
|
logger.info('%s was signed' % str(i))
|
||||||
self._core.updateBlockInfo(i, 'sig', 'true')
|
self._core.updateBlockInfo(i, 'sig', 'true')
|
||||||
else:
|
else:
|
||||||
|
logger.warn('%s has an invalid signature' % str(i))
|
||||||
self._core.updateBlockInfo(i, 'sig', 'false')
|
self._core.updateBlockInfo(i, 'sig', 'false')
|
||||||
try:
|
try:
|
||||||
logger.info('Block type is ' + blockMetadata['type'])
|
logger.info('Block type is %s' % str(blockMeta2['type']))
|
||||||
self._core.updateBlockInfo(i, 'dataType', blockMetadata['type'])
|
self._core.updateBlockInfo(i, 'dataType', blockMeta2['type'])
|
||||||
|
self.removeBlockFromProcessingList(i)
|
||||||
|
self.removeBlockFromProcessingList(i)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.warn('Block has no type')
|
logger.warn('Block has no type')
|
||||||
pass
|
pass
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
logger.warn('Could not decode block metadata')
|
logger.warn('Could not decode block metadata')
|
||||||
pass
|
self.removeBlockFromProcessingList(i)
|
||||||
|
self.processBlocksThreads -= 1
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def removeBlockFromProcessingList(self, block):
|
||||||
|
return block in blocksProcessing
|
||||||
|
|
||||||
def downloadBlock(self, hash, peerTries=3):
|
def downloadBlock(self, hash, peerTries=3):
|
||||||
'''
|
'''
|
||||||
Download a block from random order of peers
|
Download a block from random order of peers
|
||||||
|
@ -556,8 +629,11 @@ class OnionrCommunicate:
|
||||||
peerTryCount = 0
|
peerTryCount = 0
|
||||||
|
|
||||||
for i in peerList:
|
for i in peerList:
|
||||||
|
try:
|
||||||
if self.peerData[i]['failCount'] >= self.highFailureAmount:
|
if self.peerData[i]['failCount'] >= self.highFailureAmount:
|
||||||
continue
|
continue
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
if peerTryCount >= peerTries:
|
if peerTryCount >= peerTries:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -581,17 +657,11 @@ class OnionrCommunicate:
|
||||||
|
|
||||||
if digest == hash.strip():
|
if digest == hash.strip():
|
||||||
self._core.setData(data)
|
self._core.setData(data)
|
||||||
logger.info('Successfully obtained data for ' + hash, timestamp=True)
|
logger.info('Successfully obtained data for %s' % str(hash), timestamp=True)
|
||||||
retVal = True
|
retVal = True
|
||||||
break
|
break
|
||||||
'''
|
|
||||||
if data.startswith(b'-txt-'):
|
|
||||||
self._core.setBlockType(hash, 'txt')
|
|
||||||
if len(data) < 120:
|
|
||||||
logger.debug('Block text:\n' + data.decode())
|
|
||||||
'''
|
|
||||||
else:
|
else:
|
||||||
logger.warn("Failed to validate " + hash + " " + " hash calculated was " + digest)
|
logger.warn("Failed to validate %s -- hash calculated was %s" % (hash, digest))
|
||||||
peerTryCount += 1
|
peerTryCount += 1
|
||||||
|
|
||||||
return retVal
|
return retVal
|
||||||
|
@ -614,12 +684,12 @@ class OnionrCommunicate:
|
||||||
raise Exception("Could not perform self address check in performGet due to not knowing our address")
|
raise Exception("Could not perform self address check in performGet due to not knowing our address")
|
||||||
if selfCheck:
|
if selfCheck:
|
||||||
if peer.replace('/', '') == self._core.hsAdder:
|
if peer.replace('/', '') == self._core.hsAdder:
|
||||||
logger.warn('Tried to performget to own hidden service, but selfCheck was not set to false')
|
logger.warn('Tried to performGet to own hidden service, but selfCheck was not set to false')
|
||||||
return
|
return
|
||||||
|
|
||||||
# Store peer in peerData dictionary (non permanent)
|
# Store peer in peerData dictionary (non permanent)
|
||||||
if not peer in self.peerData:
|
if not peer in self.peerData:
|
||||||
self.peerData[peer] = {'connectCount': 0, 'failCount': 0, 'lastConnectTime': math.floor(time.time())}
|
self.peerData[peer] = {'connectCount': 0, 'failCount': 0, 'lastConnectTime': self._utils.getEpoch()}
|
||||||
socksPort = sys.argv[2]
|
socksPort = sys.argv[2]
|
||||||
'''We use socks5h to use tor as DNS'''
|
'''We use socks5h to use tor as DNS'''
|
||||||
proxies = {'http': 'socks5://127.0.0.1:' + str(socksPort), 'https': 'socks5://127.0.0.1:' + str(socksPort)}
|
proxies = {'http': 'socks5://127.0.0.1:' + str(socksPort), 'https': 'socks5://127.0.0.1:' + str(socksPort)}
|
||||||
|
@ -630,13 +700,14 @@ class OnionrCommunicate:
|
||||||
try:
|
try:
|
||||||
if skipHighFailureAddress and self.peerData[peer]['failCount'] > self.highFailureAmount:
|
if skipHighFailureAddress and self.peerData[peer]['failCount'] > self.highFailureAmount:
|
||||||
retData = False
|
retData = False
|
||||||
logger.debug('Skipping ' + peer + ' because of high failure rate')
|
logger.debug('Skipping %s because of high failure rate.' % peer)
|
||||||
else:
|
else:
|
||||||
logger.debug('Contacting ' + peer + ' on port ' + socksPort)
|
self.peerStatus[peer] = action
|
||||||
|
logger.debug('Contacting %s on port %s' % (peer, str(socksPort)))
|
||||||
r = requests.get(url, headers=headers, proxies=proxies, timeout=(15, 30))
|
r = requests.get(url, headers=headers, proxies=proxies, timeout=(15, 30))
|
||||||
retData = r.text
|
retData = r.text
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
logger.warn(action + " failed with peer " + peer + ": " + str(e))
|
logger.debug("%s failed with peer %s" % (action, peer))
|
||||||
retData = False
|
retData = False
|
||||||
|
|
||||||
if not retData:
|
if not retData:
|
||||||
|
@ -644,9 +715,20 @@ class OnionrCommunicate:
|
||||||
else:
|
else:
|
||||||
self.peerData[peer]['connectCount'] += 1
|
self.peerData[peer]['connectCount'] += 1
|
||||||
self.peerData[peer]['failCount'] -= 1
|
self.peerData[peer]['failCount'] -= 1
|
||||||
self.peerData[peer]['lastConnectTime'] = math.floor(time.time())
|
self.peerData[peer]['lastConnectTime'] = self._utils.getEpoch()
|
||||||
|
self._core.setAddressInfo(peer, 'lastConnect', self._utils.getEpoch())
|
||||||
return retData
|
return retData
|
||||||
|
|
||||||
|
def peerStatusTaken(self, peer, status):
|
||||||
|
'''
|
||||||
|
Returns if we are currently performing a specific action with a peer.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
if self.peerStatus[peer] == status:
|
||||||
|
return True
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
shouldRun = False
|
shouldRun = False
|
||||||
debug = True
|
debug = True
|
||||||
|
|
114
onionr/core.py
114
onionr/core.py
|
@ -17,11 +17,11 @@
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
'''
|
'''
|
||||||
import sqlite3, os, sys, time, math, base64, tarfile, getpass, simplecrypt, hashlib, nacl, logger, json, netcontroller
|
import sqlite3, os, sys, time, math, base64, tarfile, getpass, simplecrypt, hashlib, nacl, logger, json, netcontroller, math
|
||||||
#from Crypto.Cipher import AES
|
#from Crypto.Cipher import AES
|
||||||
#from Crypto import Random
|
#from Crypto import Random
|
||||||
|
|
||||||
import onionrutils, onionrcrypto, btc, onionrevents as events
|
import onionrutils, onionrcrypto, onionrproofs, onionrevents as events
|
||||||
|
|
||||||
if sys.version_info < (3, 6):
|
if sys.version_info < (3, 6):
|
||||||
try:
|
try:
|
||||||
|
@ -73,22 +73,24 @@ class Core:
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error('Failed to initialize core Onionr library.', error=error)
|
logger.error('Failed to initialize core Onionr library.', error=error)
|
||||||
logger.fatal('Cannot recover from error.')
|
logger.fatal('Cannot recover from error.')
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
return
|
return
|
||||||
|
|
||||||
def addPeer(self, peerID, name=''):
|
def addPeer(self, peerID, powID, name=''):
|
||||||
'''
|
'''
|
||||||
Adds a public key to the key database (misleading function name)
|
Adds a public key to the key database (misleading function name)
|
||||||
|
|
||||||
DOES NO SAFETY CHECKS if the ID is valid, but prepares the insertion
|
|
||||||
'''
|
'''
|
||||||
# This function simply adds a peer to the DB
|
# This function simply adds a peer to the DB
|
||||||
if not self._utils.validatePubKey(peerID):
|
if not self._utils.validatePubKey(peerID):
|
||||||
return False
|
return False
|
||||||
|
if sys.getsizeof(powID) > 60:
|
||||||
|
logger.warn("POW token for pubkey base64 representation exceeded 60 bytes")
|
||||||
|
return False
|
||||||
|
|
||||||
conn = sqlite3.connect(self.peerDB)
|
conn = sqlite3.connect(self.peerDB)
|
||||||
hashID = self._crypto.pubKeyHashID(peerID)
|
hashID = self._crypto.pubKeyHashID(peerID)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
t = (peerID, name, 'unknown', hashID)
|
t = (peerID, name, 'unknown', hashID, powID)
|
||||||
|
|
||||||
for i in c.execute("SELECT * FROM PEERS where id = '" + peerID + "';"):
|
for i in c.execute("SELECT * FROM PEERS where id = '" + peerID + "';"):
|
||||||
try:
|
try:
|
||||||
|
@ -99,7 +101,7 @@ class Core:
|
||||||
pass
|
pass
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
c.execute('INSERT INTO peers (id, name, dateSeen, hashID) VALUES(?, ?, ?, ?);', t)
|
c.execute('INSERT INTO peers (id, name, dateSeen, pow, hashID) VALUES(?, ?, ?, ?, ?);', t)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
@ -189,7 +191,8 @@ class Core:
|
||||||
speed int,
|
speed int,
|
||||||
success int,
|
success int,
|
||||||
DBHash text,
|
DBHash text,
|
||||||
failure int
|
failure int,
|
||||||
|
lastConnect int
|
||||||
);
|
);
|
||||||
''')
|
''')
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
@ -212,7 +215,8 @@ class Core:
|
||||||
bytesStored int,
|
bytesStored int,
|
||||||
trust int,
|
trust int,
|
||||||
pubkeyExchanged int,
|
pubkeyExchanged int,
|
||||||
hashID);
|
hashID text,
|
||||||
|
pow text not null);
|
||||||
''')
|
''')
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
@ -251,7 +255,7 @@ class Core:
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def addToBlockDB(self, newHash, selfInsert=False):
|
def addToBlockDB(self, newHash, selfInsert=False, dataSaved=False):
|
||||||
'''
|
'''
|
||||||
Add a hash value to the block db
|
Add a hash value to the block db
|
||||||
|
|
||||||
|
@ -263,8 +267,8 @@ class Core:
|
||||||
return
|
return
|
||||||
conn = sqlite3.connect(self.blockDB)
|
conn = sqlite3.connect(self.blockDB)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
currentTime = math.floor(time.time())
|
currentTime = self._utils.getEpoch()
|
||||||
if selfInsert:
|
if selfInsert or dataSaved:
|
||||||
selfInsert = 1
|
selfInsert = 1
|
||||||
else:
|
else:
|
||||||
selfInsert = 0
|
selfInsert = 0
|
||||||
|
@ -280,6 +284,7 @@ class Core:
|
||||||
Simply return the data associated to a hash
|
Simply return the data associated to a hash
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
|
# logger.debug('Opening %s' % (str(self.blockDataLocation) + str(hash) + '.dat'))
|
||||||
dataFile = open(self.blockDataLocation + hash + '.dat', 'rb')
|
dataFile = open(self.blockDataLocation + hash + '.dat', 'rb')
|
||||||
data = dataFile.read()
|
data = dataFile.read()
|
||||||
dataFile.close()
|
dataFile.close()
|
||||||
|
@ -387,7 +392,7 @@ class Core:
|
||||||
Add a command to the daemon queue, used by the communication daemon (communicator.py)
|
Add a command to the daemon queue, used by the communication daemon (communicator.py)
|
||||||
'''
|
'''
|
||||||
# Intended to be used by the web server
|
# Intended to be used by the web server
|
||||||
date = math.floor(time.time())
|
date = self._utils.getEpoch()
|
||||||
conn = sqlite3.connect(self.queueDB)
|
conn = sqlite3.connect(self.queueDB)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
t = (command, data, date)
|
t = (command, data, date)
|
||||||
|
@ -431,7 +436,7 @@ class Core:
|
||||||
conn.close()
|
conn.close()
|
||||||
return addressList
|
return addressList
|
||||||
|
|
||||||
def listPeers(self, randomOrder=True):
|
def listPeers(self, randomOrder=True, getPow=False):
|
||||||
'''
|
'''
|
||||||
Return a list of public keys (misleading function name)
|
Return a list of public keys (misleading function name)
|
||||||
|
|
||||||
|
@ -448,9 +453,18 @@ class Core:
|
||||||
for i in c.execute(payload):
|
for i in c.execute(payload):
|
||||||
try:
|
try:
|
||||||
if len(i[0]) != 0:
|
if len(i[0]) != 0:
|
||||||
|
if getPow:
|
||||||
|
peerList.append(i[0] + '-' + i[1])
|
||||||
|
else:
|
||||||
peerList.append(i[0])
|
peerList.append(i[0])
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
if getPow:
|
||||||
|
try:
|
||||||
|
peerList.append(self._crypto.pubKey + '-' + self._crypto.pubKeyPowToken)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
peerList.append(self._crypto.pubKey)
|
peerList.append(self._crypto.pubKey)
|
||||||
conn.close()
|
conn.close()
|
||||||
return peerList
|
return peerList
|
||||||
|
@ -513,11 +527,12 @@ class Core:
|
||||||
success int, 4
|
success int, 4
|
||||||
DBHash text, 5
|
DBHash text, 5
|
||||||
failure int 6
|
failure int 6
|
||||||
|
lastConnect 7
|
||||||
'''
|
'''
|
||||||
conn = sqlite3.connect(self.addressDB)
|
conn = sqlite3.connect(self.addressDB)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
command = (address,)
|
command = (address,)
|
||||||
infoNumbers = {'address': 0, 'type': 1, 'knownPeer': 2, 'speed': 3, 'success': 4, 'DBHash': 5, 'failure': 6}
|
infoNumbers = {'address': 0, 'type': 1, 'knownPeer': 2, 'speed': 3, 'success': 4, 'DBHash': 5, 'failure': 6, 'lastConnect': 7}
|
||||||
info = infoNumbers[info]
|
info = infoNumbers[info]
|
||||||
iterCount = 0
|
iterCount = 0
|
||||||
retVal = ''
|
retVal = ''
|
||||||
|
@ -539,7 +554,7 @@ class Core:
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
command = (data, address)
|
command = (data, address)
|
||||||
# TODO: validate key on whitelist
|
# TODO: validate key on whitelist
|
||||||
if key not in ('address', 'type', 'knownPeer', 'speed', 'success', 'DBHash', 'failure'):
|
if key not in ('address', 'type', 'knownPeer', 'speed', 'success', 'DBHash', 'failure', 'lastConnect'):
|
||||||
raise Exception("Got invalid database key when setting address info")
|
raise Exception("Got invalid database key when setting address info")
|
||||||
c.execute('UPDATE adders SET ' + key + ' = ? WHERE address=?', command)
|
c.execute('UPDATE adders SET ' + key + ' = ? WHERE address=?', command)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
@ -565,22 +580,36 @@ class Core:
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def getBlockList(self, unsaved = False):
|
def getBlockList(self, unsaved = False): # TODO: Use unsaved
|
||||||
'''
|
'''
|
||||||
Get list of our blocks
|
Get list of our blocks
|
||||||
'''
|
'''
|
||||||
conn = sqlite3.connect(self.blockDB)
|
conn = sqlite3.connect(self.blockDB)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
retData = ''
|
|
||||||
if unsaved:
|
if unsaved:
|
||||||
execute = 'SELECT hash FROM hashes WHERE dataSaved != 1 ORDER BY RANDOM();'
|
execute = 'SELECT hash FROM hashes WHERE dataSaved != 1 ORDER BY RANDOM();'
|
||||||
else:
|
else:
|
||||||
execute = 'SELECT hash FROM hashes ORDER BY RANDOM();'
|
execute = 'SELECT hash FROM hashes ORDER BY RANDOM();'
|
||||||
|
rows = list()
|
||||||
for row in c.execute(execute):
|
for row in c.execute(execute):
|
||||||
for i in row:
|
for i in row:
|
||||||
retData += i + "\n"
|
rows.append(i)
|
||||||
|
|
||||||
return retData
|
return rows
|
||||||
|
|
||||||
|
def getBlockDate(self, blockHash):
|
||||||
|
'''
|
||||||
|
Returns the date a block was received
|
||||||
|
'''
|
||||||
|
conn = sqlite3.connect(self.blockDB)
|
||||||
|
c = conn.cursor()
|
||||||
|
execute = 'SELECT dateReceived FROM hashes WHERE hash=?;'
|
||||||
|
args = (blockHash,)
|
||||||
|
for row in c.execute(execute, args):
|
||||||
|
for i in row:
|
||||||
|
return int(i)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def getBlocksByType(self, blockType):
|
def getBlocksByType(self, blockType):
|
||||||
'''
|
'''
|
||||||
|
@ -588,14 +617,14 @@ class Core:
|
||||||
'''
|
'''
|
||||||
conn = sqlite3.connect(self.blockDB)
|
conn = sqlite3.connect(self.blockDB)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
retData = ''
|
|
||||||
execute = 'SELECT hash FROM hashes WHERE dataType=?;'
|
execute = 'SELECT hash FROM hashes WHERE dataType=?;'
|
||||||
args = (blockType,)
|
args = (blockType,)
|
||||||
|
rows = list()
|
||||||
for row in c.execute(execute, args):
|
for row in c.execute(execute, args):
|
||||||
for i in row:
|
for i in row:
|
||||||
retData += i + "\n"
|
rows.append(i)
|
||||||
|
|
||||||
return retData.split('\n')
|
return rows
|
||||||
|
|
||||||
def setBlockType(self, hash, blockType):
|
def setBlockType(self, hash, blockType):
|
||||||
'''
|
'''
|
||||||
|
@ -630,32 +659,57 @@ class Core:
|
||||||
Inserts a block into the network
|
Inserts a block into the network
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
powProof = onionrproofs.POW(data)
|
||||||
|
powToken = ''
|
||||||
|
# wait for proof to complete
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
powToken = powProof.getResult()
|
||||||
|
if powToken == False:
|
||||||
|
time.sleep(0.3)
|
||||||
|
continue
|
||||||
|
powHash = powToken[0]
|
||||||
|
powToken = base64.b64encode(powToken[1])
|
||||||
|
try:
|
||||||
|
powToken = powToken.decode()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
break
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.warn("Got keyboard interrupt while working on inserting block, stopping.")
|
||||||
|
powProof.shutdown()
|
||||||
|
return ''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data.decode()
|
data.decode()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
data = data.encode()
|
data = data.encode()
|
||||||
|
|
||||||
retData = ''
|
retData = ''
|
||||||
metadata = {'type': header}
|
metadata = {'type': header, 'powHash': powHash, 'powToken': powToken}
|
||||||
|
sig = {}
|
||||||
|
|
||||||
|
metadata = json.dumps(metadata)
|
||||||
|
metadata = metadata.encode()
|
||||||
|
signature = ''
|
||||||
|
|
||||||
if sign:
|
if sign:
|
||||||
signature = self._crypto.edSign(data, self._crypto.privKey, encodeResult=True)
|
signature = self._crypto.edSign(metadata + b'\n' + data, self._crypto.privKey, encodeResult=True)
|
||||||
ourID = self._crypto.pubKeyHashID()
|
ourID = self._crypto.pubKeyHashID()
|
||||||
# Convert from bytes on some py versions?
|
# Convert from bytes on some py versions?
|
||||||
try:
|
try:
|
||||||
ourID = ourID.decode()
|
ourID = ourID.decode()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
metadata['id'] = ourID
|
metadata = {'sig': signature, 'meta': metadata.decode()}
|
||||||
metadata['sig'] = signature
|
|
||||||
|
|
||||||
metadata = json.dumps(metadata)
|
metadata = json.dumps(metadata)
|
||||||
metadata = metadata.encode()
|
metadata = metadata.encode()
|
||||||
|
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
logger.error('Will not insert empty block')
|
logger.error('Will not insert empty block')
|
||||||
else:
|
else:
|
||||||
addedHash = self.setData(metadata + data)
|
addedHash = self.setData(metadata + b'\n' + data)
|
||||||
self.addToBlockDB(addedHash, selfInsert=True)
|
self.addToBlockDB(addedHash, selfInsert=True)
|
||||||
self.setBlockType(addedHash, header)
|
self.setBlockType(addedHash, header)
|
||||||
retData = addedHash
|
retData = addedHash
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
'''
|
|
||||||
This is the future Onionr plugin manager. TODO: Add better description.
|
|
||||||
'''
|
|
||||||
|
|
||||||
# useful libraries
|
|
||||||
import logger, config
|
|
||||||
import os, sys, json
|
|
||||||
|
|
||||||
plugin_name = 'pluginmanager'
|
|
||||||
|
|
||||||
keys_data = {'keys' : {}}
|
|
||||||
|
|
||||||
# key functions
|
|
||||||
|
|
||||||
def writeKeys():
|
|
||||||
'''
|
|
||||||
Serializes and writes the keystore in memory to file
|
|
||||||
'''
|
|
||||||
|
|
||||||
file = open(keys_file, 'w')
|
|
||||||
file.write(json.dumps(keys_data, indent=4, sort_keys=True))
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
def readKeys():
|
|
||||||
'''
|
|
||||||
Loads the keystore into memory
|
|
||||||
'''
|
|
||||||
|
|
||||||
global keys_data
|
|
||||||
keys_data = json.loads(open(keys_file).read())
|
|
||||||
return keys_data
|
|
||||||
|
|
||||||
def getKey(plugin):
|
|
||||||
'''
|
|
||||||
Returns the public key for a given plugin
|
|
||||||
'''
|
|
||||||
|
|
||||||
readKeys()
|
|
||||||
return (keys_data['keys'][plugin] if plugin in keys_data['keys'] else None)
|
|
||||||
|
|
||||||
def saveKey(plugin, key):
|
|
||||||
'''
|
|
||||||
Saves the public key for a plugin to keystore
|
|
||||||
'''
|
|
||||||
|
|
||||||
keys_data['keys'][plugin] = key
|
|
||||||
writeKeys()
|
|
||||||
|
|
||||||
def check():
|
|
||||||
'''
|
|
||||||
Checks to make sure the keystore file still exists
|
|
||||||
'''
|
|
||||||
|
|
||||||
global keys_file
|
|
||||||
keys_file = pluginapi.plugins.get_data_folder(plugin_name) + 'keystore.json'
|
|
||||||
if not os.path.isfile(keys_file):
|
|
||||||
writeKeys()
|
|
||||||
|
|
||||||
# command handlers
|
|
||||||
|
|
||||||
def help():
|
|
||||||
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin> [public key/block hash]')
|
|
||||||
|
|
||||||
def commandInstallPlugin():
|
|
||||||
logger.warn('This feature is not functional or is still in development.')
|
|
||||||
if len(sys.argv) >= 3:
|
|
||||||
check()
|
|
||||||
|
|
||||||
pluginname = sys.argv[2]
|
|
||||||
pkobh = None # public key or block hash
|
|
||||||
|
|
||||||
if len(sys.argv) >= 4:
|
|
||||||
# public key or block hash specified
|
|
||||||
pkobh = sys.argv[3]
|
|
||||||
else:
|
|
||||||
# none specified, check if in config file
|
|
||||||
pkobh = getKey(pluginname)
|
|
||||||
|
|
||||||
if pkobh is None:
|
|
||||||
logger.error('No key for this plugin found in keystore, please specify.')
|
|
||||||
help()
|
|
||||||
return True
|
|
||||||
|
|
||||||
valid_hash = pluginapi.get_utils().validateHash(pkobh)
|
|
||||||
real_block = False
|
|
||||||
valid_key = pluginapi.get_utils().validatePubKey(pkobh)
|
|
||||||
real_key = False
|
|
||||||
|
|
||||||
if valid_hash:
|
|
||||||
real_block = pluginapi.get_utils().hasBlock(pkobh)
|
|
||||||
elif valid_key:
|
|
||||||
real_key = pluginapi.get_utils().hasKey(pkobh)
|
|
||||||
|
|
||||||
blockhash = None
|
|
||||||
|
|
||||||
if valid_hash and not real_block:
|
|
||||||
logger.error('Block hash not found. Perhaps it has not been synced yet?')
|
|
||||||
logger.debug('Is valid hash, but does not belong to a known block.')
|
|
||||||
return True
|
|
||||||
elif valid_hash and real_block:
|
|
||||||
blockhash = str(pkobh)
|
|
||||||
logger.debug('Using block %s...' % blockhash)
|
|
||||||
elif valid_key and not real_key:
|
|
||||||
logger.error('Public key not found. Try adding the node by address manually, if possible.')
|
|
||||||
logger.debug('Is valid key, but the key is not a known one.')
|
|
||||||
elif valid_key and real_key:
|
|
||||||
publickey = str(pkobh)
|
|
||||||
logger.debug('Using public key %s...' % publickey)
|
|
||||||
|
|
||||||
saveKey(pluginname, pkobh)
|
|
||||||
else:
|
|
||||||
logger.error('Unknown data "%s"; must be public key or block hash.' % str(pkobh))
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
help()
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def commandUninstallPlugin():
|
|
||||||
logger.info('This feature has not been created yet. Please check back later.')
|
|
||||||
return
|
|
||||||
|
|
||||||
def commandSearchPlugin():
|
|
||||||
logger.info('This feature has not been created yet. Please check back later.')
|
|
||||||
return
|
|
||||||
|
|
||||||
# event listeners
|
|
||||||
|
|
||||||
def on_init(api, data = None):
|
|
||||||
global pluginapi
|
|
||||||
pluginapi = api
|
|
||||||
check()
|
|
||||||
|
|
||||||
# register some commands
|
|
||||||
api.commands.register(['install-plugin', 'installplugin', 'plugin-install', 'install', 'plugininstall'], commandInstallPlugin)
|
|
||||||
api.commands.register(['remove-plugin', 'removeplugin', 'plugin-remove', 'uninstall-plugin', 'uninstallplugin', 'plugin-uninstall', 'uninstall', 'remove', 'pluginremove'], commandUninstallPlugin)
|
|
||||||
api.commands.register(['search', 'filter-plugins', 'search-plugins', 'searchplugins', 'search-plugin', 'searchplugin', 'findplugin', 'find-plugin', 'filterplugin', 'plugin-search', 'pluginsearch'], commandSearchPlugin)
|
|
||||||
|
|
||||||
# add help menus once the features are actually implemented
|
|
||||||
|
|
||||||
return
|
|
|
@ -222,7 +222,7 @@ def error(data, error=None, timestamp=True):
|
||||||
if not error is None:
|
if not error is None:
|
||||||
debug('Error: ' + str(error) + parse_error())
|
debug('Error: ' + str(error) + parse_error())
|
||||||
|
|
||||||
# fatal: when the something so bad has happened that the prorgam must stop
|
# fatal: when the something so bad has happened that the program must stop
|
||||||
def fatal(data, timestamp=True):
|
def fatal(data, timestamp=True):
|
||||||
if get_level() <= LEVEL_FATAL:
|
if get_level() <= LEVEL_FATAL:
|
||||||
log('#', data, colors.bg.red + colors.fg.green + colors.bold, timestamp=timestamp)
|
log('#', data, colors.bg.red + colors.fg.green + colors.bold, timestamp=timestamp)
|
||||||
|
|
|
@ -89,6 +89,7 @@ DataDirectory data/tordata/
|
||||||
torVersion.kill()
|
torVersion.kill()
|
||||||
|
|
||||||
# wait for tor to get to 100% bootstrap
|
# wait for tor to get to 100% bootstrap
|
||||||
|
try:
|
||||||
for line in iter(tor.stdout.readline, b''):
|
for line in iter(tor.stdout.readline, b''):
|
||||||
if 'Bootstrapped 100%: Done' in line.decode():
|
if 'Bootstrapped 100%: Done' in line.decode():
|
||||||
break
|
break
|
||||||
|
@ -97,6 +98,9 @@ DataDirectory data/tordata/
|
||||||
else:
|
else:
|
||||||
logger.fatal('Failed to start Tor. Try killing any other Tor processes owned by this user.')
|
logger.fatal('Failed to start Tor. Try killing any other Tor processes owned by this user.')
|
||||||
return False
|
return False
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.fatal("Got keyboard interrupt")
|
||||||
|
return False
|
||||||
|
|
||||||
logger.info('Finished starting Tor', timestamp=True)
|
logger.info('Finished starting Tor', timestamp=True)
|
||||||
self.readyState = True
|
self.readyState = True
|
||||||
|
|
|
@ -25,9 +25,10 @@ import sys
|
||||||
if sys.version_info[0] == 2 or sys.version_info[1] < 5:
|
if sys.version_info[0] == 2 or sys.version_info[1] < 5:
|
||||||
print('Error, Onionr requires Python 3.4+')
|
print('Error, Onionr requires Python 3.4+')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
import os, base64, random, getpass, shutil, subprocess, requests, time, platform, datetime, re
|
import os, base64, random, getpass, shutil, subprocess, requests, time, platform, datetime, re, json, getpass
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import api, core, config, logger, onionrplugins as plugins, onionrevents as events
|
import api, core, config, logger, onionrplugins as plugins, onionrevents as events
|
||||||
|
import onionrutils
|
||||||
from onionrutils import OnionrUtils
|
from onionrutils import OnionrUtils
|
||||||
from netcontroller import NetController
|
from netcontroller import NetController
|
||||||
|
|
||||||
|
@ -64,7 +65,7 @@ class Onionr:
|
||||||
else:
|
else:
|
||||||
# the default config file doesn't exist, try hardcoded config
|
# the default config file doesn't exist, try hardcoded config
|
||||||
config.set_config({'devmode': True, 'log': {'file': {'output': True, 'path': 'data/output.log'}, 'console': {'output': True, 'color': True}}})
|
config.set_config({'devmode': True, 'log': {'file': {'output': True, 'path': 'data/output.log'}, 'console': {'output': True, 'color': True}}})
|
||||||
if not exists:
|
if not data_exists:
|
||||||
config.save()
|
config.save()
|
||||||
config.reload() # this will read the configuration file into memory
|
config.reload() # this will read the configuration file into memory
|
||||||
|
|
||||||
|
@ -108,11 +109,10 @@ class Onionr:
|
||||||
os.mkdir('data/blocks/')
|
os.mkdir('data/blocks/')
|
||||||
|
|
||||||
# Copy default plugins into plugins folder
|
# Copy default plugins into plugins folder
|
||||||
|
|
||||||
if not os.path.exists(plugins.get_plugins_folder()):
|
if not os.path.exists(plugins.get_plugins_folder()):
|
||||||
if os.path.exists('default-plugins/'):
|
if os.path.exists('static-data/default-plugins/'):
|
||||||
names = [f for f in os.listdir("default-plugins/") if not os.path.isfile(f)]
|
names = [f for f in os.listdir("static-data/default-plugins/") if not os.path.isfile(f)]
|
||||||
shutil.copytree('default-plugins/', plugins.get_plugins_folder())
|
shutil.copytree('static-data/default-plugins/', plugins.get_plugins_folder())
|
||||||
|
|
||||||
# Enable plugins
|
# Enable plugins
|
||||||
for name in names:
|
for name in names:
|
||||||
|
@ -134,7 +134,7 @@ class Onionr:
|
||||||
|
|
||||||
# Get configuration
|
# Get configuration
|
||||||
|
|
||||||
if not exists:
|
if not data_exists:
|
||||||
# Generate default config
|
# Generate default config
|
||||||
# Hostname should only be set if different from 127.x.x.x. Important for DNS rebinding attack prevention.
|
# Hostname should only be set if different from 127.x.x.x. Important for DNS rebinding attack prevention.
|
||||||
if self.debug:
|
if self.debug:
|
||||||
|
@ -153,6 +153,8 @@ class Onionr:
|
||||||
'config': self.configure,
|
'config': self.configure,
|
||||||
'start': self.start,
|
'start': self.start,
|
||||||
'stop': self.killDaemon,
|
'stop': self.killDaemon,
|
||||||
|
'status': self.showStats,
|
||||||
|
'statistics': self.showStats,
|
||||||
'stats': self.showStats,
|
'stats': self.showStats,
|
||||||
|
|
||||||
'enable-plugin': self.enablePlugin,
|
'enable-plugin': self.enablePlugin,
|
||||||
|
@ -191,6 +193,8 @@ class Onionr:
|
||||||
'addaddress': self.addAddress,
|
'addaddress': self.addAddress,
|
||||||
'addfile': self.addFile,
|
'addfile': self.addFile,
|
||||||
|
|
||||||
|
'importblocks': self.onionrUtils.importNewBlocks,
|
||||||
|
|
||||||
'introduce': self.onionrCore.introduceNode,
|
'introduce': self.onionrCore.introduceNode,
|
||||||
'connect': self.addAddress
|
'connect': self.addAddress
|
||||||
}
|
}
|
||||||
|
@ -212,11 +216,12 @@ class Onionr:
|
||||||
'pm': 'Adds a private message to block',
|
'pm': 'Adds a private message to block',
|
||||||
'get-pms': 'Shows private messages sent to you',
|
'get-pms': 'Shows private messages sent to you',
|
||||||
'addfile': 'Create an Onionr block from a file',
|
'addfile': 'Create an Onionr block from a file',
|
||||||
|
'importblocks': 'import blocks from the disk (Onionr is transport-agnostic!)',
|
||||||
'introduce': 'Introduce your node to the public Onionr network',
|
'introduce': 'Introduce your node to the public Onionr network',
|
||||||
}
|
}
|
||||||
|
|
||||||
# initialize plugins
|
# initialize plugins
|
||||||
events.event('init', onionr = self)
|
events.event('init', onionr = self, threaded = False)
|
||||||
|
|
||||||
command = ''
|
command = ''
|
||||||
try:
|
try:
|
||||||
|
@ -389,6 +394,7 @@ class Onionr:
|
||||||
addedHash = self.onionrCore.insertBlock(messageToAdd, header='txt')
|
addedHash = self.onionrCore.insertBlock(messageToAdd, header='txt')
|
||||||
#self.onionrCore.addToBlockDB(addedHash, selfInsert=True)
|
#self.onionrCore.addToBlockDB(addedHash, selfInsert=True)
|
||||||
#self.onionrCore.setBlockType(addedHash, 'txt')
|
#self.onionrCore.setBlockType(addedHash, 'txt')
|
||||||
|
if addedHash != '':
|
||||||
logger.info("Message inserted as as block %s" % addedHash)
|
logger.info("Message inserted as as block %s" % addedHash)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -457,7 +463,10 @@ class Onionr:
|
||||||
|
|
||||||
os.makedirs(plugins.get_plugins_folder(plugin_name))
|
os.makedirs(plugins.get_plugins_folder(plugin_name))
|
||||||
with open(plugins.get_plugins_folder(plugin_name) + '/main.py', 'a') as main:
|
with open(plugins.get_plugins_folder(plugin_name) + '/main.py', 'a') as main:
|
||||||
main.write(open('static-data/default_plugin.py').read().replace('$user', os.getlogin()).replace('$date', datetime.datetime.now().strftime('%Y-%m-%d')))
|
main.write(open('static-data/default_plugin.py').read().replace('$user', os.getlogin()).replace('$date', datetime.datetime.now().strftime('%Y-%m-%d')).replace('$name', plugin_name))
|
||||||
|
|
||||||
|
with open(plugins.get_plugins_folder(plugin_name) + '/info.json', 'a') as main:
|
||||||
|
main.write(json.dumps({'author' : 'anonymous', 'description' : 'the default description of the plugin', 'version' : '1.0'}))
|
||||||
|
|
||||||
logger.info('Enabling plugin "%s"...' % plugin_name)
|
logger.info('Enabling plugin "%s"...' % plugin_name)
|
||||||
plugins.enable(plugin_name, self)
|
plugins.enable(plugin_name, self)
|
||||||
|
@ -550,8 +559,54 @@ class Onionr:
|
||||||
Displays statistics and exits
|
Displays statistics and exits
|
||||||
'''
|
'''
|
||||||
|
|
||||||
logger.info('Our pubkey: ' + self.onionrCore._crypto.pubKey)
|
try:
|
||||||
logger.info('Our address: ' + self.get_hostname())
|
# define stats messages here
|
||||||
|
messages = {
|
||||||
|
# info about local client
|
||||||
|
'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if self.onionrUtils.isCommunicatorRunning(timeout = 2) else logger.colors.fg.red + 'Offline'),
|
||||||
|
'Public Key' : self.onionrCore._crypto.pubKey,
|
||||||
|
'Address' : self.get_hostname(),
|
||||||
|
|
||||||
|
# file and folder size stats
|
||||||
|
'div1' : True, # this creates a solid line across the screen, a div
|
||||||
|
'Total Block Size' : onionrutils.humanSize(onionrutils.size('data/blocks/')),
|
||||||
|
'Total Plugin Size' : onionrutils.humanSize(onionrutils.size('data/plugins/')),
|
||||||
|
'Log File Size' : onionrutils.humanSize(onionrutils.size('data/output.log')),
|
||||||
|
|
||||||
|
# count stats
|
||||||
|
'div2' : True,
|
||||||
|
'Known Peers Count' : str(len(self.onionrCore.listPeers())),
|
||||||
|
'Enabled Plugins Count' : str(len(config.get('plugins')['enabled'])) + ' / ' + str(len(os.listdir('data/plugins/')))
|
||||||
|
}
|
||||||
|
|
||||||
|
# color configuration
|
||||||
|
colors = {
|
||||||
|
'title' : logger.colors.bold,
|
||||||
|
'key' : logger.colors.fg.lightgreen,
|
||||||
|
'val' : logger.colors.fg.green,
|
||||||
|
'border' : logger.colors.fg.lightblue,
|
||||||
|
|
||||||
|
'reset' : logger.colors.reset
|
||||||
|
}
|
||||||
|
|
||||||
|
# pre-processing
|
||||||
|
maxlength = 0
|
||||||
|
for key, val in messages.items():
|
||||||
|
if not (type(val) is bool and val is True):
|
||||||
|
maxlength = max(len(key), maxlength)
|
||||||
|
|
||||||
|
# generate stats table
|
||||||
|
logger.info(colors['title'] + 'Onionr v%s Statistics' % ONIONR_VERSION + colors['reset'])
|
||||||
|
logger.info(colors['border'] + '─' * (maxlength + 1) + '┐' + colors['reset'])
|
||||||
|
for key, val in messages.items():
|
||||||
|
if not (type(val) is bool and val is True):
|
||||||
|
logger.info(colors['key'] + str(key).rjust(maxlength) + colors['reset'] + colors['border'] + ' │ ' + colors['reset'] + colors['val'] + str(val) + colors['reset'])
|
||||||
|
else:
|
||||||
|
logger.info(colors['border'] + '─' * (maxlength + 1) + '┤' + colors['reset'])
|
||||||
|
logger.info(colors['border'] + '─' * (maxlength + 1) + '┘' + colors['reset'])
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Failed to generate statistics table.', error = e, timestamp = False)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def showHelp(self, command = None):
|
def showHelp(self, command = None):
|
||||||
|
|
|
@ -0,0 +1,443 @@
|
||||||
|
'''
|
||||||
|
Onionr - P2P Microblogging Platform & Social network.
|
||||||
|
|
||||||
|
This class contains the OnionrBlocks class which is a class for working with Onionr blocks
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
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 core as onionrcore, logger
|
||||||
|
import json, os, datetime
|
||||||
|
|
||||||
|
class Block:
|
||||||
|
def __init__(self, hash = None, core = None):
|
||||||
|
'''
|
||||||
|
Initializes Onionr
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- hash (str): the hash of the block to be imported, if any
|
||||||
|
- core (Core/str):
|
||||||
|
- if (Core): this is the Core instance to be used, don't create a new one
|
||||||
|
- if (str): treat `core` as the block content, and instead, treat `hash` as the block type
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (Block): the new Block instance
|
||||||
|
'''
|
||||||
|
|
||||||
|
# input from arguments
|
||||||
|
if (type(hash) == str) and (type(core) == str):
|
||||||
|
self.btype = hash
|
||||||
|
self.bcontent = core
|
||||||
|
self.hash = None
|
||||||
|
self.core = None
|
||||||
|
else:
|
||||||
|
self.btype = ''
|
||||||
|
self.bcontent = ''
|
||||||
|
self.hash = hash
|
||||||
|
self.core = core
|
||||||
|
|
||||||
|
# initialize variables
|
||||||
|
self.valid = True
|
||||||
|
self.raw = None
|
||||||
|
self.powHash = None
|
||||||
|
self.powToken = None
|
||||||
|
self.signed = False
|
||||||
|
self.signature = None
|
||||||
|
self.signedData = None
|
||||||
|
self.blockFile = None
|
||||||
|
self.bheader = {}
|
||||||
|
self.bmetadata = {}
|
||||||
|
|
||||||
|
# handle arguments
|
||||||
|
if self.getCore() is None:
|
||||||
|
self.core = onionrcore.Core()
|
||||||
|
if not self.getHash() is None:
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
# logic
|
||||||
|
|
||||||
|
def update(self, data = None, file = None):
|
||||||
|
'''
|
||||||
|
Loads data from a block in to the current object.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- data (str):
|
||||||
|
- if None: will load from file by hash
|
||||||
|
- else: will load from `data` string
|
||||||
|
- file (str):
|
||||||
|
- if None: will load from file specified in this parameter
|
||||||
|
- else: will load from wherever block is stored by hash
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (bool): indicates whether or not the operation was successful
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
# import from string
|
||||||
|
blockdata = data
|
||||||
|
|
||||||
|
# import from file
|
||||||
|
if blockdata is None:
|
||||||
|
filelocation = file
|
||||||
|
|
||||||
|
if filelocation is None:
|
||||||
|
if self.getHash() is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
filelocation = 'data/blocks/%s.dat' % self.getHash()
|
||||||
|
|
||||||
|
blockdata = open(filelocation, 'rb').read().decode('utf-8')
|
||||||
|
|
||||||
|
self.blockFile = filelocation
|
||||||
|
else:
|
||||||
|
self.blockFile = None
|
||||||
|
|
||||||
|
# parse block
|
||||||
|
self.raw = str(blockdata)
|
||||||
|
self.bheader = json.loads(self.getRaw()[:self.getRaw().index('\n')])
|
||||||
|
self.bcontent = self.getRaw()[self.getRaw().index('\n') + 1:]
|
||||||
|
self.bmetadata = json.loads(self.getHeader('meta'))
|
||||||
|
self.btype = self.getMetadata('type')
|
||||||
|
self.powHash = self.getMetadata('powHash')
|
||||||
|
self.powToken = self.getMetadata('powToken')
|
||||||
|
self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '')
|
||||||
|
self.signature = (None if not self.isSigned() else self.getHeader('sig'))
|
||||||
|
self.signedData = (None if not self.isSigned() else self.getHeader('meta') + '\n' + self.getContent())
|
||||||
|
self.date = self.getCore().getBlockDate(self.getHash())
|
||||||
|
|
||||||
|
if not self.getDate() is None:
|
||||||
|
self.date = datetime.datetime.fromtimestamp(self.getDate())
|
||||||
|
|
||||||
|
self.valid = True
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Failed to update block data.', error = e, timestamp = False)
|
||||||
|
|
||||||
|
self.valid = False
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
'''
|
||||||
|
Deletes the block's file and records, if they exist
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (bool): whether or not the operation was successful
|
||||||
|
'''
|
||||||
|
|
||||||
|
if self.exists():
|
||||||
|
os.remove(self.getBlockFile())
|
||||||
|
removeBlock(self.getHash())
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def save(self, sign = False, recreate = True):
|
||||||
|
'''
|
||||||
|
Saves a block to file and imports it into Onionr
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- sign (bool): whether or not to sign the block before saving
|
||||||
|
- recreate (bool): if the block already exists, whether or not to recreate the block and save under a new hash
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (bool): whether or not the operation was successful
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.isValid() is True:
|
||||||
|
if (not self.getBlockFile() is None) and (recreate is True):
|
||||||
|
with open(self.getBlockFile(), 'wb') as blockFile:
|
||||||
|
blockFile.write(self.getRaw().encode())
|
||||||
|
self.update()
|
||||||
|
else:
|
||||||
|
self.hash = self.getCore().insertBlock(self.getContent(), header = self.getType(), sign = sign)
|
||||||
|
self.update()
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warn('Not writing block; it is invalid.')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Failed to save block.', error = e, timestamp = False)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# getters
|
||||||
|
|
||||||
|
def getHash(self):
|
||||||
|
'''
|
||||||
|
Returns the hash of the block if saved to file
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (str): the hash of the block, or None
|
||||||
|
'''
|
||||||
|
|
||||||
|
return self.hash
|
||||||
|
|
||||||
|
def getCore(self):
|
||||||
|
'''
|
||||||
|
Returns the Core instance being used by the Block
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (Core): the Core instance
|
||||||
|
'''
|
||||||
|
|
||||||
|
return self.core
|
||||||
|
|
||||||
|
def getType(self):
|
||||||
|
'''
|
||||||
|
Returns the type of the block
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (str): the type of the block
|
||||||
|
'''
|
||||||
|
|
||||||
|
return self.btype
|
||||||
|
|
||||||
|
def getRaw(self):
|
||||||
|
'''
|
||||||
|
Returns the raw contents of the block, if saved to file
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (str): the raw contents of the block, or None
|
||||||
|
'''
|
||||||
|
|
||||||
|
return str(self.raw)
|
||||||
|
|
||||||
|
def getHeader(self, key = None):
|
||||||
|
'''
|
||||||
|
Returns the header information
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- key (str): only returns the value of the key in the header
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (dict/str): either the whole header as a dict, or one value
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not key is None:
|
||||||
|
return self.getHeader()[key]
|
||||||
|
else:
|
||||||
|
return self.bheader
|
||||||
|
|
||||||
|
def getMetadata(self, key = None):
|
||||||
|
'''
|
||||||
|
Returns the metadata information
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- key (str): only returns the value of the key in the metadata
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (dict/str): either the whole metadata as a dict, or one value
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not key is None:
|
||||||
|
return self.getMetadata()[key]
|
||||||
|
else:
|
||||||
|
return self.bmetadata
|
||||||
|
|
||||||
|
def getContent(self):
|
||||||
|
'''
|
||||||
|
Returns the contents of the block
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (str): the contents of the block
|
||||||
|
'''
|
||||||
|
|
||||||
|
return str(self.bcontent)
|
||||||
|
|
||||||
|
def getDate(self):
|
||||||
|
'''
|
||||||
|
Returns the date that the block was received, if loaded from file
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (datetime): the date that the block was received
|
||||||
|
'''
|
||||||
|
|
||||||
|
return self.date
|
||||||
|
|
||||||
|
def getBlockFile(self):
|
||||||
|
'''
|
||||||
|
Returns the location of the block file if it is saved
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (str): the location of the block file, or None
|
||||||
|
'''
|
||||||
|
|
||||||
|
return self.blockFile
|
||||||
|
|
||||||
|
def isValid(self):
|
||||||
|
'''
|
||||||
|
Checks if the block is valid
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (bool): whether or not the block is valid
|
||||||
|
'''
|
||||||
|
|
||||||
|
return self.valid
|
||||||
|
|
||||||
|
def isSigned(self):
|
||||||
|
'''
|
||||||
|
Checks if the block was signed
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (bool): whether or not the block is signed
|
||||||
|
'''
|
||||||
|
|
||||||
|
return self.signed
|
||||||
|
|
||||||
|
def getSignature(self):
|
||||||
|
'''
|
||||||
|
Returns the base64-encoded signature
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (str): the signature, or None
|
||||||
|
'''
|
||||||
|
|
||||||
|
return self.signature
|
||||||
|
|
||||||
|
def getSignedData(self):
|
||||||
|
'''
|
||||||
|
Returns the data that was signed
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (str): the data that was signed, or None
|
||||||
|
'''
|
||||||
|
|
||||||
|
return self.signedData
|
||||||
|
|
||||||
|
def isSigner(self, signer, encodedData = True):
|
||||||
|
'''
|
||||||
|
Checks if the block was signed by the signer inputted
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- signer (str): the public key of the signer to check against
|
||||||
|
- encodedData (bool): whether or not the `signer` argument is base64 encoded
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (bool): whether or not the signer of the block is the signer inputted
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
if (not self.isSigned()) or (not self.getCore()._utils.validatePubKey(signer)):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return bool(self.getCore()._crypto.edVerify(self.getSignedData(), signer, self.getSignature(), encodedData = encodedData))
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# setters
|
||||||
|
|
||||||
|
def setType(self, btype):
|
||||||
|
'''
|
||||||
|
Sets the type of the block
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- btype (str): the type of block to be set to
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (Block): the block instance
|
||||||
|
'''
|
||||||
|
|
||||||
|
self.btype = btype
|
||||||
|
return self
|
||||||
|
|
||||||
|
def setContent(self, bcontent):
|
||||||
|
'''
|
||||||
|
Sets the contents of the block
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- bcontent (str): the contents to be set to
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (Block): the block instance
|
||||||
|
'''
|
||||||
|
|
||||||
|
self.bcontent = str(bcontent)
|
||||||
|
return self
|
||||||
|
|
||||||
|
# static
|
||||||
|
|
||||||
|
def getBlocks(type = None, signer = None, signed = None, reverse = False, core = None):
|
||||||
|
'''
|
||||||
|
Returns a list of Block objects based on supplied filters
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- type (str): filters by block type
|
||||||
|
- signer (str/list): filters by signer (one in the list has to be a signer)
|
||||||
|
- signed (bool): filters out by whether or not the block is signed
|
||||||
|
- reverse (bool): reverses the list if True
|
||||||
|
- core (Core): lets you optionally supply a core instance so one doesn't need to be started
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (list): a list of Block objects that match the input
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
core = (core if not core is None else onionrcore.Core())
|
||||||
|
|
||||||
|
relevant_blocks = list()
|
||||||
|
blocks = (core.getBlockList() if type is None else core.getBlocksByType(type))
|
||||||
|
|
||||||
|
for block in blocks:
|
||||||
|
if Block.exists(block):
|
||||||
|
block = Block(block, core = core)
|
||||||
|
|
||||||
|
relevant = True
|
||||||
|
|
||||||
|
if (not signed is None) and (block.isSigned() != bool(signed)):
|
||||||
|
relevant = False
|
||||||
|
if not signer is None:
|
||||||
|
if isinstance(signer, (str,)):
|
||||||
|
signer = [signer]
|
||||||
|
|
||||||
|
isSigner = False
|
||||||
|
for key in signer:
|
||||||
|
if block.isSigner(key):
|
||||||
|
isSigner = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not isSigner:
|
||||||
|
relevant = False
|
||||||
|
|
||||||
|
if relevant:
|
||||||
|
relevant_blocks.append(block)
|
||||||
|
|
||||||
|
if bool(reverse):
|
||||||
|
relevant_blocks.reverse()
|
||||||
|
|
||||||
|
return relevant_blocks
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(('Failed to get blocks: %s' % str(e)) + logger.parse_error())
|
||||||
|
|
||||||
|
return list()
|
||||||
|
|
||||||
|
def exists(hash):
|
||||||
|
'''
|
||||||
|
Checks if a block is saved to file or not
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- hash (str/Block):
|
||||||
|
- if (Block): check if this block is saved to file
|
||||||
|
- if (str): check if a block by this hash is in file
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- (bool): whether or not the block file exists
|
||||||
|
'''
|
||||||
|
|
||||||
|
if hash is None:
|
||||||
|
return False
|
||||||
|
elif type(hash) == Block:
|
||||||
|
blockfile = hash.getBlockFile()
|
||||||
|
else:
|
||||||
|
blockfile = 'data/blocks/%s.dat' % hash
|
||||||
|
|
||||||
|
return os.path.exists(blockfile) and os.path.isfile(blockfile)
|
|
@ -1,26 +0,0 @@
|
||||||
'''
|
|
||||||
Onionr - P2P Microblogging Platform & Social network.
|
|
||||||
|
|
||||||
This class contains the OnionrBlocks class which is a class for working with Onionr blocks
|
|
||||||
'''
|
|
||||||
'''
|
|
||||||
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 json
|
|
||||||
class OnionrBlocks:
|
|
||||||
def __init__(self, coreInstance):
|
|
||||||
return
|
|
||||||
def metadataGenerate(self):
|
|
||||||
return
|
|
||||||
|
|
|
@ -17,15 +17,19 @@
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
'''
|
'''
|
||||||
import nacl.signing, nacl.encoding, nacl.public, nacl.secret, os, binascii, base64, hashlib, logger
|
import nacl.signing, nacl.encoding, nacl.public, nacl.hash, nacl.secret, os, binascii, base64, hashlib, logger, onionrproofs, time, math
|
||||||
|
|
||||||
class OnionrCrypto:
|
class OnionrCrypto:
|
||||||
def __init__(self, coreInstance):
|
def __init__(self, coreInstance):
|
||||||
self._core = coreInstance
|
self._core = coreInstance
|
||||||
self._keyFile = 'data/keys.txt'
|
self._keyFile = 'data/keys.txt'
|
||||||
|
self.keyPowFile = 'data/keyPow.txt'
|
||||||
self.pubKey = None
|
self.pubKey = None
|
||||||
self.privKey = None
|
self.privKey = None
|
||||||
|
|
||||||
|
self.pubKeyPowToken = None
|
||||||
|
#self.pubKeyPowHash = None
|
||||||
|
|
||||||
self.HASH_ID_ROUNDS = 2000
|
self.HASH_ID_ROUNDS = 2000
|
||||||
|
|
||||||
# Load our own pub/priv Ed25519 keys, gen & save them if they don't exist
|
# Load our own pub/priv Ed25519 keys, gen & save them if they don't exist
|
||||||
|
@ -34,12 +38,29 @@ class OnionrCrypto:
|
||||||
keys = keys.read().split(',')
|
keys = keys.read().split(',')
|
||||||
self.pubKey = keys[0]
|
self.pubKey = keys[0]
|
||||||
self.privKey = keys[1]
|
self.privKey = keys[1]
|
||||||
|
try:
|
||||||
|
with open(self.keyPowFile, 'r') as powFile:
|
||||||
|
data = powFile.read()
|
||||||
|
self.pubKeyPowToken = data
|
||||||
|
except (FileNotFoundError, IndexError):
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
keys = self.generatePubKey()
|
keys = self.generatePubKey()
|
||||||
self.pubKey = keys[0]
|
self.pubKey = keys[0]
|
||||||
self.privKey = keys[1]
|
self.privKey = keys[1]
|
||||||
with open(self._keyFile, 'w') as keyfile:
|
with open(self._keyFile, 'w') as keyfile:
|
||||||
keyfile.write(self.pubKey + ',' + self.privKey)
|
keyfile.write(self.pubKey + ',' + self.privKey)
|
||||||
|
with open(self.keyPowFile, 'w') as keyPowFile:
|
||||||
|
proof = onionrproofs.POW(self.pubKey)
|
||||||
|
logger.info('Doing necessary work to insert our public key')
|
||||||
|
while True:
|
||||||
|
time.sleep(0.2)
|
||||||
|
powToken = proof.getResult()
|
||||||
|
if powToken != False:
|
||||||
|
break
|
||||||
|
keyPowFile.write(base64.b64encode(powToken[1]).decode())
|
||||||
|
self.pubKeyPowToken = powToken[1]
|
||||||
|
self.pubKeyPowHash = powToken[0]
|
||||||
return
|
return
|
||||||
|
|
||||||
def edVerify(self, data, key, sig, encodedData=True):
|
def edVerify(self, data, key, sig, encodedData=True):
|
||||||
|
@ -60,7 +81,6 @@ class OnionrCrypto:
|
||||||
retData = key.verify(data, sig) # .encode() is not the same as nacl.encoding
|
retData = key.verify(data, sig) # .encode() is not the same as nacl.encoding
|
||||||
except nacl.exceptions.BadSignatureError:
|
except nacl.exceptions.BadSignatureError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
retData = key.verify(data, sig)
|
retData = key.verify(data, sig)
|
||||||
|
@ -138,8 +158,6 @@ class OnionrCrypto:
|
||||||
decrypted = self.symmetricDecrypt(data, key, encodedKey=True)
|
decrypted = self.symmetricDecrypt(data, key, encodedKey=True)
|
||||||
return decrypted
|
return decrypted
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def symmetricEncrypt(self, data, key, encodedKey=False, returnEncoded=True):
|
def symmetricEncrypt(self, data, key, encodedKey=False, returnEncoded=True):
|
||||||
'''Encrypt data to a 32-byte key (Salsa20-Poly1305 MAC)'''
|
'''Encrypt data to a 32-byte key (Salsa20-Poly1305 MAC)'''
|
||||||
if encodedKey:
|
if encodedKey:
|
||||||
|
@ -210,3 +228,48 @@ class OnionrCrypto:
|
||||||
prev = hasher.hexdigest()
|
prev = hasher.hexdigest()
|
||||||
result = prev
|
result = prev
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def sha3Hash(self, data):
|
||||||
|
hasher = hashlib.sha3_256()
|
||||||
|
hasher.update(data)
|
||||||
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
def blake2bHash(self, data):
|
||||||
|
try:
|
||||||
|
data = data.encode()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
return nacl.hash.blake2b(data)
|
||||||
|
|
||||||
|
def verifyPow(self, blockContent, metadata):
|
||||||
|
'''
|
||||||
|
Verifies the proof of work associated with a block
|
||||||
|
'''
|
||||||
|
retData = False
|
||||||
|
|
||||||
|
if not (('powToken' in metadata) and ('powHash' in metadata)):
|
||||||
|
return False
|
||||||
|
|
||||||
|
dataLen = len(blockContent)
|
||||||
|
|
||||||
|
expectedHash = self.blake2bHash(base64.b64decode(metadata['powToken']) + 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]
|
||||||
|
|
||||||
|
if metadata['powHash'][:difficulty] == puzzle:
|
||||||
|
# logger.debug('Validated block pow')
|
||||||
|
retData = True
|
||||||
|
else:
|
||||||
|
logger.debug("Invalid token (#1)")
|
||||||
|
else:
|
||||||
|
logger.debug('Invalid token (#2): Expected hash %s, got hash %s...' % (metadata['powHash'], expectedHash))
|
||||||
|
|
||||||
|
return retData
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import onionrplugins as plugins, logger
|
import onionrplugins, core as onionrcore, logger
|
||||||
|
|
||||||
class DaemonAPI:
|
class DaemonAPI:
|
||||||
def __init__(self, pluginapi):
|
def __init__(self, pluginapi):
|
||||||
|
@ -40,9 +40,7 @@ class DaemonAPI:
|
||||||
return
|
return
|
||||||
|
|
||||||
def local_command(self, command):
|
def local_command(self, command):
|
||||||
self.pluginapi.get_utils().localCommand(self, command)
|
return self.pluginapi.get_utils().localCommand(self, command)
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def queue_pop(self):
|
def queue_pop(self):
|
||||||
return self.get_core().daemonQueue()
|
return self.get_core().daemonQueue()
|
||||||
|
@ -52,34 +50,34 @@ class PluginAPI:
|
||||||
self.pluginapi = pluginapi
|
self.pluginapi = pluginapi
|
||||||
|
|
||||||
def start(self, name):
|
def start(self, name):
|
||||||
plugins.start(name)
|
onionrplugins.start(name)
|
||||||
|
|
||||||
def stop(self, name):
|
def stop(self, name):
|
||||||
plugins.stop(name)
|
onionrplugins.stop(name)
|
||||||
|
|
||||||
def reload(self, name):
|
def reload(self, name):
|
||||||
plugins.reload(name)
|
onionrplugins.reload(name)
|
||||||
|
|
||||||
def enable(self, name):
|
def enable(self, name):
|
||||||
plugins.enable(name)
|
onionrplugins.enable(name)
|
||||||
|
|
||||||
def disable(self, name):
|
def disable(self, name):
|
||||||
plugins.disable(name)
|
onionrplugins.disable(name)
|
||||||
|
|
||||||
def event(self, name, data = {}):
|
def event(self, name, data = {}):
|
||||||
events.event(name, data = data, onionr = self.pluginapi.get_onionr())
|
events.event(name, data = data, onionr = self.pluginapi.get_onionr())
|
||||||
|
|
||||||
def is_enabled(self, name):
|
def is_enabled(self, name):
|
||||||
return plugins.is_enabled(name)
|
return onionrplugins.is_enabled(name)
|
||||||
|
|
||||||
def get_enabled_plugins(self):
|
def get_enabled_plugins(self):
|
||||||
return plugins.get_enabled()
|
return onionrplugins.get_enabled()
|
||||||
|
|
||||||
def get_folder(self, name = None, absolute = True):
|
def get_folder(self, name = None, absolute = True):
|
||||||
return plugins.get_plugins_folder(name = name, absolute = absolute)
|
return onionrplugins.get_plugins_folder(name = name, absolute = absolute)
|
||||||
|
|
||||||
def get_data_folder(self, name, absolute = True):
|
def get_data_folder(self, name, absolute = True):
|
||||||
return plugins.get_plugin_data_folder(name, absolute = absolute)
|
return onionrplugins.get_plugin_data_folder(name, absolute = absolute)
|
||||||
|
|
||||||
def daemon_event(self, event, plugin = None):
|
def daemon_event(self, event, plugin = None):
|
||||||
return # later make local command like /client/?action=makeEvent&event=eventname&module=modulename
|
return # later make local command like /client/?action=makeEvent&event=eventname&module=modulename
|
||||||
|
@ -136,6 +134,10 @@ class pluginapi:
|
||||||
def __init__(self, onionr, data):
|
def __init__(self, onionr, data):
|
||||||
self.onionr = onionr
|
self.onionr = onionr
|
||||||
self.data = data
|
self.data = data
|
||||||
|
if self.onionr is None:
|
||||||
|
self.core = onionrcore.Core()
|
||||||
|
else:
|
||||||
|
self.core = self.onionr.onionrCore
|
||||||
|
|
||||||
self.daemon = DaemonAPI(self)
|
self.daemon = DaemonAPI(self)
|
||||||
self.plugins = PluginAPI(self)
|
self.plugins = PluginAPI(self)
|
||||||
|
@ -148,10 +150,13 @@ class pluginapi:
|
||||||
return self.data
|
return self.data
|
||||||
|
|
||||||
def get_core(self):
|
def get_core(self):
|
||||||
return self.get_onionr().onionrCore
|
return self.core
|
||||||
|
|
||||||
def get_utils(self):
|
def get_utils(self):
|
||||||
return self.get_onionr().onionrUtils
|
return self.get_core()._utils
|
||||||
|
|
||||||
|
def get_crypto(self):
|
||||||
|
return self.get_core()._crypto
|
||||||
|
|
||||||
def get_daemonapi(self):
|
def get_daemonapi(self):
|
||||||
return self.daemon
|
return self.daemon
|
||||||
|
|
|
@ -62,6 +62,7 @@ def enable(name, onionr = None, start_event = True):
|
||||||
|
|
||||||
if exists(name):
|
if exists(name):
|
||||||
enabled_plugins = get_enabled_plugins()
|
enabled_plugins = get_enabled_plugins()
|
||||||
|
if not name in enabled_plugins:
|
||||||
enabled_plugins.append(name)
|
enabled_plugins.append(name)
|
||||||
config_plugins = config.get('plugins')
|
config_plugins = config.get('plugins')
|
||||||
config_plugins['enabled'] = enabled_plugins
|
config_plugins['enabled'] = enabled_plugins
|
||||||
|
@ -73,6 +74,8 @@ def enable(name, onionr = None, start_event = True):
|
||||||
start(name)
|
start(name)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
logger.error('Failed to enable plugin \"' + name + '\", disabling plugin.')
|
logger.error('Failed to enable plugin \"' + name + '\", disabling plugin.')
|
||||||
disable(name)
|
disable(name)
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import nacl.encoding, nacl.hash, nacl.utils, time, math, threading, binascii, logger
|
import nacl.encoding, nacl.hash, nacl.utils, time, math, threading, binascii, logger, sys
|
||||||
import btc
|
import core
|
||||||
|
|
||||||
class POW:
|
class POW:
|
||||||
def pow(self, reporting = False):
|
def pow(self, reporting = False):
|
||||||
|
@ -30,20 +30,10 @@ class POW:
|
||||||
answer = ''
|
answer = ''
|
||||||
heartbeat = 200000
|
heartbeat = 200000
|
||||||
hbCount = 0
|
hbCount = 0
|
||||||
blockCheck = 300000 # How often the hasher should check if the bitcoin block is updated (slows hashing but prevents less wasted work)
|
myCore = core.Core()
|
||||||
blockCheckCount = 0
|
|
||||||
block = '' #self.bitcoinNode.getBlockHash(self.bitcoinNode.getLastBlockHeight())
|
|
||||||
while self.hashing:
|
while self.hashing:
|
||||||
'''
|
rand = nacl.utils.random()
|
||||||
if blockCheckCount == blockCheck:
|
token = nacl.hash.blake2b(rand + self.data).decode()
|
||||||
if self.reporting:
|
|
||||||
logger.debug('Refreshing Bitcoin block')
|
|
||||||
block = '' #self.bitcoinNode.getBlockHash(self.bitcoinNode.getLastBlockHeight())
|
|
||||||
blockCheckCount = 0
|
|
||||||
blockCheckCount += 1
|
|
||||||
hbCount += 1
|
|
||||||
'''
|
|
||||||
token = nacl.hash.blake2b(nacl.utils.random()).decode()
|
|
||||||
#print(token)
|
#print(token)
|
||||||
if self.puzzle == token[0:self.difficulty]:
|
if self.puzzle == token[0:self.difficulty]:
|
||||||
self.hashing = False
|
self.hashing = False
|
||||||
|
@ -56,17 +46,28 @@ class POW:
|
||||||
if self.reporting:
|
if self.reporting:
|
||||||
logger.info('Found token ' + token, timestamp=True)
|
logger.info('Found token ' + token, timestamp=True)
|
||||||
logger.info('took ' + str(endTime - startTime) + ' seconds', timestamp=True)
|
logger.info('took ' + str(endTime - startTime) + ' seconds', timestamp=True)
|
||||||
self.result = token
|
self.result = (token, rand)
|
||||||
|
|
||||||
def __init__(self, difficulty, bitcoinNode=''):
|
def __init__(self, data):
|
||||||
self.foundHash = False
|
self.foundHash = False
|
||||||
self.difficulty = difficulty
|
self.difficulty = 0
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
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.debug('Computing difficulty of ' + str(self.difficulty))
|
logger.debug('Computing difficulty of ' + str(self.difficulty))
|
||||||
|
|
||||||
self.mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode()
|
self.mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode()
|
||||||
self.puzzle = self.mainHash[0:self.difficulty]
|
self.puzzle = self.mainHash[0:self.difficulty]
|
||||||
self.bitcoinNode = bitcoinNode
|
|
||||||
#logger.debug('trying to find ' + str(self.mainHash))
|
#logger.debug('trying to find ' + str(self.mainHash))
|
||||||
tOne = threading.Thread(name='one', target=self.pow, args=(True,))
|
tOne = threading.Thread(name='one', target=self.pow, args=(True,))
|
||||||
tTwo = threading.Thread(name='two', target=self.pow, args=(True,))
|
tTwo = threading.Thread(name='two', target=self.pow, args=(True,))
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
'''
|
'''
|
||||||
# Misc functions that do not fit in the main api, but are useful
|
# Misc functions that do not fit in the main api, but are useful
|
||||||
import getpass, sys, requests, os, socket, hashlib, logger, sqlite3, config, binascii, time, base64, json
|
import getpass, sys, requests, os, socket, hashlib, logger, sqlite3, config, binascii, time, base64, json, glob, shutil, math
|
||||||
import nacl.signing, nacl.encoding
|
import nacl.signing, nacl.encoding
|
||||||
|
|
||||||
if sys.version_info < (3, 6):
|
if sys.version_info < (3, 6):
|
||||||
|
@ -71,7 +71,7 @@ class OnionrUtils:
|
||||||
if block == '':
|
if block == '':
|
||||||
logger.error('Could not send PM')
|
logger.error('Could not send PM')
|
||||||
else:
|
else:
|
||||||
logger.info('Sent PM, hash: ' + block)
|
logger.info('Sent PM, hash: %s' % block)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error('Failed to send PM.', error=error)
|
logger.error('Failed to send PM.', error=error)
|
||||||
|
|
||||||
|
@ -101,9 +101,26 @@ class OnionrUtils:
|
||||||
retVal = False
|
retVal = False
|
||||||
if newKeyList != False:
|
if newKeyList != False:
|
||||||
for key in newKeyList.split(','):
|
for key in newKeyList.split(','):
|
||||||
if not key in self._core.listPeers(randomOrder=False) and type(key) != None and key != self._core._crypto.pubKey:
|
key = key.split('-')
|
||||||
if self._core.addPeer(key):
|
try:
|
||||||
|
if len(key[0]) > 60 or len(key[1]) > 1000:
|
||||||
|
logger.warn('%s or its pow value is too large.' % key[0])
|
||||||
|
continue
|
||||||
|
except IndexError:
|
||||||
|
logger.warn('No pow token')
|
||||||
|
continue
|
||||||
|
powHash = self._core._crypto.blake2bHash(base64.b64decode(key[1]) + self._core._crypto.blake2bHash(key[0].encode()))
|
||||||
|
try:
|
||||||
|
powHash = powHash.encode()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
if powHash.startswith(b'0000'):
|
||||||
|
if not key[0] in self._core.listPeers(randomOrder=False) and type(key) != None and key[0] != self._core._crypto.pubKey:
|
||||||
|
if self._core.addPeer(key[0], key[1]):
|
||||||
retVal = True
|
retVal = True
|
||||||
|
else:
|
||||||
|
logger.warn(powHash)
|
||||||
|
logger.warn('%s pow failed' % key[0])
|
||||||
return retVal
|
return retVal
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error('Failed to merge keys.', error=error)
|
logger.error('Failed to merge keys.', error=error)
|
||||||
|
@ -120,10 +137,10 @@ class OnionrUtils:
|
||||||
for adder in newAdderList.split(','):
|
for adder in newAdderList.split(','):
|
||||||
if not adder in self._core.listAdders(randomOrder = False) and adder.strip() != self.getMyAddress():
|
if not adder in self._core.listAdders(randomOrder = False) and adder.strip() != self.getMyAddress():
|
||||||
if self._core.addAddress(adder):
|
if self._core.addAddress(adder):
|
||||||
logger.info('Added ' + adder + ' to db.', timestamp=True)
|
logger.info('Added %s to db.' % adder, timestamp = True)
|
||||||
retVal = True
|
retVal = True
|
||||||
else:
|
else:
|
||||||
logger.debug(adder + " is either our address or already in our DB")
|
logger.debug('%s is either our address or already in our DB' % adder)
|
||||||
return retVal
|
return retVal
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error('Failed to merge adders.', error = error)
|
logger.error('Failed to merge adders.', error = error)
|
||||||
|
@ -149,7 +166,7 @@ class OnionrUtils:
|
||||||
retData = requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config.get('client')['port']) + '/client/?action=' + command + '&token=' + str(config.get('client')['client_hmac']) + '&timingToken=' + self.timingToken).text
|
retData = requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config.get('client')['port']) + '/client/?action=' + command + '&token=' + str(config.get('client')['client_hmac']) + '&timingToken=' + self.timingToken).text
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
if not silent:
|
if not silent:
|
||||||
logger.error('Failed to make local request (command: ' + str(command) + ').', error=error)
|
logger.error('Failed to make local request (command: %s).' % command, error=error)
|
||||||
retData = False
|
retData = False
|
||||||
|
|
||||||
return retData
|
return retData
|
||||||
|
@ -327,7 +344,7 @@ class OnionrUtils:
|
||||||
'''
|
'''
|
||||||
Find, decrypt, and return array of PMs (array of dictionary, {from, text})
|
Find, decrypt, and return array of PMs (array of dictionary, {from, text})
|
||||||
'''
|
'''
|
||||||
#blocks = self._core.getBlockList().split('\n')
|
#blocks = self._core.getBlockList()
|
||||||
blocks = self._core.getBlocksByType('pm')
|
blocks = self._core.getBlocksByType('pm')
|
||||||
message = ''
|
message = ''
|
||||||
sender = ''
|
sender = ''
|
||||||
|
@ -336,52 +353,43 @@ class OnionrUtils:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
with open('data/blocks/' + i + '.dat', 'r') as potentialMessage:
|
with open('data/blocks/' + i + '.dat', 'r') as potentialMessage:
|
||||||
data = potentialMessage.read().split('}')
|
potentialMessage = potentialMessage.read()
|
||||||
message = data[1]
|
blockMetadata = json.loads(potentialMessage[:potentialMessage.find('\n')])
|
||||||
sigResult = ''
|
blockContent = potentialMessage[potentialMessage.find('\n') + 1:]
|
||||||
signer = ''
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
metadata = json.loads(data[0] + '}')
|
message = self._core._crypto.pubKeyDecrypt(blockContent, encodedData=True, anonymous=True)
|
||||||
except json.decoder.JSONDecodeError:
|
|
||||||
metadata = {}
|
|
||||||
'''
|
|
||||||
sigResult = self._core._crypto.edVerify(message, signer, sig, encodedData=True)
|
|
||||||
#sigResult = False
|
|
||||||
if sigResult != False:
|
|
||||||
sigResult = 'Valid signature by ' + signer
|
|
||||||
else:
|
|
||||||
sigResult = 'Invalid signature by ' + signer
|
|
||||||
'''
|
|
||||||
|
|
||||||
try:
|
|
||||||
message = self._core._crypto.pubKeyDecrypt(message, encodedData=True, anonymous=True)
|
|
||||||
except nacl.exceptions.CryptoError as e:
|
except nacl.exceptions.CryptoError as e:
|
||||||
logger.error('Unable to decrypt ' + i, error=e)
|
pass
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
message = json.loads(message.decode())
|
message = message.decode()
|
||||||
message['msg']
|
except AttributeError:
|
||||||
message['id']
|
pass
|
||||||
message['sig']
|
|
||||||
|
try:
|
||||||
|
message = json.loads(message)
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
logger.error('Could not decode PM JSON')
|
pass
|
||||||
except KeyError:
|
|
||||||
logger.error('PM is missing JSON keys')
|
|
||||||
else:
|
else:
|
||||||
if self.validatePubKey(message['id']):
|
logger.info('Decrypted %s:' % i)
|
||||||
sigResult = self._core._crypto.edVerify(message['msg'], message['id'], message['sig'], encodedData=True)
|
logger.info(message["msg"])
|
||||||
logger.info('-----------------------------------')
|
|
||||||
logger.info('Recieved message: ' + message['msg'])
|
signer = message["id"]
|
||||||
if sigResult:
|
sig = message["sig"]
|
||||||
logger.info('Valid signature by ' + message['id'])
|
|
||||||
|
if self.validatePubKey(signer):
|
||||||
|
if self._core._crypto.edVerify(message["msg"], signer, sig, encodedData=True):
|
||||||
|
logger.info("Good signature by %s" % signer)
|
||||||
else:
|
else:
|
||||||
logger.warn('Invalid signature by ' + message['id'])
|
logger.warn("Bad signature by %s" % signer)
|
||||||
|
else:
|
||||||
|
logger.warn('Bad sender id: %s' % signer)
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error('Failed to open block ' + str(i) + '.', error=error)
|
logger.error('Failed to open block %s.' % i, error=error)
|
||||||
return
|
return
|
||||||
|
|
||||||
def getPeerByHashId(self, hash):
|
def getPeerByHashId(self, hash):
|
||||||
|
@ -423,4 +431,74 @@ class OnionrUtils:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def token(self, size = 32):
|
def token(self, size = 32):
|
||||||
|
'''
|
||||||
|
Generates a secure random hex encoded token
|
||||||
|
'''
|
||||||
return binascii.hexlify(os.urandom(size))
|
return binascii.hexlify(os.urandom(size))
|
||||||
|
|
||||||
|
def importNewBlocks(self, scanDir=''):
|
||||||
|
'''
|
||||||
|
This function is intended to scan for new blocks ON THE DISK and import them
|
||||||
|
'''
|
||||||
|
blockList = self._core.getBlockList()
|
||||||
|
if scanDir == '':
|
||||||
|
scanDir = self._core.blockDataLocation
|
||||||
|
if not scanDir.endswith('/'):
|
||||||
|
scanDir += '/'
|
||||||
|
for block in glob.glob(scanDir + "*.dat"):
|
||||||
|
if block.replace(scanDir, '').replace('.dat', '') not in blockList:
|
||||||
|
logger.info('Found new block on dist %s' % block)
|
||||||
|
with open(block, 'rb') as newBlock:
|
||||||
|
block = block.replace(scanDir, '').replace('.dat', '')
|
||||||
|
if self._core._crypto.sha3Hash(newBlock.read()) == block.replace('.dat', ''):
|
||||||
|
self._core.addToBlockDB(block.replace('.dat', ''), dataSaved=True)
|
||||||
|
logger.info('Imported block %s.' % block)
|
||||||
|
else:
|
||||||
|
logger.warn('Failed to verify hash for %s' % block)
|
||||||
|
|
||||||
|
def progressBar(self, value = 0, endvalue = 100, width = None):
|
||||||
|
'''
|
||||||
|
Outputs a progress bar with a percentage. Write \n after use.
|
||||||
|
'''
|
||||||
|
|
||||||
|
if width is None or height is None:
|
||||||
|
width, height = shutil.get_terminal_size((80, 24))
|
||||||
|
|
||||||
|
bar_length = width - 6
|
||||||
|
|
||||||
|
percent = float(value) / endvalue
|
||||||
|
arrow = '─' * int(round(percent * bar_length)-1) + '>'
|
||||||
|
spaces = ' ' * (bar_length - len(arrow))
|
||||||
|
|
||||||
|
sys.stdout.write("\r┣{0}┫ {1}%".format(arrow + spaces, int(round(percent * 100))))
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def getEpoch(self):
|
||||||
|
'''returns epoch'''
|
||||||
|
return math.floor(time.time())
|
||||||
|
|
||||||
|
def size(path='.'):
|
||||||
|
'''
|
||||||
|
Returns the size of a folder's contents in bytes
|
||||||
|
'''
|
||||||
|
total = 0
|
||||||
|
if os.path.exists(path):
|
||||||
|
if os.path.isfile(path):
|
||||||
|
total = os.path.getsize(path)
|
||||||
|
else:
|
||||||
|
for entry in os.scandir(path):
|
||||||
|
if entry.is_file():
|
||||||
|
total += entry.stat().st_size
|
||||||
|
elif entry.is_dir():
|
||||||
|
total += size(entry.path)
|
||||||
|
return total
|
||||||
|
|
||||||
|
def humanSize(num, suffix='B'):
|
||||||
|
'''
|
||||||
|
Converts from bytes to a human readable format.
|
||||||
|
'''
|
||||||
|
for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
|
||||||
|
if abs(num) < 1024.0:
|
||||||
|
return "%.1f %s%s" % (num, unit, suffix)
|
||||||
|
num /= 1024.0
|
||||||
|
return "%.1f %s%s" % (num, 'Yi', suffix)
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name" : "gui",
|
||||||
|
"version" : "1.0",
|
||||||
|
"author" : "onionr"
|
||||||
|
}
|
|
@ -16,37 +16,45 @@
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Imports some useful libraries
|
# Imports some useful libraries
|
||||||
import logger, config
|
import logger, config, core
|
||||||
import os, sqlite3, core
|
import os, sqlite3, threading
|
||||||
|
from onionrblockapi import Block
|
||||||
|
|
||||||
|
plugin_name = 'gui'
|
||||||
|
|
||||||
|
def send():
|
||||||
|
global message
|
||||||
|
block = Block()
|
||||||
|
block.setType('txt')
|
||||||
|
block.setContent(message)
|
||||||
|
logger.debug('Sent message in block %s.' % block.save(sign = True))
|
||||||
|
|
||||||
|
|
||||||
def sendMessage():
|
def sendMessage():
|
||||||
global sendEntry
|
global sendEntry
|
||||||
|
|
||||||
messageToAdd = '-txt-' + sendEntry.get()
|
global message
|
||||||
#addedHash = pluginapi.get_core().setData(messageToAdd)
|
message = sendEntry.get()
|
||||||
#pluginapi.get_core().addToBlockDB(addedHash, selfInsert=True)
|
|
||||||
#pluginapi.get_core().setBlockType(addedHash, 'txt')
|
t = threading.Thread(target = send)
|
||||||
pluginapi.get_core().insertBlock(messageToAdd, header='txt', sign=True)
|
t.start()
|
||||||
sendEntry.delete(0, END)
|
|
||||||
|
sendEntry.delete(0, len(message))
|
||||||
|
|
||||||
def update():
|
def update():
|
||||||
global listedBlocks, listbox, runningCheckDelayCount, runningCheckDelay, root, daemonStatus
|
global listedBlocks, listbox, runningCheckDelayCount, runningCheckDelay, root, daemonStatus
|
||||||
|
|
||||||
# TO DO: migrate to new header format
|
for i in Block.getBlocks(type = 'txt'):
|
||||||
for i in pluginapi.get_core().getBlocksByType('txt'):
|
if i.getContent().strip() == '' or i.getHash() in listedBlocks:
|
||||||
if i.strip() == '' or i in listedBlocks:
|
|
||||||
continue
|
continue
|
||||||
blockFile = open('./data/blocks/' + i + '.dat')
|
listbox.insert(99999, str(i.getContent()))
|
||||||
listbox.insert(END, str(blockFile.read().replace('-txt-', '')))
|
listedBlocks.append(i.getHash())
|
||||||
blockFile.close()
|
listbox.see(99999)
|
||||||
listedBlocks.append(i)
|
|
||||||
listbox.see(END)
|
|
||||||
blocksList = os.listdir('./data/blocks/') # dir is your directory path
|
|
||||||
number_blocks = len(blocksList)
|
|
||||||
runningCheckDelayCount += 1
|
runningCheckDelayCount += 1
|
||||||
|
|
||||||
if runningCheckDelayCount == runningCheckDelay:
|
if runningCheckDelayCount == runningCheckDelay:
|
||||||
resp = pluginapi.get_core()._utils.localCommand('ping')
|
resp = pluginapi.daemon.local_command('ping')
|
||||||
if resp == 'pong':
|
if resp == 'pong':
|
||||||
daemonStatus.config(text = "Onionr Daemon Status: Running")
|
daemonStatus.config(text = "Onionr Daemon Status: Running")
|
||||||
else:
|
else:
|
||||||
|
@ -55,7 +63,7 @@ def update():
|
||||||
root.after(10000, update)
|
root.after(10000, update)
|
||||||
|
|
||||||
|
|
||||||
def openGUI():
|
def reallyOpenGUI():
|
||||||
import tkinter
|
import tkinter
|
||||||
global root, runningCheckDelay, runningCheckDelayCount, scrollbar, listedBlocks, nodeInfo, keyInfo, idText, idEntry, pubKeyEntry, listbox, daemonStatus, sendEntry
|
global root, runningCheckDelay, runningCheckDelayCount, scrollbar, listedBlocks, nodeInfo, keyInfo, idText, idEntry, pubKeyEntry, listbox, daemonStatus, sendEntry
|
||||||
|
|
||||||
|
@ -74,8 +82,9 @@ def openGUI():
|
||||||
nodeInfo = tkinter.Frame(root)
|
nodeInfo = tkinter.Frame(root)
|
||||||
keyInfo = tkinter.Frame(root)
|
keyInfo = tkinter.Frame(root)
|
||||||
|
|
||||||
print(pluginapi.get_onionr().get_hostname())
|
hostname = pluginapi.get_onionr().get_hostname()
|
||||||
idText = pluginapi.get_onionr().get_hostname()
|
logger.debug('Onionr Hostname: %s' % hostname)
|
||||||
|
idText = hostname
|
||||||
|
|
||||||
idEntry = tkinter.Entry(nodeInfo)
|
idEntry = tkinter.Entry(nodeInfo)
|
||||||
tkinter.Label(nodeInfo, text = "Node Address: ").pack(side=tkinter.LEFT)
|
tkinter.Label(nodeInfo, text = "Node Address: ").pack(side=tkinter.LEFT)
|
||||||
|
@ -100,17 +109,22 @@ def openGUI():
|
||||||
sendEntry.pack(side=tkinter.TOP, pady=5)
|
sendEntry.pack(side=tkinter.TOP, pady=5)
|
||||||
sendBtn.pack(side=tkinter.TOP)
|
sendBtn.pack(side=tkinter.TOP)
|
||||||
|
|
||||||
listbox = tkinter.Listbox(root, yscrollcommand=tkinter.scrollbar.set, height=15)
|
listbox = tkinter.Listbox(root, yscrollcommand=tkinter.Scrollbar.set, height=15)
|
||||||
|
|
||||||
listbox.pack(fill=tkinter.BOTH, pady=25)
|
listbox.pack(fill=tkinter.BOTH, pady=25)
|
||||||
|
|
||||||
daemonStatus = tkinter.Label(root, text="Onionr Daemon Status: unknown")
|
daemonStatus = tkinter.Label(root, text="Onionr Daemon Status: unknown")
|
||||||
daemonStatus.pack()
|
daemonStatus.pack()
|
||||||
|
|
||||||
scrollbar.config(command=tkinter.listbox.yview)
|
scrollbar.config(command=tkinter.Listbox.yview)
|
||||||
root.after(2000, update)
|
root.after(2000, update)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
|
|
||||||
|
def openGUI():
|
||||||
|
t = threading.Thread(target = reallyOpenGUI)
|
||||||
|
t.daemon = False
|
||||||
|
t.start()
|
||||||
|
|
||||||
def on_init(api, data = None):
|
def on_init(api, data = None):
|
||||||
global pluginapi
|
global pluginapi
|
||||||
pluginapi = api
|
pluginapi = api
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name" : "pluginmanager",
|
||||||
|
"version" : "1.0",
|
||||||
|
"author" : "onionr"
|
||||||
|
}
|
|
@ -0,0 +1,546 @@
|
||||||
|
'''
|
||||||
|
Onionr - P2P Microblogging Platform & Social network.
|
||||||
|
|
||||||
|
This plugin acts as a plugin manager, and allows the user to install other plugins distributed over Onionr.
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
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/>.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# useful libraries
|
||||||
|
import logger, config
|
||||||
|
import os, sys, json, time, random, shutil, base64, getpass, datetime, re
|
||||||
|
from onionrblockapi import Block
|
||||||
|
|
||||||
|
plugin_name = 'pluginmanager'
|
||||||
|
|
||||||
|
keys_data = {'keys' : {}, 'plugins' : [], 'repositories' : {}}
|
||||||
|
|
||||||
|
# key functions
|
||||||
|
|
||||||
|
def writeKeys():
|
||||||
|
'''
|
||||||
|
Serializes and writes the keystore in memory to file
|
||||||
|
'''
|
||||||
|
|
||||||
|
file = open(keys_file, 'w')
|
||||||
|
file.write(json.dumps(keys_data, indent=4, sort_keys=True))
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
def readKeys():
|
||||||
|
'''
|
||||||
|
Loads the keystore into memory
|
||||||
|
'''
|
||||||
|
|
||||||
|
global keys_data
|
||||||
|
keys_data = json.loads(open(keys_file).read())
|
||||||
|
return keys_data
|
||||||
|
|
||||||
|
def getKey(plugin):
|
||||||
|
'''
|
||||||
|
Returns the public key for a given plugin
|
||||||
|
'''
|
||||||
|
|
||||||
|
global keys_data
|
||||||
|
readKeys()
|
||||||
|
return (keys_data['keys'][plugin] if plugin in keys_data['keys'] else None)
|
||||||
|
|
||||||
|
def saveKey(plugin, key):
|
||||||
|
'''
|
||||||
|
Saves the public key for a plugin to keystore
|
||||||
|
'''
|
||||||
|
|
||||||
|
global keys_data
|
||||||
|
readKeys()
|
||||||
|
keys_data['keys'][plugin] = key
|
||||||
|
writeKeys()
|
||||||
|
|
||||||
|
def getPlugins():
|
||||||
|
'''
|
||||||
|
Returns a list of plugins installed by the plugin manager
|
||||||
|
'''
|
||||||
|
|
||||||
|
global keys_data
|
||||||
|
readKeys()
|
||||||
|
return keys_data['plugins']
|
||||||
|
|
||||||
|
def addPlugin(plugin):
|
||||||
|
'''
|
||||||
|
Saves the plugin name, to remember that it was installed by the pluginmanager
|
||||||
|
'''
|
||||||
|
|
||||||
|
global keys_data
|
||||||
|
readKeys()
|
||||||
|
if not plugin in keys_data['plugins']:
|
||||||
|
keys_data['plugins'].append(plugin)
|
||||||
|
writeKeys()
|
||||||
|
|
||||||
|
def removePlugin(plugin):
|
||||||
|
'''
|
||||||
|
Removes the plugin name from the pluginmanager's records
|
||||||
|
'''
|
||||||
|
|
||||||
|
global keys_data
|
||||||
|
readKeys()
|
||||||
|
if plugin in keys_data['plugins']:
|
||||||
|
keys_data['plugins'].remove(plugin)
|
||||||
|
writeKeys()
|
||||||
|
|
||||||
|
def getRepositories():
|
||||||
|
'''
|
||||||
|
Returns a list of plugins installed by the plugin manager
|
||||||
|
'''
|
||||||
|
|
||||||
|
global keys_data
|
||||||
|
readKeys()
|
||||||
|
return keys_data['repositories']
|
||||||
|
|
||||||
|
def addRepository(repositories, data):
|
||||||
|
'''
|
||||||
|
Saves the plugin name, to remember that it was installed by the pluginmanager
|
||||||
|
'''
|
||||||
|
|
||||||
|
global keys_data
|
||||||
|
readKeys()
|
||||||
|
keys_data['repositories'][repositories] = data
|
||||||
|
writeKeys()
|
||||||
|
|
||||||
|
def removeRepository(repositories):
|
||||||
|
'''
|
||||||
|
Removes the plugin name from the pluginmanager's records
|
||||||
|
'''
|
||||||
|
|
||||||
|
global keys_data
|
||||||
|
readKeys()
|
||||||
|
if plugin in keys_data['repositories']:
|
||||||
|
del keys_data['repositories'][repositories]
|
||||||
|
writeKeys()
|
||||||
|
|
||||||
|
def check():
|
||||||
|
'''
|
||||||
|
Checks to make sure the keystore file still exists
|
||||||
|
'''
|
||||||
|
|
||||||
|
global keys_file
|
||||||
|
keys_file = pluginapi.plugins.get_data_folder(plugin_name) + 'keystore.json'
|
||||||
|
if not os.path.isfile(keys_file):
|
||||||
|
writeKeys()
|
||||||
|
|
||||||
|
# plugin management
|
||||||
|
|
||||||
|
def sanitize(name):
|
||||||
|
return re.sub('[^0-9a-zA-Z]+', '', str(name).lower())[:255]
|
||||||
|
|
||||||
|
def blockToPlugin(block):
|
||||||
|
try:
|
||||||
|
block = Block(block)
|
||||||
|
blockContent = json.loads(block.getContent())
|
||||||
|
|
||||||
|
name = sanitize(blockContent['name'])
|
||||||
|
author = blockContent['author']
|
||||||
|
date = blockContent['date']
|
||||||
|
version = None
|
||||||
|
|
||||||
|
if 'version' in blockContent['info']:
|
||||||
|
version = blockContent['info']['version']
|
||||||
|
|
||||||
|
content = base64.b64decode(blockContent['content'].encode())
|
||||||
|
|
||||||
|
source = pluginapi.plugins.get_data_folder(plugin_name) + 'plugin.zip'
|
||||||
|
destination = pluginapi.plugins.get_folder(name)
|
||||||
|
|
||||||
|
with open(source, 'wb') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
if os.path.exists(destination) and not os.path.isfile(destination):
|
||||||
|
shutil.rmtree(destination)
|
||||||
|
|
||||||
|
shutil.unpack_archive(source, destination)
|
||||||
|
pluginapi.plugins.enable(name)
|
||||||
|
|
||||||
|
logger.info('Installation of %s complete.' % name)
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Failed to install plugin.', error = e, timestamp = False)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def pluginToBlock(plugin, import_block = True):
|
||||||
|
try:
|
||||||
|
plugin = sanitize(plugin)
|
||||||
|
|
||||||
|
directory = pluginapi.get_pluginapi().get_folder(plugin)
|
||||||
|
data_directory = pluginapi.get_pluginapi().get_data_folder(plugin)
|
||||||
|
zipfile = pluginapi.get_pluginapi().get_data_folder(plugin_name) + 'plugin.zip'
|
||||||
|
|
||||||
|
if os.path.exists(directory) and not os.path.isfile(directory):
|
||||||
|
if os.path.exists(data_directory) and not os.path.isfile(data_directory):
|
||||||
|
shutil.rmtree(data_directory)
|
||||||
|
if os.path.exists(zipfile) and os.path.isfile(zipfile):
|
||||||
|
os.remove(zipfile)
|
||||||
|
if os.path.exists(directory + '__pycache__') and not os.path.isfile(directory + '__pycache__'):
|
||||||
|
shutil.rmtree(directory + '__pycache__')
|
||||||
|
|
||||||
|
shutil.make_archive(zipfile[:-4], 'zip', directory)
|
||||||
|
data = base64.b64encode(open(zipfile, 'rb').read())
|
||||||
|
|
||||||
|
author = getpass.getuser()
|
||||||
|
description = 'Default plugin description'
|
||||||
|
info = {"name" : plugin}
|
||||||
|
try:
|
||||||
|
if os.path.exists(directory + 'info.json'):
|
||||||
|
info = json.loads(open(directory + 'info.json').read())
|
||||||
|
if 'author' in info:
|
||||||
|
author = info['author']
|
||||||
|
if 'description' in info:
|
||||||
|
description = info['description']
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
metadata = {'author' : author, 'date' : str(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')), 'name' : plugin, 'info' : info, 'compiled-by' : plugin_name, 'content' : data.decode('utf-8'), 'description' : description}
|
||||||
|
|
||||||
|
hash = pluginapi.get_core().insertBlock(json.dumps(metadata), header = 'plugin', sign = True)
|
||||||
|
|
||||||
|
if import_block:
|
||||||
|
pluginapi.get_utils().importNewBlocks()
|
||||||
|
|
||||||
|
return hash
|
||||||
|
else:
|
||||||
|
logger.error('Plugin %s does not exist.' % plugin)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Failed to convert plugin to block.', error = e, timestamp = False)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def installBlock(block):
|
||||||
|
try:
|
||||||
|
block = Block(block)
|
||||||
|
blockContent = json.loads(block.getContent())
|
||||||
|
|
||||||
|
name = sanitize(blockContent['name'])
|
||||||
|
author = blockContent['author']
|
||||||
|
date = blockContent['date']
|
||||||
|
version = None
|
||||||
|
|
||||||
|
if 'version' in blockContent['info']:
|
||||||
|
version = blockContent['info']['version']
|
||||||
|
|
||||||
|
install = False
|
||||||
|
|
||||||
|
logger.info(('Will install %s' + (' v' + version if not version is None else '') + ' (%s), by %s') % (name, date, author))
|
||||||
|
|
||||||
|
# TODO: Convert to single line if statement
|
||||||
|
if os.path.exists(pluginapi.plugins.get_folder(name)):
|
||||||
|
install = logger.confirm(message = 'Continue with installation (will overwrite existing plugin) %s?')
|
||||||
|
else:
|
||||||
|
install = logger.confirm(message = 'Continue with installation %s?')
|
||||||
|
|
||||||
|
if install:
|
||||||
|
blockToPlugin(block.getHash())
|
||||||
|
addPlugin(name)
|
||||||
|
else:
|
||||||
|
logger.info('Installation cancelled.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Failed to install plugin.', error = e, timestamp = False)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def uninstallPlugin(plugin):
|
||||||
|
try:
|
||||||
|
plugin = sanitize(plugin)
|
||||||
|
|
||||||
|
pluginFolder = pluginapi.plugins.get_folder(plugin)
|
||||||
|
exists = (os.path.exists(pluginFolder) and not os.path.isfile(pluginFolder))
|
||||||
|
installedByPluginManager = plugin in getPlugins()
|
||||||
|
remove = False
|
||||||
|
|
||||||
|
if not exists:
|
||||||
|
logger.warn('Plugin %s does not exist.' % plugin, timestamp = False)
|
||||||
|
return False
|
||||||
|
|
||||||
|
default = 'y'
|
||||||
|
if not installedByPluginManager:
|
||||||
|
logger.warn('The plugin %s was not installed by %s.' % (plugin, plugin_name), timestamp = False)
|
||||||
|
default = 'n'
|
||||||
|
remove = logger.confirm(message = 'All plugin data will be lost. Are you sure you want to proceed %s?', default = default)
|
||||||
|
|
||||||
|
if remove:
|
||||||
|
if installedByPluginManager:
|
||||||
|
removePlugin(plugin)
|
||||||
|
pluginapi.plugins.disable(plugin)
|
||||||
|
shutil.rmtree(pluginFolder)
|
||||||
|
|
||||||
|
logger.info('Uninstallation of %s complete.' % plugin)
|
||||||
|
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.info('Uninstallation cancelled.')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Failed to uninstall plugin.', error = e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# command handlers
|
||||||
|
|
||||||
|
def help():
|
||||||
|
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin> [public key/block hash]')
|
||||||
|
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin> [public key/block hash]')
|
||||||
|
|
||||||
|
def commandInstallPlugin():
|
||||||
|
if len(sys.argv) >= 3:
|
||||||
|
check()
|
||||||
|
|
||||||
|
pluginname = sys.argv[2]
|
||||||
|
pkobh = None # public key or block hash
|
||||||
|
|
||||||
|
version = None
|
||||||
|
if ':' in pluginname:
|
||||||
|
details = pluginname
|
||||||
|
pluginname = sanitize(details[0])
|
||||||
|
version = details[1]
|
||||||
|
|
||||||
|
sanitize(pluginname)
|
||||||
|
|
||||||
|
if len(sys.argv) >= 4:
|
||||||
|
# public key or block hash specified
|
||||||
|
pkobh = sys.argv[3]
|
||||||
|
else:
|
||||||
|
# none specified, check if in config file
|
||||||
|
pkobh = getKey(pluginname)
|
||||||
|
|
||||||
|
if pkobh is None:
|
||||||
|
# still nothing found, try searching repositories
|
||||||
|
logger.info('Searching for public key in repositories...')
|
||||||
|
try:
|
||||||
|
repos = getRepositories()
|
||||||
|
distributors = list()
|
||||||
|
for repo, records in repos.items():
|
||||||
|
if pluginname in records:
|
||||||
|
logger.debug('Found %s in repository %s for plugin %s.' % (records[pluginname], repo, pluginname))
|
||||||
|
distributors.append(records[pluginname])
|
||||||
|
|
||||||
|
if len(distributors) != 0:
|
||||||
|
distributor = None
|
||||||
|
|
||||||
|
if len(distributors) == 1:
|
||||||
|
logger.info('Found distributor: %s' % distributors[0])
|
||||||
|
distributor = distributors[0]
|
||||||
|
else:
|
||||||
|
distributors_message = ''
|
||||||
|
|
||||||
|
index = 1
|
||||||
|
for dist in distributors:
|
||||||
|
distributors_message += ' ' + logger.colors.bold + str(index) + ') ' + logger.colors.reset + str(dist) + '\n'
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
logger.info((logger.colors.bold + 'Found distributors (%s):' + logger.colors.reset + '\n' + distributors_message) % len(distributors))
|
||||||
|
|
||||||
|
valid = False
|
||||||
|
while not valid:
|
||||||
|
choice = logger.readline('Select the number of the key to use, from 1 to %s, or press Ctrl+C to cancel:' % (index - 1))
|
||||||
|
|
||||||
|
try:
|
||||||
|
if int(choice) < index and int(choice) >= 1:
|
||||||
|
distributor = distributors[int(choice)]
|
||||||
|
valid = True
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info('Installation cancelled.')
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not distributor is None:
|
||||||
|
pkobh = distributor
|
||||||
|
except Exception as e:
|
||||||
|
logger.warn('Failed to lookup plugin in repositories.', timestamp = False)
|
||||||
|
logger.error('asdf', error = e, timestamp = False)
|
||||||
|
|
||||||
|
if pkobh is None:
|
||||||
|
logger.error('No key for this plugin found in keystore or repositories, please specify.')
|
||||||
|
help()
|
||||||
|
return True
|
||||||
|
|
||||||
|
valid_hash = pluginapi.get_utils().validateHash(pkobh)
|
||||||
|
real_block = False
|
||||||
|
valid_key = pluginapi.get_utils().validatePubKey(pkobh)
|
||||||
|
real_key = False
|
||||||
|
|
||||||
|
if valid_hash:
|
||||||
|
real_block = Block.exists(pkobh)
|
||||||
|
elif valid_key:
|
||||||
|
real_key = pluginapi.get_utils().hasKey(pkobh)
|
||||||
|
|
||||||
|
blockhash = None
|
||||||
|
|
||||||
|
if valid_hash and not real_block:
|
||||||
|
logger.error('Block hash not found. Perhaps it has not been synced yet?')
|
||||||
|
logger.debug('Is valid hash, but does not belong to a known block.')
|
||||||
|
|
||||||
|
return True
|
||||||
|
elif valid_hash and real_block:
|
||||||
|
blockhash = str(pkobh)
|
||||||
|
logger.debug('Using block %s...' % blockhash)
|
||||||
|
|
||||||
|
installBlock(blockhash)
|
||||||
|
elif valid_key and not real_key:
|
||||||
|
logger.error('Public key not found. Try adding the node by address manually, if possible.')
|
||||||
|
logger.debug('Is valid key, but the key is not a known one.')
|
||||||
|
elif valid_key and real_key:
|
||||||
|
publickey = str(pkobh)
|
||||||
|
logger.debug('Using public key %s...' % publickey)
|
||||||
|
|
||||||
|
saveKey(pluginname, pkobh)
|
||||||
|
|
||||||
|
signedBlocks = Block.getBlocks(type = 'plugin', signed = True, signer = publickey)
|
||||||
|
|
||||||
|
mostRecentTimestamp = None
|
||||||
|
mostRecentVersionBlock = None
|
||||||
|
|
||||||
|
for block in signedBlocks:
|
||||||
|
try:
|
||||||
|
blockContent = json.loads(block.getContent())
|
||||||
|
|
||||||
|
if not (('author' in blockContent) and ('info' in blockContent) and ('date' in blockContent) and ('name' in blockContent)):
|
||||||
|
raise ValueError('Missing required parameter `date` in block %s.' % block.getHash())
|
||||||
|
|
||||||
|
blockDatetime = datetime.datetime.strptime(blockContent['date'], '%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
if blockContent['name'] == pluginname:
|
||||||
|
if ('version' in blockContent['info']) and (blockContent['info']['version'] == version) and (not version is None):
|
||||||
|
mostRecentTimestamp = blockDatetime
|
||||||
|
mostRecentVersionBlock = block.getHash()
|
||||||
|
break
|
||||||
|
elif mostRecentTimestamp is None:
|
||||||
|
mostRecentTimestamp = blockDatetime
|
||||||
|
mostRecentVersionBlock = block.getHash()
|
||||||
|
elif blockDatetime > mostRecentTimestamp:
|
||||||
|
mostRecentTimestamp = blockDatetime
|
||||||
|
mostRecentVersionBlock = block.getHash()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logger.warn('Only continue the installation is you are absolutely certain that you trust the plugin distributor. Public key of plugin distributor: %s' % publickey, timestamp = False)
|
||||||
|
installBlock(mostRecentVersionBlock)
|
||||||
|
else:
|
||||||
|
logger.error('Unknown data "%s"; must be public key or block hash.' % str(pkobh))
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin> [public key/block hash]')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def commandUninstallPlugin():
|
||||||
|
if len(sys.argv) >= 3:
|
||||||
|
uninstallPlugin(sys.argv[2])
|
||||||
|
else:
|
||||||
|
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin>')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def commandSearchPlugin():
|
||||||
|
logger.info('This feature has not been created yet. Please check back later.')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def commandAddRepository():
|
||||||
|
if len(sys.argv) >= 3:
|
||||||
|
check()
|
||||||
|
|
||||||
|
blockhash = sys.argv[2]
|
||||||
|
|
||||||
|
if pluginapi.get_utils().validateHash(blockhash):
|
||||||
|
if Block.exists(blockhash):
|
||||||
|
try:
|
||||||
|
blockContent = json.loads(Block(blockhash).getContent())
|
||||||
|
|
||||||
|
pluginslist = dict()
|
||||||
|
|
||||||
|
for pluginname, distributor in blockContent['plugins'].items():
|
||||||
|
if pluginapi.get_utils().validatePubKey(distributor):
|
||||||
|
pluginslist[pluginname] = distributor
|
||||||
|
|
||||||
|
logger.debug('Found %s records in repository.' % len(pluginslist))
|
||||||
|
|
||||||
|
if len(pluginslist) != 0:
|
||||||
|
addRepository(blockhash, pluginslist)
|
||||||
|
logger.info('Successfully added repository.')
|
||||||
|
else:
|
||||||
|
logger.error('Repository contains no records, not importing.')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Failed to parse block.', error = e)
|
||||||
|
else:
|
||||||
|
logger.error('Block hash not found. Perhaps it has not been synced yet?')
|
||||||
|
logger.debug('Is valid hash, but does not belong to a known block.')
|
||||||
|
else:
|
||||||
|
logger.error('Unknown data "%s"; must be block hash.' % str(pkobh))
|
||||||
|
else:
|
||||||
|
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' [block hash]')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def commandRemoveRepository():
|
||||||
|
if len(sys.argv) >= 3:
|
||||||
|
check()
|
||||||
|
|
||||||
|
blockhash = sys.argv[2]
|
||||||
|
|
||||||
|
if pluginapi.get_utils().validateHash(blockhash):
|
||||||
|
if blockhash in getRepositories():
|
||||||
|
try:
|
||||||
|
removeRepository(blockhash)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Failed to parse block.', error = e)
|
||||||
|
else:
|
||||||
|
logger.error('Repository has not been imported, nothing to remove.')
|
||||||
|
else:
|
||||||
|
logger.error('Unknown data "%s"; must be block hash.' % str(pkobh))
|
||||||
|
else:
|
||||||
|
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' [block hash]')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def commandPublishPlugin():
|
||||||
|
if len(sys.argv) >= 3:
|
||||||
|
check()
|
||||||
|
|
||||||
|
pluginname = sanitize(sys.argv[2])
|
||||||
|
pluginfolder = pluginapi.plugins.get_folder(pluginname)
|
||||||
|
|
||||||
|
if os.path.exists(pluginfolder) and not os.path.isfile(pluginfolder):
|
||||||
|
block = pluginToBlock(pluginname)
|
||||||
|
logger.info('Plugin saved in block %s.' % block)
|
||||||
|
else:
|
||||||
|
logger.error('Plugin %s does not exist.' % pluginname, timestamp = False)
|
||||||
|
else:
|
||||||
|
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin>')
|
||||||
|
|
||||||
|
# event listeners
|
||||||
|
|
||||||
|
def on_init(api, data = None):
|
||||||
|
global pluginapi
|
||||||
|
pluginapi = api
|
||||||
|
check()
|
||||||
|
|
||||||
|
# register some commands
|
||||||
|
api.commands.register(['install-plugin', 'installplugin', 'plugin-install', 'install', 'plugininstall'], commandInstallPlugin)
|
||||||
|
api.commands.register(['remove-plugin', 'removeplugin', 'plugin-remove', 'uninstall-plugin', 'uninstallplugin', 'plugin-uninstall', 'uninstall', 'remove', 'pluginremove'], commandUninstallPlugin)
|
||||||
|
api.commands.register(['search', 'filter-plugins', 'search-plugins', 'searchplugins', 'search-plugin', 'searchplugin', 'findplugin', 'find-plugin', 'filterplugin', 'plugin-search', 'pluginsearch'], commandSearchPlugin)
|
||||||
|
api.commands.register(['add-repo', 'add-repository', 'addrepo', 'addrepository', 'repository-add', 'repo-add', 'repoadd', 'addrepository', 'add-plugin-repository', 'add-plugin-repo', 'add-pluginrepo', 'add-pluginrepository', 'addpluginrepo', 'addpluginrepository'], commandAddRepository)
|
||||||
|
api.commands.register(['remove-repo', 'remove-repository', 'removerepo', 'removerepository', 'repository-remove', 'repo-remove', 'reporemove', 'removerepository', 'remove-plugin-repository', 'remove-plugin-repo', 'remove-pluginrepo', 'remove-pluginrepository', 'removepluginrepo', 'removepluginrepository', 'rm-repo', 'rm-repository', 'rmrepo', 'rmrepository', 'repository-rm', 'repo-rm', 'reporm', 'rmrepository', 'rm-plugin-repository', 'rm-plugin-repo', 'rm-pluginrepo', 'rm-pluginrepository', 'rmpluginrepo', 'rmpluginrepository'], commandRemoveRepository)
|
||||||
|
api.commands.register(['publish-plugin', 'plugin-publish', 'publishplugin', 'pluginpublish', 'publish'], commandPublishPlugin)
|
||||||
|
|
||||||
|
# add help menus once the features are actually implemented
|
||||||
|
|
||||||
|
return
|
|
@ -12,5 +12,9 @@
|
||||||
"output": true,
|
"output": true,
|
||||||
"color": true
|
"color": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"allocations":{
|
||||||
|
"disk": 1000000000,
|
||||||
|
"netTotal": 1000000000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
'''
|
'''
|
||||||
Default plugin template file
|
$name plugin template file.
|
||||||
Generated on $date by $user.
|
Generated on $date by $user.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Imports some useful libraries
|
# Imports some useful libraries
|
||||||
import logger, config
|
import logger, config
|
||||||
|
|
||||||
|
plugin_name = '$name'
|
||||||
|
|
||||||
def on_init(api, data = None):
|
def on_init(api, data = None):
|
||||||
'''
|
'''
|
||||||
This event is called after Onionr is initialized, but before the command
|
This event is called after Onionr is initialized, but before the command
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
'''
|
'''
|
||||||
import unittest, sys, os, base64, tarfile, shutil, simplecrypt, logger, btc
|
import unittest, sys, os, base64, tarfile, shutil, simplecrypt, logger #, btc
|
||||||
|
|
||||||
class OnionrTests(unittest.TestCase):
|
class OnionrTests(unittest.TestCase):
|
||||||
def testPython3(self):
|
def testPython3(self):
|
||||||
|
@ -56,7 +56,7 @@ class OnionrTests(unittest.TestCase):
|
||||||
myCore = core.Core()
|
myCore = core.Core()
|
||||||
if not os.path.exists('data/peers.db'):
|
if not os.path.exists('data/peers.db'):
|
||||||
myCore.createPeerDB()
|
myCore.createPeerDB()
|
||||||
if myCore.addPeer('6M5MXL237OK57ITHVYN5WGHANPGOMKS5C3PJLHBBNKFFJQOIDOJA====') and not myCore.addPeer('NFXHMYLMNFSAU==='):
|
if myCore.addPeer('6M5MXL237OK57ITHVYN5WGHANPGOMKS5C3PJLHBBNKFFJQOIDOJA====', '1cSix9Ao/yQSdo0sNif8cm2uTcYnSphb4JdZL/3WkN4=') and not myCore.addPeer('NFXHMYLMNFSAU===', '1cSix9Ao/yQSdo0sNif8cm2uTcYnSphb4JdZL/3WkN4='):
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
else:
|
else:
|
||||||
self.assertTrue(False)
|
self.assertTrue(False)
|
||||||
|
|
Loading…
Reference in New Issue