Switch to using new python3-oxenmq
This commit is contained in:
parent
e2bff03fcf
commit
cd214df1b2
|
@ -1,3 +0,0 @@
|
||||||
[submodule "pylokimq"]
|
|
||||||
path = pylokimq
|
|
||||||
url = https://github.com/majestrate/pylokimq.git
|
|
36
README.md
36
README.md
|
@ -5,25 +5,9 @@ awesome, safe.
|
||||||
|
|
||||||
## Prerequisite packages
|
## Prerequisite packages
|
||||||
|
|
||||||
sudo apt install build-essential pkg-config libsodium-dev libzmq3-dev python3-dev python3-flask python3-babel python3-pygments
|
sudo apt install build-essential pkg-config libsodium-dev libzmq3-dev python3-dev python3-flask python3-babel python3-pygments python3-oxenmq
|
||||||
|
|
||||||
## Building and running
|
Note that the last requirement (python3-oxenmq) comes from the Oxen repository (https://deb.oxen.io).
|
||||||
|
|
||||||
Quick and dirty setup instructions for now:
|
|
||||||
|
|
||||||
git submodule update --init --recursive
|
|
||||||
cd pylokimq
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake ..
|
|
||||||
make -j6
|
|
||||||
cd ../..
|
|
||||||
ln -s pylokimq/build/pylokimq/pylokimq.cpython-*.so .
|
|
||||||
|
|
||||||
(Note that we require a very recent python3-jinja package (2.11+), which may not be installed by the
|
|
||||||
above.)
|
|
||||||
|
|
||||||
You'll also need to run oxend with `--lmq-local-control ipc:///path/to/loki-observer/mainnet.sock`.
|
|
||||||
|
|
||||||
## Running in debug mode
|
## Running in debug mode
|
||||||
|
|
||||||
|
@ -32,11 +16,11 @@ To run it in debug mode (production requires setting up a WSGI server, see below
|
||||||
FLASK_APP=observer flask run --reload --debugger
|
FLASK_APP=observer flask run --reload --debugger
|
||||||
|
|
||||||
This mode seems to be a bit flakey, though -- reloading, in particular, seems to break things and
|
This mode seems to be a bit flakey, though -- reloading, in particular, seems to break things and
|
||||||
make it just silently exit after a while.
|
make it just silently exit after a while, so only do this for quick and dirty testing.
|
||||||
|
|
||||||
## Setting up for production with uwsgi-emperor:
|
## Setting up for production with uwsgi-emperor:
|
||||||
|
|
||||||
Do all of the above, but instead of running it with flask, set up uwsgi-emperor as follows:
|
Do the above, but instead of running it with flask directly, set up uwsgi-emperor as follows:
|
||||||
|
|
||||||
apt install uwsgi-emperor uwsgi-plugin-python3
|
apt install uwsgi-emperor uwsgi-plugin-python3
|
||||||
|
|
||||||
|
@ -47,24 +31,24 @@ in `/etc/uwsgi-emperor/emperor.ini` add configuration of:
|
||||||
cap = setgid,setuid
|
cap = setgid,setuid
|
||||||
emperor-tyrant = true
|
emperor-tyrant = true
|
||||||
|
|
||||||
Create a "vassal" config for loki-observer, `/etc/uwsgi-emperor/vassals/loki-observer.ini`, containing:
|
Create a "vassal" config for oxen-observer, `/etc/uwsgi-emperor/vassals/oxen-observer.ini`, containing:
|
||||||
|
|
||||||
[uwsgi]
|
[uwsgi]
|
||||||
chdir = /path/to/loki-observer
|
chdir = /path/to/oxen-observer
|
||||||
socket = mainnet.wsgi
|
socket = mainnet.wsgi
|
||||||
plugins = python3,logfile
|
plugins = python3,logfile
|
||||||
processes = 4
|
processes = 4
|
||||||
manage-script-name = true
|
manage-script-name = true
|
||||||
mount = /=mainnet:app
|
mount = /=mainnet:app
|
||||||
|
|
||||||
logger = file:logfile=/path/to/loki-observer/mainnet.log
|
logger = file:logfile=/path/to/oxen-observer/mainnet.log
|
||||||
|
|
||||||
Set ownership of this user to whatever use you want it to run as, and set the group to `_loki` (so
|
Set ownership of this user to whatever user you want it to run as, and set the group to `_loki` (so
|
||||||
that it can open the oxend unix socket):
|
that it can open the oxend unix socket):
|
||||||
|
|
||||||
chown MYUSERNAME:_loki /etc/uwsgi-emperor/vassals/loki-observer.ini
|
chown MYUSERNAME:_loki /etc/uwsgi-emperor/vassals/loki-observer.ini
|
||||||
|
|
||||||
In the loki-observer/mainnet.py, set:
|
In the oxen-observer/mainnet.py, set:
|
||||||
|
|
||||||
config.oxend_rpc = 'ipc:///var/lib/loki/oxend.sock'
|
config.oxend_rpc = 'ipc:///var/lib/loki/oxend.sock'
|
||||||
|
|
||||||
|
@ -88,4 +72,4 @@ make uwsgi restart (for example because you are changing things) then it is suff
|
||||||
apache2/uwsgi-emperor layers).
|
apache2/uwsgi-emperor layers).
|
||||||
|
|
||||||
If you want to set up a testnet or devnet observer the procedure is essentially the same, but
|
If you want to set up a testnet or devnet observer the procedure is essentially the same, but
|
||||||
using testnet.py or devnet.py pointing to a oxend.sock from a testnet or devnet oxend.
|
using testnet.py or devnet.py pointing to the oxend.sock from a testnet or devnet oxend.
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
from observer import app, config
|
from observer import app, config
|
||||||
|
import oxenmq
|
||||||
|
|
||||||
config.oxend_rpc = 'ipc://oxend/devnet.sock'
|
config.oxend_rpc = oxenmq.Address('ipc://oxend/devnet.sock')
|
||||||
|
|
30
lmq.py
30
lmq.py
|
@ -1,19 +1,19 @@
|
||||||
import pylokimq
|
import oxenmq
|
||||||
import config
|
import config
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
lmq, oxend = None, None
|
omq, oxend = None, None
|
||||||
def lmq_connection():
|
def omq_connection():
|
||||||
global lmq, oxend
|
global omq, oxend
|
||||||
if lmq is None:
|
if omq is None:
|
||||||
lmq = pylokimq.LokiMQ(pylokimq.LogLevel.warn)
|
omq = oxenmq.OxenMQ(log_level=oxenmq.LogLevel.warn)
|
||||||
lmq.max_message_size = 200*1024*1024
|
omq.max_message_size = 200*1024*1024
|
||||||
lmq.start()
|
omq.start()
|
||||||
if oxend is None:
|
if oxend is None:
|
||||||
oxend = lmq.connect_remote(config.oxend_rpc)
|
oxend = omq.connect_remote(config.oxend_rpc)
|
||||||
return (lmq, oxend)
|
return (omq, oxend)
|
||||||
|
|
||||||
cached = {}
|
cached = {}
|
||||||
cached_args = {}
|
cached_args = {}
|
||||||
|
@ -30,9 +30,9 @@ class FutureJSON():
|
||||||
Cache entries are *not* purged, they are only replaced, so using dynamic data in the key would
|
Cache entries are *not* purged, they are only replaced, so using dynamic data in the key would
|
||||||
result in unbounded memory growth.
|
result in unbounded memory growth.
|
||||||
|
|
||||||
lmq - the lmq object
|
omq - the omq object
|
||||||
oxend - the oxend lmq connection id object
|
oxend - the oxend omq connection id object
|
||||||
endpoint - the lmq endpoint, e.g. 'rpc.get_info'
|
endpoint - the omq endpoint, e.g. 'rpc.get_info'
|
||||||
cache_seconds - how long to cache the response; can be None to not cache it at all
|
cache_seconds - how long to cache the response; can be None to not cache it at all
|
||||||
cache_key - fixed string to enable different caches of the same endpoint
|
cache_key - fixed string to enable different caches of the same endpoint
|
||||||
args - if not None, a value to pass (after converting to JSON) as the request parameter. Typically a dict.
|
args - if not None, a value to pass (after converting to JSON) as the request parameter. Typically a dict.
|
||||||
|
@ -40,7 +40,7 @@ class FutureJSON():
|
||||||
timeout - maximum time to spend waiting for a reply
|
timeout - maximum time to spend waiting for a reply
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, lmq, oxend, endpoint, cache_seconds=3, *, cache_key='', args=None, fail_okay=False, timeout=10):
|
def __init__(self, omq, oxend, endpoint, cache_seconds=3, *, cache_key='', args=None, fail_okay=False, timeout=10):
|
||||||
self.endpoint = endpoint
|
self.endpoint = endpoint
|
||||||
self.cache_key = self.endpoint + cache_key
|
self.cache_key = self.endpoint + cache_key
|
||||||
self.fail_okay = fail_okay
|
self.fail_okay = fail_okay
|
||||||
|
@ -53,7 +53,7 @@ class FutureJSON():
|
||||||
else:
|
else:
|
||||||
self.json = None
|
self.json = None
|
||||||
self.args = args
|
self.args = args
|
||||||
self.future = lmq.request_future(oxend, self.endpoint, [] if self.args is None else [self.args], timeout=timeout)
|
self.future = omq.request_future(oxend, self.endpoint, [] if self.args is None else [self.args], timeout=timeout)
|
||||||
self.cache_seconds = cache_seconds
|
self.cache_seconds = cache_seconds
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
from observer import app, config
|
from observer import app, config
|
||||||
|
import oxenmq
|
||||||
|
|
||||||
config.oxend_rpc = 'ipc://oxend/mainnet.sock'
|
config.oxend_rpc = oxenmq.Address('ipc://oxend/mainnet.sock')
|
||||||
|
|
173
observer.py
173
observer.py
|
@ -25,7 +25,7 @@ import base58
|
||||||
import sha3
|
import sha3
|
||||||
import config
|
import config
|
||||||
import local_config
|
import local_config
|
||||||
from lmq import FutureJSON, lmq_connection
|
from lmq import FutureJSON, omq_connection
|
||||||
|
|
||||||
# Make a dict of config.* to pass to templating
|
# Make a dict of config.* to pass to templating
|
||||||
conf = {x: getattr(config, x) for x in dir(config) if not x.startswith('__')}
|
conf = {x: getattr(config, x) for x in dir(config) if not x.startswith('__')}
|
||||||
|
@ -178,8 +178,8 @@ def css():
|
||||||
return flask.send_from_directory('static', 'style.css')
|
return flask.send_from_directory('static', 'style.css')
|
||||||
|
|
||||||
|
|
||||||
def get_sns_future(lmq, oxend):
|
def get_sns_future(omq, oxend):
|
||||||
return FutureJSON(lmq, oxend, 'rpc.get_service_nodes', 5,
|
return FutureJSON(omq, oxend, 'rpc.get_service_nodes', 5,
|
||||||
args={
|
args={
|
||||||
'all': False,
|
'all': False,
|
||||||
'fields': { x: True for x in ('service_node_pubkey', 'requested_unlock_height', 'last_reward_block_height',
|
'fields': { x: True for x in ('service_node_pubkey', 'requested_unlock_height', 'last_reward_block_height',
|
||||||
|
@ -209,8 +209,8 @@ def get_sns(sns_future, info_future):
|
||||||
return awaiting_sns, active_sns, inactive_sns
|
return awaiting_sns, active_sns, inactive_sns
|
||||||
|
|
||||||
|
|
||||||
def get_quorums_future(lmq, oxend, height):
|
def get_quorums_future(omq, oxend, height):
|
||||||
return FutureJSON(lmq, oxend, 'rpc.get_quorum_state', 30,
|
return FutureJSON(omq, oxend, 'rpc.get_quorum_state', 30,
|
||||||
args={ 'start_height': height-55, 'end_height': height })
|
args={ 'start_height': height-55, 'end_height': height })
|
||||||
|
|
||||||
|
|
||||||
|
@ -227,8 +227,8 @@ def get_quorums(quorums_future):
|
||||||
print("Something getting wrong in quorums: found unknown quorum_type={}".format(q['quorum_type']), file=sys.stderr)
|
print("Something getting wrong in quorums: found unknown quorum_type={}".format(q['quorum_type']), file=sys.stderr)
|
||||||
return quo
|
return quo
|
||||||
|
|
||||||
def get_mempool_future(lmq, oxend):
|
def get_mempool_future(omq, oxend):
|
||||||
return FutureJSON(lmq, oxend, 'rpc.get_transaction_pool', 5, args={"tx_extra":True, "stake_info":True})
|
return FutureJSON(omq, oxend, 'rpc.get_transaction_pool', 5, args={"tx_extra":True, "stake_info":True})
|
||||||
|
|
||||||
def parse_mempool(mempool_future):
|
def parse_mempool(mempool_future):
|
||||||
# mempool RPC return values are about as nasty as can be. For each mempool tx, we get back
|
# mempool RPC return values are about as nasty as can be. For each mempool tx, we get back
|
||||||
|
@ -264,21 +264,22 @@ def template_globals():
|
||||||
@app.route('/page/<int:page>/<int:per_page>')
|
@app.route('/page/<int:page>/<int:per_page>')
|
||||||
@app.route('/range/<int:first>/<int:last>')
|
@app.route('/range/<int:first>/<int:last>')
|
||||||
@app.route('/autorefresh/<int:refresh>')
|
@app.route('/autorefresh/<int:refresh>')
|
||||||
|
@app.route('/v<int:style>') # debug while mucking with stylesheets
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def main(refresh=None, page=0, per_page=None, first=None, last=None):
|
def main(refresh=None, page=0, per_page=None, first=None, last=None, style=None):
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
inforeq = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
inforeq = FutureJSON(omq, oxend, 'rpc.get_info', 1)
|
||||||
stake = FutureJSON(lmq, oxend, 'rpc.get_staking_requirement', 10)
|
stake = FutureJSON(omq, oxend, 'rpc.get_staking_requirement', 10)
|
||||||
base_fee = FutureJSON(lmq, oxend, 'rpc.get_fee_estimate', 10)
|
base_fee = FutureJSON(omq, oxend, 'rpc.get_fee_estimate', 10)
|
||||||
hfinfo = FutureJSON(lmq, oxend, 'rpc.hard_fork_info', 10)
|
hfinfo = FutureJSON(omq, oxend, 'rpc.hard_fork_info', 10)
|
||||||
mempool = get_mempool_future(lmq, oxend)
|
mempool = get_mempool_future(omq, oxend)
|
||||||
sns = get_sns_future(lmq, oxend)
|
sns = get_sns_future(omq, oxend)
|
||||||
checkpoints = FutureJSON(lmq, oxend, 'rpc.get_checkpoints', args={"count": 3})
|
checkpoints = FutureJSON(omq, oxend, 'rpc.get_checkpoints', args={"count": 3})
|
||||||
|
|
||||||
# This call is slow the first time it gets called in oxend but will be fast after that, so call
|
# This call is slow the first time it gets called in oxend but will be fast after that, so call
|
||||||
# it with a very short timeout. It's also an admin-only command, so will always fail if we're
|
# it with a very short timeout. It's also an admin-only command, so will always fail if we're
|
||||||
# using a restricted RPC interface.
|
# using a restricted RPC interface.
|
||||||
coinbase = FutureJSON(lmq, oxend, 'admin.get_coinbase_tx_sum', 10, timeout=1, fail_okay=True,
|
coinbase = FutureJSON(omq, oxend, 'admin.get_coinbase_tx_sum', 10, timeout=1, fail_okay=True,
|
||||||
args={"height":0, "count":2**31-1})
|
args={"height":0, "count":2**31-1})
|
||||||
|
|
||||||
custom_per_page = ''
|
custom_per_page = ''
|
||||||
|
@ -307,7 +308,7 @@ def main(refresh=None, page=0, per_page=None, first=None, last=None):
|
||||||
end_height = max(0, height - per_page*page - 1)
|
end_height = max(0, height - per_page*page - 1)
|
||||||
start_height = max(0, end_height - per_page + 1)
|
start_height = max(0, end_height - per_page + 1)
|
||||||
|
|
||||||
blocks = FutureJSON(lmq, oxend, 'rpc.get_block_headers_range', cache_key='main', args={
|
blocks = FutureJSON(omq, oxend, 'rpc.get_block_headers_range', cache_key='main', args={
|
||||||
'start_height': start_height,
|
'start_height': start_height,
|
||||||
'end_height': end_height,
|
'end_height': end_height,
|
||||||
'get_tx_hashes': True,
|
'get_tx_hashes': True,
|
||||||
|
@ -324,7 +325,7 @@ def main(refresh=None, page=0, per_page=None, first=None, last=None):
|
||||||
if 'tx_hashes' in b:
|
if 'tx_hashes' in b:
|
||||||
txids += b['tx_hashes']
|
txids += b['tx_hashes']
|
||||||
if txids:
|
if txids:
|
||||||
txs = parse_txs(tx_req(lmq, oxend, txids, cache_key='mempool').get())
|
txs = parse_txs(tx_req(omq, oxend, txids, cache_key='mempool').get())
|
||||||
i = 0
|
i = 0
|
||||||
for tx in txs:
|
for tx in txs:
|
||||||
if 'vin' in tx['info'] and len(tx['info']['vin']) == 1 and 'gen' in tx['info']['vin'][0]:
|
if 'vin' in tx['info'] and len(tx['info']['vin']) == 1 and 'gen' in tx['info']['vin'][0]:
|
||||||
|
@ -366,9 +367,9 @@ def main(refresh=None, page=0, per_page=None, first=None, last=None):
|
||||||
|
|
||||||
@app.route('/txpool')
|
@app.route('/txpool')
|
||||||
def mempool():
|
def mempool():
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
info = FutureJSON(omq, oxend, 'rpc.get_info', 1)
|
||||||
mempool = get_mempool_future(lmq, oxend)
|
mempool = get_mempool_future(omq, oxend)
|
||||||
|
|
||||||
return flask.render_template('mempool.html',
|
return flask.render_template('mempool.html',
|
||||||
info=info.get(),
|
info=info.get(),
|
||||||
|
@ -377,9 +378,9 @@ def mempool():
|
||||||
|
|
||||||
@app.route('/service_nodes')
|
@app.route('/service_nodes')
|
||||||
def sns():
|
def sns():
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
info = FutureJSON(omq, oxend, 'rpc.get_info', 1)
|
||||||
awaiting, active, inactive = get_sns(get_sns_future(lmq, oxend), info)
|
awaiting, active, inactive = get_sns(get_sns_future(omq, oxend), info)
|
||||||
|
|
||||||
return flask.render_template('service_nodes.html',
|
return flask.render_template('service_nodes.html',
|
||||||
info=info.get(),
|
info=info.get(),
|
||||||
|
@ -388,8 +389,8 @@ def sns():
|
||||||
inactive_sns=inactive,
|
inactive_sns=inactive,
|
||||||
)
|
)
|
||||||
|
|
||||||
def tx_req(lmq, oxend, txids, cache_key='single', **kwargs):
|
def tx_req(omq, oxend, txids, cache_key='single', **kwargs):
|
||||||
return FutureJSON(lmq, oxend, 'rpc.get_transactions', cache_seconds=10, cache_key=cache_key,
|
return FutureJSON(omq, oxend, 'rpc.get_transactions', cache_seconds=10, cache_key=cache_key,
|
||||||
args={
|
args={
|
||||||
"txs_hashes": txids,
|
"txs_hashes": txids,
|
||||||
"decode_as_json": True,
|
"decode_as_json": True,
|
||||||
|
@ -399,36 +400,36 @@ def tx_req(lmq, oxend, txids, cache_key='single', **kwargs):
|
||||||
},
|
},
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
def sn_req(lmq, oxend, pubkey, **kwargs):
|
def sn_req(omq, oxend, pubkey, **kwargs):
|
||||||
return FutureJSON(lmq, oxend, 'rpc.get_service_nodes', 5, cache_key='single',
|
return FutureJSON(omq, oxend, 'rpc.get_service_nodes', 5, cache_key='single',
|
||||||
args={"service_node_pubkeys": [pubkey]}, **kwargs
|
args={"service_node_pubkeys": [pubkey]}, **kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def block_header_req(lmq, oxend, hash_or_height, **kwargs):
|
def block_header_req(omq, oxend, hash_or_height, **kwargs):
|
||||||
if isinstance(hash_or_height, int) or (len(hash_or_height) <= 10 and hash_or_height.isdigit()):
|
if isinstance(hash_or_height, int) or (len(hash_or_height) <= 10 and hash_or_height.isdigit()):
|
||||||
return FutureJSON(lmq, oxend, 'rpc.get_block_header_by_height', cache_key='single',
|
return FutureJSON(omq, oxend, 'rpc.get_block_header_by_height', cache_key='single',
|
||||||
args={ "height": int(hash_or_height) }, **kwargs)
|
args={ "height": int(hash_or_height) }, **kwargs)
|
||||||
else:
|
else:
|
||||||
return FutureJSON(lmq, oxend, 'rpc.get_block_header_by_hash', cache_key='single',
|
return FutureJSON(omq, oxend, 'rpc.get_block_header_by_hash', cache_key='single',
|
||||||
args={ 'hash': hash_or_height }, **kwargs)
|
args={ 'hash': hash_or_height }, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def block_with_txs_req(lmq, oxend, hash_or_height, **kwargs):
|
def block_with_txs_req(omq, oxend, hash_or_height, **kwargs):
|
||||||
args = { 'get_tx_hashes': True }
|
args = { 'get_tx_hashes': True }
|
||||||
if isinstance(hash_or_height, int) or (len(hash_or_height) <= 10 and hash_or_height.isdigit()):
|
if isinstance(hash_or_height, int) or (len(hash_or_height) <= 10 and hash_or_height.isdigit()):
|
||||||
args['height'] = int(hash_or_height)
|
args['height'] = int(hash_or_height)
|
||||||
else:
|
else:
|
||||||
args['hash'] = hash_or_height
|
args['hash'] = hash_or_height
|
||||||
|
|
||||||
return FutureJSON(lmq, oxend, 'rpc.get_block', cache_key='single', args=args, **kwargs)
|
return FutureJSON(omq, oxend, 'rpc.get_block', cache_key='single', args=args, **kwargs)
|
||||||
|
|
||||||
def ons_info(lmq, oxend, name,ons_type,**kwargs):
|
def ons_info(omq, oxend, name,ons_type,**kwargs):
|
||||||
if ons_type == 2:
|
if ons_type == 2:
|
||||||
name=name+'.loki'
|
name=name+'.loki'
|
||||||
name_hash = nacl.hash.blake2b(name.encode(), encoder = nacl.encoding.Base64Encoder)
|
name_hash = nacl.hash.blake2b(name.encode(), encoder = nacl.encoding.Base64Encoder)
|
||||||
|
|
||||||
return FutureJSON(lmq, oxend, 'rpc.ons_names_to_owners', args={
|
return FutureJSON(omq, oxend, 'rpc.ons_names_to_owners', args={
|
||||||
"entries": [{'name_hash':name_hash.decode('ascii'),'types':[ons_type]}]})
|
"entries": [{'name_hash':name_hash.decode('ascii'),'types':[ons_type]}]})
|
||||||
|
|
||||||
|
|
||||||
|
@ -436,8 +437,8 @@ def ons_info(lmq, oxend, name,ons_type,**kwargs):
|
||||||
@app.route('/ons/<string:name>/<int:more_details>')
|
@app.route('/ons/<string:name>/<int:more_details>')
|
||||||
def show_ons(name, more_details=False):
|
def show_ons(name, more_details=False):
|
||||||
name = name.lower()
|
name = name.lower()
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
info = FutureJSON(omq, oxend, 'rpc.get_info', 1)
|
||||||
|
|
||||||
if len(name) > 64 or not all(c.isalnum() or c in '_-' for c in name):
|
if len(name) > 64 or not all(c.isalnum() or c in '_-' for c in name):
|
||||||
return flask.render_template('not_found.html',
|
return flask.render_template('not_found.html',
|
||||||
|
@ -453,7 +454,7 @@ def show_ons(name, more_details=False):
|
||||||
LOKINET_ENCRYPTED_LENGTH = 144 # The user must update their session mapping.
|
LOKINET_ENCRYPTED_LENGTH = 144 # The user must update their session mapping.
|
||||||
|
|
||||||
for ons_type in ons_types:
|
for ons_type in ons_types:
|
||||||
onsinfo = ons_info(lmq, oxend, name, ons_types[ons_type]).get()
|
onsinfo = ons_info(omq, oxend, name, ons_types[ons_type]).get()
|
||||||
|
|
||||||
if 'entries' not in onsinfo:
|
if 'entries' not in onsinfo:
|
||||||
# If returned with no data from the RPC
|
# If returned with no data from the RPC
|
||||||
|
@ -469,7 +470,7 @@ def show_ons(name, more_details=False):
|
||||||
if len(onsinfo['encrypted_value']) not in [SESSION_ENCRYPTED_LENGTH, WALLET_ENCRYPTED_LENGTH, LOKINET_ENCRYPTED_LENGTH]:
|
if len(onsinfo['encrypted_value']) not in [SESSION_ENCRYPTED_LENGTH, WALLET_ENCRYPTED_LENGTH, LOKINET_ENCRYPTED_LENGTH]:
|
||||||
# Encryption involves a much more expensive argon2-based calculation for HF15 registrations.
|
# Encryption involves a much more expensive argon2-based calculation for HF15 registrations.
|
||||||
# Owners should be notified they should update to the new encryption format.
|
# Owners should be notified they should update to the new encryption format.
|
||||||
ons_data[ons_type] = ons_info(lmq, oxend, name,ons_types[ons_type]).get()['entries'][0]
|
ons_data[ons_type] = ons_info(omq, oxend, name,ons_types[ons_type]).get()['entries'][0]
|
||||||
ons_data[ons_type]['mapping'] = 'Owner needs to update their ID for mapping info.'
|
ons_data[ons_type]['mapping'] = 'Owner needs to update their ID for mapping info.'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -560,11 +561,11 @@ def show_ons(name, more_details=False):
|
||||||
@app.route('/sn/<hex64:pubkey>')
|
@app.route('/sn/<hex64:pubkey>')
|
||||||
@app.route('/sn/<hex64:pubkey>/<int:more_details>')
|
@app.route('/sn/<hex64:pubkey>/<int:more_details>')
|
||||||
def show_sn(pubkey, more_details=False):
|
def show_sn(pubkey, more_details=False):
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
info = FutureJSON(omq, oxend, 'rpc.get_info', 1)
|
||||||
hfinfo = FutureJSON(lmq, oxend, 'rpc.hard_fork_info', 10)
|
hfinfo = FutureJSON(omq, oxend, 'rpc.hard_fork_info', 10)
|
||||||
sn = sn_req(lmq, oxend, pubkey).get()
|
sn = sn_req(omq, oxend, pubkey).get()
|
||||||
quos = get_quorums_future(lmq, oxend, info.get()['height'])
|
quos = get_quorums_future(omq, oxend, info.get()['height'])
|
||||||
|
|
||||||
|
|
||||||
if 'service_node_states' not in sn or not sn['service_node_states']:
|
if 'service_node_states' not in sn or not sn['service_node_states']:
|
||||||
|
@ -640,7 +641,7 @@ def parse_txs(txs_rpc):
|
||||||
return txs_rpc['txs']
|
return txs_rpc['txs']
|
||||||
|
|
||||||
|
|
||||||
def get_block_txs_future(lmq, oxend, block):
|
def get_block_txs_future(omq, oxend, block):
|
||||||
hashes = []
|
hashes = []
|
||||||
if 'tx_hashes' in block:
|
if 'tx_hashes' in block:
|
||||||
hashes += block['tx_hashes']
|
hashes += block['tx_hashes']
|
||||||
|
@ -653,7 +654,7 @@ def get_block_txs_future(lmq, oxend, block):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Something getting wrong: cannot parse block json for block {}: {}".format(block_height, e), file=sys.stderr)
|
print("Something getting wrong: cannot parse block json for block {}: {}".format(block_height, e), file=sys.stderr)
|
||||||
|
|
||||||
return tx_req(lmq, oxend, hashes, cache_key='block')
|
return tx_req(omq, oxend, hashes, cache_key='block')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/block/<int:height>')
|
@app.route('/block/<int:height>')
|
||||||
|
@ -661,15 +662,15 @@ def get_block_txs_future(lmq, oxend, block):
|
||||||
@app.route('/block/<hex64:hash>')
|
@app.route('/block/<hex64:hash>')
|
||||||
@app.route('/block/<hex64:hash>/<int:more_details>')
|
@app.route('/block/<hex64:hash>/<int:more_details>')
|
||||||
def show_block(height=None, hash=None, more_details=False):
|
def show_block(height=None, hash=None, more_details=False):
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
info = FutureJSON(omq, oxend, 'rpc.get_info', 1)
|
||||||
hfinfo = FutureJSON(lmq, oxend, 'rpc.hard_fork_info', 10)
|
hfinfo = FutureJSON(omq, oxend, 'rpc.hard_fork_info', 10)
|
||||||
if height is not None:
|
if height is not None:
|
||||||
val = height
|
val = height
|
||||||
elif hash is not None:
|
elif hash is not None:
|
||||||
val = hash
|
val = hash
|
||||||
|
|
||||||
block = None if val is None else block_with_txs_req(lmq, oxend, val).get()
|
block = None if val is None else block_with_txs_req(omq, oxend, val).get()
|
||||||
if block is None:
|
if block is None:
|
||||||
return flask.render_template("not_found.html",
|
return flask.render_template("not_found.html",
|
||||||
info=info.get(),
|
info=info.get(),
|
||||||
|
@ -681,10 +682,10 @@ def show_block(height=None, hash=None, more_details=False):
|
||||||
|
|
||||||
next_block = None
|
next_block = None
|
||||||
block_height = block['block_header']['height']
|
block_height = block['block_header']['height']
|
||||||
txs = get_block_txs_future(lmq, oxend, block)
|
txs = get_block_txs_future(omq, oxend, block)
|
||||||
|
|
||||||
if info.get()['height'] > 1 + block_height:
|
if info.get()['height'] > 1 + block_height:
|
||||||
next_block = block_header_req(lmq, oxend, '{}'.format(block_height + 1))
|
next_block = block_header_req(omq, oxend, '{}'.format(block_height + 1))
|
||||||
|
|
||||||
if more_details:
|
if more_details:
|
||||||
formatter = HtmlFormatter(cssclass="syntax-highlight", style="native")
|
formatter = HtmlFormatter(cssclass="syntax-highlight", style="native")
|
||||||
|
@ -712,17 +713,17 @@ def show_block(height=None, hash=None, more_details=False):
|
||||||
|
|
||||||
@app.route('/block/latest')
|
@app.route('/block/latest')
|
||||||
def show_block_latest():
|
def show_block_latest():
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
height = FutureJSON(lmq, oxend, 'rpc.get_info', 1).get()['height'] - 1
|
height = FutureJSON(omq, oxend, 'rpc.get_info', 1).get()['height'] - 1
|
||||||
return flask.redirect(flask.url_for('show_block', height=height), code=302)
|
return flask.redirect(flask.url_for('show_block', height=height), code=302)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/tx/<hex64:txid>')
|
@app.route('/tx/<hex64:txid>')
|
||||||
@app.route('/tx/<hex64:txid>/<int:more_details>')
|
@app.route('/tx/<hex64:txid>/<int:more_details>')
|
||||||
def show_tx(txid, more_details=False):
|
def show_tx(txid, more_details=False):
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
info = FutureJSON(omq, oxend, 'rpc.get_info', 1)
|
||||||
txs = tx_req(lmq, oxend, [txid]).get()
|
txs = tx_req(omq, oxend, [txid]).get()
|
||||||
|
|
||||||
if 'txs' not in txs or not txs['txs']:
|
if 'txs' not in txs or not txs['txs']:
|
||||||
return flask.render_template('not_found.html',
|
return flask.render_template('not_found.html',
|
||||||
|
@ -735,7 +736,7 @@ def show_tx(txid, more_details=False):
|
||||||
# If this is a state change, see if we have the quorum stored to provide context
|
# If this is a state change, see if we have the quorum stored to provide context
|
||||||
testing_quorum = None
|
testing_quorum = None
|
||||||
if tx['info']['version'] >= 4 and 'sn_state_change' in tx['extra']:
|
if tx['info']['version'] >= 4 and 'sn_state_change' in tx['extra']:
|
||||||
testing_quorum = FutureJSON(lmq, oxend, 'rpc.get_quorum_state', 60, cache_key='tx_state_change',
|
testing_quorum = FutureJSON(omq, oxend, 'rpc.get_quorum_state', 60, cache_key='tx_state_change',
|
||||||
args={ 'quorum_type': 0, 'start_height': tx['extra']['sn_state_change']['height'] })
|
args={ 'quorum_type': 0, 'start_height': tx['extra']['sn_state_change']['height'] })
|
||||||
|
|
||||||
kindex_info = {} # { amount => { keyindex => {output-info} } }
|
kindex_info = {} # { amount => { keyindex => {output-info} } }
|
||||||
|
@ -759,14 +760,14 @@ def show_tx(txid, more_details=False):
|
||||||
del inp['key']['key_offsets']
|
del inp['key']['key_offsets']
|
||||||
|
|
||||||
outs_req = [{"amount":inp['key']['amount'], "index":ki} for inp in tx['info']['vin'] for ki in inp['key']['key_indices']]
|
outs_req = [{"amount":inp['key']['amount'], "index":ki} for inp in tx['info']['vin'] for ki in inp['key']['key_indices']]
|
||||||
outputs = FutureJSON(lmq, oxend, 'rpc.get_outs', args={
|
outputs = FutureJSON(omq, oxend, 'rpc.get_outs', args={
|
||||||
'get_txid': True,
|
'get_txid': True,
|
||||||
'outputs': outs_req,
|
'outputs': outs_req,
|
||||||
}).get()
|
}).get()
|
||||||
if outputs and 'outs' in outputs and len(outputs['outs']) == len(outs_req):
|
if outputs and 'outs' in outputs and len(outputs['outs']) == len(outs_req):
|
||||||
outputs = outputs['outs']
|
outputs = outputs['outs']
|
||||||
# Also load block details for all of those outputs:
|
# Also load block details for all of those outputs:
|
||||||
block_info_req = FutureJSON(lmq, oxend, 'rpc.get_block_header_by_height', args={
|
block_info_req = FutureJSON(omq, oxend, 'rpc.get_block_header_by_height', args={
|
||||||
'heights': [o["height"] for o in outputs]
|
'heights': [o["height"] for o in outputs]
|
||||||
})
|
})
|
||||||
i = 0
|
i = 0
|
||||||
|
@ -816,9 +817,9 @@ def show_tx(txid, more_details=False):
|
||||||
|
|
||||||
@app.route('/quorums')
|
@app.route('/quorums')
|
||||||
def show_quorums():
|
def show_quorums():
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
info = FutureJSON(omq, oxend, 'rpc.get_info', 1)
|
||||||
quos = get_quorums_future(lmq, oxend, info.get()['height'])
|
quos = get_quorums_future(omq, oxend, info.get()['height'])
|
||||||
|
|
||||||
return flask.render_template('quorums.html',
|
return flask.render_template('quorums.html',
|
||||||
info=info.get(),
|
info=info.get(),
|
||||||
|
@ -832,8 +833,8 @@ base32z_map = {base32z_dict[i]: i for i in range(len(base32z_dict))}
|
||||||
|
|
||||||
@app.route('/search')
|
@app.route('/search')
|
||||||
def search():
|
def search():
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
info = FutureJSON(omq, oxend, 'rpc.get_info', 1)
|
||||||
val = (flask.request.args.get('value') or '').strip()
|
val = (flask.request.args.get('value') or '').strip()
|
||||||
if val and len(val) < 10 and val.isdigit(): # Block height
|
if val and len(val) < 10 and val.isdigit(): # Block height
|
||||||
return flask.redirect(flask.url_for('show_block', height=val), code=301)
|
return flask.redirect(flask.url_for('show_block', height=val), code=301)
|
||||||
|
@ -848,9 +849,9 @@ def search():
|
||||||
|
|
||||||
if len(val) == 64:
|
if len(val) == 64:
|
||||||
# Initiate all the lookups at once, then redirect to whichever one responds affirmatively
|
# Initiate all the lookups at once, then redirect to whichever one responds affirmatively
|
||||||
snreq = sn_req(lmq, oxend, val)
|
snreq = sn_req(omq, oxend, val)
|
||||||
blreq = block_header_req(lmq, oxend, val, fail_okay=True)
|
blreq = block_header_req(omq, oxend, val, fail_okay=True)
|
||||||
txreq = tx_req(lmq, oxend, [val])
|
txreq = tx_req(omq, oxend, [val])
|
||||||
|
|
||||||
sn = snreq.get()
|
sn = snreq.get()
|
||||||
if sn and 'service_node_states' in sn and sn['service_node_states']:
|
if sn and 'service_node_states' in sn and sn['service_node_states']:
|
||||||
|
@ -881,9 +882,9 @@ def search():
|
||||||
|
|
||||||
@app.route('/api/networkinfo')
|
@app.route('/api/networkinfo')
|
||||||
def api_networkinfo():
|
def api_networkinfo():
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
info = FutureJSON(omq, oxend, 'rpc.get_info', 1)
|
||||||
hfinfo = FutureJSON(lmq, oxend, 'rpc.hard_fork_info', 10)
|
hfinfo = FutureJSON(omq, oxend, 'rpc.hard_fork_info', 10)
|
||||||
|
|
||||||
info = info.get()
|
info = info.get()
|
||||||
data = {**info}
|
data = {**info}
|
||||||
|
@ -895,9 +896,9 @@ def api_networkinfo():
|
||||||
|
|
||||||
@app.route('/api/emission')
|
@app.route('/api/emission')
|
||||||
def api_emission():
|
def api_emission():
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
info = FutureJSON(omq, oxend, 'rpc.get_info', 1)
|
||||||
coinbase = FutureJSON(lmq, oxend, 'admin.get_coinbase_tx_sum', 10, timeout=1, fail_okay=True,
|
coinbase = FutureJSON(omq, oxend, 'admin.get_coinbase_tx_sum', 10, timeout=1, fail_okay=True,
|
||||||
args={"height":0, "count":2**31-1}).get()
|
args={"height":0, "count":2**31-1}).get()
|
||||||
if not coinbase:
|
if not coinbase:
|
||||||
return flask.jsonify(None)
|
return flask.jsonify(None)
|
||||||
|
@ -917,10 +918,10 @@ def api_emission():
|
||||||
|
|
||||||
@app.route('/api/service_node_stats')
|
@app.route('/api/service_node_stats')
|
||||||
def api_service_node_stats():
|
def api_service_node_stats():
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
info = FutureJSON(omq, oxend, 'rpc.get_info', 1)
|
||||||
stakinginfo = FutureJSON(lmq, oxend, 'rpc.get_staking_requirement', 30)
|
stakinginfo = FutureJSON(omq, oxend, 'rpc.get_staking_requirement', 30)
|
||||||
sns = get_sns_future(lmq, oxend)
|
sns = get_sns_future(omq, oxend)
|
||||||
sns = sns.get()
|
sns = sns.get()
|
||||||
if 'service_node_states' not in sns:
|
if 'service_node_states' not in sns:
|
||||||
return flask.jsonify({"status": "Error retrieving SN stats"}), 500
|
return flask.jsonify({"status": "Error retrieving SN stats"}), 500
|
||||||
|
@ -952,8 +953,8 @@ def api_service_node_stats():
|
||||||
|
|
||||||
@app.route('/api/circulating_supply')
|
@app.route('/api/circulating_supply')
|
||||||
def api_circulating_supply():
|
def api_circulating_supply():
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
coinbase = FutureJSON(lmq, oxend, 'admin.get_coinbase_tx_sum', 10, timeout=1, fail_okay=True,
|
coinbase = FutureJSON(omq, oxend, 'admin.get_coinbase_tx_sum', 10, timeout=1, fail_okay=True,
|
||||||
args={"height":0, "count":2**31-1}).get()
|
args={"height":0, "count":2**31-1}).get()
|
||||||
return flask.jsonify((coinbase["emission_amount"] - coinbase["burn_amount"]) // 1_000_000_000 if coinbase else None)
|
return flask.jsonify((coinbase["emission_amount"] - coinbase["burn_amount"]) // 1_000_000_000 if coinbase else None)
|
||||||
|
|
||||||
|
@ -961,8 +962,8 @@ def api_circulating_supply():
|
||||||
# FIXME: need better error handling here
|
# FIXME: need better error handling here
|
||||||
@app.route('/api/transaction/<hex64:txid>')
|
@app.route('/api/transaction/<hex64:txid>')
|
||||||
def api_tx(txid):
|
def api_tx(txid):
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
tx = tx_req(lmq, oxend, [txid]).get()
|
tx = tx_req(omq, oxend, [txid]).get()
|
||||||
txs = parse_txs(tx)
|
txs = parse_txs(tx)
|
||||||
return flask.jsonify({
|
return flask.jsonify({
|
||||||
"status": tx['status'],
|
"status": tx['status'],
|
||||||
|
@ -972,9 +973,9 @@ def api_tx(txid):
|
||||||
@app.route('/api/block/<int:height>')
|
@app.route('/api/block/<int:height>')
|
||||||
@app.route('/api/block/<hex64:blkid>')
|
@app.route('/api/block/<hex64:blkid>')
|
||||||
def api_block(blkid=None, height=None):
|
def api_block(blkid=None, height=None):
|
||||||
lmq, oxend = lmq_connection()
|
omq, oxend = omq_connection()
|
||||||
block = block_with_txs_req(lmq, oxend, blkid if blkid is not None else height).get()
|
block = block_with_txs_req(omq, oxend, blkid if blkid is not None else height).get()
|
||||||
txs = get_block_txs_future(lmq, oxend, block)
|
txs = get_block_txs_future(omq, oxend, block)
|
||||||
|
|
||||||
if 'block_header' in block:
|
if 'block_header' in block:
|
||||||
data = block['block_header'].copy()
|
data = block['block_header'].copy()
|
||||||
|
|
1
pylokimq
1
pylokimq
|
@ -1 +0,0 @@
|
||||||
Subproject commit 558c679c55c5a366b57110a55b6cbc68c447925d
|
|
|
@ -1,3 +1,4 @@
|
||||||
from observer import app, config
|
from observer import app, config
|
||||||
|
import oxenmq
|
||||||
|
|
||||||
config.oxend_rpc = 'ipc://oxend/testnet.sock'
|
config.oxend_rpc = oxenmq.Address('ipc://oxend/testnet.sock')
|
||||||
|
|
Loading…
Reference in New Issue