diff --git a/mmgen_node_tools/BlocksInfo.py b/mmgen_node_tools/BlocksInfo.py index 28395b4..27dfa08 100755 --- a/mmgen_node_tools/BlocksInfo.py +++ b/mmgen_node_tools/BlocksInfo.py @@ -23,7 +23,6 @@ mmgen_node_tools.BlocksInfo: Display information about a block or range of block import re,json from collections import namedtuple from time import strftime,gmtime -from decimal import Decimal from mmgen.common import * from mmgen.rpc import json_encoder @@ -46,14 +45,14 @@ class BlocksInfo: '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( None, 'bs', '{:>3}', '90%', 'Fee', 'feerate_percentiles', 4 ), - 'fee75': bf( None, 'bs', '{:>3}', '75%', 'Fee', 'feerate_percentiles', 3 ), - 'fee50': bf( None, 'bs', '{:>3}', '50%', 'Fee', 'feerate_percentiles', 2 ), - 'fee25': bf( None, 'bs', '{:>3}', '25%', 'Fee', 'feerate_percentiles', 1 ), - 'fee10': bf( None, 'bs', '{:>3}', '10%', 'Fee', 'feerate_percentiles', 0 ), - 'fee_max': bf( None, 'bs', '{:>5}', 'Max', 'Fee', 'maxfeerate', None ), - 'fee_avg': bf( None, 'bs', '{:>3}', 'Avg', 'Fee', 'avgfeerate', None ), - 'fee_min': bf( None, 'bs', '{:>3}', 'Min', 'Fee', 'minfeerate', 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 ), @@ -101,24 +100,6 @@ class BlocksInfo: noindent_stats = ['col_avg'] avg_stats_skip = {'block', 'hash', 'date', 'version','miner'} - stats_deps = { - 'avg': set(fields) - avg_stats_skip, - 'col_avg':set(fields) - avg_stats_skip, - 'mini_avg':{'interval','size','weight'}, - 'total': {'interval','subsidy','totalfee','nTx','inputs','outputs','utxo_inc'}, - 'range': {}, - 'diff': {}, - } - - fmt_funcs = { - '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) ), - 'tf': lambda arg: '{:.8f}'.format(arg * Decimal('0.00000001')), - 'su': lambda arg: str(arg * Decimal('0.00000001')).rstrip('0').rstrip('.'), - 'di': lambda arg: '{:.2e}'.format(arg), - } range_data = namedtuple('parsed_range_data',['first','last','from_tip','nblocks','step']) @@ -186,8 +167,44 @@ class BlocksInfo: self.opt = opt self.tip = rpc.blockcount + 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() + have_segwit = self.rpc.info('segwit_is_active') + + if not have_segwit: + del self.fields['weight'] + self.dfl_fields = tuple(f for f in self.dfl_fields if f != 'weight') + + 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'}, + 'range': {}, + 'diff': {}, + } + + self.fmt_funcs = { + '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) ), + '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(arg), + } + + if g.coin == 'BCH': + self.fmt_funcs.update({ + 'su': lambda arg: str(arg).rstrip('0').rstrip('.'), + 'fe': lambda arg: str(int(arg * to_satoshi)), + 'tf': lambda arg: '{:.8f}'.format(arg), + }) + self.fnames = tuple( [f for f in self.fields if self.fields[f].src == 'bh' or f == 'interval'] if opt.header_info else get_fields() if opt.fields else @@ -222,7 +239,8 @@ class BlocksInfo: self.bs_keys = set( [v.key1 for v in self.fvals if v.src == 'bs'] + - ['total_size','total_weight'] + ['total_size'] + + (['total_weight'] if have_segwit else []) ) if 'miner' in self.fnames: @@ -406,7 +424,8 @@ class BlocksInfo: await self.rpc.call('getblockstats',hdr['hash'],list(self.bs_keys)) ) self.total_bytes += bs['total_size'] - self.total_weight += bs['total_weight'] + if 'total_weight' in bs: + self.total_weight += bs['total_weight'] blk_data['bs'] = bs if 'miner' in self.fnames: @@ -506,7 +525,7 @@ class BlocksInfo: async def create_diff_stats(self): c = self.rpc - rel = self.tip % 2016 + rel = self.tip % self.rpc.proto.diff_adjust_interval tip_hdr = ( self.hdrs[-1] if self.hdrs[-1]['height'] == self.tip else @@ -520,14 +539,14 @@ class BlocksInfo: sample_blks = rel bdi = ( tip_hdr['time'] - rel_hdr['time'] ) / rel else: - sample_blks = min_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 = float(tip_hdr['difficulty'] / start_hdr['difficulty']) time1 = rel_hdr['time'] - start_hdr['time'] time2 = tip_hdr['time'] - rel_hdr['time'] bdi = ((time1 * diff_adj) + time2) / sample_blks - rem = 2016 - rel + rem = self.rpc.proto.diff_adjust_interval - rel return ( 'difficulty', ( 'Difficulty Statistics:', @@ -572,13 +591,15 @@ class BlocksInfo: spec_convs = { 'interval': spec_conv(0, lambda arg: secs_to_ms(arg)), 'utxo_inc': spec_conv(-1, '{:<+}'), - 'mb_per_hour': spec_conv(0, '{:.4f}'), + 'mb_per_hour': spec_conv(0, '{}'), }, spec_vals = ( spec_val( 'mb_per_hour', 'MB/hr', 'interval', lambda values: 'bs' in self.deps, - lambda values: (self.total_bytes / 10000) / (self.total_solve_time / 36) + lambda values: ( + '{:.4f}'.format((self.total_bytes / 10000) / (self.total_solve_time / 36)) + if self.total_solve_time else 'N/A' ), ), ) ) diff --git a/mmgen_node_tools/data/version b/mmgen_node_tools/data/version index 58dfba3..2d22ff0 100644 --- a/mmgen_node_tools/data/version +++ b/mmgen_node_tools/data/version @@ -1 +1 @@ -3.1.dev7 +3.1.dev8 diff --git a/mmgen_node_tools/main_blocks_info.py b/mmgen_node_tools/main_blocks_info.py index 117da98..9295cd0 100755 --- a/mmgen_node_tools/main_blocks_info.py +++ b/mmgen_node_tools/main_blocks_info.py @@ -148,6 +148,11 @@ This program requires a txindex-enabled daemon for correct operation. f = fmt_list(BlocksInfo.fields,fmt='bare'), s = fmt_list(BlocksInfo.all_stats,fmt='bare'), p = g.prog_name ) + }, + 'code': { + 'notes': lambda proto,s: s.format( + adj_interval = proto.diff_adjust_interval, + ) } } @@ -158,7 +163,7 @@ async def main(): from mmgen.protocol import init_proto_from_opts from mmgen.rpc import rpc_init - proto = init_proto_from_opts() + proto = init_proto_from_opts(need_amt=True) cls = JSONBlocksInfo if opt.json else BlocksInfo diff --git a/setup.cfg b/setup.cfg index f3d9a66..327cad7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,7 +23,7 @@ python_requires = >=3.7 include_package_data = True install_requires = - mmgen>=13.3.dev6 + mmgen>=13.3.dev8 packages = mmgen_node_tools diff --git a/test/test-release.d/cfg.sh b/test/test-release.d/cfg.sh index 53dd32a..59db1dd 100755 --- a/test/test-release.d/cfg.sh +++ b/test/test-release.d/cfg.sh @@ -10,7 +10,7 @@ # Testing status # mmnode-addrbal OK -# mmnode-blocks-info - +# mmnode-blocks-info OK # mmnode-feeview - # mmnode-halving-calculator OK # mmnode-netrate - diff --git a/test/test_py_d/ts_regtest.py b/test/test_py_d/ts_regtest.py index 4fb3eec..d6b037e 100755 --- a/test/test_py_d/ts_regtest.py +++ b/test/test_py_d/ts_regtest.py @@ -45,6 +45,7 @@ class TestSuiteRegtest(TestSuiteBase): ('subgroup.halving_calculator', []), ('subgroup.fund_addrbal', []), ('subgroup.addrbal', ['fund_addrbal']), + ('subgroup.blocks_info', ['addrbal']), ('stop', 'stopping regtest daemon'), ) cmd_subgroups = { @@ -74,7 +75,14 @@ class TestSuiteRegtest(TestSuiteBase): ('addrbal_nobal3', 'getting address balances (one null balance)'), ('addrbal_nobal3_tabular1', 'getting address balances (one null balance, tabular output)'), ('addrbal_nobal3_tabular2', 'getting address balances (one null balance, tabular, show first block)'), - ) + ), + 'blocks_info': ( + "'mmnode-blocks-info' script", + ('blocks_info1', "blocks-info (--help)"), + ('blocks_info2', "blocks-info (no args)"), + ('blocks_info3', "blocks-info +100"), + ('blocks_info4', "blocks-info --miner-info --fields=all --stats=all +1"), + ), } def __init__(self,trunner,cfgs,spawn): @@ -86,11 +94,6 @@ class TestSuiteRegtest(TestSuiteBase): self.proto = init_proto(self.proto.coin,network='regtest',need_amt=True) self.addrs = gen_addrs(self.proto,'regtest',[1,2,3,4,5]) - os.environ['MMGEN_TEST_SUITE_REGTEST'] = '1' - - def __del__(self): - os.environ['MMGEN_TEST_SUITE_REGTEST'] = '' - def setup(self): stop_test_daemons(self.proto.network_id,force=True,remove_datadir=True) from shutil import rmtree @@ -207,6 +210,41 @@ class TestSuiteRegtest(TestSuiteBase): self.addrs[3] + ' - - - -', ]) + def blocks_info(self,args,expect_list): + t = self.spawn('mmnode-blocks-info',args) + t.match_expect_list(expect_list) + return t + + def blocks_info1(self): + return self.blocks_info( args1 + ['--help'], ['USAGE:','OPTIONS:']) + + def blocks_info2(self): + return self.blocks_info( args1, [ + 'Current height: 396', + ]) + + def blocks_info3(self): + return self.blocks_info( args1 + ['+100'], [ + 'Range: 297-396', + 'Current height: 396', + 'Next diff adjust: 2016' + ]) + + def blocks_info4(self): + n1,i1,o1,n2,i2,o2 = (2,1,3,6,3,9) if g.coin == 'BCH' else (2,1,4,6,3,12) + return self.blocks_info( args1 + ['--miner-info','--fields=all','--stats=all','+3'], [ + 'Averages', + f'nTx: {n1}', + f'Inputs: {i1}', + f'Outputs: {o1}', + 'Totals', + f'nTx: {n2}', + f'Inputs: {i2}', + f'Outputs: {o2}', + 'Current height: 396', + 'Next diff adjust: 2016' + ]) + def stop(self): if opt.no_daemon_stop: self.spawn('',msg_only=True) diff --git a/test/unit_tests_d/ut_BlocksInfo.py b/test/unit_tests_d/ut_BlocksInfo.py index 1448e97..e83cca0 100755 --- a/test/unit_tests_d/ut_BlocksInfo.py +++ b/test/unit_tests_d/ut_BlocksInfo.py @@ -65,6 +65,11 @@ fields_vecs = ( class dummyRPC: blockcount = tip + def info(self,arg): + return True + class proto: + class coin_amt: + satoshi = 0.00000001 class dummyOpt: fields = None