diff --git a/.style.yapf b/.style.yapf
index 13eb3febd..3e0cd3af9 100644
--- a/.style.yapf
+++ b/.style.yapf
@@ -3,6 +3,8 @@ based_on_style=facebook
column_limit=240
indent_width=4
continuation_indent_width=4
+blank_line_before_class_docstring=True
+blank_line_before_nested_class_or_def=True
blank_lines_around_top_level_definition=2
blank_lines_between_top_level_imports_and_variables=1
no_spaces_around_selected_binary_operators=*,/
diff --git a/bitdust/blockchain/bismuth_miner.py b/bitdust/blockchain/bismuth_miner.py
index 1ecea6606..7d59ba86d 100644
--- a/bitdust/blockchain/bismuth_miner.py
+++ b/bitdust/blockchain/bismuth_miner.py
@@ -5,7 +5,6 @@
import socks
import hashlib
import random
-import traceback
#------------------------------------------------------------------------------
@@ -95,11 +94,8 @@ def check_start_mining():
global _OwnCoinsLastTime
global _WantMoreCoins
global _MiningIsOn
- try:
- cur_balance = bismuth_wallet.my_balance()
- except:
- traceback.print_exc()
- cur_balance = 'N/A'
+
+ cur_balance = bismuth_wallet.my_balance()
lg.info('my wallet address is %s and current balance is %s' % (bismuth_wallet.my_wallet_address(), cur_balance))
if _Debug:
@@ -108,11 +104,7 @@ def check_start_mining():
if cur_balance == 'N/A':
reactor.callLater(10, check_start_mining) # @UndefinedVariable
return
- try:
- cur_balance = float(cur_balance)
- except:
- reactor.callLater(10, check_start_mining) # @UndefinedVariable
- return
+
if _WantMoreCoins and (not _OwnCoinsLastTime or (time.time() - _OwnCoinsLastTime > 60)):
_OwnCoinsLastTime = time.time()
_WantMoreCoins = False
diff --git a/bitdust/blockchain/bismuth_node.py b/bitdust/blockchain/bismuth_node.py
index b4d247441..cced5e4d7 100644
--- a/bitdust/blockchain/bismuth_node.py
+++ b/bitdust/blockchain/bismuth_node.py
@@ -521,6 +521,7 @@ def check_db_for_bootstrap(node):
class CustomLogHandler(logging.Handler):
+
def emit(self, record):
try:
if _Debug:
diff --git a/bitdust/blockchain/bismuth_wallet.py b/bitdust/blockchain/bismuth_wallet.py
index c89e7eae2..fa10c6c29 100644
--- a/bitdust/blockchain/bismuth_wallet.py
+++ b/bitdust/blockchain/bismuth_wallet.py
@@ -125,17 +125,24 @@ def my_wallet_address():
def my_balance():
- return client().balance()
+ try:
+ _balance = float(client().balance())
+ except:
+ lg.exc()
+ return 'N/A'
+ return _balance
def latest_transactions(num, offset, for_display, mempool_included):
return client().latest_transactions(num, offset, for_display, mempool_included)
-def send_transaction(recipient, amount, operation='', data=''):
+def send_transaction(recipient, amount, operation='', data='', raise_errors=False):
error_reply = []
ret = client().send(recipient=recipient, amount=amount, operation=operation, data=data, error_reply=error_reply)
if not ret:
+ if raise_errors:
+ raise Exception(error_reply)
return error_reply
return ret
diff --git a/bitdust/blockchain/blockchain_explorer.py b/bitdust/blockchain/blockchain_explorer.py
new file mode 100644
index 000000000..67f8fe670
--- /dev/null
+++ b/bitdust/blockchain/blockchain_explorer.py
@@ -0,0 +1,396 @@
+import sys
+import time
+import sqlite3
+import base64
+
+#------------------------------------------------------------------------------
+
+if __name__ == '__main__':
+ import os.path as _p
+ sys.path.insert(0, _p.abspath(_p.join(_p.dirname(_p.abspath(sys.argv[0])), '..')))
+ src_dir_path = _p.dirname(_p.dirname(_p.dirname(_p.abspath(sys.argv[0]))))
+ sys.path.insert(0, src_dir_path)
+ sys.path.insert(0, _p.join(src_dir_path, 'bitdust_forks', 'Bismuth'))
+
+#------------------------------------------------------------------------------
+
+from twisted.internet import reactor
+from twisted.web import server, resource
+
+#------------------------------------------------------------------------------
+
+from bitdust.logs import lg
+
+from bitdust.lib import strng
+
+from bitdust.main import settings
+from bitdust.main import config
+
+from bitdust.interface import web_html_template
+
+from bitdust.blockchain import bismuth_node
+
+#------------------------------------------------------------------------------
+
+_Debug = True
+_DebugLevel = 10
+
+#------------------------------------------------------------------------------
+
+_DataDirPath = None
+_ExplorerHost = None
+_ExplorerPort = None
+_WebListener = None
+
+#------------------------------------------------------------------------------
+
+
+def init():
+ global _DataDirPath
+ global _ExplorerHost
+ global _ExplorerPort
+ global _WebListener
+
+ _DataDirPath = settings.ServiceDir('bismuth_blockchain')
+ _ExplorerHost = config.conf().getString('services/blockchain-explorer/host', '127.0.0.1')
+ _ExplorerPort = config.conf().getInt('services/blockchain-explorer/web-port', 19080)
+
+ root = BlockchainRootPage()
+ root.putChild(b'', BlockchainMainPage())
+ try:
+ _WebListener = reactor.listenTCP(_ExplorerPort, server.Site(root)) # @UndefinedVariable
+ if _Debug:
+ lg.out(_DebugLevel, ' have started web server at port %d hostname=%s' % (_ExplorerPort, strng.to_text(_ExplorerHost)))
+ except:
+ if _Debug:
+ lg.err('exception while trying to listen port ' + str(self.web_port))
+ lg.exc()
+ if _Debug:
+ lg.args(_DebugLevel, data_dir_path=_DataDirPath)
+ return True
+
+
+def shutdown():
+ global _WebListener
+ if _Debug:
+ lg.dbg(_DebugLevel, '')
+ if _WebListener:
+ _WebListener.stopListening()
+ if _Debug:
+ lg.out(_DebugLevel, ' stopped web listener')
+ _WebListener = None
+ return True
+
+
+#------------------------------------------------------------------------------
+
+
+def execute(cursor, query, param):
+ while True:
+ try:
+ cursor.execute(query, param)
+ break
+ except Exception as e:
+ print('Database query: {} {}'.format(cursor, query))
+ print('Database retry reason: {}'.format(e))
+ time.sleep(0.2)
+ return cursor
+
+
+#------------------------------------------------------------------------------
+
+
+class BlockchainMainPage(resource.Resource):
+
+ def render_GET(self, request):
+ global _ExplorerHost
+
+ page_size = 500
+
+ page_num = request.args.get(b'page', [])
+ if page_num:
+ try:
+ page_num = int(strng.to_text(page_num[0]))
+ except:
+ lg.exc()
+ page_num = 0
+ else:
+ page_num = 0
+
+ page_max = -1
+
+ src = ''
+
+ conn = sqlite3.connect(bismuth_node.nod().ledger_path, timeout=60.0)
+ c = conn.cursor()
+ execute(c, 'SELECT * FROM transactions ORDER BY block_height DESC, timestamp DESC LIMIT ? OFFSET ?;', (
+ page_size,
+ page_size*page_num,
+ ))
+ _all = c.fetchall()
+ c.close()
+ conn.close()
+ conn = None
+ c = None
+
+ view = []
+ b = -1
+ x_old = 'init'
+
+ for x in _all:
+ if int(x[0]) == 1:
+ page_max = page_num
+
+ if x[0] != x_old:
+ color_cell = '#F8F8F8'
+ view.append('
|
') #block separator
+ else:
+ color_cell = 'white'
+
+ view.append(''.format(color_cell))
+
+ if x[0] != x_old:
+ b = b + 1
+
+ if x_old != x[0]:
+ view.append('{} | '.format(x[0])) #block height
+ else:
+ view.append('{} | '.format(x[0])) #block height
+
+ view.append('{}'.format(time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(float(x[1])))))
+ view.append(' | {}… | '.format(x[2][:6])) #from
+ view.append('{}… | '.format(x[3][:6])) #to
+ view.append('{0:g} | '.format(float(x[4]))) #amount
+
+ if x_old != x[0]:
+ view.append('{}… | '.format(x[7][:6])) #block hash
+ else:
+ view.append(' | ') #block hash
+
+ view.append('{0:g} | '.format(float(x[8]))) #fee
+ view.append('{0:g} | '.format(float(x[9]))) #reward
+
+ view.append('{}{} | '.format(x[10][:16], '…' if len(x[10]) > 16 else '')) #operation
+ view.append('{}{} | '.format(x[11][:24], '…' if len(x[11]) > 24 else '')) #openfield
+
+ view.append('{}… | '.format(
+ strng.to_text(base64.b64encode(strng.to_bin(x[5]), altchars=b'-_')),
+ x[5][:6],
+ )) #TXID
+
+ view.append('
')
+
+ x_old = x[0]
+
+ src += ''
+ src += '
\n'
+ src += '
\n'
+ src += '
\n'
+
+ src += '
\n'
+ src += '
BitDust blockchain explorer
\n'
+ src += '\n'
+
+ src += '
\n'
+ src += '\n'
+ src += '
\n'
+
+ src += '
\n'
+
+ src += ''
+
+ src += '
\n'
+
+ src += '\n'
+ src += 'Block | \n'
+ src += 'Timestamp | \n'
+ src += 'From | \n'
+ src += 'To | \n'
+ src += 'Amount | \n'
+ src += 'Hash | \n'
+ src += 'Fee | \n'
+ src += 'Reward | \n'
+ src += 'Operation | \n'
+ src += 'Openfield | \n'
+ src += 'TXID | \n'
+ src += '
\n'
+
+ src += ''.join(view)
+
+ src += '
\n'
+ src += '
\n'
+
+ src += '
\n'
+ src += '
\n'
+ src += '\n'
+ src += '
\n'
+
+ src += '
\n'
+ src += '
\n'
+ src += '
\n'
+ src += '
\n'
+
+ html_src = web_html_template.WEB_ROOT_TEMPLATE % dict(
+ title='BitDust blockchain explorer',
+ site_url='https://bitdust.io',
+ basepath='https://bitdust.io/',
+ wikipath='https://bitdust.io/wiki/',
+ idserverspath='https://identities.bitdust.io/',
+ blockchainpath='https://blockchain.bitdust.io/',
+ div_main_class='main blockchain',
+ div_main_body=src,
+ google_analytics='',
+ )
+ return strng.to_bin(html_src)
+
+
+#------------------------------------------------------------------------------
+
+
+class BlockchainTransactionPage(resource.Resource):
+
+ def __init__(self, tx_id):
+ resource.Resource.__init__(self)
+ self.tx_id = tx_id
+
+ def render_GET(self, request):
+ src = ''
+ src += ''
+ src += '
\n'
+ src += '
\n'
+ src += '
\n'
+
+ try:
+ _t = strng.to_text(base64.b64decode(self.tx_id, altchars='-_'))
+ except:
+ src += '
invalid transaction ID
\n'
+ src += '
\n'
+ src += '
\n'
+ src += '
\n'
+ src += '
\n'
+ html_src = web_html_template.WEB_ROOT_TEMPLATE % dict(
+ title='BitDust blockchain explorer',
+ site_url='https://bitdust.io',
+ basepath='https://bitdust.io/',
+ wikipath='https://bitdust.io/wiki/',
+ idserverspath='https://identities.bitdust.io/',
+ blockchainpath='https://blockchain.bitdust.io/',
+ div_main_class='main blockchain-transaction',
+ div_main_body=src,
+ google_analytics='',
+ )
+ return strng.to_bin(html_src)
+
+ try:
+ conn = sqlite3.connect(bismuth_node.nod().ledger_path, timeout=60.0)
+ c = conn.cursor()
+ execute(c, 'SELECT * FROM transactions WHERE substr(signature,1,4)=substr(?1,1,4) and signature like ?1;', (_t + '%', ))
+ raw = c.fetchone()
+ c.close()
+ conn.close()
+ conn = None
+ c = None
+ except:
+ src += 'reading failed
\n'
+ src += '\n'
+ src += '\n'
+ src += '\n'
+ src += '\n'
+ html_src = web_html_template.WEB_ROOT_TEMPLATE % dict(
+ title='BitDust blockchain explorer',
+ site_url='https://bitdust.io',
+ basepath='https://bitdust.io/',
+ wikipath='https://bitdust.io/wiki/',
+ idserverspath='https://identities.bitdust.io/',
+ blockchainpath='https://blockchain.bitdust.io/',
+ div_main_class='main blockchain-transaction',
+ div_main_body=src,
+ google_analytics='',
+ )
+ return strng.to_bin(html_src)
+
+ src += 'block: {}
\n'.format(raw[0])
+ src += 'block hash: {}
\n'.format(raw[7])
+ src += 'timestamp: {}
\n'.format(time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(float(raw[1]))))
+ src += 'sender: {}
\n'.format(raw[2])
+ src += 'recipient: {}
\n'.format(raw[3])
+ src += 'amount: {0:g}
\n'.format(raw[4])
+ src += 'fee: {0:g}
\n'.format(raw[8])
+ src += 'reward: {0:g}
\n'.format(raw[9])
+ src += 'operation: {}
\n'.format(raw[10])
+ src += 'openfield: {}
\n'.format(raw[11])
+ src += 'signature:\n{}
\n'.format(self.tx_id)
+ src += '\n'
+ src += '\n'
+ src += '\n'
+ src += '\n'
+
+ html_src = web_html_template.WEB_ROOT_TEMPLATE % dict(
+ title='BitDust blockchain explorer',
+ site_url='https://bitdust.io',
+ basepath='https://bitdust.io/',
+ wikipath='https://bitdust.io/wiki/',
+ idserverspath='https://identities.bitdust.io/',
+ blockchainpath='https://blockchain.bitdust.io/',
+ div_main_class='main blockchain-transaction',
+ div_main_body=src,
+ google_analytics='',
+ )
+ return strng.to_bin(html_src)
+
+
+#------------------------------------------------------------------------------
+
+
+class BlockchainRootPage(resource.Resource):
+
+ def getChild(self, path, request):
+ if not path:
+ return self
+ try:
+ path = strng.to_text(path)
+ except:
+ return resource.NoResource('Not found')
+ if path:
+ return BlockchainTransactionPage(path)
+ return resource.NoResource('Not found')
+
+
+#------------------------------------------------------------------------------
+
+
+def main():
+ bismuth_node.init()
+ settings.init()
+ reactor.addSystemEventTrigger('before', 'shutdown', shutdown) # @UndefinedVariable
+ reactor.callWhenRunning(init) # @UndefinedVariable
+ reactor.run() # @UndefinedVariable
+ settings.shutdown()
+
+
+#------------------------------------------------------------------------------
+
+if __name__ == '__main__':
+ main()
diff --git a/bitdust/customer/payment.py b/bitdust/customer/payment.py
index 7f9a7f4e7..7301ff3e9 100644
--- a/bitdust/customer/payment.py
+++ b/bitdust/customer/payment.py
@@ -31,21 +31,87 @@
#------------------------------------------------------------------------------
+import os
+
+#------------------------------------------------------------------------------
+
from bitdust.logs import lg
from bitdust.lib import utime
+from bitdust.lib import strng
+from bitdust.lib import jsn
+
+from bitdust.system import bpio
+from bitdust.system import local_fs
+
+from bitdust.main import settings
+
+from bitdust.crypt import key
+
+from bitdust.blockchain import bismuth_wallet
+
+from bitdust.userid import id_url
+
+#------------------------------------------------------------------------------
+
+
+def get_supplier_contracts_dir(supplier_idurl):
+ supplier_idurl = id_url.field(supplier_idurl)
+ supplier_contracts_prefix = '{}_{}'.format(
+ supplier_idurl.username,
+ strng.to_text(key.HashSHA(supplier_idurl.to_public_key(), hexdigest=True)),
+ )
+ return os.path.join(settings.ServiceDir('service_customer_contracts'), supplier_contracts_prefix)
+
+
+def save_storage_contract(supplier_idurl, json_data):
+ supplier_contracts_dir = get_supplier_contracts_dir(supplier_idurl)
+ if not os.path.isdir(supplier_contracts_dir):
+ bpio._dirs_make(supplier_contracts_dir)
+ contract_path = os.path.join(supplier_contracts_dir, str(utime.unpack_time(json_data['started'])))
+ json_data['supplier'] = supplier_idurl
+ local_fs.WriteTextFile(contract_path, jsn.dumps(json_data))
+ return contract_path
+
+
+def list_storage_contracts(supplier_idurl):
+ supplier_contracts_dir = get_supplier_contracts_dir(supplier_idurl)
+ if not os.path.isdir(supplier_contracts_dir):
+ return []
+ l = []
+ for contract_filename in os.listdir(supplier_contracts_dir):
+ contract_path = os.path.join(supplier_contracts_dir, contract_filename)
+ json_data = jsn.loads_text(local_fs.ReadTextFile(contract_path))
+ l.append(json_data)
+ return l
+
-from bitdust.contacts import contactsdb
+def list_contracted_suppliers():
+ supplier_idurls = []
+ for supplier_contracts_prefix in os.listdir(settings.ServiceDir('service_customer_contracts')):
+ supplier_contracts_dir = os.path.join(settings.ServiceDir('service_customer_contracts'), supplier_contracts_prefix)
+ for contract_filename in os.listdir(supplier_contracts_dir):
+ contract_path = os.path.join(supplier_contracts_dir, contract_filename)
+ json_data = jsn.loads_text(local_fs.ReadTextFile(contract_path))
+ supplier_idurl = json_data.get('supplier', None)
+ if supplier_idurl:
+ if not id_url.is_in(supplier_idurl, supplier_idurls):
+ supplier_idurls.append(supplier_idurl)
+ break
+ return supplier_idurls
-from bitdust.customer import supplier_connector
#------------------------------------------------------------------------------
def pay_for_storage():
+ cur_balance = bismuth_wallet.my_balance()
+ if cur_balance == 'N/A':
+ lg.err('my current balance is not available, payments are not possible at the moment')
+ return False
now = utime.utcnow_to_sec1970()
- for supplier_idurl in contactsdb.suppliers():
- supplier_contracts = supplier_connector.list_storage_contracts(supplier_idurl)
+ for supplier_idurl in list_contracted_suppliers():
+ supplier_contracts = list_storage_contracts(supplier_idurl)
unpaid_contracts = []
for json_data in supplier_contracts:
if json_data.get('paid'):
@@ -53,12 +119,52 @@ def pay_for_storage():
if now < utime.unpack_time(json_data['complete_after']):
continue
unpaid_contracts.append(json_data)
+ sum_to_pay = 0
+ pay_before_earliest = None
+ pay_before_latest = None
+ supplier_wallet_address = None
+ started = None
+ complete_after = None
if unpaid_contracts:
unpaid_contracts.sort(key=lambda i: utime.unpack_time(i['started']))
+ started = unpaid_contracts[0]['started']
+ complete_after = unpaid_contracts[-1]['complete_after']
pay_before_earliest = utime.unpack_time(unpaid_contracts[0]['pay_before'])
pay_before_latest = utime.unpack_time(unpaid_contracts[-1]['pay_before'])
- sum_to_pay = 0
+ supplier_wallet_address = unpaid_contracts[-1]['wallet_address']
for unpaid_contract in unpaid_contracts:
sum_to_pay += unpaid_contract['value']
- if _Debug:
- lg.args(_DebugLevel, s=supplier_idurl, to_pay=sum_to_pay, pay_before_earliest=utime.pack_time(pay_before_earliest), pay_before_latest=utime.pack_time(pay_before_latest))
+ if _Debug:
+ lg.args(
+ _DebugLevel,
+ s=supplier_idurl,
+ unpaid=len(unpaid_contracts),
+ debt=sum_to_pay,
+ pay_earliest=utime.pack_time(pay_before_earliest),
+ pay_latest=utime.pack_time(pay_before_latest),
+ started=started,
+ complete_after=complete_after,
+ )
+ if sum_to_pay:
+ if now > pay_before_earliest:
+ if sum_to_pay >= cur_balance:
+ lg.warn('my current balance %r is not enough to pay %r to %r' % (cur_balance, sum_to_pay, supplier_idurl))
+ continue
+ try:
+ ret = bismuth_wallet.send_transaction(
+ recipient=supplier_wallet_address,
+ amount=sum_to_pay,
+ operation='storage',
+ data='{} {}'.format(started, complete_after),
+ raise_errors=True,
+ )
+ except:
+ lg.exc()
+ return False
+ if _Debug:
+ lg.args(_DebugLevel, recipient=supplier_wallet_address, ret=ret)
+ cur_balance = bismuth_wallet.my_balance()
+ if cur_balance == 'N/A':
+ lg.err('my current balance is not available, payments are not possible at the moment')
+ return False
+ return True
diff --git a/bitdust/customer/supplier_connector.py b/bitdust/customer/supplier_connector.py
index 93af43363..662174776 100644
--- a/bitdust/customer/supplier_connector.py
+++ b/bitdust/customer/supplier_connector.py
@@ -68,7 +68,6 @@
from bitdust.automats import automat
from bitdust.system import bpio
-from bitdust.system import local_fs
from bitdust.main import settings
from bitdust.main import events
@@ -76,11 +75,8 @@
from bitdust.lib import strng
from bitdust.lib import nameurl
from bitdust.lib import diskspace
-from bitdust.lib import utime
from bitdust.lib import jsn
-from bitdust.crypt import key
-
from bitdust.contacts import contactsdb
from bitdust.services import driver
@@ -167,39 +163,6 @@ def total_connectors():
#------------------------------------------------------------------------------
-def get_supplier_contracts_dir(supplier_idurl):
- supplier_idurl = id_url.field(supplier_idurl)
- supplier_contracts_prefix = '{}_{}'.format(
- supplier_idurl.username,
- strng.to_text(key.HashSHA(supplier_idurl.to_public_key(), hexdigest=True)),
- )
- return os.path.join(settings.ServiceDir('service_customer_contracts'), supplier_contracts_prefix)
-
-
-def save_storage_contract(supplier_idurl, json_data):
- supplier_contracts_dir = get_supplier_contracts_dir(supplier_idurl)
- if not os.path.isdir(supplier_contracts_dir):
- bpio._dirs_make(supplier_contracts_dir)
- contract_path = os.path.join(supplier_contracts_dir, str(utime.unpack_time(json_data['started'])))
- local_fs.WriteTextFile(contract_path, jsn.dumps(json_data))
- return contract_path
-
-
-def list_storage_contracts(supplier_idurl):
- supplier_contracts_dir = get_supplier_contracts_dir(supplier_idurl)
- if not os.path.isdir(supplier_contracts_dir):
- return []
- l = []
- for contract_filename in os.listdir(supplier_contracts_dir):
- contract_path = os.path.join(supplier_contracts_dir, contract_filename)
- json_data = jsn.loads_text(local_fs.ReadTextFile(contract_path))
- l.append(json_data)
- return l
-
-
-#------------------------------------------------------------------------------
-
-
class SupplierConnector(automat.Automat):
"""
This class implements all the functionality of the ``supplier_connector()``
@@ -667,7 +630,8 @@ def _supplier_service_acked(self, response, info):
self.latest_supplier_ack = None
self.automat('fail', None)
return
- save_storage_contract(self.supplier_idurl, the_contract)
+ from bitdust.customer import payment
+ payment.save_storage_contract(self.supplier_idurl, the_contract)
self.automat('ack', response)
def _supplier_service_failed(self, response, info):
diff --git a/bitdust/interface/api.py b/bitdust/interface/api.py
index b8845d232..01951e11b 100644
--- a/bitdust/interface/api.py
+++ b/bitdust/interface/api.py
@@ -5846,11 +5846,7 @@ def blockchain_info():
}
if driver.is_on('service_bismuth_wallet'):
from bitdust.blockchain import bismuth_wallet
- try:
- cur_balance = bismuth_wallet.my_balance()
- except:
- lg.exc()
- cur_balance = 'N/A'
+ cur_balance = bismuth_wallet.my_balance()
ret['wallet'] = {
'balance': cur_balance,
'address': bismuth_wallet.my_wallet_address(),
@@ -5878,11 +5874,7 @@ def blockchain_wallet_balance():
if not driver.is_on('service_bismuth_wallet'):
return ERROR('service_bismuth_wallet() is not started')
from bitdust.blockchain import bismuth_wallet
- try:
- cur_balance = bismuth_wallet.my_balance()
- except:
- lg.exc()
- cur_balance = 'N/A'
+ cur_balance = bismuth_wallet.my_balance()
return OK({
'balance': cur_balance,
'address': bismuth_wallet.my_wallet_address(),
diff --git a/bitdust/interface/cmd_line_json.py b/bitdust/interface/cmd_line_json.py
index 102815dd4..b14d8c971 100644
--- a/bitdust/interface/cmd_line_json.py
+++ b/bitdust/interface/cmd_line_json.py
@@ -768,6 +768,7 @@ def cmd_api(opts, args, overDict, executablePath):
'jsn',
'json',
'gc',
+ 'config',
]:
continue
method = getattr(api, item, None)
diff --git a/bitdust/interface/web_html_template.py b/bitdust/interface/web_html_template.py
new file mode 100644
index 000000000..7353c0b59
--- /dev/null
+++ b/bitdust/interface/web_html_template.py
@@ -0,0 +1,204 @@
+WEB_ROOT_TEMPLATE = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %(title)s
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+%(div_main_body)s
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+%(google_analytics)s
+
+
+
+"""
+
+div_main_class = 'main wiki'
+
+div_main_body = ''
+
+GOOGLE_ANALITICS = """
+
+
+
+"""
+
+google_analytics = GOOGLE_ANALITICS % dict(google_analytics_code='', )
diff --git a/bitdust/lib/utime.py b/bitdust/lib/utime.py
index 8fd6bb915..c5e05677d 100644
--- a/bitdust/lib/utime.py
+++ b/bitdust/lib/utime.py
@@ -77,6 +77,8 @@ def sec1970_to_datetime_utc(seconds=-1):
"""
Converts seconds since 1970 year to datetime object in UTC form.
"""
+ if seconds is None:
+ return None
if seconds == -1:
seconds = utcnow_to_sec1970()
return datetime.datetime.utcfromtimestamp(seconds)
@@ -114,6 +116,8 @@ def pack_time(seconds=-1, timespec='seconds'):
"""
Converts seconds since 1970 year to ISO formatted string.
"""
+ if seconds is None:
+ return None
return sec1970_to_datetime_utc(seconds).isoformat(timespec=timespec)
@@ -121,4 +125,6 @@ def unpack_time(text):
"""
Converts ISO formatted string to seconds since 1970 year.
"""
+ if text is None:
+ return None
return datetime_to_sec1970(_fromisoformat(text))
diff --git a/bitdust/main/config_defaults.py b/bitdust/main/config_defaults.py
index 84254620e..3202f90cc 100644
--- a/bitdust/main/config_defaults.py
+++ b/bitdust/main/config_defaults.py
@@ -92,6 +92,10 @@ def reset(conf_obj):
conf_obj.setDefaultValue('services/blockchain-authority/requests-reading-offset', 0)
conf_obj.setDefaultValue('services/blockchain-authority/requests-reading-limit', 50)
+ conf_obj.setDefaultValue('services/blockchain-explorer/enabled', 'false')
+ conf_obj.setDefaultValue('services/blockchain-explorer/host', '127.0.0.1')
+ conf_obj.setDefaultValue('services/blockchain-explorer/web-port', 19080)
+
conf_obj.setDefaultValue('services/bismuth-blockchain/enabled', 'false')
conf_obj.setDefaultValue('services/bismuth-node/enabled', 'false')
diff --git a/bitdust/main/config_types.py b/bitdust/main/config_types.py
index 8f24ed4a1..82a5d04ba 100644
--- a/bitdust/main/config_types.py
+++ b/bitdust/main/config_types.py
@@ -110,6 +110,9 @@ def defaults():
'services/blockchain-authority/registration-bonus-coins': TYPE_POSITIVE_INTEGER,
'services/blockchain-authority/requests-reading-offset': TYPE_POSITIVE_INTEGER,
'services/blockchain-authority/requests-reading-limit': TYPE_NON_ZERO_POSITIVE_INTEGER,
+ 'services/blockchain-explorer/enabled': TYPE_BOOLEAN,
+ 'services/blockchain-explorer/host': TYPE_STRING,
+ 'services/blockchain-explorer/web-port': TYPE_PORT_NUMBER,
'services/bismuth-blockchain/enabled': TYPE_BOOLEAN,
'services/bismuth-node/enabled': TYPE_BOOLEAN,
'services/bismuth-node/host': TYPE_STRING,
@@ -164,7 +167,6 @@ def defaults():
'services/identity-propagate/max-servers': TYPE_NON_ZERO_POSITIVE_INTEGER,
'services/identity-propagate/automatic-rotate-enabled': TYPE_BOOLEAN,
'services/identity-propagate/health-check-interval-seconds': TYPE_POSITIVE_INTEGER,
- 'services/identity-server/enabled': TYPE_BOOLEAN,
'services/ip-port-responder/enabled': TYPE_BOOLEAN,
'services/keys-registry/enabled': TYPE_BOOLEAN,
'services/keys-storage/enabled': TYPE_BOOLEAN,
diff --git a/bitdust/services/service_blockchain_explorer.py b/bitdust/services/service_blockchain_explorer.py
new file mode 100644
index 000000000..d4bad3a31
--- /dev/null
+++ b/bitdust/services/service_blockchain_explorer.py
@@ -0,0 +1,59 @@
+#!/usr/bin/python
+# service_blockchain_explorer.py
+#
+# Copyright (C) 2008 Veselin Penev, https://bitdust.io
+#
+# This file (service_blockchain_explorer.py) is part of BitDust Software.
+#
+# BitDust is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BitDust Software 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with BitDust Software. If not, see .
+#
+# Please contact us if you have any questions at bitdust.io@gmail.com
+#
+#
+#
+#
+"""
+..
+
+module:: service_blockchain_explorer
+"""
+
+from __future__ import absolute_import
+from bitdust.services.local_service import LocalService
+
+
+def create_service():
+ return BlockchainExplorerService()
+
+
+class BlockchainExplorerService(LocalService):
+
+ service_name = 'service_blockchain_explorer'
+ config_path = 'services/blockchain-explorer/enabled'
+
+ def dependent_on(self):
+ return [
+ 'service_bismuth_node',
+ ]
+
+ def installed(self):
+ return True
+
+ def start(self):
+ from bitdust.blockchain import blockchain_explorer
+ return blockchain_explorer.init()
+
+ def stop(self):
+ from bitdust.blockchain import blockchain_explorer
+ return blockchain_explorer.shutdown()
diff --git a/bitdust/services/service_customer_contracts.py b/bitdust/services/service_customer_contracts.py
index 2177cdb42..8ecdc733c 100644
--- a/bitdust/services/service_customer_contracts.py
+++ b/bitdust/services/service_customer_contracts.py
@@ -51,7 +51,7 @@ def dependent_on(self):
def start(self):
from twisted.internet import task # @UnresolvedImport
self.payment_loop = task.LoopingCall(self.on_payment_task)
- self.payment_loop.start(60*60)
+ self.payment_loop.start(60*60, now=True)
return True
def stop(self):
diff --git a/bitdust/userid/id_server.py b/bitdust/userid/id_server.py
index d1d69ddf4..10b493962 100644
--- a/bitdust/userid/id_server.py
+++ b/bitdust/userid/id_server.py
@@ -64,6 +64,7 @@
if __name__ == '__main__':
import os.path as _p
sys.path.insert(0, _p.abspath(_p.join(_p.dirname(_p.abspath(sys.argv[0])), '..')))
+ sys.path.insert(0, _p.abspath(_p.join(_p.dirname(_p.abspath(sys.argv[0])), '..', '..')))
#------------------------------------------------------------------------------
@@ -79,6 +80,8 @@
from bitdust.lib import misc
from bitdust.lib import net_misc
+from bitdust.interface import web_html_template
+
from bitdust.main import settings
from bitdust.userid import identity
@@ -111,10 +114,12 @@ def A(event=None, *args, **kwargs):
class IdServer(automat.Automat):
+
"""
This class implements all the functionality of the ``id_server()`` state
machine.
"""
+
def init(self):
"""
Method to initialize additional variables and flags at creation of the
@@ -328,6 +333,7 @@ def _save_identity(self, inputfilename):
class IdServerProtocol(basic.Int32StringReceiver):
+
def __init__(self):
self.fpath = None # string with path/filename
self.fin = None # integer file descriptor like os.open() returns
@@ -398,6 +404,7 @@ def connectionLost(self, reason):
class IdServerFactory(ServerFactory):
+
def buildProtocol(self, addr):
p = IdServerProtocol()
p.factory = self
@@ -408,6 +415,7 @@ def buildProtocol(self, addr):
class WebMainPage(resource.Resource):
+
def render_POST(self, request):
inp = BytesIO(request.content.read())
fin, fpath = tmpfile.make('idsrv', extension='.xml')
@@ -461,7 +469,7 @@ def render_GET(self, request):
url = '/' + filename
name = filename[:-4]
src += '%s
\n' % (strng.to_text(url), strng.to_text(name))
- src += '\n\n\n\n\n'
+ src += ' | \n
\n\n'
src += '
Total identities on "%s": %d
\n' % (strng.to_text(A().hostname), len(files))
src += 'Other known identity servers:\n'
for idhost in sorted(known_servers.by_host().keys()):
@@ -480,6 +488,7 @@ def render_GET(self, request):
class WebRoot(resource.Resource):
+
def getChild(self, path, request):
if not path:
return self
@@ -502,27 +511,102 @@ def getChild(self, path, request):
#------------------------------------------------------------------------------
+class KnownIDServersWebRoot(resource.Resource):
+
+ def getChild(self, path, request):
+ if not path:
+ return self
+ return resource.NoResource('Not found')
+
+
+class KnownIDServersWebMainPage(resource.Resource):
+
+ def render_GET(self, request):
+ src = ''
+ src += '
\n'
+ src += '
\n'
+ src += '
\n'
+ src += '
\n'
+
+ src += '
\n'
+ src += '
BitDust identity servers
\n'
+ src += '\n'
+
+ src += '
\n'
+ for idhost in sorted(known_servers.by_host().keys()):
+ idport = known_servers.by_host()[idhost][0]
+ if idport != 80:
+ idhost += ':%d' % idport
+ src += '
\n'
+ src += '
\n' % strng.to_text(idhost)
+ src += '
\n'
+ src += '
\n' % (strng.to_text(idhost), strng.to_text(idhost))
+ src += '
\n'
+ src += '
\n'
+ src += '
\n'
+ src += '
\n'
+
+ src += '
\n'
+ src += '
\n'
+ src += '
\n'
+ src += '
\n'
+
+ html_src = web_html_template.WEB_ROOT_TEMPLATE % dict(
+ title='BitDust identity servers',
+ site_url='https://bitdust.io',
+ basepath='https://bitdust.io/',
+ wikipath='https://bitdust.io/wiki/',
+ idserverspath='https://identities.bitdust.io/',
+ blockchainpath='https://blockchain.bitdust.io/',
+ div_main_class='main idservers',
+ div_main_body=src,
+ google_analytics='',
+ )
+ return strng.to_bin(html_src)
+
+
+def render_known_id_servers(web_port):
+ root = KnownIDServersWebRoot()
+ root.putChild(b'', KnownIDServersWebMainPage())
+ reactor.listenTCP(web_port, server.Site(root)) # @UndefinedVariable
+
+
+#------------------------------------------------------------------------------
+
+
def main():
bpio.init()
settings.init()
+ lg.set_debug_level(_DebugLevel)
+
+ serve_known_id_servers = False
if len(sys.argv) > 1:
- web_port = int(sys.argv[1])
+ if sys.argv[1].strip() == 'known_id_servers':
+ serve_known_id_servers = True
+ web_port = settings.getIdServerWebPort()
+ else:
+ web_port = int(sys.argv[1])
else:
web_port = settings.getIdServerWebPort()
if len(sys.argv) > 2:
tcp_port = int(sys.argv[2])
else:
tcp_port = settings.getIdServerTCPPort()
- lg.set_debug_level(20)
- lg.out(2, 'starting ID server ...')
- reactor.addSystemEventTrigger( # @UndefinedVariable
- 'before',
- 'shutdown',
- A().automat,
- 'shutdown',
- )
- reactor.callWhenRunning(A, 'init', (web_port, tcp_port)) # @UndefinedVariable
- reactor.callLater(0, A, 'start') # @UndefinedVariable
+
+ if serve_known_id_servers:
+ web_port = int(sys.argv[2])
+ lg.out(2, 'serving known ID servers web_port=%d' % web_port)
+ reactor.callWhenRunning(render_known_id_servers, web_port) # @UndefinedVariable
+
+ else:
+ lg.out(2, 'starting ID server web_port=%d tcp_port=%d' % (
+ web_port,
+ tcp_port,
+ ))
+ reactor.addSystemEventTrigger('before', 'shutdown', A().automat, 'shutdown') # @UndefinedVariable
+ reactor.callWhenRunning(A, 'init', (web_port, tcp_port)) # @UndefinedVariable
+ reactor.callLater(0, A, 'start') # @UndefinedVariable
+
reactor.run() # @UndefinedVariable
settings.shutdown()
lg.out(2, 'reactor stopped, EXIT')
diff --git a/bitdust/userid/id_url.py b/bitdust/userid/id_url.py
index 770b9fa70..833094c77 100644
--- a/bitdust/userid/id_url.py
+++ b/bitdust/userid/id_url.py
@@ -903,7 +903,7 @@ def __hash__(self):
def __repr__(self):
if _Debug:
lg.args(_DebugLevel*2, latest_as_string=self.latest_as_string)
- return '[%s%s]' % ('' if self.is_latest() else '*', self.latest_as_string)
+ return '{%s%s}' % ('' if self.is_latest() else '*', self.latest_as_string)
def __str__(self):
if _Debug:
diff --git a/bitdust_forks/Bismuth/apihandler.py b/bitdust_forks/Bismuth/apihandler.py
index decfdbca0..39feaee9a 100644
--- a/bitdust_forks/Bismuth/apihandler.py
+++ b/bitdust_forks/Bismuth/apihandler.py
@@ -703,7 +703,7 @@ def api_gettransaction(self, socket_handler, db_handler, peers):
if self.config.old_sqlite:
db_handler.execute_param(db_handler.h, 'SELECT * FROM transactions WHERE signature like ?1', (transaction_id + '%', ))
else:
- db_handler.execute_param(db_handler.h, 'SELECT * FROM transactions WHERE substr(signature,1,4)=substr(?1,1,4) and signature like ?1', (transaction_id + '%', ))
+ db_handler.execute_param(db_handler.h, 'SELECT * FROM transactions WHERE substr(signature,1,4)=substr(?1,1,4) and signature like ?1', (transaction_id + '%', ))
raw = db_handler.h.fetchone()
if not format:
connections.send(socket_handler, raw)
diff --git a/bitdust_forks/Bismuth/dbhandler.py b/bitdust_forks/Bismuth/dbhandler.py
index c04da050a..8c7ca3c57 100644
--- a/bitdust_forks/Bismuth/dbhandler.py
+++ b/bitdust_forks/Bismuth/dbhandler.py
@@ -166,7 +166,7 @@ def txsearch(self, address=None, recipient=None, operation=None, openfield=None,
sql += ' AND '.join(queries)
sql += ' LIMIT ?, ?;'
try:
- print('DB:txsearch', sql, params)
+ # print('DB:txsearch', sql, params)
self.execute_param(self.h, sql, tuple(params))
result = self.h.fetchall()
except:
diff --git a/bitdust_forks/Bismuth/worker.py b/bitdust_forks/Bismuth/worker.py
index 9fac411e8..33932c1db 100644
--- a/bitdust_forks/Bismuth/worker.py
+++ b/bitdust_forks/Bismuth/worker.py
@@ -27,11 +27,11 @@ def sendsync(sdef, peer_ip, status, node):
"""
# TODO: ERROR, does **not** save anything. code or comment wrong.
node.logger.app_log.debug(f'Outbound: Synchronization with {peer_ip} finished after: {status}, sending new sync request')
- time.sleep(Decimal(node.pause))
+ time.sleep(int(node.pause))
while node.db_lock.locked():
if node.IS_STOPPING:
return
- time.sleep(Decimal(node.pause))
+ time.sleep(int(node.pause))
send(sdef, 'sendsync')
@@ -332,6 +332,8 @@ def worker(host, port, node):
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
print(exc_type, fname, exc_tb.tb_lineno)
"""
+ import traceback
+ traceback.print_exc()
# remove from active pool
node.peers.remove_client(this_client)
diff --git a/tests/test_id_url.py b/tests/test_id_url.py
index 951853a0d..fc39a64e9 100644
--- a/tests/test_id_url.py
+++ b/tests/test_id_url.py
@@ -280,9 +280,9 @@ def test_identity_cached(self):
self._cache_identity('bob')
self.assertTrue(id_url.field(alice_text) != id_url.field(bob))
self.assertEqual(str(id_url.field(alice_text)), 'http://127.0.0.1:8084/alice.xml')
- self.assertEqual(repr(id_url.field(alice_text)), '[http://127.0.0.1:8084/alice.xml]')
+ self.assertEqual(repr(id_url.field(alice_text)), '{http://127.0.0.1:8084/alice.xml}')
self.assertEqual('=%s=' % id_url.field(alice_text), '=http://127.0.0.1:8084/alice.xml=')
- self.assertEqual('=%r=' % id_url.field(alice_text), '=[http://127.0.0.1:8084/alice.xml]=')
+ self.assertEqual('=%r=' % id_url.field(alice_text), '={http://127.0.0.1:8084/alice.xml}=')
def test_identity_not_cached(self):
self._cache_identity('alice')
@@ -601,15 +601,15 @@ def test_fake_name(self):
def test_latest_vs_original(self):
idurl_hans_not_cached = id_url.field(hans1)
- self.assertEqual('=%r=' % idurl_hans_not_cached, '=[http://first.com/hans.xml]=')
+ self.assertEqual('=%r=' % idurl_hans_not_cached, '={http://first.com/hans.xml}=')
self._cache_identity('hans1')
idurl_hans_cached = id_url.field(hans1)
- self.assertEqual('=%r=' % idurl_hans_cached, '=[http://first.com/hans.xml]=')
- self.assertEqual('=%r=' % id_url.field(hans1), '=[http://first.com/hans.xml]=')
+ self.assertEqual('=%r=' % idurl_hans_cached, '={http://first.com/hans.xml}=')
+ self.assertEqual('=%r=' % id_url.field(hans1), '={http://first.com/hans.xml}=')
self._cache_identity('hans2')
- self.assertEqual('=%r=' % id_url.field(hans1), '=[*http://second.net/hans.xml]=')
+ self.assertEqual('=%r=' % id_url.field(hans1), '={*http://second.net/hans.xml}=')
self._cache_identity('hans3')
- self.assertEqual('=%r=' % id_url.field(hans1), '=[*http://third.org/hans.xml]=')
+ self.assertEqual('=%r=' % id_url.field(hans1), '={*http://third.org/hans.xml}=')
self.assertEqual(id_url.field(hans1), id_url.field(hans2))
self.assertNotEqual(id_url.field(hans1).original(), id_url.field(hans2).original())
self.assertEqual(id_url.field(hans1), id_url.field(hans3))
@@ -620,21 +620,21 @@ def test_latest_vs_original(self):
self.assertEqual(id_url.field(hans2).to_text(), hans3)
self.assertEqual(id_url.field(hans3).to_text(), hans3)
idurl_hans1 = id_url.field(hans1)
- self.assertEqual('=%r=' % idurl_hans1, '=[*http://third.org/hans.xml]=')
+ self.assertEqual('=%r=' % idurl_hans1, '={*http://third.org/hans.xml}=')
self.assertFalse(idurl_hans1.refresh())
- self.assertEqual('=%r=' % idurl_hans1, '=[*http://third.org/hans.xml]=')
+ self.assertEqual('=%r=' % idurl_hans1, '={*http://third.org/hans.xml}=')
idurl_hans2 = id_url.field(hans2)
- self.assertEqual('=%r=' % idurl_hans2, '=[*http://third.org/hans.xml]=')
+ self.assertEqual('=%r=' % idurl_hans2, '={*http://third.org/hans.xml}=')
self.assertFalse(idurl_hans2.refresh())
- self.assertEqual('=%r=' % idurl_hans2, '=[*http://third.org/hans.xml]=')
+ self.assertEqual('=%r=' % idurl_hans2, '={*http://third.org/hans.xml}=')
idurl_hans3 = id_url.field(hans3)
- self.assertEqual('=%r=' % idurl_hans3, '=[http://third.org/hans.xml]=')
+ self.assertEqual('=%r=' % idurl_hans3, '={http://third.org/hans.xml}=')
self.assertFalse(idurl_hans3.refresh())
- self.assertEqual('=%r=' % idurl_hans3, '=[http://third.org/hans.xml]=')
+ self.assertEqual('=%r=' % idurl_hans3, '={http://third.org/hans.xml}=')
self.assertTrue(idurl_hans_not_cached.refresh())
- self.assertEqual('=%r=' % idurl_hans_not_cached, '=[http://third.org/hans.xml]=')
+ self.assertEqual('=%r=' % idurl_hans_not_cached, '={http://third.org/hans.xml}=')
self.assertTrue(idurl_hans_cached.refresh())
- self.assertEqual('=%r=' % idurl_hans_cached, '=[http://third.org/hans.xml]=')
+ self.assertEqual('=%r=' % idurl_hans_cached, '={http://third.org/hans.xml}=')
def test_latest_revision_order_123(self):
self._cache_identity('hans1')