Loki -> Oxen
This commit is contained in:
parent
868fd7dc0b
commit
007baea846
12
README.md
12
README.md
|
@ -1,6 +1,6 @@
|
||||||
# Loki Observer OMG block explorer
|
# Oxen Observer OMG block explorer
|
||||||
|
|
||||||
Block explorer using Loki 8+ LMQ RPC interface that does everything through RPC requests. Sexy,
|
Block explorer using Oxen 8+ LMQ RPC interface that does everything through RPC requests. Sexy,
|
||||||
awesome, safe.
|
awesome, safe.
|
||||||
|
|
||||||
## Prerequisite packages
|
## Prerequisite packages
|
||||||
|
@ -23,7 +23,7 @@ Quick and dirty setup instructions for now:
|
||||||
(Note that we require a very recent python3-jinja package (2.11+), which may not be installed by the
|
(Note that we require a very recent python3-jinja package (2.11+), which may not be installed by the
|
||||||
above.)
|
above.)
|
||||||
|
|
||||||
You'll also need to run lokid with `--lmq-local-control ipc:///path/to/loki-observer/mainnet.sock`.
|
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
|
||||||
|
|
||||||
|
@ -60,13 +60,13 @@ Create a "vassal" config for loki-observer, `/etc/uwsgi-emperor/vassals/loki-obs
|
||||||
logger = file:logfile=/path/to/loki-observer/mainnet.log
|
logger = file:logfile=/path/to/loki-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 use you want it to run as, and set the group to `_loki` (so
|
||||||
that it can open the lokid 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 loki-observer/mainnet.py, set:
|
||||||
|
|
||||||
config.lokid_rpc = 'ipc:///var/lib/loki/lokid.sock'
|
config.oxend_rpc = 'ipc:///var/lib/loki/oxend.sock'
|
||||||
|
|
||||||
and finally, proxy requests from the webserver to the wsgi socket. For Apache I do this with:
|
and finally, proxy requests from the webserver to the wsgi socket. For Apache I do this with:
|
||||||
|
|
||||||
|
@ -88,4 +88,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 lokid.sock from a testnet or devnet lokid.
|
using testnet.py or devnet.py pointing to a oxend.sock from a testnet or devnet oxend.
|
||||||
|
|
10
config.py
10
config.py
|
@ -7,10 +7,10 @@
|
||||||
# into `mainnet.py`/`testnet.py`/etc.
|
# into `mainnet.py`/`testnet.py`/etc.
|
||||||
|
|
||||||
|
|
||||||
# LMQ RPC endpoint of lokid; can be a unix socket 'ipc:///path/to/lokid.sock' (preferred) or a tcp
|
# LMQ RPC endpoint of oxend; can be a unix socket 'ipc:///path/to/oxend.sock' (preferred) or a tcp
|
||||||
# socket 'tcp://127.0.0.1:5678'. Typically you want this running with admin permission.
|
# socket 'tcp://127.0.0.1:5678'. Typically you want this running with admin permission.
|
||||||
# Leave this as None here, and set it for each observer in the mainnet.py/testnet.py/etc. script.
|
# Leave this as None here, and set it for each observer in the mainnet.py/testnet.py/etc. script.
|
||||||
lokid_rpc = None
|
oxend_rpc = None
|
||||||
|
|
||||||
# Default blocks per page for the index.
|
# Default blocks per page for the index.
|
||||||
blocks_per_page=20
|
blocks_per_page=20
|
||||||
|
@ -25,9 +25,9 @@ autorefresh_option=True
|
||||||
enable_mixins_details=True
|
enable_mixins_details=True
|
||||||
|
|
||||||
# URLs to networks other than the one we are on:
|
# URLs to networks other than the one we are on:
|
||||||
mainnet_url='https://blocks.lokinet.dev'
|
mainnet_url='https://oxen.observer'
|
||||||
testnet_url='https://testnet.lokinet.dev'
|
testnet_url='https://testnet.oxen.observer'
|
||||||
devnet_url='https://devnet.lokinet.dev'
|
devnet_url='https://devnet.oxen.observer'
|
||||||
|
|
||||||
# Same as above, but these apply if we are on a .loki URL:
|
# Same as above, but these apply if we are on a .loki URL:
|
||||||
lokinet_mainnet_url='http://blocks.loki'
|
lokinet_mainnet_url='http://blocks.loki'
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
from observer import app, config
|
from observer import app, config
|
||||||
|
|
||||||
config.lokid_rpc = 'ipc://lokid/devnet.sock'
|
config.oxend_rpc = 'ipc://oxend/devnet.sock'
|
||||||
|
|
16
lmq.py
16
lmq.py
|
@ -4,16 +4,16 @@ import json
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
lmq, lokid = None, None
|
lmq, oxend = None, None
|
||||||
def lmq_connection():
|
def lmq_connection():
|
||||||
global lmq, lokid
|
global lmq, oxend
|
||||||
if lmq is None:
|
if lmq is None:
|
||||||
lmq = pylokimq.LokiMQ(pylokimq.LogLevel.warn)
|
lmq = pylokimq.LokiMQ(pylokimq.LogLevel.warn)
|
||||||
lmq.max_message_size = 200*1024*1024
|
lmq.max_message_size = 200*1024*1024
|
||||||
lmq.start()
|
lmq.start()
|
||||||
if lokid is None:
|
if oxend is None:
|
||||||
lokid = lmq.connect_remote(config.lokid_rpc)
|
oxend = lmq.connect_remote(config.oxend_rpc)
|
||||||
return (lmq, lokid)
|
return (lmq, oxend)
|
||||||
|
|
||||||
cached = {}
|
cached = {}
|
||||||
cached_args = {}
|
cached_args = {}
|
||||||
|
@ -31,7 +31,7 @@ class FutureJSON():
|
||||||
result in unbounded memory growth.
|
result in unbounded memory growth.
|
||||||
|
|
||||||
lmq - the lmq object
|
lmq - the lmq object
|
||||||
lokid - the lokid lmq connection id object
|
oxend - the oxend lmq connection id object
|
||||||
endpoint - the lmq endpoint, e.g. 'rpc.get_info'
|
endpoint - the lmq 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
|
||||||
|
@ -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, lokid, endpoint, cache_seconds=3, *, cache_key='', args=None, fail_okay=False, timeout=10):
|
def __init__(self, lmq, 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(lokid, self.endpoint, [] if self.args is None else [self.args], timeout=timeout)
|
self.future = lmq.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,3 @@
|
||||||
from observer import app, config
|
from observer import app, config
|
||||||
|
|
||||||
config.lokid_rpc = 'ipc://lokid/mainnet.sock'
|
config.oxend_rpc = 'ipc://oxend/mainnet.sock'
|
||||||
|
|
160
observer.py
160
observer.py
|
@ -115,10 +115,10 @@ def format_si(value):
|
||||||
i += 1
|
i += 1
|
||||||
return filter_round(value) + '{}'.format(si_suffix[i])
|
return filter_round(value) + '{}'.format(si_suffix[i])
|
||||||
|
|
||||||
@app.template_filter('loki')
|
@app.template_filter('oxen')
|
||||||
def format_loki(atomic, tag=True, fixed=False, decimals=9, zero=None):
|
def format_oxen(atomic, tag=True, fixed=False, decimals=9, zero=None):
|
||||||
"""Formats an atomic current value as a human currency value.
|
"""Formats an atomic current value as a human currency value.
|
||||||
tag - if False then don't append " LOKI"
|
tag - if False then don't append " OXEN"
|
||||||
fixed - if True then don't strip insignificant trailing 0's and '.'
|
fixed - if True then don't strip insignificant trailing 0's and '.'
|
||||||
decimals - at how many decimal we should round; the default is full precision
|
decimals - at how many decimal we should round; the default is full precision
|
||||||
fixed - if specified, replace 0 with this string
|
fixed - if specified, replace 0 with this string
|
||||||
|
@ -130,7 +130,7 @@ def format_loki(atomic, tag=True, fixed=False, decimals=9, zero=None):
|
||||||
if not fixed and decimals > 0:
|
if not fixed and decimals > 0:
|
||||||
disp = disp.rstrip('0').rstrip('.')
|
disp = disp.rstrip('0').rstrip('.')
|
||||||
if tag:
|
if tag:
|
||||||
disp += ' LOKI'
|
disp += ' OXEN'
|
||||||
return disp
|
return disp
|
||||||
|
|
||||||
# For some inexplicable reason some hex fields are provided as array of byte integer values rather
|
# For some inexplicable reason some hex fields are provided as array of byte integer values rather
|
||||||
|
@ -169,8 +169,8 @@ def css():
|
||||||
return flask.send_from_directory('static', 'style.css')
|
return flask.send_from_directory('static', 'style.css')
|
||||||
|
|
||||||
|
|
||||||
def get_sns_future(lmq, lokid):
|
def get_sns_future(lmq, oxend):
|
||||||
return FutureJSON(lmq, lokid, 'rpc.get_service_nodes', 5,
|
return FutureJSON(lmq, 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',
|
||||||
|
@ -200,8 +200,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, lokid, height):
|
def get_quorums_future(lmq, oxend, height):
|
||||||
return FutureJSON(lmq, lokid, 'rpc.get_quorum_state', 30,
|
return FutureJSON(lmq, oxend, 'rpc.get_quorum_state', 30,
|
||||||
args={ 'start_height': height-55, 'end_height': height })
|
args={ 'start_height': height-55, 'end_height': height })
|
||||||
|
|
||||||
|
|
||||||
|
@ -218,8 +218,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, lokid):
|
def get_mempool_future(lmq, oxend):
|
||||||
return FutureJSON(lmq, lokid, 'rpc.get_transaction_pool', 5, args={"tx_extra":True, "stake_info":True})
|
return FutureJSON(lmq, 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
|
||||||
|
@ -257,19 +257,19 @@ def template_globals():
|
||||||
@app.route('/autorefresh/<int:refresh>')
|
@app.route('/autorefresh/<int:refresh>')
|
||||||
@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):
|
||||||
lmq, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
inforeq = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
inforeq = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
||||||
stake = FutureJSON(lmq, lokid, 'rpc.get_staking_requirement', 10)
|
stake = FutureJSON(lmq, oxend, 'rpc.get_staking_requirement', 10)
|
||||||
base_fee = FutureJSON(lmq, lokid, 'rpc.get_fee_estimate', 10)
|
base_fee = FutureJSON(lmq, oxend, 'rpc.get_fee_estimate', 10)
|
||||||
hfinfo = FutureJSON(lmq, lokid, 'rpc.hard_fork_info', 10)
|
hfinfo = FutureJSON(lmq, oxend, 'rpc.hard_fork_info', 10)
|
||||||
mempool = get_mempool_future(lmq, lokid)
|
mempool = get_mempool_future(lmq, oxend)
|
||||||
sns = get_sns_future(lmq, lokid)
|
sns = get_sns_future(lmq, oxend)
|
||||||
checkpoints = FutureJSON(lmq, lokid, 'rpc.get_checkpoints', args={"count": 3})
|
checkpoints = FutureJSON(lmq, oxend, 'rpc.get_checkpoints', args={"count": 3})
|
||||||
|
|
||||||
# This call is slow the first time it gets called in lokid 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, lokid, 'admin.get_coinbase_tx_sum', 10, timeout=1, fail_okay=True,
|
coinbase = FutureJSON(lmq, 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 = ''
|
||||||
|
@ -298,7 +298,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, lokid, 'rpc.get_block_headers_range', cache_key='main', args={
|
blocks = FutureJSON(lmq, 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,
|
||||||
|
@ -313,7 +313,7 @@ def main(refresh=None, page=0, per_page=None, first=None, last=None):
|
||||||
txids.append(b['miner_tx_hash'])
|
txids.append(b['miner_tx_hash'])
|
||||||
if 'tx_hashes' in b:
|
if 'tx_hashes' in b:
|
||||||
txids += b['tx_hashes']
|
txids += b['tx_hashes']
|
||||||
txs = parse_txs(tx_req(lmq, lokid, txids, cache_key='mempool').get())
|
txs = parse_txs(tx_req(lmq, oxend, txids, cache_key='mempool').get())
|
||||||
i = 0
|
i = 0
|
||||||
for tx in txs:
|
for tx in txs:
|
||||||
# TXs should come back in the same order so we can just skip ahead one when the block
|
# TXs should come back in the same order so we can just skip ahead one when the block
|
||||||
|
@ -353,9 +353,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, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
||||||
mempool = get_mempool_future(lmq, lokid)
|
mempool = get_mempool_future(lmq, oxend)
|
||||||
|
|
||||||
return flask.render_template('mempool.html',
|
return flask.render_template('mempool.html',
|
||||||
info=info.get(),
|
info=info.get(),
|
||||||
|
@ -364,9 +364,9 @@ def mempool():
|
||||||
|
|
||||||
@app.route('/service_nodes')
|
@app.route('/service_nodes')
|
||||||
def sns():
|
def sns():
|
||||||
lmq, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
||||||
awaiting, active, inactive = get_sns(get_sns_future(lmq, lokid), info)
|
awaiting, active, inactive = get_sns(get_sns_future(lmq, oxend), info)
|
||||||
|
|
||||||
return flask.render_template('service_nodes.html',
|
return flask.render_template('service_nodes.html',
|
||||||
info=info.get(),
|
info=info.get(),
|
||||||
|
@ -375,8 +375,8 @@ def sns():
|
||||||
inactive_sns=inactive,
|
inactive_sns=inactive,
|
||||||
)
|
)
|
||||||
|
|
||||||
def tx_req(lmq, lokid, txids, cache_key='single', **kwargs):
|
def tx_req(lmq, oxend, txids, cache_key='single', **kwargs):
|
||||||
return FutureJSON(lmq, lokid, 'rpc.get_transactions', cache_seconds=10, cache_key=cache_key,
|
return FutureJSON(lmq, 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,
|
||||||
|
@ -386,38 +386,38 @@ def tx_req(lmq, lokid, txids, cache_key='single', **kwargs):
|
||||||
},
|
},
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
def sn_req(lmq, lokid, pubkey, **kwargs):
|
def sn_req(lmq, oxend, pubkey, **kwargs):
|
||||||
return FutureJSON(lmq, lokid, 'rpc.get_service_nodes', 5, cache_key='single',
|
return FutureJSON(lmq, 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, lokid, hash_or_height, **kwargs):
|
def block_header_req(lmq, 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, lokid, 'rpc.get_block_header_by_height', cache_key='single',
|
return FutureJSON(lmq, 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, lokid, 'rpc.get_block_header_by_hash', cache_key='single',
|
return FutureJSON(lmq, 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, lokid, hash_or_height, **kwargs):
|
def block_with_txs_req(lmq, 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, lokid, 'rpc.get_block', cache_key='single', args=args, **kwargs)
|
return FutureJSON(lmq, oxend, 'rpc.get_block', cache_key='single', args=args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/service_node/<hex64:pubkey>') # For backwards compatibility with old explorer URLs
|
@app.route('/service_node/<hex64:pubkey>') # For backwards compatibility with old explorer URLs
|
||||||
@app.route('/sn/<hex64:pubkey>')
|
@app.route('/sn/<hex64:pubkey>')
|
||||||
def show_sn(pubkey):
|
def show_sn(pubkey):
|
||||||
lmq, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
||||||
hfinfo = FutureJSON(lmq, lokid, 'rpc.hard_fork_info', 10)
|
hfinfo = FutureJSON(lmq, oxend, 'rpc.hard_fork_info', 10)
|
||||||
sn = sn_req(lmq, lokid, pubkey).get()
|
sn = sn_req(lmq, oxend, pubkey).get()
|
||||||
|
|
||||||
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']:
|
||||||
return flask.render_template('not_found.html',
|
return flask.render_template('not_found.html',
|
||||||
|
@ -452,7 +452,7 @@ def parse_txs(txs_rpc):
|
||||||
|
|
||||||
for tx in txs_rpc['txs']:
|
for tx in txs_rpc['txs']:
|
||||||
if 'info' not in tx:
|
if 'info' not in tx:
|
||||||
# We have serialized JSON data inside a field in the JSON, because of lokid's
|
# We have serialized JSON data inside a field in the JSON, because of oxend's
|
||||||
# multiple incompatible JSON generators 🤮:
|
# multiple incompatible JSON generators 🤮:
|
||||||
tx['info'] = json.loads(tx["as_json"])
|
tx['info'] = json.loads(tx["as_json"])
|
||||||
del tx['as_json']
|
del tx['as_json']
|
||||||
|
@ -462,7 +462,7 @@ def parse_txs(txs_rpc):
|
||||||
return txs_rpc['txs']
|
return txs_rpc['txs']
|
||||||
|
|
||||||
|
|
||||||
def get_block_txs_future(lmq, lokid, block):
|
def get_block_txs_future(lmq, oxend, block):
|
||||||
hashes = []
|
hashes = []
|
||||||
if 'tx_hashes' in block:
|
if 'tx_hashes' in block:
|
||||||
hashes += block['tx_hashes']
|
hashes += block['tx_hashes']
|
||||||
|
@ -475,7 +475,7 @@ def get_block_txs_future(lmq, lokid, 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, lokid, hashes, cache_key='block')
|
return tx_req(lmq, oxend, hashes, cache_key='block')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/block/<int:height>')
|
@app.route('/block/<int:height>')
|
||||||
|
@ -483,15 +483,15 @@ def get_block_txs_future(lmq, lokid, 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, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
||||||
hfinfo = FutureJSON(lmq, lokid, 'rpc.hard_fork_info', 10)
|
hfinfo = FutureJSON(lmq, 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, lokid, val).get()
|
block = None if val is None else block_with_txs_req(lmq, 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(),
|
||||||
|
@ -503,10 +503,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, lokid, block)
|
txs = get_block_txs_future(lmq, oxend, block)
|
||||||
|
|
||||||
if info.get()['height'] > 1 + block_height:
|
if info.get()['height'] > 1 + block_height:
|
||||||
next_block = block_header_req(lmq, lokid, '{}'.format(block_height + 1))
|
next_block = block_header_req(lmq, 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")
|
||||||
|
@ -534,17 +534,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, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
height = FutureJSON(lmq, lokid, 'rpc.get_info', 1).get()['height'] - 1
|
height = FutureJSON(lmq, 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, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
||||||
txs = tx_req(lmq, lokid, [txid]).get()
|
txs = tx_req(lmq, 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',
|
||||||
|
@ -557,7 +557,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, lokid, 'rpc.get_quorum_state', 60, cache_key='tx_state_change',
|
testing_quorum = FutureJSON(lmq, 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} } }
|
||||||
|
@ -581,14 +581,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, lokid, 'rpc.get_outs', args={
|
outputs = FutureJSON(lmq, 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, lokid, 'rpc.get_block_header_by_height', args={
|
block_info_req = FutureJSON(lmq, 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
|
||||||
|
@ -637,9 +637,9 @@ def show_tx(txid, more_details=False):
|
||||||
|
|
||||||
@app.route('/quorums')
|
@app.route('/quorums')
|
||||||
def show_quorums():
|
def show_quorums():
|
||||||
lmq, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
||||||
quos = get_quorums_future(lmq, lokid, info.get()['height'])
|
quos = get_quorums_future(lmq, oxend, info.get()['height'])
|
||||||
|
|
||||||
return flask.render_template('quorums.html',
|
return flask.render_template('quorums.html',
|
||||||
info=info.get(),
|
info=info.get(),
|
||||||
|
@ -649,8 +649,8 @@ def show_quorums():
|
||||||
|
|
||||||
@app.route('/search')
|
@app.route('/search')
|
||||||
def search():
|
def search():
|
||||||
lmq, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
info = FutureJSON(lmq, 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
|
||||||
|
@ -664,9 +664,9 @@ def search():
|
||||||
)
|
)
|
||||||
|
|
||||||
# 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, lokid, val)
|
snreq = sn_req(lmq, oxend, val)
|
||||||
blreq = block_header_req(lmq, lokid, val, fail_okay=True)
|
blreq = block_header_req(lmq, oxend, val, fail_okay=True)
|
||||||
txreq = tx_req(lmq, lokid, [val])
|
txreq = tx_req(lmq, oxend, [val])
|
||||||
|
|
||||||
sn = snreq.get()
|
sn = snreq.get()
|
||||||
if 'service_node_states' in sn and sn['service_node_states']:
|
if 'service_node_states' in sn and sn['service_node_states']:
|
||||||
|
@ -686,9 +686,9 @@ def search():
|
||||||
|
|
||||||
@app.route('/api/networkinfo')
|
@app.route('/api/networkinfo')
|
||||||
def api_networkinfo():
|
def api_networkinfo():
|
||||||
lmq, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
||||||
hfinfo = FutureJSON(lmq, lokid, 'rpc.hard_fork_info', 10)
|
hfinfo = FutureJSON(lmq, oxend, 'rpc.hard_fork_info', 10)
|
||||||
|
|
||||||
info = info.get()
|
info = info.get()
|
||||||
data = {**info}
|
data = {**info}
|
||||||
|
@ -700,9 +700,9 @@ def api_networkinfo():
|
||||||
|
|
||||||
@app.route('/api/emission')
|
@app.route('/api/emission')
|
||||||
def api_emission():
|
def api_emission():
|
||||||
lmq, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
info = FutureJSON(lmq, lokid, 'rpc.get_info', 1)
|
info = FutureJSON(lmq, oxend, 'rpc.get_info', 1)
|
||||||
coinbase = FutureJSON(lmq, lokid, 'admin.get_coinbase_tx_sum', 10, timeout=1, fail_okay=True,
|
coinbase = FutureJSON(lmq, 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)
|
||||||
|
@ -722,8 +722,8 @@ def api_emission():
|
||||||
|
|
||||||
@app.route('/api/circulating_supply')
|
@app.route('/api/circulating_supply')
|
||||||
def api_circulating_supply():
|
def api_circulating_supply():
|
||||||
lmq, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
coinbase = FutureJSON(lmq, lokid, 'admin.get_coinbase_tx_sum', 10, timeout=1, fail_okay=True,
|
coinbase = FutureJSON(lmq, 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"]) // 1000000000 if coinbase else None)
|
return flask.jsonify((coinbase["emission_amount"] - coinbase["burn_amount"]) // 1000000000 if coinbase else None)
|
||||||
|
|
||||||
|
@ -731,8 +731,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, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
tx = tx_req(lmq, lokid, [txid]).get()
|
tx = tx_req(lmq, oxend, [txid]).get()
|
||||||
txs = parse_txs(tx)
|
txs = parse_txs(tx)
|
||||||
return flask.jsonify({
|
return flask.jsonify({
|
||||||
"status": tx['status'],
|
"status": tx['status'],
|
||||||
|
@ -742,9 +742,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, lokid = lmq_connection()
|
lmq, oxend = lmq_connection()
|
||||||
block = block_with_txs_req(lmq, lokid, blkid if blkid is not None else height).get()
|
block = block_with_txs_req(lmq, oxend, blkid if blkid is not None else height).get()
|
||||||
txs = get_block_txs_future(lmq, lokid, block)
|
txs = get_block_txs_future(lmq, oxend, block)
|
||||||
|
|
||||||
if 'block_header' in block:
|
if 'block_header' in block:
|
||||||
data = block['block_header'].copy()
|
data = block['block_header'].copy()
|
||||||
|
@ -761,7 +761,7 @@ ticker_cache, ticker_cache_expires = {}, None
|
||||||
@app.route('/api/price/<fiat>')
|
@app.route('/api/price/<fiat>')
|
||||||
def api_price(fiat=None):
|
def api_price(fiat=None):
|
||||||
global ticker_cache, ticker_cache_expires, ticker_vs, ticker_vs_expires
|
global ticker_cache, ticker_cache_expires, ticker_vs, ticker_vs_expires
|
||||||
# TODO: will need to change to 'oxen' when the ticker changes:
|
# TODO: will need to change to 'oxen' when/if the ticker changes:
|
||||||
ticker = 'loki-network'
|
ticker = 'loki-network'
|
||||||
|
|
||||||
if not ticker_cache or not ticker_cache_expires or ticker_cache_expires < time.time():
|
if not ticker_cache or not ticker_cache_expires or ticker_cache_expires < time.time():
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<title>{% block title %}{% endblock %}Loki{{' TESTNET' if info and info.testnet else ' DEVNET' if info and info.devnet else ''}}
|
<title>{% block title %}{% endblock %}Oxen{{' TESTNET' if info and info.testnet else ' DEVNET' if info and info.devnet else ''}}
|
||||||
Blockchain Explorer</title>
|
Blockchain Explorer</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/style.css">
|
<link rel="stylesheet" type="text/css" href="/style.css">
|
||||||
{% if refresh %}
|
{% if refresh %}
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
<div id="header" class="Wrapper">
|
<div id="header" class="Wrapper">
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div id="header-content">
|
<div id="header-content">
|
||||||
<h1 class="Header"><a href="/">Loki
|
<h1 class="Header"><a href="/">Oxen
|
||||||
{%if info and info.testnet%}
|
{%if info and info.testnet%}
|
||||||
<span style="color:#ff6b62">Testnet</span>
|
<span style="color:#ff6b62">Testnet</span>
|
||||||
{%elif info and info.devnet%}
|
{%elif info and info.devnet%}
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
<p style="margin-top:10px">
|
<p style="margin-top:10px">
|
||||||
<a href="https://github.com/loki-project/loki-observer">Source Code</a>
|
<a href="https://github.com/loki-project/loki-observer">Source Code</a>
|
||||||
| Explorer Revision: {{server.revision}}
|
| Explorer Revision: {{server.revision}}
|
||||||
| Loki Version: {{info.version}}
|
| Oxen Version: {{info.version}}
|
||||||
</p>
|
</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -52,19 +52,19 @@ Validator bits: {{"{:011b}".format(block.info.pulse.validator_bitset)}}"><label>
|
||||||
{%set sum_burned = transactions | selectattr('extra.burn_amount') | sum(attribute='extra.burn_amount') %}
|
{%set sum_burned = transactions | selectattr('extra.burn_amount') | sum(attribute='extra.burn_amount') %}
|
||||||
{%set sum_fees = transactions | selectattr('info.rct_signatures') | selectattr('info.rct_signatures.txnFee') | sum(attribute='info.rct_signatures.txnFee') - sum_burned%}
|
{%set sum_fees = transactions | selectattr('info.rct_signatures') | selectattr('info.rct_signatures.txnFee') | sum(attribute='info.rct_signatures.txnFee') - sum_burned%}
|
||||||
|
|
||||||
<span title="{{(block_header.reward - sum_fees) | loki(fixed=True)}} created in this block.{%if sum_fees > 0%}
|
<span title="{{(block_header.reward - sum_fees) | oxen(fixed=True)}} created in this block.{%if sum_fees > 0%}
|
||||||
|
|
||||||
Note that this value does not include earned transaction fees ({{sum_fees | loki(fixed=True, decimals=4)}}){%endif%}"><label>Block reward:</label>
|
Note that this value does not include earned transaction fees ({{sum_fees | oxen(fixed=True, decimals=4)}}){%endif%}"><label>Block reward:</label>
|
||||||
{{(block_header.reward - sum_fees) | loki(decimals=4)}}</span>
|
{{(block_header.reward - sum_fees) | oxen(decimals=4)}}</span>
|
||||||
|
|
||||||
{%if sum_fees > 0%}
|
{%if sum_fees > 0%}
|
||||||
<span title="Earned TX fees: {{sum_fees | loki(fixed=True)}}"><label>Block TX fees:</label> {{ sum_fees | loki(fixed=True, decimals=4) }}</span>
|
<span title="Earned TX fees: {{sum_fees | oxen(fixed=True)}}"><label>Block TX fees:</label> {{ sum_fees | oxen(fixed=True, decimals=4) }}</span>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
|
|
||||||
{%if sum_burned > 0%}
|
{%if sum_burned > 0%}
|
||||||
<span title="{{sum_burned | loki(fixed=True)}} burned in the transactions included in block">
|
<span title="{{sum_burned | oxen(fixed=True)}} burned in the transactions included in block">
|
||||||
<label>Burned fees:</label>
|
<label>Burned fees:</label>
|
||||||
{{sum_burned | loki(decimals=4)}} <span class="icon">🔥</span>
|
{{sum_burned | oxen(decimals=4)}} <span class="icon">🔥</span>
|
||||||
</span>
|
</span>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ Note that this value does not include earned transaction fees ({{sum_fees | loki
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/tx/{{miner_tx.tx_hash}}">{{miner_tx.tx_hash}}</a></td>
|
<td><a href="/tx/{{miner_tx.tx_hash}}">{{miner_tx.tx_hash}}</a></td>
|
||||||
<td>{{miner_tx.info.vout | sum(attribute='amount') | loki}}</td>
|
<td>{{miner_tx.info.vout | sum(attribute='amount') | oxen}}</td>
|
||||||
<td>{{miner_tx.size}}</td>
|
<td>{{miner_tx.size}}</td>
|
||||||
<td>{{miner_tx.info.version}}</td>
|
<td>{{miner_tx.info.version}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
<td><a href="/tx/{{tx.id_hash}}">{{tx.id_hash}}</a></td>
|
<td><a href="/tx/{{tx.id_hash}}">{{tx.id_hash}}</a></td>
|
||||||
<td>
|
<td>
|
||||||
{%if 'rct_signatures' in tx.info%}
|
{%if 'rct_signatures' in tx.info%}
|
||||||
{{fee.display(tx)}} / {{(tx.info.rct_signatures.txnFee * 1000 / tx.blob_size) | loki(tag=false, decimals=4)}}
|
{{fee.display(tx)}} / {{(tx.info.rct_signatures.txnFee * 1000 / tx.blob_size) | oxen(tag=false, decimals=4)}}
|
||||||
{%else%}
|
{%else%}
|
||||||
N/A
|
N/A
|
||||||
{%endif%}
|
{%endif%}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
{%include 'include/sn_kcf.html'%}
|
{%include 'include/sn_kcf.html'%}
|
||||||
<td>{{sn.staking_requirement | loki(tag=false, fixed=true)}}</td>
|
<td>{{sn.staking_requirement | oxen(tag=false, fixed=true)}}</td>
|
||||||
<td>{{sn.last_reward_block_height}}</td>
|
<td>{{sn.last_reward_block_height}}</td>
|
||||||
<td>{{sn.last_uptime_proof | from_timestamp | ago if sn.last_uptime_proof > 0 else "Not Received"}}</td>
|
<td>{{sn.last_uptime_proof | from_timestamp | ago if sn.last_uptime_proof > 0 else "Not Received"}}</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -16,15 +16,15 @@
|
||||||
{%for sn in (awaiting_sns | sort(attribute='portions_for_operator,contribution_open,contribution_required,service_node_pubkey'))[:limit_awaiting]%}
|
{%for sn in (awaiting_sns | sort(attribute='portions_for_operator,contribution_open,contribution_required,service_node_pubkey'))[:limit_awaiting]%}
|
||||||
<tr>
|
<tr>
|
||||||
{%include 'include/sn_kcf.html'%}
|
{%include 'include/sn_kcf.html'%}
|
||||||
<td>{{sn.total_contributed | loki(tag=false, fixed=true)}}</td>
|
<td>{{sn.total_contributed | oxen(tag=false, fixed=true)}}</td>
|
||||||
{%if sn.total_reserved >= sn.staking_requirement%}
|
{%if sn.total_reserved >= sn.staking_requirement%}
|
||||||
<td title="All remaining contribution room is reserved for specific contributors">
|
<td title="All remaining contribution room is reserved for specific contributors">
|
||||||
⛔ {{sn.contribution_open | loki(tag=false, fixed=true)}}
|
⛔ {{sn.contribution_open | oxen(tag=false, fixed=true)}}
|
||||||
</td>
|
</td>
|
||||||
{%else%}
|
{%else%}
|
||||||
<td>{{sn.contribution_open | loki(tag=false, fixed=true)}}</td>
|
<td>{{sn.contribution_open | oxen(tag=false, fixed=true)}}</td>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
<td>{{ (0 if sn.num_contributions >= 4 else (sn.contribution_open / (4 - sn.num_contributions)) | round(method='ceil')) | loki(tag=false, fixed=true) }}</td>
|
<td>{{ (0 if sn.num_contributions >= 4 else (sn.contribution_open / (4 - sn.num_contributions)) | round(method='ceil')) | oxen(tag=false, fixed=true) }}</td>
|
||||||
<td>
|
<td>
|
||||||
{%if sn.requested_unlock_height%}
|
{%if sn.requested_unlock_height%}
|
||||||
<span title="Service Node unlock in progress (unlocks at block {{sn.requested_unlock_height}})">🔓</span>
|
<span title="Service Node unlock in progress (unlocks at block {{sn.requested_unlock_height}})">🔓</span>
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
|
|
||||||
<td><a href="/sn/{{sn.service_node_pubkey}}">{{sn.service_node_pubkey}}</a></td>
|
<td><a href="/sn/{{sn.service_node_pubkey}}">{{sn.service_node_pubkey}}</a></td>
|
||||||
<td title="
|
<td title="
|
||||||
{%-for c in sn.contributors%}{%for lc in c.locked_contributions%}{{c.address | truncate(15)}} ({{lc.amount | loki(decimals=0)}} = {{(lc.amount / sn.staking_requirement * 100) | round(1) | chop0}}%)
|
{%-for c in sn.contributors%}{%for lc in c.locked_contributions%}{{c.address | truncate(15)}} ({{lc.amount | oxen(decimals=0)}} = {{(lc.amount / sn.staking_requirement * 100) | round(1) | chop0}}%)
|
||||||
{%endfor%}{%endfor%}"><span class="icon">{{sn.contributors | length}}/4</span></td>
|
{%endfor%}{%endfor%}"><span class="icon">{{sn.contributors | length}}/4</span></td>
|
||||||
<td>{%if sn.portions_for_operator != portions_base%}{{ (sn.portions_for_operator / portions_base * 100) | round(3) | chop0 }}{%endif%}</td>
|
<td>{%if sn.portions_for_operator != portions_base%}{{ (sn.portions_for_operator / portions_base * 100) | round(3) | chop0 }}{%endif%}</td>
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
(tx.info.rct_signatures.txnFee if 'rct_signatures' in tx.info and 'txnFee' in tx.info.rct_signatures else 0)
|
(tx.info.rct_signatures.txnFee if 'rct_signatures' in tx.info and 'txnFee' in tx.info.rct_signatures else 0)
|
||||||
-%}
|
-%}
|
||||||
{% if fee > 0 or show_zero -%}
|
{% if fee > 0 or show_zero -%}
|
||||||
{{ fee | loki(tag=False, fixed=True, decimals=4) }}
|
{{ fee | oxen(tag=False, fixed=True, decimals=4) }}
|
||||||
{%- if 'burn_amount' in tx.extra %}
|
{%- if 'burn_amount' in tx.extra %}
|
||||||
<span class="icon" title="= {{(fee - tx.extra.burn_amount) | loki}} TX fee
|
<span class="icon" title="= {{(fee - tx.extra.burn_amount) | oxen}} TX fee
|
||||||
+ {{tx.extra.burn_amount | loki}} burned">🔥</span>
|
+ {{tx.extra.burn_amount | oxen}} burned">🔥</span>
|
||||||
{%-endif-%}
|
{%-endif-%}
|
||||||
{%endif-%}
|
{%endif-%}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
|
@ -74,14 +74,14 @@
|
||||||
<span><label>Network difficulty:</label> {{info.difficulty}}</span>
|
<span><label>Network difficulty:</label> {{info.difficulty}}</span>
|
||||||
<span><label>Hash rate:</label> ~{{(info.difficulty / info.target) | si }}H/s</span>
|
<span><label>Hash rate:</label> ~{{(info.difficulty / info.target) | si }}H/s</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span><label>Staking requirement:</label> {{stake.staking_requirement | loki}}</span>
|
<span><label>Staking requirement:</label> {{stake.staking_requirement | oxen}}</span>
|
||||||
<span title="{{(2500 * fees.fee_per_byte + 2*fees.fee_per_output) | loki}} for a typical simple transaction (~2.5kB, 2 outputs)">
|
<span title="{{(2500 * fees.fee_per_byte + 2*fees.fee_per_output) | oxen}} for a typical simple transaction (~2.5kB, 2 outputs)">
|
||||||
<label>Base fee:</label>
|
<label>Base fee:</label>
|
||||||
{{fees.fee_per_output | loki}}/output + {{(fees.fee_per_byte * 1000) | loki}}/kB
|
{{fees.fee_per_output | oxen}}/output + {{(fees.fee_per_byte * 1000) | oxen}}/kB
|
||||||
</span>
|
</span>
|
||||||
<span title="{{(2500 * fees.blink_fee_per_byte + 2*fees.blink_fee_per_output) | loki}} for a typical simple blink transaction (~2.5kB, 2 outputs)">
|
<span title="{{(2500 * fees.blink_fee_per_byte + 2*fees.blink_fee_per_output) | oxen}} for a typical simple blink transaction (~2.5kB, 2 outputs)">
|
||||||
<label>Blink fee:</label>
|
<label>Blink fee:</label>
|
||||||
{{fees.blink_fee_per_output | loki}}/output + {{(fees.blink_fee_per_byte * 1000) | loki}}/kB
|
{{fees.blink_fee_per_output | oxen}}/output + {{(fees.blink_fee_per_byte * 1000) | oxen}}/kB
|
||||||
</span>
|
</span>
|
||||||
<span title="{{(info.block_size_limit / 2) | si}}B soft limit, {{info.block_size_limit | si}}B hard limit. Blocks may include TXes up to the soft limit without penalty and incur increasing reward penalties as they approach the hard limit.">
|
<span title="{{(info.block_size_limit / 2) | si}}B soft limit, {{info.block_size_limit | si}}B hard limit. Blocks may include TXes up to the soft limit without penalty and incur increasing reward penalties as they approach the hard limit.">
|
||||||
<label>Block size limit:</label>
|
<label>Block size limit:</label>
|
||||||
|
@ -96,10 +96,10 @@
|
||||||
{% if not emission or emission.status == 'BUSY' %}
|
{% if not emission or emission.status == 'BUSY' %}
|
||||||
(still calculating...)</span>
|
(still calculating...)</span>
|
||||||
{% elif emission.status == 'OK' %}
|
{% elif emission.status == 'OK' %}
|
||||||
{{(emission.emission_amount - emission.burn_amount) | loki}}</span>
|
{{(emission.emission_amount - emission.burn_amount) | oxen}}</span>
|
||||||
<span><label>(Coinbase:</label> {{emission.emission_amount | loki}}</span>
|
<span><label>(Coinbase:</label> {{emission.emission_amount | oxen}}</span>
|
||||||
<span><label>Fees:</label> {{emission.fee_amount | loki}}</span>
|
<span><label>Fees:</label> {{emission.fee_amount | oxen}}</span>
|
||||||
<span><label>Burned:</label> {{emission.burn_amount | loki}}<label>).</label></span>
|
<span><label>Burned:</label> {{emission.burn_amount | oxen}}<label>).</label></span>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
<p style="padding: 0px; margin-top: 2px; font-size: 0.9em">
|
<p style="padding: 0px; margin-top: 2px; font-size: 0.9em">
|
||||||
* — Circulating supply may exclude any currently, publicised locked tokens, otherwise it is equal to the Coinbase minus burned coins.
|
* — Circulating supply may exclude any currently, publicised locked tokens, otherwise it is equal to the Coinbase minus burned coins.
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
<span>🚫 Deregistration</span>
|
<span>🚫 Deregistration</span>
|
||||||
<span>📋 IP Change Penalty</span>
|
<span>📋 IP Change Penalty</span>
|
||||||
<span>🔓 Stake Unlock</span>
|
<span>🔓 Stake Unlock</span>
|
||||||
<span>🎫 Loki Name System Purchase</span>
|
<span>🎫 Oxen Name System Purchase</span>
|
||||||
<span>💾 LNS Update</span>
|
<span>💾 LNS Update</span>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
@ -172,7 +172,7 @@
|
||||||
<td>{{symbol.display(b.txs[0])}}</td>
|
<td>{{symbol.display(b.txs[0])}}</td>
|
||||||
<td><a href="/tx/{{b.miner_tx_hash}}">{{b.miner_tx_hash}}</a></td>
|
<td><a href="/tx/{{b.miner_tx_hash}}">{{b.miner_tx_hash}}</a></td>
|
||||||
<td>{{fee.display(b.txs[0])}}</td>
|
<td>{{fee.display(b.txs[0])}}</td>
|
||||||
<td>{{b.reward | loki(tag=False, fixed=True, decimals=2)}}</td>
|
<td>{{b.reward | oxen(tag=False, fixed=True, decimals=2)}}</td>
|
||||||
<td>0/{{b.txs[0].info.vout | length}}</td>
|
<td>0/{{b.txs[0].info.vout | length}}</td>
|
||||||
<td>{{b.txs[0].size | si}}</td>
|
<td>{{b.txs[0].size | si}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<span>🚫 Deregistration</span>
|
<span>🚫 Deregistration</span>
|
||||||
<span>📋 IP Change Penalty</span>
|
<span>📋 IP Change Penalty</span>
|
||||||
<span>🔓 Stake Unlock</span>
|
<span>🔓 Stake Unlock</span>
|
||||||
<span>🎫 Loki Name System Purchase</span>
|
<span>🎫 Oxen Name System Purchase</span>
|
||||||
<span>💾 LNS Update</span>
|
<span>💾 LNS Update</span>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<a href="/block/{{sn.state_height}}">{{sn.state_height}}</a>
|
<a href="/block/{{sn.state_height}}">{{sn.state_height}}</a>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span><label>Staking requirement:</label> {{sn.staking_requirement | loki}}</span>
|
<span><label>Staking requirement:</label> {{sn.staking_requirement | oxen}}</span>
|
||||||
|
|
||||||
{%if not solo_node%}
|
{%if not solo_node%}
|
||||||
<span><label>Operator fee:</label> {{(sn.portions_for_operator / portions_base * 100) | round(3) | chop0}}%</span>
|
<span><label>Operator fee:</label> {{(sn.portions_for_operator / portions_base * 100) | round(3) | chop0}}%</span>
|
||||||
|
@ -50,12 +50,12 @@
|
||||||
<span><label>Total contributed:</label>
|
<span><label>Total contributed:</label>
|
||||||
{%if sn.total_contributed >= sn.staking_requirement%}100%
|
{%if sn.total_contributed >= sn.staking_requirement%}100%
|
||||||
{%else%}
|
{%else%}
|
||||||
{{sn.total_contributed | loki}} ({{(sn.total_contributed / sn.staking_requirement * 100) | round(2) | chop0}}%)
|
{{sn.total_contributed | oxen}} ({{(sn.total_contributed / sn.staking_requirement * 100) | round(2) | chop0}}%)
|
||||||
{%endif%}
|
{%endif%}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{%if sn.total_reserved != sn.total_contributed%}
|
{%if sn.total_reserved != sn.total_contributed%}
|
||||||
<span><label>Total reserved:</label> {{sn.total_reserved | loki}}</span>
|
<span><label>Total reserved:</label> {{sn.total_reserved | oxen}}</span>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
|
@ -132,10 +132,10 @@
|
||||||
{%endif%}
|
{%endif%}
|
||||||
</p>
|
</p>
|
||||||
{%else%}
|
{%else%}
|
||||||
<p class="sn-awaiting">Awaiting registration. This service node has <span class="loki required">{{(sn.staking_requirement - sn.total_contributed) | loki}}</span>
|
<p class="sn-awaiting">Awaiting registration. This service node has <span class="oxen required">{{(sn.staking_requirement - sn.total_contributed) | oxen}}</span>
|
||||||
remaining to be contributed.
|
remaining to be contributed.
|
||||||
{%if sn.num_open_spots > 0%}
|
{%if sn.num_open_spots > 0%}
|
||||||
The minimum required stake contribution is <span class="loki required">{{((sn.staking_requirement - sn.total_reserved) / sn.num_open_spots) | loki}}</span>.
|
The minimum required stake contribution is <span class="oxen required">{{((sn.staking_requirement - sn.total_reserved) / sn.num_open_spots) | oxen}}</span>.
|
||||||
{%endif%}
|
{%endif%}
|
||||||
</p>
|
</p>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
|
@ -166,12 +166,12 @@
|
||||||
{%for c in sn.contributors%}
|
{%for c in sn.contributors%}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{c.address}}</td>
|
<td>{{c.address}}</td>
|
||||||
<td>{{c.amount | loki}}
|
<td>{{c.amount | oxen}}
|
||||||
{%-if c.locked_contributions and c.locked_contributions|length > 1%}
|
{%-if c.locked_contributions and c.locked_contributions|length > 1%}
|
||||||
({{c.locked_contributions|length}} contributions)
|
({{c.locked_contributions|length}} contributions)
|
||||||
{%endif-%}
|
{%endif-%}
|
||||||
</td>
|
</td>
|
||||||
<td>{{c.reserved | loki}}</td>
|
<td>{{c.reserved | oxen}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
{%else%}
|
{%else%}
|
||||||
{% import 'include/tx_fee.html' as fee %}
|
{% import 'include/tx_fee.html' as fee %}
|
||||||
{{fee.display(tx)}}
|
{{fee.display(tx)}}
|
||||||
({{(tx.info.rct_signatures.txnFee * 1000 / tx.size) | loki(tag=false, decimals=6)}})
|
({{(tx.info.rct_signatures.txnFee * 1000 / tx.size) | oxen(tag=false, decimals=6)}})
|
||||||
{%endif%}
|
{%endif%}
|
||||||
</span>
|
</span>
|
||||||
<span title="{{tx.size}} bytes"><label>TX Size:</label> {{tx.size|si}}B</span>
|
<span title="{{tx.size}} bytes"><label>TX Size:</label> {{tx.size|si}}B</span>
|
||||||
|
@ -138,9 +138,9 @@ This tx does not includes a vote from this testing service node (only 7 votes ar
|
||||||
<p><label>Unlock signature:</label> {{unlock_signature}}</p> {# FIXME #}
|
<p><label>Unlock signature:</label> {{unlock_signature}}</p> {# FIXME #}
|
||||||
{% elif tx.info.type == 4 and 'lns' in tx.extra %}
|
{% elif tx.info.type == 4 and 'lns' in tx.extra %}
|
||||||
{% if 'buy' in tx.extra.lns %}
|
{% if 'buy' in tx.extra.lns %}
|
||||||
<h2>🎫 Loki Name Service Registration</h2>
|
<h2>🎫 Oxen Name Service Registration</h2>
|
||||||
{% elif 'update' in tx.extra.lns %}
|
{% elif 'update' in tx.extra.lns %}
|
||||||
<h2>💾 Loki Name Service Update</h2>
|
<h2>💾 Oxen Name Service Update</h2>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{#FIXME - show some metadata?#}
|
{#FIXME - show some metadata?#}
|
||||||
{% elif 'sn_registration' in tx.extra %}
|
{% elif 'sn_registration' in tx.extra %}
|
||||||
|
@ -187,7 +187,7 @@ This tx does not includes a vote from this testing service node (only 7 votes ar
|
||||||
{%if tx.info.vout%}
|
{%if tx.info.vout%}
|
||||||
<h2>Outputs</h2>
|
<h2>Outputs</h2>
|
||||||
<h4 class="Subtitle">{{tx.info.vout|length}} output(s) for total of
|
<h4 class="Subtitle">{{tx.info.vout|length}} output(s) for total of
|
||||||
{{tx.info.vout | sum(attribute='amount') | loki(zero='<span title="Regular LOKI tx amounts are private">???</span>') | safe}}</h4>
|
{{tx.info.vout | sum(attribute='amount') | oxen(zero='<span title="Regular LOKI tx amounts are private">???</span>') | safe}}</h4>
|
||||||
<div class="TitleDivider"></div>
|
<div class="TitleDivider"></div>
|
||||||
<div class="tx-outputs">
|
<div class="tx-outputs">
|
||||||
<table class="Table">
|
<table class="Table">
|
||||||
|
@ -203,7 +203,7 @@ This tx does not includes a vote from this testing service node (only 7 votes ar
|
||||||
{%for out in tx.info.vout%}
|
{%for out in tx.info.vout%}
|
||||||
<tr>
|
<tr>
|
||||||
<td><label>{{loop.index0}}:</label> {{out.target.key}}</td>
|
<td><label>{{loop.index0}}:</label> {{out.target.key}}</td>
|
||||||
<td>{{out.amount | loki(zero='?')}}</td>
|
<td>{{out.amount | oxen(zero='?')}}</td>
|
||||||
<td>{%if 'output_indices' in tx%}{{tx.output_indices[loop.index0]}}{# FIXME: of {{num_outputs}}#}{%endif%}</td>
|
<td>{%if 'output_indices' in tx%}{{tx.output_indices[loop.index0]}}{# FIXME: of {{num_outputs}}#}{%endif%}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
|
@ -256,7 +256,7 @@ This tx does not includes a vote from this testing service node (only 7 votes ar
|
||||||
<p style="margin: 0px">Prove to someone that you have sent them Loki in this transaction</p>
|
<p style="margin: 0px">Prove to someone that you have sent them Loki in this transaction</p>
|
||||||
<p style="margin: 0px">
|
<p style="margin: 0px">
|
||||||
TX private key can be obtained using <i>get_tx_key</i>
|
TX private key can be obtained using <i>get_tx_key</i>
|
||||||
command in <i>loki-wallet-cli</i> command line tool
|
command in <i>oxen-wallet-cli</i> command line tool
|
||||||
<br/>
|
<br/>
|
||||||
{%if enable_js%}
|
{%if enable_js%}
|
||||||
Note: Address/Subaddress and TX private key are NOT sent to the server, as the calculations are done on the client side
|
Note: Address/Subaddress and TX private key are NOT sent to the server, as the calculations are done on the client side
|
||||||
|
@ -522,7 +522,7 @@ This tx does not includes a vote from this testing service node (only 7 votes ar
|
||||||
|
|
||||||
<h2>Inputs</h2>
|
<h2>Inputs</h2>
|
||||||
<h4 class="Subtitle">{{tx.info.vin|length}} input(s) for total of
|
<h4 class="Subtitle">{{tx.info.vin|length}} input(s) for total of
|
||||||
{{tx.info.vin | sum(attribute='key.amount') | loki(zero='<span title="Regular LOKI tx amounts are private">???</span>') | safe}}</h4>
|
{{tx.info.vin | sum(attribute='key.amount') | oxen(zero='<span title="Regular LOKI tx amounts are private">???</span>') | safe}}</h4>
|
||||||
<div class="TitleDivider"></div>
|
<div class="TitleDivider"></div>
|
||||||
|
|
||||||
{#FIXME#}
|
{#FIXME#}
|
||||||
|
@ -556,7 +556,7 @@ This tx does not includes a vote from this testing service node (only 7 votes ar
|
||||||
{%endif%}
|
{%endif%}
|
||||||
{%endif%}
|
{%endif%}
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right">Amount: {{inp.key.amount | loki(zero='?')}}</td>
|
<td style="text-align: right">Amount: {{inp.key.amount | oxen(zero='?')}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{%if config.enable_mixins_details%}
|
{%if config.enable_mixins_details%}
|
||||||
<tr class="TableHeader">
|
<tr class="TableHeader">
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
from observer import app, config
|
from observer import app, config
|
||||||
|
|
||||||
config.lokid_rpc = 'ipc://lokid/testnet.sock'
|
config.oxend_rpc = 'ipc://oxend/testnet.sock'
|
||||||
|
|
Loading…
Reference in New Issue