Browse Source

mmnode-blocks-info: support BCH; add test

The MMGen Project 2 years ago
parent
commit
8ca7f43ae8

+ 55 - 34
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' ),
 				),
 			)
 		)

+ 1 - 1
mmgen_node_tools/data/version

@@ -1 +1 @@
-3.1.dev7
+3.1.dev8

+ 6 - 1
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
 

+ 1 - 1
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

+ 1 - 1
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             -

+ 44 - 6
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)

+ 5 - 0
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