whitespace, minor changes (16 files)
This commit is contained in:
parent
0bad23b77b
commit
0b79ef719b
16 changed files with 629 additions and 707 deletions
|
|
@ -20,58 +20,57 @@
|
|||
mmgen_node_tools.BlocksInfo: Display information about a block or range of blocks
|
||||
"""
|
||||
|
||||
import re,json
|
||||
import re, json
|
||||
from collections import namedtuple
|
||||
from time import strftime,gmtime
|
||||
from time import strftime, gmtime
|
||||
from decimal import Decimal
|
||||
|
||||
from mmgen.util import msg,Msg,Msg_r,die,suf,secs_to_ms,secs_to_dhms,is_int
|
||||
from mmgen.util import msg, Msg, Msg_r, die, suf, secs_to_ms, secs_to_dhms, is_int
|
||||
from mmgen.rpc.util import json_encoder
|
||||
|
||||
class RangeParser:
|
||||
|
||||
debug = False
|
||||
|
||||
def __init__(self,caller,arg):
|
||||
def __init__(self, caller, arg):
|
||||
self.caller = caller
|
||||
self.arg = self.orig_arg = arg
|
||||
|
||||
def parse(self,target):
|
||||
ret = getattr(self,'parse_'+target)()
|
||||
def parse(self, target):
|
||||
ret = getattr(self, 'parse_'+target)()
|
||||
if self.debug:
|
||||
msg(f'arg after parse({target}): {self.arg}')
|
||||
return ret
|
||||
|
||||
def finalize(self):
|
||||
if self.arg:
|
||||
die(1,f'{self.orig_arg!r}: invalid range specifier')
|
||||
die(1, f'{self.orig_arg!r}: invalid range specifier')
|
||||
|
||||
def parse_from_tip(self):
|
||||
m = re.match(r'-([0-9]+)(.*)',self.arg)
|
||||
m = re.match(r'-([0-9]+)(.*)', self.arg)
|
||||
if m:
|
||||
res,self.arg = (m[1],m[2])
|
||||
res, self.arg = (m[1], m[2])
|
||||
return self.caller.check_nblocks(int(res))
|
||||
|
||||
def parse_abs_range(self):
|
||||
m = re.match(r'([^+-]+)(-([^+-]+)){0,1}(.*)',self.arg)
|
||||
m = re.match(r'([^+-]+)(-([^+-]+)){0,1}(.*)', self.arg)
|
||||
if m:
|
||||
if self.debug:
|
||||
msg(f'abs_range parse: first={m[1]}, last={m[3]}')
|
||||
self.arg = m[4]
|
||||
return (
|
||||
self.caller.conv_blkspec(m[1]),
|
||||
self.caller.conv_blkspec(m[3]) if m[3] else None
|
||||
)
|
||||
return (None,None)
|
||||
self.caller.conv_blkspec(m[3]) if m[3] else None)
|
||||
return (None, None)
|
||||
|
||||
def parse_add(self):
|
||||
m = re.match(r'\+([0-9*]+)(.*)',self.arg)
|
||||
m = re.match(r'\+([0-9*]+)(.*)', self.arg)
|
||||
if m:
|
||||
res,self.arg = (m[1],m[2])
|
||||
res, self.arg = (m[1], m[2])
|
||||
if res.strip('*') != res:
|
||||
die(1,f"'+{res}': malformed nBlocks specifier")
|
||||
die(1, f"'+{res}': malformed nBlocks specifier")
|
||||
if len(res) > 30:
|
||||
die(1,f"'+{res}': overly long nBlocks specifier")
|
||||
die(1, f"'+{res}': overly long nBlocks specifier")
|
||||
return self.caller.check_nblocks(eval(res)) # res is only digits plus '*', so eval safe
|
||||
|
||||
class BlocksInfo:
|
||||
|
|
@ -81,33 +80,33 @@ class BlocksInfo:
|
|||
total_solve_time = 0
|
||||
header_printed = False
|
||||
|
||||
bf = namedtuple('block_info_fields',['fmt_func','src','fs','hdr1','hdr2','key1','key2'])
|
||||
bf = namedtuple('block_info_fields', ['fmt_func', 'src', 'fs', 'hdr1', 'hdr2', 'key1', 'key2'])
|
||||
# bh=getblockheader, bs=getblockstats, lo=local
|
||||
fields = {
|
||||
'block': bf( None, 'bh', '{:<6}', '', 'Block', 'height', None ),
|
||||
'hash': bf( None, 'bh', '{:<64}', '', 'Hash', 'hash', None ),
|
||||
'date': bf( 'da', 'bh', '{:<19}', '', 'Date', 'time', None ),
|
||||
'interval': bf( 'td', 'lo', '{:>8}', 'Solve', 'Time ', 'interval', None ),
|
||||
'subsidy': bf( 'su', 'bs', '{:<5}', 'Sub-', 'sidy', 'subsidy', None ),
|
||||
'totalfee': bf( 'tf', 'bs', '{:>10}', '', 'Total Fee', 'totalfee', None ),
|
||||
'size': bf( None, 'bs', '{:>7}', '', 'Size', 'total_size', None ),
|
||||
'weight': bf( None, 'bs', '{:>7}', '', 'Weight', 'total_weight', None ),
|
||||
'fee90': bf( 'fe', 'bs', '{:>3}', '90%', 'Fee', 'feerate_percentiles', 4 ),
|
||||
'fee75': bf( 'fe', 'bs', '{:>3}', '75%', 'Fee', 'feerate_percentiles', 3 ),
|
||||
'fee50': bf( 'fe', 'bs', '{:>3}', '50%', 'Fee', 'feerate_percentiles', 2 ),
|
||||
'fee25': bf( 'fe', 'bs', '{:>3}', '25%', 'Fee', 'feerate_percentiles', 1 ),
|
||||
'fee10': bf( 'fe', 'bs', '{:>3}', '10%', 'Fee', 'feerate_percentiles', 0 ),
|
||||
'fee_max': bf( 'fe', 'bs', '{:>5}', 'Max', 'Fee', 'maxfeerate', None ),
|
||||
'fee_avg': bf( 'fe', 'bs', '{:>3}', 'Avg', 'Fee', 'avgfeerate', None ),
|
||||
'fee_min': bf( 'fe', 'bs', '{:>3}', 'Min', 'Fee', 'minfeerate', None ),
|
||||
'nTx': bf( None, 'bh', '{:>5}', '', ' nTx ', 'nTx', None ),
|
||||
'inputs': bf( None, 'bs', '{:>5}', 'In- ', 'puts', 'ins', None ),
|
||||
'outputs': bf( None, 'bs', '{:>5}', 'Out-', 'puts', 'outs', None ),
|
||||
'utxo_inc': bf( None, 'bs', '{:>6}', ' UTXO', ' Incr', 'utxo_increase', None ),
|
||||
'version': bf( None, 'bh', '{:<8}', '', 'Version', 'versionHex', None ),
|
||||
'difficulty': bf( 'di', 'bh', '{:<8}', 'Diffi-','culty', 'difficulty', None ),
|
||||
'miner': bf( None, 'lo', '{:<5}', '', 'Miner', 'miner', None ),
|
||||
}
|
||||
'block': bf(None, 'bh', '{:<6}', '', 'Block', 'height', None),
|
||||
'hash': bf(None, 'bh', '{:<64}', '', 'Hash', 'hash', None),
|
||||
'date': bf('da', 'bh', '{:<19}', '', 'Date', 'time', None),
|
||||
'interval': bf('td', 'lo', '{:>8}', 'Solve', 'Time ', 'interval', None),
|
||||
'subsidy': bf('su', 'bs', '{:<5}', 'Sub-', 'sidy', 'subsidy', None),
|
||||
'totalfee': bf('tf', 'bs', '{:>10}', '', 'Total Fee', 'totalfee', None),
|
||||
'size': bf(None, 'bs', '{:>7}', '', 'Size', 'total_size', None),
|
||||
'weight': bf(None, 'bs', '{:>7}', '', 'Weight', 'total_weight', None),
|
||||
'fee90': bf('fe', 'bs', '{:>3}', '90%', 'Fee', 'feerate_percentiles', 4),
|
||||
'fee75': bf('fe', 'bs', '{:>3}', '75%', 'Fee', 'feerate_percentiles', 3),
|
||||
'fee50': bf('fe', 'bs', '{:>3}', '50%', 'Fee', 'feerate_percentiles', 2),
|
||||
'fee25': bf('fe', 'bs', '{:>3}', '25%', 'Fee', 'feerate_percentiles', 1),
|
||||
'fee10': bf('fe', 'bs', '{:>3}', '10%', 'Fee', 'feerate_percentiles', 0),
|
||||
'fee_max': bf('fe', 'bs', '{:>5}', 'Max', 'Fee', 'maxfeerate', None),
|
||||
'fee_avg': bf('fe', 'bs', '{:>3}', 'Avg', 'Fee', 'avgfeerate', None),
|
||||
'fee_min': bf('fe', 'bs', '{:>3}', 'Min', 'Fee', 'minfeerate', None),
|
||||
'nTx': bf(None, 'bh', '{:>5}', '', ' nTx ', 'nTx', None),
|
||||
'inputs': bf(None, 'bs', '{:>5}', 'In- ', 'puts', 'ins', None),
|
||||
'outputs': bf(None, 'bs', '{:>5}', 'Out-', 'puts', 'outs', None),
|
||||
'utxo_inc': bf(None, 'bs', '{:>6}', ' UTXO', ' Incr', 'utxo_increase', None),
|
||||
'version': bf(None, 'bh', '{:<8}', '', 'Version', 'versionHex', None),
|
||||
'difficulty': bf('di', 'bh', '{:<8}', 'Diffi-','culty', 'difficulty', None),
|
||||
'miner': bf(None, 'lo', '{:<5}', '', 'Miner', 'miner', None)}
|
||||
|
||||
dfl_fields = (
|
||||
'block',
|
||||
'date',
|
||||
|
|
@ -121,8 +120,8 @@ class BlocksInfo:
|
|||
'fee10',
|
||||
'fee_avg',
|
||||
'fee_min',
|
||||
'version',
|
||||
)
|
||||
'version')
|
||||
|
||||
fixed_fields = (
|
||||
'block', # until ≈ 09/01/2028 (block 1000000)
|
||||
'hash',
|
||||
|
|
@ -131,36 +130,34 @@ class BlocksInfo:
|
|||
'weight', # until ≈ 2.5x block size increase
|
||||
'version',
|
||||
'subsidy', # until ≈ 01/04/2028 (increases by 1 digit per halving until 9th halving [max 10 digits])
|
||||
'difficulty', # until 1.00e+100 (i.e. never)
|
||||
)
|
||||
'difficulty') # until 1.00e+100 (i.e. never)
|
||||
|
||||
# column width adjustment data:
|
||||
fs_lsqueeze = ('totalfee','inputs','outputs','nTx')
|
||||
fs_lsqueeze = ('totalfee', 'inputs', 'outputs', 'nTx')
|
||||
fs_rsqueeze = ()
|
||||
fs_groups = (
|
||||
('fee10','fee25','fee50','fee75','fee90','fee_avg','fee_min','fee_max'),
|
||||
)
|
||||
('fee10', 'fee25', 'fee50', 'fee75', 'fee90', 'fee_avg', 'fee_min', 'fee_max'))
|
||||
fs_lsqueeze2 = ('interval',)
|
||||
|
||||
all_stats = ['col_avg','range','avg','mini_avg','total','diff']
|
||||
dfl_stats = ['range','mini_avg','diff']
|
||||
all_stats = ['col_avg', 'range', 'avg', 'mini_avg', 'total', 'diff']
|
||||
dfl_stats = ['range', 'mini_avg', 'diff']
|
||||
noindent_stats = ['col_avg']
|
||||
|
||||
avg_stats_skip = {'block', 'hash', 'date', 'version','miner'}
|
||||
avg_stats_skip = {'block', 'hash', 'date', 'version', 'miner'}
|
||||
|
||||
range_data = namedtuple('parsed_range_data',['first','last','from_tip','nblocks','step'])
|
||||
range_data = namedtuple('parsed_range_data', ['first', 'last', 'from_tip', 'nblocks', 'step'])
|
||||
|
||||
t_fmt = lambda self,t: f'{t/86400:.2f} days' if t > 172800 else f'{t/3600:.2f} hrs'
|
||||
t_fmt = lambda self, t: f'{t/86400:.2f} days' if t > 172800 else f'{t/3600:.2f} hrs'
|
||||
|
||||
@classmethod
|
||||
def parse_cslist(cls,uarg,full_set,dfl_set,desc):
|
||||
def parse_cslist(cls, uarg, full_set, dfl_set, desc):
|
||||
|
||||
def make_list(m,func):
|
||||
def make_list(m, func):
|
||||
groups_lc = [set(e.lower() for e in gi.split(',')) for gi in m.groups()]
|
||||
for group in groups_lc:
|
||||
for e in group:
|
||||
if e not in full_set_lc:
|
||||
die(1,f'{e!r}: unrecognized {desc}')
|
||||
die(1, f'{e!r}: unrecognized {desc}')
|
||||
# display elements in order:
|
||||
return [e for e in full_set if e.lower() in func(groups_lc)]
|
||||
|
||||
|
|
@ -168,33 +165,31 @@ class BlocksInfo:
|
|||
dfl_set_lc = set(e.lower() for e in dfl_set)
|
||||
cspat = r'(\w+(?:,\w+)*)'
|
||||
|
||||
for pat,func in (
|
||||
( rf'{cspat}$', lambda g: g[0] ),
|
||||
( rf'\+{cspat}$', lambda g: dfl_set_lc | g[0] ),
|
||||
( rf'\-{cspat}$', lambda g: dfl_set_lc - g[0] ),
|
||||
( rf'\+{cspat}\-{cspat}$', lambda g: ( dfl_set_lc | g[0] ) - g[1] ),
|
||||
( rf'\-{cspat}\+{cspat}$', lambda g: ( dfl_set_lc - g[0] ) | g[1] ),
|
||||
( rf'all\-{cspat}$', lambda g: full_set_lc - g[0] )
|
||||
):
|
||||
m = re.match(pat,uarg,re.ASCII|re.IGNORECASE)
|
||||
for pat, func in (
|
||||
(rf'{cspat}$', lambda g: g[0]),
|
||||
(rf'\+{cspat}$', lambda g: dfl_set_lc | g[0]),
|
||||
(rf'\-{cspat}$', lambda g: dfl_set_lc - g[0]),
|
||||
(rf'\+{cspat}\-{cspat}$', lambda g: (dfl_set_lc | g[0]) - g[1]),
|
||||
(rf'\-{cspat}\+{cspat}$', lambda g: (dfl_set_lc - g[0]) | g[1]),
|
||||
(rf'all\-{cspat}$', lambda g: full_set_lc - g[0])):
|
||||
m = re.match(pat, uarg, re.ASCII|re.IGNORECASE)
|
||||
if m:
|
||||
return make_list(m,func)
|
||||
return make_list(m, func)
|
||||
else:
|
||||
die(1,f'{uarg}: invalid parameter')
|
||||
die(1, f'{uarg}: invalid parameter')
|
||||
|
||||
def __init__(self,cfg,cmd_args,rpc):
|
||||
def __init__(self, cfg, cmd_args, rpc):
|
||||
|
||||
def parse_cs_uarg(uarg,full_set,dfl_set,desc):
|
||||
def parse_cs_uarg(uarg, full_set, dfl_set, desc):
|
||||
return (
|
||||
full_set if uarg == 'all' else [] if uarg == 'none' else
|
||||
self.parse_cslist(uarg,full_set,dfl_set,desc)
|
||||
)
|
||||
self.parse_cslist(uarg, full_set, dfl_set, desc))
|
||||
|
||||
def get_fields():
|
||||
return parse_cs_uarg(self.cfg.fields,list(self.fields),self.dfl_fields,'field')
|
||||
return parse_cs_uarg(self.cfg.fields, list(self.fields), self.dfl_fields, 'field')
|
||||
|
||||
def get_stats():
|
||||
return parse_cs_uarg(self.cfg.stats.lower(),self.all_stats,self.dfl_stats,'stat')
|
||||
return parse_cs_uarg(self.cfg.stats.lower(), self.all_stats, self.dfl_stats, 'stat')
|
||||
|
||||
def parse_cmd_args(): # => (block_list, first, last, step)
|
||||
match cmd_args:
|
||||
|
|
@ -217,7 +212,7 @@ class BlocksInfo:
|
|||
from_satoshi = self.rpc.proto.coin_amt.satoshi
|
||||
to_satoshi = 1 / from_satoshi
|
||||
|
||||
self.block_list,self.first,self.last,self.step = parse_cmd_args()
|
||||
self.block_list, self.first, self.last, self.step = parse_cmd_args()
|
||||
|
||||
have_segwit = self.rpc.info('segwit_is_active')
|
||||
|
||||
|
|
@ -228,35 +223,33 @@ class BlocksInfo:
|
|||
self.stats_deps = {
|
||||
'avg': set(self.fields) - self.avg_stats_skip,
|
||||
'col_avg': set(self.fields) - self.avg_stats_skip,
|
||||
'mini_avg': {'interval','size'} | ({'weight'} if have_segwit else set()),
|
||||
'total': {'interval','subsidy','totalfee','nTx','inputs','outputs','utxo_inc'},
|
||||
'mini_avg': {'interval', 'size'} | ({'weight'} if have_segwit else set()),
|
||||
'total': {'interval', 'subsidy', 'totalfee', 'nTx', 'inputs', 'outputs', 'utxo_inc'},
|
||||
'range': {},
|
||||
'diff': {},
|
||||
}
|
||||
'diff': {}}
|
||||
|
||||
self.fmt_funcs = {
|
||||
'da': lambda arg: strftime('%Y-%m-%d %X',gmtime(arg)),
|
||||
'da': lambda arg: strftime('%Y-%m-%d %X', gmtime(arg)),
|
||||
'td': lambda arg: (
|
||||
'-{:02}:{:02}'.format(abs(arg)//60,abs(arg)%60) if arg < 0 else
|
||||
' {:02}:{:02}'.format(arg//60,arg%60) ),
|
||||
'-{:02}:{:02}'.format(abs(arg)//60, abs(arg)%60) if arg < 0 else
|
||||
' {:02}:{:02}'.format(arg//60, arg%60)),
|
||||
'tf': lambda arg: '{:.8f}'.format(arg * from_satoshi),
|
||||
'su': lambda arg: str(arg * from_satoshi).rstrip('0').rstrip('.'),
|
||||
'fe': lambda arg: str(arg),
|
||||
'di': lambda arg: '{:.2e}'.format(Decimal(arg)),
|
||||
}
|
||||
'di': lambda arg: '{:.2e}'.format(Decimal(arg))}
|
||||
|
||||
if self.cfg.coin == 'BCH':
|
||||
self.fmt_funcs.update({
|
||||
'su': lambda arg: str(arg).rstrip('0').rstrip('.'),
|
||||
'fe': lambda arg: str(int(Decimal(arg) * to_satoshi)),
|
||||
'tf': lambda arg: '{:.8f}'.format(Decimal(arg)),
|
||||
})
|
||||
'tf': lambda arg: '{:.8f}'.format(Decimal(arg))})
|
||||
|
||||
self.fnames = tuple(
|
||||
[f for f in self.fields if self.fields[f].src == 'bh' or f == 'interval'] if self.cfg.header_info else
|
||||
get_fields() if self.cfg.fields else
|
||||
self.dfl_fields
|
||||
)
|
||||
[f for f in self.fields if self.fields[f].src == 'bh' or f == 'interval']
|
||||
if self.cfg.header_info
|
||||
else get_fields() if self.cfg.fields
|
||||
else self.dfl_fields)
|
||||
|
||||
if self.cfg.miner_info and 'miner' not in self.fnames:
|
||||
self.fnames += ('miner',)
|
||||
|
||||
|
|
@ -266,15 +259,15 @@ class BlocksInfo:
|
|||
if 'diff' in self.stats and not self.cfg.stats and self.last != self.tip:
|
||||
self.stats.remove('diff')
|
||||
|
||||
if {'avg','col_avg'} <= set(self.stats) and self.cfg.stats_only:
|
||||
if {'avg', 'col_avg'} <= set(self.stats) and self.cfg.stats_only:
|
||||
self.stats.remove('col_avg')
|
||||
|
||||
if {'avg','mini_avg'} <= set(self.stats):
|
||||
if {'avg', 'mini_avg'} <= set(self.stats):
|
||||
self.stats.remove('mini_avg')
|
||||
|
||||
if self.cfg.full_stats:
|
||||
add_fnames = {fname for sname in self.stats for fname in self.stats_deps[sname]}
|
||||
self.fnames = tuple(f for f in self.fields if f in {'block'} | set(self.fnames) | add_fnames )
|
||||
self.fnames = tuple(f for f in self.fields if f in {'block'} | set(self.fnames) | add_fnames)
|
||||
else:
|
||||
if 'col_avg' in self.stats and not self.fnames:
|
||||
self.stats.remove('col_avg')
|
||||
|
|
@ -287,8 +280,7 @@ class BlocksInfo:
|
|||
self.bs_keys = set(
|
||||
[v.key1 for v in self.fvals if v.src == 'bs'] +
|
||||
['total_size'] +
|
||||
(['total_weight'] if have_segwit else [])
|
||||
)
|
||||
(['total_weight'] if have_segwit else []))
|
||||
|
||||
if 'miner' in self.fnames:
|
||||
# capturing parens must contain only ASCII chars!
|
||||
|
|
@ -302,17 +294,16 @@ class BlocksInfo:
|
|||
rb'([\x20-\x7e]{9,})',
|
||||
rb'[/^]([a-zA-Z0-9&. #/-]{5,})',
|
||||
rb'[/^]([_a-zA-Z0-9&. #/-]+)/',
|
||||
rb'^\x03...\W{0,5}([\\_a-zA-Z0-9&. #/-]+)[/\\]',
|
||||
)]
|
||||
rb'^\x03...\W{0,5}([\\_a-zA-Z0-9&. #/-]+)[/\\]')]
|
||||
|
||||
self.block_data = namedtuple('block_data',self.fnames)
|
||||
self.deps = { v.src for v in self.fvals }
|
||||
self.block_data = namedtuple('block_data', self.fnames)
|
||||
self.deps = {v.src for v in self.fvals}
|
||||
|
||||
def gen_fs(self,fnames,fill=[],fill_char='-',add_name=False):
|
||||
def gen_fs(self, fnames, fill=[], fill_char='-', add_name=False):
|
||||
for i in range(len(fnames)):
|
||||
name = fnames[i]
|
||||
ls = (' ','')[name in self.fs_lsqueeze + self.fs_lsqueeze2]
|
||||
rs = (' ','')[name in self.fs_rsqueeze]
|
||||
ls = (' ', '')[name in self.fs_lsqueeze + self.fs_lsqueeze2]
|
||||
rs = (' ', '')[name in self.fs_rsqueeze]
|
||||
if i < len(fnames) - 1 and fnames[i+1] in self.fs_lsqueeze2:
|
||||
rs = ''
|
||||
if i:
|
||||
|
|
@ -321,7 +312,7 @@ class BlocksInfo:
|
|||
ls = ''
|
||||
break
|
||||
repl = (name if add_name else '') + ':' + (fill_char if name in fill else '')
|
||||
yield (ls + self.fields[name].fs.replace(':',repl) + rs)
|
||||
yield (ls + self.fields[name].fs.replace(':', repl) + rs)
|
||||
|
||||
def conv_blkspec(self, arg):
|
||||
match arg:
|
||||
|
|
@ -347,22 +338,22 @@ class BlocksInfo:
|
|||
case _:
|
||||
return arg
|
||||
|
||||
def parse_rangespec(self,arg):
|
||||
def parse_rangespec(self, arg):
|
||||
|
||||
p = RangeParser(self,arg)
|
||||
p = RangeParser(self, arg)
|
||||
|
||||
from_tip = p.parse('from_tip')
|
||||
first,last = (self.tip-from_tip,None) if from_tip else p.parse('abs_range')
|
||||
add1 = p.parse('add')
|
||||
add2 = p.parse('add')
|
||||
from_tip = p.parse('from_tip')
|
||||
first, last = (self.tip-from_tip, None) if from_tip else p.parse('abs_range')
|
||||
add1 = p.parse('add')
|
||||
add2 = p.parse('add')
|
||||
p.finalize()
|
||||
|
||||
if add2 and last is not None:
|
||||
die(1,f'{arg!r}: invalid range specifier')
|
||||
die(1, f'{arg!r}: invalid range specifier')
|
||||
|
||||
nblocks,step = (add1,add2) if last is None else (None,add1)
|
||||
nblocks, step = (add1, add2) if last is None else (None, add1)
|
||||
|
||||
if p.debug: msg(repr(self.range_data(first,last,from_tip,nblocks,step)))
|
||||
if p.debug: msg(repr(self.range_data(first, last, from_tip, nblocks, step)))
|
||||
|
||||
if nblocks:
|
||||
if first is None:
|
||||
|
|
@ -373,12 +364,12 @@ class BlocksInfo:
|
|||
last = self.conv_blkspec(last or first)
|
||||
|
||||
if p.debug:
|
||||
msg(repr(self.range_data(first,last,from_tip,nblocks,step)))
|
||||
msg(repr(self.range_data(first, last, from_tip, nblocks, step)))
|
||||
|
||||
if first > last:
|
||||
die(1,f'{first}-{last}: invalid block range')
|
||||
die(1, f'{first}-{last}: invalid block range')
|
||||
|
||||
return self.range_data(first,last,from_tip,nblocks,step)
|
||||
return self.range_data(first, last, from_tip, nblocks, step)
|
||||
|
||||
async def process_blocks(self):
|
||||
|
||||
|
|
@ -388,7 +379,7 @@ class BlocksInfo:
|
|||
|
||||
c = self.rpc
|
||||
|
||||
heights = self.block_list or range(self.first,self.last+1)
|
||||
heights = self.block_list or range(self.first, self.last+1)
|
||||
self.hdrs = await get_hdrs(heights)
|
||||
|
||||
if self.block_list:
|
||||
|
|
@ -397,8 +388,7 @@ class BlocksInfo:
|
|||
else:
|
||||
self.first_prev_hdr = (
|
||||
self.hdrs[0] if heights[0] == 0 else
|
||||
await c.call('getblockheader',await c.call('getblockhash',heights[0]-1))
|
||||
)
|
||||
await c.call('getblockheader', await c.call('getblockhash', heights[0]-1)))
|
||||
|
||||
self.t_cur = self.first_prev_hdr['time']
|
||||
self.res = []
|
||||
|
|
@ -409,16 +399,16 @@ class BlocksInfo:
|
|||
ret = await self.process_block(self.hdrs[n])
|
||||
self.res.append(ret)
|
||||
if self.fnames and not self.cfg.stats_only:
|
||||
self.output_block(ret,n)
|
||||
self.output_block(ret, n)
|
||||
|
||||
def output_block(self,data,n):
|
||||
def output_block(self, data, n):
|
||||
def gen():
|
||||
for k,v in data._asdict().items():
|
||||
for k, v in data._asdict().items():
|
||||
func = self.fields[k].fmt_func
|
||||
yield self.fmt_funcs[func](v) if func else v
|
||||
Msg(self.fs.format(*gen()))
|
||||
|
||||
async def process_block(self,hdr):
|
||||
async def process_block(self, hdr):
|
||||
|
||||
self.t_diff = hdr['time'] - self.t_cur
|
||||
self.t_cur = hdr['time']
|
||||
|
|
@ -426,14 +416,12 @@ class BlocksInfo:
|
|||
|
||||
blk_data = {
|
||||
'bh': hdr,
|
||||
'lo': { 'interval': self.t_diff }
|
||||
}
|
||||
'lo': {'interval': self.t_diff}}
|
||||
|
||||
if 'bs' in self.deps:
|
||||
bs = (
|
||||
self.genesis_stats if hdr['height'] == 0 else
|
||||
await self.rpc.call('getblockstats',hdr['hash'],list(self.bs_keys))
|
||||
)
|
||||
await self.rpc.call('getblockstats', hdr['hash'], list(self.bs_keys)))
|
||||
self.total_bytes += bs['total_size']
|
||||
if 'total_weight' in bs:
|
||||
self.total_weight += bs['total_weight']
|
||||
|
|
@ -446,14 +434,13 @@ class BlocksInfo:
|
|||
for v in self.fvals:
|
||||
yield (
|
||||
blk_data[v.src][v.key1] if v.key2 is None else
|
||||
blk_data[v.src][v.key1][v.key2]
|
||||
)
|
||||
blk_data[v.src][v.key1][v.key2])
|
||||
|
||||
return self.block_data(*gen())
|
||||
|
||||
async def get_miner_string(self,H):
|
||||
tx0 = (await self.rpc.call('getblock',H))['tx'][0]
|
||||
bd = await self.rpc.call('getrawtransaction',tx0,1)
|
||||
async def get_miner_string(self, H):
|
||||
tx0 = (await self.rpc.call('getblock', H))['tx'][0]
|
||||
bd = await self.rpc.call('getrawtransaction', tx0, 1)
|
||||
if type(bd) == tuple:
|
||||
return '---'
|
||||
else:
|
||||
|
|
@ -464,13 +451,12 @@ class BlocksInfo:
|
|||
trmap_in = {
|
||||
'\\': ' ',
|
||||
'/': ' ',
|
||||
',': ' ',
|
||||
}
|
||||
trmap = { ord(a):b for a,b in trmap_in.items() }
|
||||
',': ' '}
|
||||
trmap = {ord(a): b for a, b in trmap_in.items()}
|
||||
for pat in self.miner_pats:
|
||||
m = pat.search(cb)
|
||||
if m:
|
||||
return re.sub( r'\s+', ' ', m[1].decode().strip('^').translate(trmap).strip() )
|
||||
return re.sub(r'\s+', ' ', m[1].decode().strip('^').translate(trmap).strip())
|
||||
return ''
|
||||
|
||||
def print_header(self):
|
||||
|
|
@ -484,11 +470,11 @@ class BlocksInfo:
|
|||
yield self.fs.format(*hdr1)
|
||||
yield self.fs.format(*hdr2)
|
||||
|
||||
def process_stats(self,sname):
|
||||
method = getattr(self,f'create_{sname}_stats',None)
|
||||
return self.output_stats(method() if method else self.create_stats(sname),sname)
|
||||
def process_stats(self, sname):
|
||||
method = getattr(self, f'create_{sname}_stats', None)
|
||||
return self.output_stats(method() if method else self.create_stats(sname), sname)
|
||||
|
||||
def fmt_stat_item(self,fs,s):
|
||||
def fmt_stat_item(self, fs, s):
|
||||
return fs.format(s) if type(fs) == str else fs(s)
|
||||
|
||||
async def output_stats(self, res, sname):
|
||||
|
|
@ -497,7 +483,7 @@ class BlocksInfo:
|
|||
for d in data:
|
||||
match d:
|
||||
case [a, b]:
|
||||
yield (indent + a).format(**{k:self.fmt_stat_item(*v) for k, v in b.items()})
|
||||
yield (indent + a).format(**{k: self.fmt_stat_item(*v) for k, v in b.items()})
|
||||
case [a, _, b, c]:
|
||||
yield (indent + a).format(self.fmt_stat_item(b, c))
|
||||
case str():
|
||||
|
|
@ -524,15 +510,14 @@ class BlocksInfo:
|
|||
'range': ('{}', self.hdrs[-1]['height'] - self.hdrs[0]['height'] + 1),
|
||||
'elapsed': (self.t_fmt, elapsed),
|
||||
'nBlocks': ('{}', total_blks),
|
||||
'step': ('{}', self.step),
|
||||
}
|
||||
)
|
||||
if elapsed:
|
||||
yield ( 'Start: {}', 'start_date', self.fmt_funcs['da'], self.hdrs[0]['time'] )
|
||||
yield ( 'End: {}', 'end_date', self.fmt_funcs['da'], self.hdrs[-1]['time'] )
|
||||
yield ( 'Avg BDI: {} min', 'avg_bdi', '{:.2f}', elapsed / nblocks / 60 )
|
||||
'step': ('{}', self.step)})
|
||||
|
||||
return ( 'range', gen() )
|
||||
if elapsed:
|
||||
yield ('Start: {}', 'start_date', self.fmt_funcs['da'], self.hdrs[0]['time'])
|
||||
yield ('End: {}', 'end_date', self.fmt_funcs['da'], self.hdrs[-1]['time'])
|
||||
yield ('Avg BDI: {} min', 'avg_bdi', '{:.2f}', elapsed / nblocks / 60)
|
||||
|
||||
return ('range', gen())
|
||||
|
||||
async def create_diff_stats(self):
|
||||
|
||||
|
|
@ -541,18 +526,17 @@ class BlocksInfo:
|
|||
|
||||
tip_hdr = (
|
||||
self.hdrs[-1] if self.hdrs[-1]['height'] == self.tip else
|
||||
await c.call('getblockheader',await c.call('getblockhash',self.tip))
|
||||
)
|
||||
await c.call('getblockheader', await c.call('getblockhash', self.tip)))
|
||||
|
||||
min_sample_blks = 432 # ≈3 days
|
||||
rel_hdr = await c.call('getblockheader',await c.call('getblockhash',self.tip-rel))
|
||||
rel_hdr = await c.call('getblockheader', await c.call('getblockhash', self.tip-rel))
|
||||
|
||||
if rel >= min_sample_blks:
|
||||
sample_blks = rel
|
||||
bdi = ( tip_hdr['time'] - rel_hdr['time'] ) / rel
|
||||
bdi = (tip_hdr['time'] - rel_hdr['time']) / rel
|
||||
else:
|
||||
sample_blks = min(min_sample_blks,self.tip)
|
||||
start_hdr = await c.call('getblockheader',await c.call('getblockhash',self.tip-sample_blks))
|
||||
sample_blks = min(min_sample_blks, self.tip)
|
||||
start_hdr = await c.call('getblockheader', await c.call('getblockhash', self.tip-sample_blks))
|
||||
diff_adj = Decimal(tip_hdr['difficulty']) / Decimal(start_hdr['difficulty'])
|
||||
time1 = rel_hdr['time'] - start_hdr['time']
|
||||
time2 = tip_hdr['time'] - rel_hdr['time']
|
||||
|
|
@ -560,7 +544,7 @@ class BlocksInfo:
|
|||
|
||||
rem = self.rpc.proto.diff_adjust_interval - rel
|
||||
|
||||
return ( 'difficulty', (
|
||||
return ('difficulty', (
|
||||
'Difficulty Statistics:',
|
||||
('Current height: {}', 'chain_tip', '{}', self.tip),
|
||||
('Next diff adjust: {next_diff_adjust} (in {blks_remaining} block%s [{time_remaining}])' % suf(rem),
|
||||
|
|
@ -593,105 +577,98 @@ class BlocksInfo:
|
|||
def gen():
|
||||
for field in self.fnames:
|
||||
if field in self.avg_stats_skip:
|
||||
yield ( field, ('{}','') )
|
||||
yield (field, ('{}', ''))
|
||||
else:
|
||||
ret = self.sum_field_avg(field)
|
||||
func = self.fields[field].fmt_func
|
||||
yield ( field, ( (self.fmt_funcs[func] if func else '{}'), ret ))
|
||||
yield (field, ((self.fmt_funcs[func] if func else '{}'), ret))
|
||||
if not self.header_printed:
|
||||
self.print_header()
|
||||
fs = ''.join(self.gen_fs(self.fnames,fill=self.avg_stats_skip,add_name=True)).strip()
|
||||
return ('column_averages', ('Column averages:', (fs, dict(gen())) ))
|
||||
fs = ''.join(self.gen_fs(self.fnames, fill=self.avg_stats_skip, add_name=True)).strip()
|
||||
return ('column_averages', ('Column averages:', (fs, dict(gen()))))
|
||||
|
||||
def avg_stats_data(self,data,spec_conv,spec_val):
|
||||
def avg_stats_data(self, data, spec_conv, spec_val):
|
||||
coin = self.rpc.proto.coin
|
||||
|
||||
return data(
|
||||
hdr = 'Averages for processed blocks:',
|
||||
func = self.sum_field_avg,
|
||||
spec_sufs = { 'subsidy': f' {coin}', 'totalfee': f' {coin}' },
|
||||
spec_sufs = {'subsidy': f' {coin}', 'totalfee': f' {coin}'},
|
||||
spec_convs = {
|
||||
'interval': spec_conv(0, lambda arg: secs_to_ms(arg)),
|
||||
'interval': spec_conv(0, lambda arg: secs_to_ms(arg)),
|
||||
'utxo_inc': spec_conv(-1, '{:<+}'),
|
||||
'mb_per_hour': spec_conv(0, '{}'),
|
||||
},
|
||||
'mb_per_hour': spec_conv(0, '{}')},
|
||||
spec_vals = (
|
||||
spec_val(
|
||||
'mb_per_hour', 'MB/hr', 'interval',
|
||||
lambda values: 'bs' in self.deps,
|
||||
lambda values: (
|
||||
'{:.4f}'.format((self.total_bytes / 10000) / (self.total_solve_time / 36))
|
||||
if self.total_solve_time else 'N/A' ),
|
||||
),
|
||||
)
|
||||
)
|
||||
if self.total_solve_time else 'N/A')),
|
||||
))
|
||||
|
||||
mini_avg_stats_data = avg_stats_data
|
||||
|
||||
def total_stats_data(self,data,spec_conv,spec_val):
|
||||
def total_stats_data(self, data, spec_conv, spec_val):
|
||||
coin = self.rpc.proto.coin
|
||||
return data(
|
||||
hdr = 'Totals for processed blocks:',
|
||||
func = self.sum_field_total,
|
||||
spec_sufs = { 'subsidy': f' {coin}', 'totalfee': f' {coin}', 'reward': f' {coin}' },
|
||||
spec_sufs = {'subsidy': f' {coin}', 'totalfee': f' {coin}', 'reward': f' {coin}'},
|
||||
spec_convs = {
|
||||
'interval': spec_conv(0, lambda arg: secs_to_dhms(arg)),
|
||||
'interval': spec_conv(0, lambda arg: secs_to_dhms(arg)),
|
||||
'utxo_inc': spec_conv(-1, '{:<+}'),
|
||||
'reward': spec_conv(0, self.fmt_funcs['tf']),
|
||||
},
|
||||
'reward': spec_conv(0, self.fmt_funcs['tf'])},
|
||||
spec_vals = (
|
||||
spec_val(
|
||||
'reward', 'Reward', 'totalfee',
|
||||
lambda values: {'subsidy','totalfee'} <= set(values),
|
||||
lambda values: values['subsidy'] + values['totalfee']
|
||||
),
|
||||
)
|
||||
)
|
||||
lambda values: {'subsidy', 'totalfee'} <= set(values),
|
||||
lambda values: values['subsidy'] + values['totalfee']),
|
||||
))
|
||||
|
||||
async def create_stats(self,sname):
|
||||
async def create_stats(self, sname):
|
||||
|
||||
def convert_stats_hdr(field):
|
||||
v = self.fields[field]
|
||||
return '{} {}'.format(v.hdr1.strip(), v.hdr2.strip()).replace('- ','') if v.hdr1 else v.hdr2.strip()
|
||||
return '{} {}'.format(
|
||||
v.hdr1.strip(), v.hdr2.strip()).replace('- ', '') if v.hdr1 else v.hdr2.strip()
|
||||
|
||||
d = getattr(self,f'{sname}_stats_data')(
|
||||
namedtuple('stats_data',['hdr','func','spec_sufs','spec_convs','spec_vals']),
|
||||
namedtuple('spec_conv',['width_adj','conv']),
|
||||
namedtuple('spec_val',['name','lbl','insert_after','condition','code'])
|
||||
)
|
||||
d = getattr(self, f'{sname}_stats_data')(
|
||||
namedtuple('stats_data', ['hdr', 'func', 'spec_sufs', 'spec_convs', 'spec_vals']),
|
||||
namedtuple('spec_conv', ['width_adj', 'conv']),
|
||||
namedtuple('spec_val', ['name', 'lbl', 'insert_after', 'condition', 'code']))
|
||||
|
||||
fnames = [n for n in self.fnames if n in self.stats_deps[sname]]
|
||||
lbls = {n:convert_stats_hdr(n) for n in fnames}
|
||||
values = {n:d.func(n) for n in fnames}
|
||||
col1_w = max((len(l) for l in lbls.values()),default=0) + 2
|
||||
lbls = {n: convert_stats_hdr(n) for n in fnames}
|
||||
values = {n: d.func(n) for n in fnames}
|
||||
col1_w = max((len(l) for l in lbls.values()), default=0) + 2
|
||||
|
||||
print(d.spec_vals)
|
||||
for v in d.spec_vals:
|
||||
print(v)
|
||||
if v.condition(values):
|
||||
try: idx = fnames.index(v.insert_after) + 1
|
||||
except: idx = 0
|
||||
fnames.insert(idx,v.name)
|
||||
fnames.insert(idx, v.name)
|
||||
lbls[v.name] = v.lbl
|
||||
values[v.name] = v.code(values)
|
||||
|
||||
def gen():
|
||||
for n,fname in enumerate(fnames):
|
||||
for n, fname in enumerate(fnames):
|
||||
spec_conv = d.spec_convs.get(fname)
|
||||
yield (
|
||||
'{lbl:{wid}} {{}}{suf}'.format(
|
||||
lbl = lbls[fname] + ':',
|
||||
wid = col1_w + (spec_conv.width_adj if spec_conv else 0),
|
||||
suf = d.spec_sufs.get(fname) or ''
|
||||
),
|
||||
suf = d.spec_sufs.get(fname) or ''),
|
||||
fname,
|
||||
spec_conv.conv if spec_conv else (
|
||||
(lambda x: self.fmt_funcs[x] if x else '{}')(self.fields[fname].fmt_func)
|
||||
),
|
||||
values[fname]
|
||||
)
|
||||
(lambda x: self.fmt_funcs[x] if x else '{}')(self.fields[fname].fmt_func)),
|
||||
values[fname])
|
||||
|
||||
return ( sname, (d.hdr,) + tuple(gen()) )
|
||||
return (sname, (d.hdr,) + tuple(gen()))
|
||||
|
||||
def process_stats_pre(self,i):
|
||||
def process_stats_pre(self, i):
|
||||
if (self.fnames and not self.cfg.stats_only) or i != 0:
|
||||
Msg('')
|
||||
|
||||
|
|
@ -724,13 +701,12 @@ class BlocksInfo:
|
|||
'totalfee': 0,
|
||||
'txs': 1,
|
||||
'utxo_increase': 1,
|
||||
'utxo_size_inc': 117
|
||||
}
|
||||
'utxo_size_inc': 117}
|
||||
|
||||
class JSONBlocksInfo(BlocksInfo):
|
||||
|
||||
def __init__(self,cfg,cmd_args,rpc):
|
||||
super().__init__(cfg,cmd_args,rpc)
|
||||
def __init__(self, cfg, cmd_args, rpc):
|
||||
super().__init__(cfg, cmd_args, rpc)
|
||||
if self.cfg.json_raw:
|
||||
self.output_block = self.output_block_raw
|
||||
self.fmt_stat_item = self.fmt_stat_item_raw
|
||||
|
|
@ -741,22 +717,22 @@ class JSONBlocksInfo(BlocksInfo):
|
|||
await super().process_blocks()
|
||||
Msg_r(']')
|
||||
|
||||
def output_block_raw(self,data,n):
|
||||
Msg_r( (', ','')[n==0] + json.dumps(data._asdict(),cls=json_encoder) )
|
||||
def output_block_raw(self, data, n):
|
||||
Msg_r((', ', '')[n==0] + json.dumps(data._asdict(), cls=json_encoder))
|
||||
|
||||
def output_block(self,data,n):
|
||||
def output_block(self, data, n):
|
||||
def gen():
|
||||
for k,v in data._asdict().items():
|
||||
for k, v in data._asdict().items():
|
||||
func = self.fields[k].fmt_func
|
||||
yield ( k, (self.fmt_funcs[func](v) if func else v) )
|
||||
Msg_r( (', ','')[n==0] + json.dumps(dict(gen()),cls=json_encoder) )
|
||||
yield (k, (self.fmt_funcs[func](v) if func else v))
|
||||
Msg_r((', ', '')[n==0] + json.dumps(dict(gen()), cls=json_encoder))
|
||||
|
||||
def print_header(self): pass
|
||||
|
||||
def fmt_stat_item_raw(self,fs,s):
|
||||
def fmt_stat_item_raw(self, fs, s):
|
||||
return s
|
||||
|
||||
async def output_stats(self,res,sname):
|
||||
async def output_stats(self, res, sname):
|
||||
|
||||
def gen(data):
|
||||
for d in data:
|
||||
|
|
@ -771,10 +747,10 @@ class JSONBlocksInfo(BlocksInfo):
|
|||
case _:
|
||||
assert False, f'{d}: invalid stats data'
|
||||
|
||||
varname,data = await res
|
||||
Msg_r(', "{}_data": {}'.format( varname, json.dumps(dict(gen(data)),cls=json_encoder) ))
|
||||
varname, data = await res
|
||||
Msg_r(', "{}_data": {}'.format(varname, json.dumps(dict(gen(data)), cls=json_encoder)))
|
||||
|
||||
def process_stats_pre(self,i): pass
|
||||
def process_stats_pre(self, i): pass
|
||||
|
||||
def finalize_output(self):
|
||||
Msg('}')
|
||||
|
|
|
|||
|
|
@ -14,66 +14,66 @@ mmgen_node_tools.PeerBlocks: List blocks in flight, disconnect stalling nodes
|
|||
|
||||
import asyncio
|
||||
from collections import namedtuple
|
||||
from mmgen.util import msg,msg_r,is_int
|
||||
from mmgen.term import get_term,get_terminal_size,get_char
|
||||
from mmgen.util import msg, msg_r, is_int
|
||||
from mmgen.term import get_term, get_terminal_size, get_char
|
||||
from mmgen.ui import line_input
|
||||
from .PollDisplay import PollDisplay
|
||||
|
||||
RED,RESET = ('\033[31m','\033[0m')
|
||||
COLORS = ['\033[38;5;%s;1m' % c for c in list(range(247,256)) + [231]]
|
||||
ERASE_ALL,CUR_HOME = ('\033[J','\033[H')
|
||||
CUR_HIDE,CUR_SHOW = ('\033[?25l','\033[?25h')
|
||||
RED, RESET = ('\033[31m', '\033[0m')
|
||||
COLORS = ['\033[38;5;%s;1m' % c for c in list(range(247, 256)) + [231]]
|
||||
ERASE_ALL, CUR_HOME = ('\033[J', '\033[H')
|
||||
CUR_HIDE, CUR_SHOW = ('\033[?25l', '\033[?25h')
|
||||
term = None
|
||||
|
||||
class Display(PollDisplay):
|
||||
|
||||
poll_secs = 2
|
||||
|
||||
def __init__(self,cfg):
|
||||
def __init__(self, cfg):
|
||||
|
||||
super().__init__(cfg)
|
||||
|
||||
global term,term_width
|
||||
global term, term_width
|
||||
if not term:
|
||||
term = get_term()
|
||||
term.init(noecho=True)
|
||||
term_width = self.cfg.columns or get_terminal_size().width
|
||||
msg_r(CUR_HOME+ERASE_ALL+CUR_HOME)
|
||||
|
||||
async def get_info(self,rpc):
|
||||
async def get_info(self, rpc):
|
||||
return await rpc.call('getpeerinfo')
|
||||
|
||||
def display(self,count):
|
||||
def display(self, count):
|
||||
msg_r(
|
||||
CUR_HOME
|
||||
+ (ERASE_ALL if count == 1 else '')
|
||||
+ 'CONNECTED PEERS ({a}) {b} - poll {c}'.format(
|
||||
a = len(self.info),
|
||||
b = self.desc,
|
||||
c = count ).ljust(term_width)[:term_width]
|
||||
c = count).ljust(term_width)[:term_width]
|
||||
+ '\n'
|
||||
+ ('\n'.join(self.gen_display()) + '\n' if self.info else '')
|
||||
+ ERASE_ALL
|
||||
+ f"Type a peer number to disconnect, 'q' to quit, or any other key for {self.other_desc} display:"
|
||||
+ '\b' )
|
||||
+ '\b')
|
||||
|
||||
async def disconnect_node(self,rpc,addr):
|
||||
return await rpc.call('disconnectnode',addr)
|
||||
async def disconnect_node(self, rpc, addr):
|
||||
return await rpc.call('disconnectnode', addr)
|
||||
|
||||
def get_input(self):
|
||||
s = get_char(immed_chars='q0123456789',prehold_protect=False,num_bytes=1)
|
||||
s = get_char(immed_chars='q0123456789', prehold_protect=False, num_bytes=1)
|
||||
if not is_int(s):
|
||||
return s
|
||||
with self.info_lock:
|
||||
msg('')
|
||||
term.reset()
|
||||
# readline required for correct operation here; without it, user must re-type first digit
|
||||
ret = line_input( self.cfg, 'peer number> ', insert_txt=s, hold_protect=False )
|
||||
ret = line_input(self.cfg, 'peer number> ', insert_txt=s, hold_protect=False)
|
||||
term.init(noecho=True)
|
||||
self.enable_display = False # prevent display from updating before process_input()
|
||||
return ret
|
||||
|
||||
async def process_input(self,rpc):
|
||||
async def process_input(self, rpc):
|
||||
|
||||
ids = tuple(str(i['id']) for i in self.info)
|
||||
ret = False
|
||||
|
|
@ -83,7 +83,7 @@ class Display(PollDisplay):
|
|||
from mmgen.exception import RPCFailure
|
||||
addr = self.info[ids.index(self.input)]['addr']
|
||||
try:
|
||||
await self.disconnect_node(rpc,addr)
|
||||
await self.disconnect_node(rpc, addr)
|
||||
except RPCFailure:
|
||||
msg_r(f'Unable to disconnect peer {self.input} ({addr})')
|
||||
else:
|
||||
|
|
@ -105,8 +105,8 @@ class BlocksDisplay(Display):
|
|||
|
||||
def gen_display(self):
|
||||
|
||||
pd = namedtuple('peer_data',['id','blks_data','blks_width'])
|
||||
bd = namedtuple('block_datum',['num','disp'])
|
||||
pd = namedtuple('peer_data', ['id', 'blks_data', 'blks_width'])
|
||||
bd = namedtuple('block_datum', ['num', 'disp'])
|
||||
|
||||
def gen_block_data():
|
||||
global min_height
|
||||
|
|
@ -114,15 +114,15 @@ class BlocksDisplay(Display):
|
|||
for d in self.info:
|
||||
if d.get('inflight'):
|
||||
blocks = d['inflight']
|
||||
min_height = min(blocks) if not min_height else min(min_height,min(blocks))
|
||||
line = ' '.join(map(str,blocks))[:blks_field_width]
|
||||
min_height = min(blocks) if not min_height else min(min_height, min(blocks))
|
||||
line = ' '.join(map(str, blocks))[:blks_field_width]
|
||||
blocks_disp = line.split()
|
||||
yield pd(
|
||||
d['id'],
|
||||
[bd(blocks[i],blocks_disp[i]) for i in range(len(blocks_disp))],
|
||||
len(line) )
|
||||
[bd(blocks[i], blocks_disp[i]) for i in range(len(blocks_disp))],
|
||||
len(line))
|
||||
else:
|
||||
yield pd(d['id'],[],0)
|
||||
yield pd(d['id'], [], 0)
|
||||
|
||||
def gen_line(peer_data):
|
||||
for blk in peer_data.blks_data:
|
||||
|
|
@ -136,7 +136,7 @@ class BlocksDisplay(Display):
|
|||
for peer_data in tuple(gen_block_data()):
|
||||
yield fs.format(
|
||||
peer_data.id,
|
||||
' '.join(gen_line(peer_data)) + ' ' * (blks_field_width - peer_data.blks_width) )
|
||||
' '.join(gen_line(peer_data)) + ' ' * (blks_field_width - peer_data.blks_width))
|
||||
|
||||
class PeersDisplay(Display):
|
||||
|
||||
|
|
@ -152,5 +152,4 @@ class PeersDisplay(Display):
|
|||
A = id_width,
|
||||
b = d['addr'],
|
||||
B = addr_width,
|
||||
c = d['subver']
|
||||
).ljust(term_width)[:term_width]
|
||||
c = d['subver']).ljust(term_width)[:term_width]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
mmgen_node_tools.PollDisplay: update and display RPC data; get input from user
|
||||
"""
|
||||
|
||||
import sys,threading
|
||||
import sys, threading
|
||||
from mmgen.util import msg
|
||||
from mmgen.term import get_char
|
||||
|
||||
|
|
@ -22,18 +22,18 @@ class PollDisplay:
|
|||
input = None
|
||||
poll_secs = 1
|
||||
|
||||
def __init__(self,cfg):
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.info_lock = threading.Lock() # self.info accessed by 2 threads
|
||||
self.display_kill_flag = threading.Event()
|
||||
|
||||
def get_input(self):
|
||||
return get_char(immed_chars='q',prehold_protect=False,num_bytes=1)
|
||||
return get_char(immed_chars='q', prehold_protect=False, num_bytes=1)
|
||||
|
||||
async def process_input(self,rpc):
|
||||
async def process_input(self, rpc):
|
||||
return True
|
||||
|
||||
async def run(self,rpc):
|
||||
async def run(self, rpc):
|
||||
|
||||
async def do_display():
|
||||
with self.info_lock:
|
||||
|
|
@ -68,7 +68,7 @@ class PollDisplay:
|
|||
self.display_kill_flag.set()
|
||||
|
||||
while True:
|
||||
threading.Thread(target=get_input,daemon=True).start()
|
||||
threading.Thread(target=get_input, daemon=True).start()
|
||||
await do_display()
|
||||
if await process_input():
|
||||
break
|
||||
|
|
|
|||
|
|
@ -19,28 +19,28 @@
|
|||
mmgen_node_tools.Sound: audio-related functions for MMGen node tools
|
||||
"""
|
||||
|
||||
import sys,os,time
|
||||
import sys, os, time
|
||||
|
||||
from mmgen.util import die
|
||||
|
||||
from mmgen_node_tools.Util import do_system
|
||||
|
||||
_alsa_config_file = '/tmp/alsa-config-' + os.path.basename(sys.argv[0])
|
||||
_dvols = { 'Master': 78, 'Speaker': 78, 'Headphone': 15, 'PCM': 190 }
|
||||
_dvols = {'Master': 78, 'Speaker': 78, 'Headphone': 15, 'PCM': 190}
|
||||
|
||||
def timespec2secs(ts):
|
||||
import re
|
||||
mul = { 's': 1, 'm': 60, 'h': 60*60, 'd': 60*60*24 }
|
||||
mul = {'s': 1, 'm': 60, 'h': 60*60, 'd': 60*60*24}
|
||||
pat = r'^([0-9]+)([smhd]*)$'
|
||||
m = re.match(pat,ts)
|
||||
m = re.match(pat, ts)
|
||||
if m is None:
|
||||
die(2,"'%s': invalid time specifier" % ts)
|
||||
a,b = m.groups()
|
||||
a, b = m.groups()
|
||||
return int(a) * (mul[b] if b else 1)
|
||||
|
||||
def parse_repeat_spec(rs):
|
||||
return [(timespec2secs(i),timespec2secs(j))
|
||||
for i,j in [a.split(':') for a in rs.split(',')]]
|
||||
return [(timespec2secs(i), timespec2secs(j))
|
||||
for i, j in [a.split(':') for a in rs.split(',')]]
|
||||
|
||||
def init_sound():
|
||||
def _restore_sound():
|
||||
|
|
@ -51,33 +51,33 @@ def init_sound():
|
|||
atexit.register(_restore_sound)
|
||||
do_system('sudo alsactl store -f ' + _alsa_config_file)
|
||||
|
||||
def play_sound(fn,vol,repeat_spec='',remote_host='',kill_flg=None,testing=False):
|
||||
def play_sound(fn, vol, repeat_spec='', remote_host='', kill_flg=None, testing=False):
|
||||
if not remote_host:
|
||||
do_system('sudo alsactl store -f ' + _alsa_config_file)
|
||||
for k in 'Master','Speaker','Headphone':
|
||||
do_system(('sudo amixer -q set %s on' % k),testing)
|
||||
for k in 'Master', 'Speaker', 'Headphone':
|
||||
do_system(('sudo amixer -q set %s on' % k), testing)
|
||||
# do_system('amixer -q set Headphone off')
|
||||
|
||||
vols = dict([(k,int(_dvols[k] * float(vol) / 100)) for k in _dvols])
|
||||
vols = dict([(k, int(_dvols[k] * float(vol) / 100)) for k in _dvols])
|
||||
for k in vols:
|
||||
do_system('sudo amixer -q set %s %s' % (k,vols[k]),testing)
|
||||
do_system('sudo amixer -q set %s %s' % (k, vols[k]), testing)
|
||||
|
||||
fn = os.path.expanduser(fn)
|
||||
cmd = (
|
||||
'aplay -q %s' % fn,
|
||||
'ssh %s mmnode-play-sound -v%d %s' % (remote_host,vol,fn)
|
||||
'ssh %s mmnode-play-sound -v%d %s' % (remote_host, vol, fn)
|
||||
)[bool(remote_host)]
|
||||
|
||||
if repeat_spec and kill_flg:
|
||||
for interval,duration in parse_repeat_spec(repeat_spec):
|
||||
for interval, duration in parse_repeat_spec(repeat_spec):
|
||||
start = time.time()
|
||||
while time.time() < start + duration:
|
||||
do_system(cmd,testing)
|
||||
do_system(cmd, testing)
|
||||
if kill_flg.wait(interval):
|
||||
if not remote_host:
|
||||
do_system('sudo alsactl restore -f ' + _alsa_config_file)
|
||||
return
|
||||
else: # Play once
|
||||
do_system(cmd,testing)
|
||||
do_system(cmd, testing)
|
||||
if not remote_host:
|
||||
do_system('sudo alsactl restore -f ' + _alsa_config_file)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -21,53 +21,52 @@ mmgen_node_tools.Util: utility functions for MMGen node tools
|
|||
|
||||
import time
|
||||
|
||||
def get_hms(t=None,utc=False,no_secs=False):
|
||||
def get_hms(t=None, utc=False, no_secs=False):
|
||||
secs = t or time.time()
|
||||
ret = (time.localtime,time.gmtime)[utc](secs)
|
||||
fs,n = (('{:02}:{:02}:{:02}',6),('{:02}:{:02}',5))[no_secs]
|
||||
ret = (time.localtime, time.gmtime)[utc](secs)
|
||||
fs, n = (('{:02}:{:02}:{:02}', 6), ('{:02}:{:02}', 5))[no_secs]
|
||||
return fs.format(*ret[3:n])
|
||||
|
||||
def get_day_hms(t=None,utc=False):
|
||||
def get_day_hms(t=None, utc=False):
|
||||
secs = t or time.time()
|
||||
ret = (time.localtime,time.gmtime)[utc](secs)
|
||||
ret = (time.localtime, time.gmtime)[utc](secs)
|
||||
return '{:04}-{:02}-{:02} {:02}:{:02}:{:02}'.format(*ret[0:6])
|
||||
|
||||
def do_system(cmd,testing=False,shell=False):
|
||||
def do_system(cmd, testing=False, shell=False):
|
||||
if testing:
|
||||
from mmgen.util import msg
|
||||
msg("Would execute: '%s'" % cmd)
|
||||
return True
|
||||
else:
|
||||
import subprocess
|
||||
return subprocess.call((cmd if shell else cmd.split()),shell,stderr=subprocess.PIPE)
|
||||
return subprocess.call((cmd if shell else cmd.split()), shell, stderr=subprocess.PIPE)
|
||||
|
||||
def get_url(url,gzip_ok=False,proxy=None,timeout=60,verbose=False,debug=False):
|
||||
def get_url(url, gzip_ok=False, proxy=None, timeout=60, verbose=False, debug=False):
|
||||
if debug:
|
||||
print('get_url():')
|
||||
print(' url', url)
|
||||
print(' gzip_ok:',gzip_ok, 'proxy:',proxy, 'timeout:',timeout, 'verbose:',verbose)
|
||||
import pycurl,io
|
||||
print(' gzip_ok:', gzip_ok, 'proxy:', proxy, 'timeout:', timeout, 'verbose:', verbose)
|
||||
import pycurl, io
|
||||
c = pycurl.Curl()
|
||||
c_out = io.StringIO()
|
||||
c.setopt(pycurl.WRITEFUNCTION,c_out.write)
|
||||
c.setopt(pycurl.TIMEOUT,timeout)
|
||||
c.setopt(pycurl.FOLLOWLOCATION,True)
|
||||
c.setopt(pycurl.COOKIEFILE,'')
|
||||
c.setopt(pycurl.VERBOSE,verbose)
|
||||
c.setopt(pycurl.WRITEFUNCTION, c_out.write)
|
||||
c.setopt(pycurl.TIMEOUT, timeout)
|
||||
c.setopt(pycurl.FOLLOWLOCATION, True)
|
||||
c.setopt(pycurl.COOKIEFILE, '')
|
||||
c.setopt(pycurl.VERBOSE, verbose)
|
||||
if gzip_ok:
|
||||
c.setopt(pycurl.USERAGENT,'Lynx/2.8.9dev.8 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/3.4.9')
|
||||
c.setopt(pycurl.USERAGENT, 'Lynx/2.8.9dev.8 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/3.4.9')
|
||||
c.setopt(pycurl.HTTPHEADER, [
|
||||
'Accept: text/html, text/plain, text/sgml, text/css, application/xhtml+xml, */*;q=0.01',
|
||||
'Accept-Encoding: gzip',
|
||||
'Accept-Language: en']
|
||||
)
|
||||
'Accept-Language: en'])
|
||||
if proxy:
|
||||
c.setopt(pycurl.PROXY,proxy)
|
||||
c.setopt(pycurl.URL,url)
|
||||
c.setopt(pycurl.PROXY, proxy)
|
||||
c.setopt(pycurl.URL, url)
|
||||
c.perform()
|
||||
text = c_out.getvalue()
|
||||
if text[:2] == '\x1f\x8b': # gzip magic number
|
||||
c_out.seek(0,0)
|
||||
c_out.seek(0, 0)
|
||||
import gzip
|
||||
with gzip.GzipFile(fileobj=c_out) as f:
|
||||
text = f.read()
|
||||
|
|
@ -103,20 +102,19 @@ big_digits = {
|
|||
"""
|
||||
}
|
||||
|
||||
_bnums_c,_bpunc_c = [[l.strip('\n') + ' ' * (big_digits[m]*big_digits['n'])
|
||||
_bnums_c, _bpunc_c = [[l.strip('\n') + ' ' * (big_digits[m]*big_digits['n'])
|
||||
for l in big_digits[k][1:].split('\n')]
|
||||
for k,m in (('nums','w'),('punc','pw'))]
|
||||
for k, m in (('nums', 'w'), ('punc', 'pw'))]
|
||||
|
||||
_bnums_n,_bpunc_n = [[[l[0+(j*w):w+(j*w)] for l in i]
|
||||
for j in range(big_digits[n])] for n,w,i in
|
||||
(('n',big_digits['w'],_bnums_c),('pn',big_digits['pw'],_bpunc_c))]
|
||||
_bnums_n, _bpunc_n = [[[l[0+(j*w):w+(j*w)] for l in i]
|
||||
for j in range(big_digits[n])] for n, w, i in
|
||||
(('n', big_digits['w'], _bnums_c), ('pn', big_digits['pw'], _bpunc_c))]
|
||||
|
||||
def display_big_digits(s,pre='',suf=''):
|
||||
s = [int((d,10,11)[(d in '.:')+(d==':')]) for d in s]
|
||||
def display_big_digits(s, pre='', suf=''):
|
||||
s = [int((d, 10, 11)[(d in '.:')+(d==':')]) for d in s]
|
||||
return pre + ('\n'+pre).join(
|
||||
[''.join([(_bnums_n+_bpunc_n)[d][l] for d in s]) + suf for l in range(big_digits['h'])]
|
||||
)
|
||||
[''.join([(_bnums_n+_bpunc_n)[d][l] for d in s]) + suf for l in range(big_digits['h'])])
|
||||
|
||||
if __name__ == '__main__':
|
||||
num = '2345.17'
|
||||
print(display_big_digits(num,pre='+ ',suf=' +'))
|
||||
print(display_big_digits(num, pre='+ ', suf=' +'))
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import sys
|
|||
|
||||
from mmgen.obj import CoinTxID
|
||||
from mmgen.cfg import Config
|
||||
from mmgen.util import msg,Msg,die,suf,make_timestr,async_run
|
||||
from mmgen.util import msg, Msg, die, suf, make_timestr, async_run
|
||||
from mmgen.color import red
|
||||
|
||||
opts_data = {
|
||||
|
|
@ -32,16 +32,16 @@ opts_data = {
|
|||
}
|
||||
}
|
||||
|
||||
def do_output(proto,addr_data,blk_hdrs):
|
||||
def do_output(proto, addr_data, blk_hdrs):
|
||||
|
||||
col1w = len(str(len(addr_data)))
|
||||
indent = ' ' * (col1w + 2)
|
||||
|
||||
for n,(addr,unspents) in enumerate(addr_data.items(),1):
|
||||
for n, (addr, unspents) in enumerate(addr_data.items(), 1):
|
||||
Msg(f'\n{n:{col1w}}) Address: {addr.hl(addr.view_pref)}')
|
||||
|
||||
if unspents:
|
||||
heights = { u['height'] for u in unspents }
|
||||
heights = {u['height'] for u in unspents}
|
||||
Msg('{}Balance: {}'.format(
|
||||
indent,
|
||||
sum(proto.coin_amt(u['amount']) for u in unspents).hl2(unit=True, fs='{:,}'))),
|
||||
|
|
@ -50,22 +50,21 @@ def do_output(proto,addr_data,blk_hdrs):
|
|||
red(str(len(unspents))),
|
||||
suf(unspents),
|
||||
red(str(len(heights))),
|
||||
suf(heights) ))
|
||||
suf(heights)))
|
||||
blk_w = len(str(unspents[-1]['height']))
|
||||
fs = '%s{:%s} {:19} {:64} {:4} {}' % (indent,max(5,blk_w))
|
||||
Msg(fs.format('Block','Date','TxID','Vout',' Amount'))
|
||||
fs = '%s{:%s} {:19} {:64} {:4} {}' % (indent, max(5, blk_w))
|
||||
Msg(fs.format('Block', 'Date', 'TxID', 'Vout', ' Amount'))
|
||||
for u in unspents:
|
||||
Msg(fs.format(
|
||||
u['height'],
|
||||
make_timestr( blk_hdrs[u['height']]['time'] ),
|
||||
make_timestr(blk_hdrs[u['height']]['time']),
|
||||
CoinTxID(u['txid']).hl(),
|
||||
red(str(u['vout']).rjust(4)),
|
||||
proto.coin_amt(u['amount']).fmt(6, color=True, prec=8)
|
||||
))
|
||||
proto.coin_amt(u['amount']).fmt(6, color=True, prec=8)))
|
||||
else:
|
||||
Msg(f'{indent}No balance')
|
||||
|
||||
def do_output_tabular(proto,addr_data,blk_hdrs):
|
||||
def do_output_tabular(proto, addr_data, blk_hdrs):
|
||||
|
||||
col1w = len(str(len(addr_data))) + 1
|
||||
max_addrw = max(len(addr) for addr in addr_data)
|
||||
|
|
@ -75,9 +74,9 @@ def do_output_tabular(proto,addr_data,blk_hdrs):
|
|||
lb_w = max(len(h) for h in lb_heights)
|
||||
|
||||
fs = (
|
||||
' {n:>%s} {a} {u} {b:>%s} {t:19} {B:>%s} {T:19} {A}' % (col1w,max(5,fb_w),max(4,lb_w))
|
||||
' {n:>%s} {a} {u} {b:>%s} {t:19} {B:>%s} {T:19} {A}' % (col1w, max(5, fb_w), max(4, lb_w))
|
||||
if cfg.first_block else
|
||||
' {n:>%s} {a} {u} {B:>%s} {T:19} {A}' % (col1w,max(4,lb_w)) )
|
||||
' {n:>%s} {a} {u} {B:>%s} {T:19} {A}' % (col1w, max(4, lb_w)))
|
||||
|
||||
Msg('\n' + fs.format(
|
||||
n = '',
|
||||
|
|
@ -87,20 +86,19 @@ def do_output_tabular(proto,addr_data,blk_hdrs):
|
|||
t = 'Block',
|
||||
B = 'Last',
|
||||
T = 'Block',
|
||||
A = ' Amount' ))
|
||||
A = ' Amount'))
|
||||
|
||||
for n,(addr,unspents) in enumerate(addr_data.items(),1):
|
||||
for n, (addr, unspents) in enumerate(addr_data.items(), 1):
|
||||
if unspents:
|
||||
Msg(fs.format(
|
||||
n = str(n) + ')',
|
||||
a = addr.fmt(addr.view_pref, max_addrw, color=True),
|
||||
u = red(str(len(unspents)).rjust(5)),
|
||||
b = unspents[0]['height'],
|
||||
t = make_timestr( blk_hdrs[unspents[0]['height']]['time'] ),
|
||||
t = make_timestr(blk_hdrs[unspents[0]['height']]['time']),
|
||||
B = unspents[-1]['height'],
|
||||
T = make_timestr( blk_hdrs[unspents[-1]['height']]['time'] ),
|
||||
A = sum(proto.coin_amt(u['amount']) for u in unspents).fmt(7, color=True, prec=8)
|
||||
))
|
||||
T = make_timestr(blk_hdrs[unspents[-1]['height']]['time']),
|
||||
A = sum(proto.coin_amt(u['amount']) for u in unspents).fmt(7, color=True, prec=8)))
|
||||
else:
|
||||
Msg(fs.format(
|
||||
n = str(n) + ')',
|
||||
|
|
@ -110,61 +108,61 @@ def do_output_tabular(proto,addr_data,blk_hdrs):
|
|||
t = '',
|
||||
B = '-',
|
||||
T = '',
|
||||
A = ' -' ))
|
||||
A = ' -'))
|
||||
|
||||
async def main(req_addrs):
|
||||
|
||||
proto = cfg._proto
|
||||
|
||||
from mmgen.addr import CoinAddr
|
||||
addrs = [CoinAddr(proto,addr) for addr in req_addrs]
|
||||
addrs = [CoinAddr(proto, addr) for addr in req_addrs]
|
||||
|
||||
from mmgen.rpc import rpc_init
|
||||
rpc = await rpc_init(cfg,ignore_wallet=True)
|
||||
rpc = await rpc_init(cfg, ignore_wallet=True)
|
||||
|
||||
height = await rpc.call('getblockcount')
|
||||
Msg(f'{proto.coin} {proto.network.upper()} [height {height}]')
|
||||
|
||||
from mmgen.proto.btc.misc import scantxoutset
|
||||
res = await scantxoutset( cfg, rpc, [f'addr({addr})' for addr in addrs] )
|
||||
res = await scantxoutset(cfg, rpc, [f'addr({addr})' for addr in addrs])
|
||||
|
||||
if not res['success']:
|
||||
die(1,'UTXO scanning failed or was interrupted')
|
||||
die(1, 'UTXO scanning failed or was interrupted')
|
||||
elif not res['unspents']:
|
||||
msg('Address has no balance' if len(addrs) == 1 else
|
||||
'Addresses have no balances' )
|
||||
'Addresses have no balances')
|
||||
else:
|
||||
addr_data = {k:[] for k in addrs}
|
||||
|
||||
if 'desc' in res['unspents'][0]:
|
||||
import re
|
||||
for unspent in sorted(res['unspents'],key=lambda x: x['height']):
|
||||
addr = re.match('addr\((.*?)\)',unspent['desc'])[1]
|
||||
for unspent in sorted(res['unspents'], key=lambda x: x['height']):
|
||||
addr = re.match('addr\((.*?)\)', unspent['desc'])[1]
|
||||
addr_data[addr].append(unspent)
|
||||
else:
|
||||
from mmgen.proto.btc.tx.base import decodeScriptPubKey
|
||||
for unspent in sorted(res['unspents'],key=lambda x: x['height']):
|
||||
for unspent in sorted(res['unspents'], key=lambda x: x['height']):
|
||||
ds = decodeScriptPubKey(proto, unspent['scriptPubKey'])
|
||||
addr_data[ds.addr].append(unspent)
|
||||
|
||||
good_addrs = len([v for v in addr_data.values() if v])
|
||||
|
||||
Msg('Total: {} in {} address{}'.format(
|
||||
proto.coin_amt(res['total_amount']).hl2(unit=True,fs='{:,}'),
|
||||
proto.coin_amt(res['total_amount']).hl2(unit=True, fs='{:,}'),
|
||||
red(str(good_addrs)),
|
||||
suf(good_addrs,'es')
|
||||
))
|
||||
suf(good_addrs, 'es')))
|
||||
|
||||
blk_heights = {i['height'] for i in res['unspents']}
|
||||
blk_hashes = await rpc.batch_call('getblockhash', [(h,) for h in blk_heights])
|
||||
blk_hdrs = await rpc.batch_call('getblockheader', [(H,) for H in blk_hashes])
|
||||
|
||||
(do_output_tabular if cfg.tabular else do_output)( proto, addr_data, dict(zip(blk_heights,blk_hdrs)) )
|
||||
(do_output_tabular if cfg.tabular else do_output)(
|
||||
proto, addr_data, dict(zip(blk_heights, blk_hdrs)))
|
||||
|
||||
cfg = Config( opts_data=opts_data, init_opts={'rpc_backend':'aiohttp'} )
|
||||
cfg = Config(opts_data=opts_data, init_opts={'rpc_backend': 'aiohttp'})
|
||||
|
||||
if len(cfg._args) < 1:
|
||||
die(1,'This command requires at least one coin address argument')
|
||||
die(1, 'This command requires at least one coin address argument')
|
||||
|
||||
try:
|
||||
async_run(cfg, main, args=[cfg._args])
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@
|
|||
mmnode-blocks-info: Display information about a block or range of blocks
|
||||
"""
|
||||
|
||||
from mmgen.cfg import gc,Config
|
||||
from mmgen.util import async_run,fmt_list
|
||||
from .BlocksInfo import BlocksInfo,JSONBlocksInfo
|
||||
from mmgen.cfg import gc, Config
|
||||
from mmgen.util import async_run, fmt_list
|
||||
from .BlocksInfo import BlocksInfo, JSONBlocksInfo
|
||||
|
||||
opts_data = {
|
||||
'sets': [
|
||||
|
|
@ -145,14 +145,13 @@ EXAMPLES:
|
|||
$ {p} --rpc-backend=aio -H +1000
|
||||
|
||||
This program requires a txindex-enabled daemon for correct operation.
|
||||
""" },
|
||||
"""},
|
||||
'code': {
|
||||
'notes': lambda cfg,proto,s: s.format(
|
||||
'notes': lambda cfg, proto, s: s.format(
|
||||
I = proto.diff_adjust_interval,
|
||||
F = fmt_list(BlocksInfo.fields,fmt='bare'),
|
||||
S = fmt_list(BlocksInfo.all_stats,fmt='bare'),
|
||||
p = gc.prog_name,
|
||||
)
|
||||
F = fmt_list(BlocksInfo.fields, fmt='bare'),
|
||||
S = fmt_list(BlocksInfo.all_stats, fmt='bare'),
|
||||
p = gc.prog_name)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -172,7 +171,7 @@ async def main():
|
|||
await m.process_blocks()
|
||||
|
||||
if m.last:
|
||||
for i,sname in enumerate(m.stats):
|
||||
for i, sname in enumerate(m.stats):
|
||||
m.process_stats_pre(i)
|
||||
await m.process_stats(sname)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ mmnode-feeview: Visualize the fee structure of a node’s mempool
|
|||
"""
|
||||
|
||||
from mmgen.cfg import Config
|
||||
from mmgen.util import async_run,die,fmt,make_timestr,check_int_between
|
||||
from mmgen.util2 import int2bytespec,parse_bytespec
|
||||
from mmgen.util import async_run, die, fmt, make_timestr, check_int_between
|
||||
from mmgen.util2 import int2bytespec, parse_bytespec
|
||||
|
||||
min_prec,max_prec,dfl_prec = (0,6,4)
|
||||
min_prec, max_prec, dfl_prec = (0, 6, 4)
|
||||
fee_brackets = [
|
||||
1, 2, 3, 4, 5, 6,
|
||||
8, 10, 12, 14, 16, 18,
|
||||
|
|
@ -42,9 +42,9 @@ fee_brackets = [
|
|||
|
||||
opts_data = {
|
||||
'sets': [
|
||||
('detail',True,'ranges',True),
|
||||
('detail',True,'show_mb_col',True),
|
||||
('detail',True,'precision',6),
|
||||
('detail', True, 'ranges', True),
|
||||
('detail', True, 'show_mb_col', True),
|
||||
('detail', True, 'precision', 6),
|
||||
],
|
||||
'text': {
|
||||
'desc': 'Visualize the fee structure of a node’s mempool',
|
||||
|
|
@ -83,37 +83,37 @@ cfg = Config(opts_data=opts_data)
|
|||
|
||||
if cfg.ignore_below:
|
||||
if cfg.show_empty:
|
||||
die(1,'Conflicting options: --ignore-below, --show-empty')
|
||||
die(1, 'Conflicting options: --ignore-below, --show-empty')
|
||||
ignore_below = parse_bytespec(cfg.ignore_below)
|
||||
|
||||
precision = (
|
||||
check_int_between(cfg.precision, min_prec, max_prec, desc='--precision arg')
|
||||
if cfg.precision else dfl_prec )
|
||||
if cfg.precision else dfl_prec)
|
||||
|
||||
from mmgen.term import get_terminal_size
|
||||
width = cfg.columns or get_terminal_size().width
|
||||
|
||||
class fee_bracket:
|
||||
def __init__(self,top,bottom):
|
||||
def __init__(self, top, bottom):
|
||||
self.top = top
|
||||
self.bottom = bottom
|
||||
self.tx_bytes = 0
|
||||
self.tx_bytes_cum = 0
|
||||
self.skip = False
|
||||
|
||||
def log(data,fn):
|
||||
def log(data, fn):
|
||||
import json
|
||||
from mmgen.rpc.util import json_encoder
|
||||
from mmgen.fileutil import write_data_to_file
|
||||
write_data_to_file(
|
||||
cfg = cfg,
|
||||
outfile = fn,
|
||||
data = json.dumps(data,cls=json_encoder,sort_keys=True,indent=4),
|
||||
data = json.dumps(data, cls=json_encoder, sort_keys=True, indent=4),
|
||||
desc = 'mempool',
|
||||
ask_overwrite = False )
|
||||
ask_overwrite = False)
|
||||
|
||||
def create_data(coin_amt,mempool):
|
||||
out = [fee_bracket(fee_brackets[i],fee_brackets[i-1] if i else 0) for i in range(len(fee_brackets))]
|
||||
def create_data(coin_amt, mempool):
|
||||
out = [fee_bracket(fee_brackets[i], fee_brackets[i-1] if i else 0) for i in range(len(fee_brackets))]
|
||||
|
||||
# populate fee brackets:
|
||||
size_key = 'size' if proto.coin == 'BCH' else 'vsize'
|
||||
|
|
@ -143,7 +143,7 @@ def create_data(coin_amt,mempool):
|
|||
|
||||
return out
|
||||
|
||||
def gen_header(host,mempool,blockcount):
|
||||
def gen_header(host, mempool, blockcount):
|
||||
|
||||
yield fmt(f"""
|
||||
Mempool Fee Structure
|
||||
|
|
@ -159,27 +159,26 @@ def gen_header(host,mempool,blockcount):
|
|||
elif cfg.ignore_below:
|
||||
yield 'Ignoring fee brackets with less than {:,} bytes ({})'.format(
|
||||
ignore_below,
|
||||
int2bytespec(ignore_below,'MB','0.6',strip=True,add_space=True),
|
||||
)
|
||||
int2bytespec(ignore_below, 'MB', '0.6', strip=True, add_space=True))
|
||||
|
||||
if cfg.include_current:
|
||||
yield 'Including transactions in current fee bracket in Total MB amounts'
|
||||
|
||||
def fmt_mb(n):
|
||||
return int2bytespec(n,'MB',f'0.{precision}',print_sym=False)
|
||||
return int2bytespec(n, 'MB', f'0.{precision}', print_sym=False)
|
||||
|
||||
def gen_body(data):
|
||||
tx_bytes_max = max((i.tx_bytes for i in data),default=0)
|
||||
top_max = max((i.top for i in data),default=0)
|
||||
bot_max = max((i.bottom for i in data),default=0)
|
||||
col1_w = max(len(f'{bot_max}-{top_max}') if cfg.ranges else len(f'{top_max}'),6)
|
||||
tx_bytes_max = max((i.tx_bytes for i in data), default=0)
|
||||
top_max = max((i.top for i in data), default=0)
|
||||
bot_max = max((i.bottom for i in data), default=0)
|
||||
col1_w = max(len(f'{bot_max}-{top_max}') if cfg.ranges else len(f'{top_max}'), 6)
|
||||
col2_w = len(fmt_mb(tx_bytes_max)) if cfg.show_mb_col else 0
|
||||
col3_w = len(fmt_mb(data[-1].tx_bytes_cum)) if data else 0
|
||||
col4_w = width - col1_w - col2_w - col3_w - (4 if col2_w else 3)
|
||||
if cfg.show_mb_col:
|
||||
fs = '{a:<%i} {b:>%i} {c:>%i} {d}' % (col1_w,col2_w,col3_w)
|
||||
fs = '{a:<%i} {b:>%i} {c:>%i} {d}' % (col1_w, col2_w, col3_w)
|
||||
else:
|
||||
fs = '{a:<%i} {c:>%i} {d}' % (col1_w,col3_w)
|
||||
fs = '{a:<%i} {c:>%i} {d}' % (col1_w, col3_w)
|
||||
|
||||
yield fs.format(a='', b='', c=f'{"Total":<{col3_w}}', d='')
|
||||
yield fs.format(a='sat/B', b=f'{"MB":<{col2_w}}', c=f'{"MB":<{col3_w}}', d='')
|
||||
|
|
@ -188,16 +187,16 @@ def gen_body(data):
|
|||
if not i.skip:
|
||||
cum_bytes = i.tx_bytes_cum + i.tx_bytes if cfg.include_current else i.tx_bytes_cum
|
||||
yield fs.format(
|
||||
a = '{}-{}'.format(i.bottom,i.top) if cfg.ranges else i.top,
|
||||
a = '{}-{}'.format(i.bottom, i.top) if cfg.ranges else i.top,
|
||||
b = fmt_mb(i.tx_bytes),
|
||||
c = fmt_mb(cum_bytes),
|
||||
d = '-' * int(col4_w * ( i.tx_bytes / tx_bytes_max )) )
|
||||
d = '-' * int(col4_w * (i.tx_bytes / tx_bytes_max)))
|
||||
|
||||
yield fs.format(
|
||||
a = 'TOTAL',
|
||||
b = '',
|
||||
c = fmt_mb(data[-1].tx_bytes_cum + data[-1].tx_bytes if data else 0),
|
||||
d = '' )
|
||||
d = '')
|
||||
|
||||
async def main():
|
||||
|
||||
|
|
@ -205,19 +204,19 @@ async def main():
|
|||
proto = cfg._proto
|
||||
|
||||
from mmgen.rpc import rpc_init
|
||||
c = await rpc_init(cfg,ignore_wallet=True)
|
||||
c = await rpc_init(cfg, ignore_wallet=True)
|
||||
|
||||
mempool = await c.call('getrawmempool',True)
|
||||
mempool = await c.call('getrawmempool', True)
|
||||
|
||||
if cfg.log:
|
||||
log(mempool,'mempool.json')
|
||||
log(mempool, 'mempool.json')
|
||||
|
||||
data = create_data(proto.coin_amt,mempool)
|
||||
data = create_data(proto.coin_amt, mempool)
|
||||
cfg._util.stdout_or_pager(
|
||||
'\n'.join(gen_header(
|
||||
c.host,
|
||||
mempool,
|
||||
await c.call('getblockcount') )) + '\n\n' +
|
||||
'\n'.join(gen_body(data)) + '\n' )
|
||||
await c.call('getblockcount'))) + '\n\n' +
|
||||
'\n'.join(gen_body(data)) + '\n')
|
||||
|
||||
async_run(cfg, main)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ from mmgen.util import async_run
|
|||
bdr_proj = 9.95
|
||||
|
||||
opts_data = {
|
||||
'sets': [('mined',True,'list',True)],
|
||||
'sets': [('mined', True, 'list', True)],
|
||||
'text': {
|
||||
'desc': 'Estimate date(s) of future block subsidy halving(s)',
|
||||
'usage':'[opts]',
|
||||
|
|
@ -41,7 +41,7 @@ opts_data = {
|
|||
{bdr_proj:.5f} min)
|
||||
-s, --sample-size=N Block range to calculate block discovery interval for next
|
||||
halving estimate (default: dynamically calculated)
|
||||
""" }
|
||||
"""}
|
||||
}
|
||||
|
||||
cfg = Config(opts_data=opts_data)
|
||||
|
|
@ -53,14 +53,14 @@ def date(t):
|
|||
return '{}-{:02}-{:02} {:02}:{:02}:{:02}'.format(*time.gmtime(t)[:6])
|
||||
|
||||
def dhms(t):
|
||||
t,neg = (-t,'-') if t < 0 else (t,' ')
|
||||
t, neg = (-t, '-') if t < 0 else (t, ' ')
|
||||
return f'{neg}{t//60//60//24} days, {t//60//60%24:02}:{t//60%60:02}:{t%60:02} h/m/s'
|
||||
|
||||
def time_diff_warning(t_diff):
|
||||
if abs(t_diff) > 60*60:
|
||||
print('Warning: block tip time is {} {} clock time!'.format(
|
||||
dhms(abs(t_diff)),
|
||||
('behind','ahead of')[t_diff<0]))
|
||||
('behind', 'ahead of')[t_diff<0]))
|
||||
|
||||
async def main():
|
||||
|
||||
|
|
@ -72,9 +72,9 @@ async def main():
|
|||
tip = await c.call('getblockcount')
|
||||
assert tip > 1, 'block tip must be > 1'
|
||||
remaining = proto.halving_interval - tip % proto.halving_interval
|
||||
sample_size = int(cfg.sample_size) if cfg.sample_size else min(tip-1,max(remaining,144))
|
||||
sample_size = int(cfg.sample_size) if cfg.sample_size else min(tip-1, max(remaining, 144))
|
||||
|
||||
cur,old = await c.gathered_call('getblockstats',((tip,),(tip - sample_size,)))
|
||||
cur, old = await c.gathered_call('getblockstats', ((tip,), (tip - sample_size,)))
|
||||
|
||||
clock_time = int(time.time())
|
||||
time_diff_warning(clock_time - cur['time'])
|
||||
|
|
@ -98,8 +98,7 @@ async def main():
|
|||
f'Current block discovery interval (over last {sample_size} blocks): {bdr/60:0.2f} min\n\n'
|
||||
f'Current clock time (UTC): {date(clock_time)}\n'
|
||||
f'Est. halving date (UTC): {date(t_next)}\n'
|
||||
f'Est. time until halving: {dhms(cur["time"] + t_rem - clock_time)}'
|
||||
)
|
||||
f'Est. time until halving: {dhms(cur["time"] + t_rem - clock_time)}')
|
||||
|
||||
async def print_halvings():
|
||||
halving_blocknums = [i*proto.halving_interval for i in range(proto.max_halvings+1)][1:]
|
||||
|
|
@ -108,13 +107,13 @@ async def main():
|
|||
nhist = len(hist_halvings)
|
||||
nSubsidy = int(proto.start_subsidy / proto.coin_amt.satoshi)
|
||||
|
||||
block0_hash = await c.call('getblockhash',0)
|
||||
block0_date = (await c.call('getblock',block0_hash))['time']
|
||||
block0_hash = await c.call('getblockhash', 0)
|
||||
block0_date = (await c.call('getblock', block0_hash))['time']
|
||||
|
||||
def gen_data():
|
||||
total_mined = 0
|
||||
date = block0_date
|
||||
for n,blk in enumerate(halving_blocknums):
|
||||
for n, blk in enumerate(halving_blocknums):
|
||||
mined = (nSubsidy >> n) * proto.halving_interval
|
||||
if n == 0:
|
||||
mined -= nSubsidy # subtract unspendable genesis block subsidy
|
||||
|
|
@ -123,13 +122,11 @@ async def main():
|
|||
bdi = (
|
||||
(hist_halvings[n]['time'] - date) / (proto.halving_interval * 60) if n < nhist
|
||||
else bdr/60 if n == nhist
|
||||
else bdr_proj
|
||||
)
|
||||
else bdr_proj)
|
||||
date = (
|
||||
hist_halvings[n]['time'] if n < nhist
|
||||
else t_next + int((n - nhist) * halving_secs)
|
||||
)
|
||||
yield ( n, sub, blk, mined, total_mined, bdi, date )
|
||||
else t_next + int((n - nhist) * halving_secs))
|
||||
yield (n, sub, blk, mined, total_mined, bdi, date)
|
||||
if sub == 0:
|
||||
break
|
||||
|
||||
|
|
@ -150,8 +147,7 @@ async def main():
|
|||
e = 'BDI (mins)',
|
||||
f = 'SUBSIDY ({proto.coin})',
|
||||
g = f'MINED ({proto.coin})',
|
||||
h = f'TOTAL MINED ({proto.coin})'
|
||||
)
|
||||
h = f'TOTAL MINED ({proto.coin})')
|
||||
+ '\n'
|
||||
+ fs.format(
|
||||
a = '-' * 7,
|
||||
|
|
@ -159,22 +155,20 @@ async def main():
|
|||
c = '-' * 19,
|
||||
d = '-' * 2,
|
||||
e = '-' * 10,
|
||||
f = '-' * 13,
|
||||
f = '-' * 17,
|
||||
g = '-' * 17,
|
||||
h = '-' * 17
|
||||
)
|
||||
h = '-' * 17)
|
||||
+ '\n'
|
||||
+ '\n'.join(fs.format(
|
||||
a = n + 1,
|
||||
b = blk,
|
||||
c = date(t),
|
||||
d = ' P' if n > nhist else '' if n < nhist else ' E',
|
||||
e = f'{bdr:8.5f}',
|
||||
f = proto.coin_amt(sub, from_unit='satoshi').fmt(2, prec=8),
|
||||
g = proto.coin_amt(mined, from_unit='satoshi').fmt(8, prec=8),
|
||||
h = proto.coin_amt(total_mined, from_unit='satoshi').fmt(8, prec=8)
|
||||
) for n, sub, blk, mined, total_mined, bdr, t in gen_data())
|
||||
)
|
||||
a = n + 1,
|
||||
b = blk,
|
||||
c = date(t),
|
||||
d = ' P' if n > nhist else '' if n < nhist else ' E',
|
||||
e = f'{bdr:8.5f}',
|
||||
f = proto.coin_amt(sub, from_unit='satoshi').fmt(2, prec=8),
|
||||
g = proto.coin_amt(mined, from_unit='satoshi').fmt(8, prec=8),
|
||||
h = proto.coin_amt(total_mined, from_unit='satoshi').fmt(8, prec=8)
|
||||
) for n, sub, blk, mined, total_mined, bdr, t in gen_data()))
|
||||
|
||||
if cfg.list:
|
||||
await print_halvings()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
mmnode-netrate: Bitcoin daemon network rate monitor
|
||||
"""
|
||||
|
||||
import sys,time
|
||||
import sys, time
|
||||
|
||||
from mmgen.cfg import Config
|
||||
from mmgen.util import async_run
|
||||
|
|
@ -32,39 +32,38 @@ opts_data = {
|
|||
'options': """
|
||||
-h, --help Print this help message
|
||||
--, --longhelp Print help message for long options (common options)
|
||||
"""
|
||||
}
|
||||
"""}
|
||||
}
|
||||
|
||||
cfg = Config(opts_data=opts_data)
|
||||
|
||||
ERASE_LINE,CUR_UP = '\033[K','\033[1A'
|
||||
ERASE_LINE, CUR_UP = '\033[K', '\033[1A'
|
||||
|
||||
async def main():
|
||||
|
||||
from mmgen.rpc import rpc_init
|
||||
c = await rpc_init(cfg,ignore_wallet=True)
|
||||
c = await rpc_init(cfg, ignore_wallet=True)
|
||||
|
||||
async def get_data():
|
||||
d = await c.call('getnettotals')
|
||||
return [float(e) for e in (d['totalbytesrecv'],d['totalbytessent'],d['timemillis'])]
|
||||
return [float(e) for e in (d['totalbytesrecv'], d['totalbytessent'], d['timemillis'])]
|
||||
|
||||
rs,ss,ts = (None,None,None)
|
||||
rs, ss, ts = (None, None, None)
|
||||
while True:
|
||||
r,s,t = await get_data()
|
||||
r, s, t = await get_data()
|
||||
|
||||
if rs is not None:
|
||||
sys.stderr.write(
|
||||
'\rrcvd: {:9.2f} kB/s\nsent: {:9.2f} kB/s '.format(
|
||||
(r-rs)/(t-ts),
|
||||
(s-ss)/(t-ts) ))
|
||||
(s-ss)/(t-ts)))
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
if rs is not None:
|
||||
sys.stderr.write('{}{}{}'.format(ERASE_LINE,CUR_UP,ERASE_LINE))
|
||||
sys.stderr.write('{}{}{}'.format(ERASE_LINE, CUR_UP, ERASE_LINE))
|
||||
|
||||
rs,ss,ts = (r,s,t)
|
||||
rs, ss, ts = (r, s, t)
|
||||
|
||||
try:
|
||||
async_run(cfg, main)
|
||||
|
|
|
|||
|
|
@ -27,8 +27,7 @@ opts_data = {
|
|||
'options': """
|
||||
-h, --help Print this help message
|
||||
--, --longhelp Print help message for long options (common options)
|
||||
"""
|
||||
}
|
||||
"""}
|
||||
}
|
||||
|
||||
from mmgen.cfg import Config
|
||||
|
|
@ -37,9 +36,9 @@ cfg = Config(opts_data=opts_data)
|
|||
async def main():
|
||||
|
||||
from mmgen.rpc import rpc_init
|
||||
rpc = await rpc_init(cfg,ignore_wallet=True)
|
||||
rpc = await rpc_init(cfg, ignore_wallet=True)
|
||||
|
||||
from .PeerBlocks import BlocksDisplay,PeersDisplay
|
||||
from .PeerBlocks import BlocksDisplay, PeersDisplay
|
||||
blocks = BlocksDisplay(cfg)
|
||||
peers = PeersDisplay(cfg)
|
||||
|
||||
|
|
|
|||
|
|
@ -208,29 +208,26 @@ Customize output by editing the file
|
|||
|
||||
To add a portfolio, edit the file
|
||||
~/{pf_cfg}
|
||||
"""
|
||||
},
|
||||
"""},
|
||||
'code': {
|
||||
'options': lambda s: s.format(
|
||||
dfl_cachedir = os.path.relpath(dfl_cachedir,start=homedir),
|
||||
ds = fmt_dict(DataSource.get_sources(),fmt='equal_compact'),
|
||||
dfl_cachedir = os.path.relpath(dfl_cachedir, start=homedir),
|
||||
ds = fmt_dict(DataSource.get_sources(), fmt='equal_compact'),
|
||||
al = DataSource.coinpaprika.dfl_asset_limit,
|
||||
pc = fmt_list(Ticker.percent_cols,fmt='bare'),
|
||||
),
|
||||
pc = fmt_list(Ticker.percent_cols, fmt='bare')),
|
||||
'notes': lambda s: s.format(
|
||||
assets = fmt_list(assets_list_gen(cfg_in),fmt='col',indent=' '),
|
||||
cfg = os.path.relpath(cfg_in.cfg_file,start=homedir),
|
||||
pf_cfg = os.path.relpath(cfg_in.portfolio_file,start=homedir),
|
||||
assets = fmt_list(assets_list_gen(cfg_in), fmt='col', indent=' '),
|
||||
cfg = os.path.relpath(cfg_in.cfg_file, start=homedir),
|
||||
pf_cfg = os.path.relpath(cfg_in.portfolio_file, start=homedir),
|
||||
al = DataSource.coinpaprika.dfl_asset_limit,
|
||||
cc = src_cls['cc'](),
|
||||
fi = src_cls['fi'](),
|
||||
)
|
||||
fi = src_cls['fi']())
|
||||
}
|
||||
}
|
||||
|
||||
import os
|
||||
|
||||
from mmgen.util import fmt_list,fmt_dict
|
||||
from mmgen.util import fmt_list, fmt_dict
|
||||
from mmgen.cfg import Config
|
||||
from . import Ticker
|
||||
|
||||
|
|
@ -238,7 +235,7 @@ gcfg = Config(opts_data=opts_data, caller_post_init=True)
|
|||
|
||||
Ticker.make_cfg(gcfg)
|
||||
|
||||
from .Ticker import dfl_cachedir,homedir,DataSource,assets_list_gen,cfg_in,src_cls
|
||||
from .Ticker import dfl_cachedir, homedir, DataSource, assets_list_gen, cfg_in, src_cls
|
||||
|
||||
gcfg._post_init()
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ mmnode-txfind: Find a transaction in the blockchain or mempool
|
|||
import sys
|
||||
|
||||
from mmgen.cfg import Config
|
||||
from mmgen.util import msg,Msg,die,is_hex_str,async_run
|
||||
from mmgen.util import msg, Msg, die, is_hex_str, async_run
|
||||
|
||||
opts_data = {
|
||||
'text': {
|
||||
|
|
@ -48,29 +48,26 @@ msg_data = {
|
|||
'normal': {
|
||||
'none': 'Transaction not found in blockchain or mempool',
|
||||
'block': 'Transaction is in block {b} ({c} confirmations)',
|
||||
'mem': 'Transaction is in mempool',
|
||||
},
|
||||
'mem': 'Transaction is in mempool'},
|
||||
'quiet': {
|
||||
'none': 'None',
|
||||
'block': '{b} {c}',
|
||||
'mem': 'mempool',
|
||||
}
|
||||
}
|
||||
'mem': 'mempool'}}
|
||||
|
||||
async def main(txid):
|
||||
if len(txid) != 64 or not is_hex_str(txid):
|
||||
die(2,f'{txid}: invalid transaction ID')
|
||||
die(2, f'{txid}: invalid transaction ID')
|
||||
|
||||
if cfg.verbose:
|
||||
msg(f'TxID: {txid}')
|
||||
|
||||
from mmgen.rpc import rpc_init
|
||||
c = await rpc_init(cfg,ignore_wallet=True)
|
||||
c = await rpc_init(cfg, ignore_wallet=True)
|
||||
|
||||
exitval = 0
|
||||
try:
|
||||
tip1 = await c.call('getblockcount')
|
||||
ret = await c.call('getrawtransaction',txid,True)
|
||||
ret = await c.call('getrawtransaction', txid, True)
|
||||
tip2 = await c.call('getblockcount')
|
||||
except:
|
||||
Msg('\r' + msgs['none'])
|
||||
|
|
@ -90,6 +87,6 @@ cfg = Config(opts_data=opts_data)
|
|||
msgs = msg_data['quiet' if cfg.quiet else 'normal']
|
||||
|
||||
if len(cfg._args) != 1:
|
||||
die(1,'One transaction ID must be specified')
|
||||
die(1, 'One transaction ID must be specified')
|
||||
|
||||
sys.exit(async_run(cfg, main, args=[cfg._args[0]]))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue