The MMGen Project 1 год назад
Родитель
Сommit
30d8a4a871

+ 19 - 19
mmgen_node_tools/BlocksInfo.py

@@ -109,7 +109,7 @@ class BlocksInfo:
 	def parse_cslist(cls,uarg,full_set,dfl_set,desc):
 
 		def make_list(m,func):
-			groups_lc = [set(e.lower() for e in g.split(',')) for g in m.groups()]
+			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:
@@ -135,7 +135,7 @@ class BlocksInfo:
 		else:
 			die(1,f'{uarg}: invalid parameter')
 
-	def __init__(self,cmd_args,opt,rpc):
+	def __init__(self,cfg,cmd_args,rpc):
 
 		def parse_cs_uarg(uarg,full_set,dfl_set,desc):
 			return (
@@ -144,10 +144,10 @@ class BlocksInfo:
 			)
 
 		def get_fields():
-			return parse_cs_uarg(opt.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(opt.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)
 			if not cmd_args:
@@ -163,8 +163,8 @@ class BlocksInfo:
 			else:
 				return ([self.conv_blkspec(a) for a in cmd_args],None,None,None)
 
+		self.cfg = cfg
 		self.rpc = rpc
-		self.opt = opt
 		self.tip = rpc.blockcount
 
 		from_satoshi = self.rpc.proto.coin_amt.satoshi
@@ -198,7 +198,7 @@ class BlocksInfo:
 			'di': lambda arg: '{:.2e}'.format(arg),
 		}
 
-		if g.coin == 'BCH':
+		if self.cfg.coin == 'BCH':
 			self.fmt_funcs.update({
 				'su': lambda arg: str(arg).rstrip('0').rstrip('.'),
 				'fe': lambda arg: str(int(arg * to_satoshi)),
@@ -206,26 +206,26 @@ class BlocksInfo:
 			})
 
 		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
+			[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 opt.miner_info and 'miner' not in self.fnames:
+		if self.cfg.miner_info and 'miner' not in self.fnames:
 			self.fnames += ('miner',)
 
-		self.stats = get_stats() if opt.stats else self.dfl_stats
+		self.stats = get_stats() if self.cfg.stats else self.dfl_stats
 
 		# Display diff stats by default only if user-requested range ends with chain tip
-		if 'diff' in self.stats and not opt.stats and self.last != self.tip:
+		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 opt.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):
 			self.stats.remove('mini_avg')
 
-		if opt.full_stats:
+		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 )
 		else:
@@ -397,7 +397,7 @@ class BlocksInfo:
 				self.t_cur = self.prev_hdrs[n]['time']
 			ret = await self.process_block(self.hdrs[n])
 			self.res.append(ret)
-			if self.fnames and not self.opt.stats_only:
+			if self.fnames and not self.cfg.stats_only:
 				self.output_block(ret,n)
 
 	def output_block(self,data,n):
@@ -447,7 +447,7 @@ class BlocksInfo:
 			return '---'
 		else:
 			cb = bytes.fromhex(bd['vin'][0]['coinbase'])
-			if self.opt.raw_miner_info:
+			if self.cfg.raw_miner_info:
 				return repr(cb)
 			else:
 				trmap_in = {
@@ -670,7 +670,7 @@ class BlocksInfo:
 		return ( sname, (d.hdr,) + tuple(gen()) )
 
 	def process_stats_pre(self,i):
-		if (self.fnames and not self.opt.stats_only) or i != 0:
+		if (self.fnames and not self.cfg.stats_only) or i != 0:
 			Msg('')
 
 	def finalize_output(self): pass
@@ -707,9 +707,9 @@ class BlocksInfo:
 
 class JSONBlocksInfo(BlocksInfo):
 
-	def __init__(self,cmd_args,opt,rpc):
-		super().__init__(cmd_args,opt,rpc)
-		if opt.json_raw:
+	def __init__(self,cfg,cmd_args,opt,rpc):
+		super().__init__(cfg,cmd_args,opt,rpc)
+		if self.cfg.json_raw:
 			self.output_block = self.output_block_raw
 			self.fmt_stat_item = self.fmt_stat_item_raw
 		Msg_r('{')

+ 4 - 5
mmgen_node_tools/PeerBlocks.py

@@ -14,7 +14,6 @@ mmgen_node_tools.PeerBlocks: List blocks in flight, disconnect stalling nodes
 
 import asyncio
 from collections import namedtuple
-from mmgen.globalvars import g
 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
@@ -30,15 +29,15 @@ class Display(PollDisplay):
 
 	poll_secs = 2
 
-	def __init__(self):
+	def __init__(self,cfg):
 
-		super().__init__()
+		super().__init__(cfg)
 
 		global term,term_width
 		if not term:
 			term = get_term()
 			term.init(noecho=True)
-			term_width = g.columns or get_terminal_size().width
+			term_width = self.cfg.columns or get_terminal_size().width
 			msg_r(CUR_HOME+ERASE_ALL+CUR_HOME)
 
 	async def get_info(self,rpc):
@@ -69,7 +68,7 @@ class Display(PollDisplay):
 			msg('')
 			term.reset()
 			# readline required for correct operation here; without it, user must re-type first digit
-			ret = line_input('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

+ 2 - 1
mmgen_node_tools/PollDisplay.py

@@ -22,7 +22,8 @@ class PollDisplay():
 	input = None
 	poll_secs = 1
 
-	def __init__(self):
+	def __init__(self,cfg):
+		self.cfg = cfg
 		self.info_lock = threading.Lock() # self.info accessed by 2 threads
 		self.display_kill_flag = threading.Event()
 

+ 37 - 39
mmgen_node_tools/Ticker.py

@@ -28,10 +28,8 @@ import sys,os,time,json,yaml
 from subprocess import run,PIPE,CalledProcessError
 from decimal import Decimal
 from collections import namedtuple
-from mmgen.opts import opt
-from mmgen.globalvars import g
 from mmgen.color import *
-from mmgen.util import die,fmt_list,msg,msg_r,Msg,vmsg,suf,fmt,stdout_or_pager
+from mmgen.util import die,fmt_list,msg,msg_r,suf,fmt
 
 homedir = os.getenv('HOME')
 cachedir = os.path.join(homedir,'.cache','mmgen-node-tools')
@@ -173,23 +171,23 @@ def get_src_data(curl_cmd):
 
 	if cfg.btc_only:
 		fn = os.path.join(cfg.cachedir,'ticker-btc.json')
-		timeout = 5 if g.test_suite else btc_ratelimit
+		timeout = 5 if gcfg.test_suite else btc_ratelimit
 	else:
 		fn = os.path.join(cfg.cachedir,'ticker.json')
-		timeout = 5 if g.test_suite else ratelimit
+		timeout = 5 if gcfg.test_suite else ratelimit
 
 	fn_rel = os.path.relpath(fn,start=homedir)
 
 	if not os.path.exists(fn):
 		open(fn,'w').write('{}')
 
-	if opt.cached_data:
+	if gcfg.cached_data:
 		json_text = open(fn).read()
 	else:
 		elapsed = int(time.time() - os.stat(fn).st_mtime)
 		if elapsed >= timeout:
 			msg_r(f'Fetching data from {api_host}...')
-			vmsg('')
+			gcfg._util.vmsg('')
 			try:
 				cp = run(curl_cmd,check=True,stdout=PIPE)
 			except CalledProcessError as e:
@@ -212,14 +210,14 @@ def get_src_data(curl_cmd):
 		die(2,'Retrieved data is not valid JSON, exiting')
 
 	if not data:
-		if opt.cached_data:
+		if gcfg.cached_data:
 			die(1,'No cached data!  Run command without --cached-data option to retrieve data from remote host')
 		else:
 			die(2,'Remote host returned no data!')
 	elif 'error' in data:
 		die(1,data['error'])
 
-	if opt.cached_data:
+	if gcfg.cached_data:
 		msg(f'Using cached data from ~/{fn_rel}')
 	else:
 		open(fn,'w').write(json_text)
@@ -248,7 +246,7 @@ def main(cfg_parm,cfg_in_parm):
 					'--header', 'Accept: application/json',
 				] +
 				(['--proxy', cfg.proxy] if cfg.proxy else []) +
-				(['--silent'] if not opt.verbose else []) +
+				(['--silent'] if not gcfg.verbose else []) +
 				[api_url + ('/btc-bitcoin' if cfg.btc_only else '')]
 			)
 
@@ -264,27 +262,27 @@ def main(cfg_parm,cfg_in_parm):
 	update_sample_file(cfg_in.cfg_file)
 	update_sample_file(cfg_in.portfolio_file)
 
-	if opt.portfolio and not cfg_in.portfolio:
+	if gcfg.portfolio and not cfg_in.portfolio:
 		die(1,'No portfolio configured!\nTo configure a portfolio, edit the file ~/{}'.format(
 			os.path.relpath(cfg_in.portfolio_file,start=homedir)))
 
 	curl_cmd = get_curl_cmd()
 
-	if opt.print_curl:
+	if gcfg.print_curl:
 		Msg(curl_cmd + '\n' + ' '.join(curl_cmd))
 		return
 
 	parsed_json = [get_src_data(curl_cmd)] if cfg.btc_only else get_src_data(curl_cmd)
 
-	if opt.list_ids:
+	if gcfg.list_ids:
 		from mmgen.ui import do_pager
 		do_pager('\n'.join(e['id'] for e in parsed_json))
 		return
 
 	global now
-	now = 1659465400 if g.test_suite else time.time() # 1659524400 1659445900
+	now = 1659465400 if gcfg.test_suite else time.time() # 1659524400 1659445900
 
-	stdout_or_pager(
+	gcfg._util.stdout_or_pager(
 		'\n'.join(getattr(Ticker,cfg.clsname)(dict(gen_data(parsed_json))).gen_output()) + '\n'
 	)
 
@@ -366,12 +364,12 @@ def make_cfg(cmd_args,cfg_in):
 			usr_columns )
 
 	def get_portfolio_assets(ret=()):
-		if cfg_in.portfolio and opt.portfolio:
+		if cfg_in.portfolio and gcfg.portfolio:
 			ret = (parse_asset_id(e,True) for e in cfg_in.portfolio)
-		return ( 'portfolio', tuple(e for e in ret if (not opt.btc) or e.symbol == 'BTC') )
+		return ( 'portfolio', tuple(e for e in ret if (not gcfg.btc) or e.symbol == 'BTC') )
 
 	def get_portfolio():
-		return {k:Decimal(v) for k,v in cfg_in.portfolio.items() if (not opt.btc) or k == 'btc-bitcoin'}
+		return {k:Decimal(v) for k,v in cfg_in.portfolio.items() if (not gcfg.btc) or k == 'btc-bitcoin'}
 
 	def parse_add_precision(s):
 		if not s:
@@ -385,8 +383,8 @@ def make_cfg(cmd_args,cfg_in):
 	def create_rows():
 		rows = (
 			('trade_pair',) + query if (query and query.to_asset) else
-			('bitcoin',parse_asset_id('btc-bitcoin')) if opt.btc else
-			get_rows_from_cfg( add_data={'fiat':['usd-us-dollar']} if opt.add_columns else None )
+			('bitcoin',parse_asset_id('btc-bitcoin')) if gcfg.btc else
+			get_rows_from_cfg( add_data={'fiat':['usd-us-dollar']} if gcfg.add_columns else None )
 		)
 
 		for hdr,data in (
@@ -416,8 +414,8 @@ def make_cfg(cmd_args,cfg_in):
 	asset_data    = namedtuple('asset_data',['symbol','id','amount','rate','rate_asset'])
 	asset_tuple   = namedtuple('asset_tuple',['symbol','id'])
 
-	usr_rows    = parse_usr_asset_arg(opt.add_rows)
-	usr_columns = parse_usr_asset_arg(opt.add_columns)
+	usr_rows    = parse_usr_asset_arg(gcfg.add_rows)
+	usr_columns = parse_usr_asset_arg(gcfg.add_columns)
 	query       = parse_query_arg(cmd_args[0]) if cmd_args else None
 
 	return cfg_tuple(
@@ -425,19 +423,19 @@ def make_cfg(cmd_args,cfg_in):
 		usr_rows    = usr_rows,
 		usr_columns = usr_columns,
 		query       = query,
-		adjust      = ( lambda x: (100 + x) / 100 if x else 1 )( Decimal(opt.adjust or 0) ),
+		adjust      = ( lambda x: (100 + x) / 100 if x else 1 )( Decimal(gcfg.adjust or 0) ),
 		clsname     = 'trading' if query else 'overview',
-		btc_only    = opt.btc,
-		add_prec    = parse_add_precision(opt.add_precision),
-		cachedir    = opt.cachedir or cfg_in.cfg.get('cachedir') or cachedir,
-		proxy       = None if opt.proxy == '' else (opt.proxy or cfg_in.cfg.get('proxy')),
-		portfolio   = get_portfolio() if cfg_in.portfolio and opt.portfolio and not query else None
+		btc_only    = gcfg.btc,
+		add_prec    = parse_add_precision(gcfg.add_precision),
+		cachedir    = gcfg.cachedir or cfg_in.cfg.get('cachedir') or cachedir,
+		proxy       = None if gcfg.proxy == '' else (gcfg.proxy or cfg_in.cfg.get('proxy')),
+		portfolio   = get_portfolio() if cfg_in.portfolio and gcfg.portfolio and not query else None
 	)
 
 def get_cfg_in():
 	ret = namedtuple('cfg_in_data',['cfg','portfolio','cfg_file','portfolio_file'])
 	cfg_file,portfolio_file = (
-		[os.path.join(g.data_dir_root,'node_tools',fn) for fn in (cfg_fn,portfolio_fn)]
+		[os.path.join(gcfg.data_dir_root,'node_tools',fn) for fn in (cfg_fn,portfolio_fn)]
 	)
 	cfg_data,portfolio_data = (
 		[yaml.safe_load(open(fn).read()) if os.path.exists(fn) else None for fn in (cfg_file,portfolio_file)]
@@ -466,10 +464,10 @@ class Ticker:
 
 		def __init__(self,data):
 
-			self.comma = ',' if opt.thousands_comma else ''
+			self.comma = ',' if gcfg.thousands_comma else ''
 
 			self.col1_wid = max(len('TOTAL'),(
-				max(len(self.create_label(d['id'])) for d in data.values()) if opt.name_labels else
+				max(len(self.create_label(d['id'])) for d in data.values()) if gcfg.name_labels else
 				max(len(d['symbol']) for d in data.values())
 			)) + 1
 
@@ -482,7 +480,7 @@ class Ticker:
 
 		def format_last_update_col(self,cross_assets=()):
 
-			if opt.elapsed:
+			if gcfg.elapsed:
 				from mmgen.util2 import format_elapsed_hr
 				fmt_func = format_elapsed_hr
 			else:
@@ -627,7 +625,7 @@ class Ticker:
 					amt_fmt = amt_fmt.rstrip('0').rstrip('.')
 
 			return self.fs_num.format(
-				lbl = (self.create_label(d['id']) if opt.name_labels else d['symbol']),
+				lbl = (self.create_label(d['id']) if gcfg.name_labels else d['symbol']),
 				pc1 = fmt_pct(d.get('percent_change_7d')),
 				pc2 = fmt_pct(d.get('percent_change_24h')),
 				upd = d.get('last_updated_fmt'),
@@ -670,13 +668,13 @@ class Ticker:
 				[asset.id for asset in self.usr_col_assets] +
 				[a for a,b in (
 					( 'btc-bitcoin',  not cfg.btc_only ),
-					( 'pct7d', opt.percent_change ),
-					( 'pct24h', opt.percent_change ),
-					( 'update_time', opt.update_time ),
+					( 'pct7d', gcfg.percent_change ),
+					( 'pct24h', gcfg.percent_change ),
+					( 'update_time', gcfg.update_time ),
 				) if b]
 			)
 			cols2 = list(cols)
-			if opt.update_time:
+			if gcfg.update_time:
 				cols2.pop()
 			cols2.append('amt')
 
@@ -759,7 +757,7 @@ class Ticker:
 			if self.show_adj:
 				self.fs_str += ' {p_adj}'
 				self.hl_wid += self.max_wid + 1
-			if opt.update_time:
+			if gcfg.update_time:
 				self.fs_str += '  {upd}'
 				self.hl_wid += self.upd_w + 2
 
@@ -772,7 +770,7 @@ class Ticker:
 				if self.show_adj else '' )
 
 			return self.fs_str.format(
-				lbl = (self.create_label(id) if opt.name_labels else d['symbol']),
+				lbl = (self.create_label(id) if gcfg.name_labels else d['symbol']),
 				p_spot = green(p_spot) if id in self.hl_ids else p_spot,
 				p_adj  = yellow(p_adj) if id in self.hl_ids else p_adj,
 				upd = d.get('last_updated_fmt'),

+ 1 - 1
mmgen_node_tools/data/version

@@ -1 +1 @@
-3.1.dev14
+3.1.dev15

+ 8 - 9
mmgen_node_tools/main_addrbal.py

@@ -71,7 +71,7 @@ def do_output_tabular(proto,addr_data,blk_hdrs):
 
 	fs = (
 		' {n:>%s} {a} {u} {b:>%s} {t:19}  {B:>%s} {T:19} {A}' % (col1w,max(5,fb_w),max(4,lb_w))
-			if opt.first_block else
+			if cfg.first_block else
 		' {n:>%s} {a} {u} {B:>%s} {T:19} {A}' % (col1w,max(4,lb_w)) )
 
 	Msg('\n' + fs.format(
@@ -109,20 +109,19 @@ def do_output_tabular(proto,addr_data,blk_hdrs):
 
 async def main(req_addrs):
 
-	from mmgen.protocol import init_proto_from_opts
-	proto = init_proto_from_opts(need_amt=True)
+	proto = cfg._proto
 
 	from mmgen.addr import CoinAddr
 	addrs = [CoinAddr(proto,addr) for addr in req_addrs]
 
 	from mmgen.rpc import rpc_init
-	rpc = await rpc_init(proto)
+	rpc = await rpc_init( cfg, proto )
 
 	height = await rpc.call('getblockcount')
 	Msg(f'{proto.coin} {proto.network.upper()} [height {height}]')
 
 	from mmgen.proto.btc.misc import scantxoutset
-	res = await scantxoutset( 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')
@@ -155,16 +154,16 @@ async def main(req_addrs):
 		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 opt.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)) )
 
-cmd_args = opts.init(opts_data,init_opts={'rpc_backend':'aiohttp'})
+cfg = opts.init(opts_data,init_opts={'rpc_backend':'aiohttp'})
 
-if len(cmd_args) < 1:
+if len(cfg._args) < 1:
 	die(1,'This command requires at least one coin address argument')
 
 from mmgen.obj import CoinTxID,Int
 
 try:
-	async_run(main(cmd_args))
+	async_run(main(cfg._args))
 except KeyboardInterrupt:
 	sys.stderr.write('\n')

+ 9 - 10
mmgen_node_tools/main_blocks_info.py

@@ -20,7 +20,9 @@
 mmnode-blocks-info: Display information about a block or range of blocks
 """
 
-from mmgen.common import *
+import mmgen.opts as opts
+from mmgen.globalvars import gc
+from mmgen.util import async_run,fmt_list
 from .BlocksInfo import BlocksInfo,JSONBlocksInfo
 
 opts_data = {
@@ -146,29 +148,26 @@ EXAMPLES:
 This program requires a txindex-enabled daemon for correct operation.
 """ },
 	'code': {
-		'notes': lambda 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 = g.prog_name,
+			p = gc.prog_name,
 		)
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
 async def main():
 
-	from mmgen.protocol import init_proto_from_opts
 	from mmgen.rpc import rpc_init
 
-	proto = init_proto_from_opts(need_amt=True)
+	cls = JSONBlocksInfo if cfg.json else BlocksInfo
 
-	cls = JSONBlocksInfo if opt.json else BlocksInfo
+	m = cls( cfg, cfg._args, await rpc_init(cfg,cfg._proto) )
 
-	m = cls( cmd_args, opt, await rpc_init(proto) )
-
-	if m.fnames and not opt.no_header:
+	if m.fnames and not cfg.no_header:
 		m.print_header()
 
 	await m.process_blocks()

+ 22 - 22
mmgen_node_tools/main_feeview.py

@@ -39,7 +39,7 @@ fee_brackets = [
 	100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 2100000000000000,
 ]
 
-opts.init({
+cfg = opts.init({
 	'sets': [
 		('detail',True,'ranges',True),
 		('detail',True,'show_mb_col',True),
@@ -78,17 +78,17 @@ factors.
 }
 })
 
-if opt.ignore_below:
-	if opt.show_empty:
+if cfg.ignore_below:
+	if cfg.show_empty:
 		die(1,'Conflicting options: --ignore-below, --show-empty')
-	ignore_below = parse_bytespec(opt.ignore_below)
+	ignore_below = parse_bytespec(cfg.ignore_below)
 
 precision = (
-	check_int_between(opt.precision,min_prec,max_prec,'--precision arg')
-	if opt.precision else dfl_prec )
+	check_int_between(cfg.precision,min_prec,max_prec,'--precision arg')
+	if cfg.precision else dfl_prec )
 
 from mmgen.term import get_terminal_size
-width = g.columns or get_terminal_size().width
+width = cfg.columns or get_terminal_size().width
 
 class fee_bracket:
 	def __init__(self,top,bottom):
@@ -103,6 +103,7 @@ def log(data,fn):
 	from mmgen.rpc 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),
 		desc = 'mempool',
@@ -130,9 +131,9 @@ def create_data(coin_amt,mempool):
 	# calculate cumulative byte totals, filter rows:
 	tBytes = 0
 	for i in out:
-		if not (i.tx_bytes or opt.show_empty):
+		if not (i.tx_bytes or cfg.show_empty):
 			i.skip = True
-		if opt.ignore_below and i.tx_bytes < ignore_below:
+		if cfg.ignore_below and i.tx_bytes < ignore_below:
 			i.skip = True
 		i.tx_bytes_cum = tBytes
 		tBytes += i.tx_bytes
@@ -150,15 +151,15 @@ def gen_header(host,mempool,blockcount):
 		TX count: {len(mempool)}
 		""")).strip()
 
-	if opt.show_empty:
+	if cfg.show_empty:
 		yield('Displaying all fee brackets')
-	elif opt.ignore_below:
+	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),
 			))
 
-	if opt.include_current:
+	if cfg.include_current:
 		yield('Including transactions in current fee bracket in Total MB amounts')
 
 def fmt_mb(n):
@@ -168,11 +169,11 @@ 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 opt.ranges else len(f'{top_max}'),6)
-	col2_w = len(fmt_mb(tx_bytes_max)) if opt.show_mb_col else 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 opt.show_mb_col:
+	if cfg.show_mb_col:
 		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)
@@ -182,9 +183,9 @@ def gen_body(data):
 
 	for i in data:
 		if not i.skip:
-			cum_bytes = i.tx_bytes_cum + i.tx_bytes if opt.include_current else i.tx_bytes_cum
+			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 opt.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 )) ))
@@ -197,20 +198,19 @@ def gen_body(data):
 
 async def main():
 
-	from mmgen.protocol import init_proto_from_opts
 	global proto
-	proto = init_proto_from_opts(need_amt=True)
+	proto = cfg._proto
 
 	from mmgen.rpc import rpc_init
-	c = await rpc_init(proto)
+	c = await rpc_init( cfg, proto )
 
 	mempool = await c.call('getrawmempool',True)
 
-	if opt.log:
+	if cfg.log:
 		log(mempool,'mempool.json')
 
 	data = create_data(proto.coin_amt,mempool)
-	stdout_or_pager(
+	cfg._util.stdout_or_pager(
 		'\n'.join(gen_header(
 			c.host,
 			mempool,

+ 8 - 9
mmgen_node_tools/main_halving_calculator.py

@@ -26,7 +26,7 @@ from mmgen.common import *
 
 bdr_proj = 9.95
 
-opts.init({
+cfg = opts.init({
 	'sets': [('mined',True,'list',True)],
 	'text': {
 		'desc': 'Estimate date(s) of future block subsidy halving(s)',
@@ -43,8 +43,8 @@ opts.init({
 """ }
 })
 
-if opt.bdr_proj:
-	bdr_proj = float(opt.bdr_proj)
+if cfg.bdr_proj:
+	bdr_proj = float(cfg.bdr_proj)
 
 def date(t):
 	return '{}-{:02}-{:02} {:02}:{:02}:{:02}'.format(*time.gmtime(t)[:6])
@@ -61,16 +61,15 @@ def time_diff_warning(t_diff):
 
 async def main():
 
-	from mmgen.protocol import init_proto_from_opts
-	proto = init_proto_from_opts(need_amt=True)
+	proto = cfg._proto
 
 	from mmgen.rpc import rpc_init
-	c = await rpc_init(proto)
+	c = await rpc_init( cfg, proto )
 
 	tip = await c.call('getblockcount')
 	assert tip > 1, 'block tip must be > 1'
 	remaining = proto.halving_interval - tip % proto.halving_interval
-	sample_size = int(opt.sample_size) if opt.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,)))
 
@@ -134,7 +133,7 @@ async def main():
 		fs = (
 			'  {a:<7} {b:>8}  {c:19}{d:2}  {e:10}  {f}',
 			'  {a:<7} {b:>8}  {c:19}{d:2}  {e:10}  {f:17} {g:17}  {h}'
-		)[bool(opt.mined)]
+		)[bool(cfg.mined)]
 
 		print(
 			f'Historical/Estimated/Projected Halvings ({proto.coin}):\n\n'
@@ -174,7 +173,7 @@ async def main():
 						) for n,sub,blk,mined,total_mined,bdr,t in gen_data())
 		)
 
-	if opt.list:
+	if cfg.list:
 		await print_halvings()
 	else:
 		print_current_stats()

+ 2 - 5
mmgen_node_tools/main_netrate.py

@@ -34,17 +34,14 @@ opts_data = {
 	}
 }
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
 ERASE_LINE,CUR_UP = '\033[K','\033[1A'
 
 async def main():
 
-	from mmgen.protocol import init_proto_from_opts
-	proto = init_proto_from_opts()
-
 	from mmgen.rpc import rpc_init
-	c = await rpc_init(proto)
+	c = await rpc_init( cfg, cfg._proto )
 
 	async def get_data():
 		d = await c.call('getnettotals')

+ 5 - 8
mmgen_node_tools/main_peerblocks.py

@@ -20,7 +20,7 @@
 mmnode-peerblocks: List blocks in flight, disconnect stalling nodes
 """
 
-from mmgen.opts import init
+import mmgen.opts as opts
 from mmgen.util import async_run
 
 opts_data = {
@@ -36,17 +36,14 @@ opts_data = {
 
 async def main():
 
-	init(opts_data)
-
-	from mmgen.protocol import init_proto_from_opts
-	proto = init_proto_from_opts()
+	cfg = opts.init(opts_data)
 
 	from mmgen.rpc import rpc_init
-	rpc = await rpc_init(proto)
+	rpc = await rpc_init( cfg, cfg._proto )
 
 	from .PeerBlocks import BlocksDisplay,PeersDisplay
-	blocks = BlocksDisplay()
-	peers = PeersDisplay()
+	blocks = BlocksDisplay(cfg)
+	peers = PeersDisplay(cfg)
 
 	while True:
 		await blocks.run(rpc)

+ 6 - 3
mmgen_node_tools/main_ticker.py

@@ -197,12 +197,15 @@ To add a portfolio, edit the file
 	}
 }
 
-cmd_args = opts.init(opts_data,do_post_init=True)
+gcfg = opts.init(opts_data,do_post_init=True)
+
+import mmgen_node_tools.Ticker as Ticker
+Ticker.gcfg = gcfg
 
 cfg_in = get_cfg_in()
 
-cfg = make_cfg(cmd_args,cfg_in)
+cfg = make_cfg(gcfg._args,cfg_in)
 
-opts.post_init()
+opts.post_init(gcfg)
 
 main(cfg,cfg_in)

+ 6 - 9
mmgen_node_tools/main_txfind.py

@@ -58,14 +58,11 @@ async def main(txid):
 	if len(txid) != 64 or not is_hex_str(txid):
 		die(2,f'{txid}: invalid transaction ID')
 
-	if opt.verbose:
+	if cfg.verbose:
 		msg(f'TxID: {txid}')
 
-	from mmgen.protocol import init_proto_from_opts
-	proto = init_proto_from_opts()
-
 	from mmgen.rpc import rpc_init
-	c = await rpc_init(proto)
+	c = await rpc_init(cfg._proto)
 
 	exitval = 0
 	try:
@@ -85,11 +82,11 @@ async def main(txid):
 
 	return exitval
 
-cmd_args = opts.init(opts_data)
+cfg = opts.init(opts_data)
 
-msgs = msg_data['quiet' if opt.quiet else 'normal']
+msgs = msg_data['quiet' if cfg.quiet else 'normal']
 
-if len(cmd_args) != 1:
+if len(cfg._args) != 1:
 	die(1,'One transaction ID must be specified')
 
-sys.exit(async_run(main(cmd_args[0])))
+sys.exit(async_run(main(cfg._args[0])))

+ 1 - 1
setup.cfg

@@ -23,7 +23,7 @@ python_requires = >=3.7
 include_package_data = True
 
 install_requires =
-	mmgen>=13.3.dev20
+	mmgen>=13.3.dev42
 
 packages =
 	mmgen_node_tools

+ 1 - 1
test/test_py_d/ts_main.py

@@ -45,7 +45,7 @@ class TestSuiteMain(TestSuiteBase):
 			f'mmnode-peerblocks',
 			args,
 			pexpect_spawn = pexpect_spawn )
-		if opt.exact_output: # disable echoing of input
+		if cfg.exact_output: # disable echoing of input
 			t.p.logfile = None
 			t.p.logfile_read = sys.stdout
 		if expect_list:

+ 2 - 2
test/test_py_d/ts_misc.py

@@ -97,7 +97,7 @@ class TestSuiteScripts(TestSuiteBase):
 
 	@property
 	def nt_datadir(self):
-		return os.path.join( g.data_dir_root, 'node_tools' )
+		return os.path.join( cfg.data_dir_root, 'node_tools' )
 
 	def ticker_setup(self):
 		self.spawn('',msg_only=True)
@@ -120,7 +120,7 @@ class TestSuiteScripts(TestSuiteBase):
 
 	def ticker2(self):
 		t = self.ticker(cached=False)
-		if not opt.skipping_deps:
+		if not cfg.skipping_deps:
 			t.expect('Creating')
 			t.expect('Creating')
 		t.expect('proxy host could not be resolved')

+ 8 - 10
test/test_py_d/ts_regtest.py

@@ -13,8 +13,6 @@ test.test_py_d.ts_regtest: Regtest tests for the test.py test suite
 """
 
 import os
-from mmgen.globalvars import g
-from mmgen.opts import opt
 from mmgen.util import die,gmsg
 from mmgen.protocol import init_proto
 from mmgen.proto.btc.regtest import MMGenRegtest
@@ -28,7 +26,7 @@ args2 = ['--bob','--rpc-backend=http']
 
 def gen_addrs(proto,network,keys):
 	from mmgen.tool.api import tool_api
-	tool = tool_api()
+	tool = tool_api(cfg)
 	tool.init_coin(proto.coin,'regtest')
 	tool.addrtype = proto.mmtypes[-1]
 	return [tool.privhex2addr('{:064x}'.format(key)) for key in keys]
@@ -105,9 +103,9 @@ class TestSuiteRegtest(TestSuiteBase):
 			return
 		if self.proto.testnet:
 			die(2,'--testnet and --regtest options incompatible with regtest test suite')
-		self.proto = init_proto(self.proto.coin,network='regtest',need_amt=True)
+		self.proto = init_proto( cfg, self.proto.coin, network='regtest', need_amt=True )
 		self.addrs = gen_addrs(self.proto,'regtest',[1,2,3,4,5])
-		self.regtest = MMGenRegtest(self.proto.coin)
+		self.regtest = MMGenRegtest(cfg,self.proto.coin)
 
 	def setup(self):
 		stop_test_daemons(self.proto.network_id,force=True,remove_datadir=True)
@@ -128,7 +126,7 @@ class TestSuiteRegtest(TestSuiteBase):
 		return self.halving_calculator(['--help'],['USAGE:'])
 
 	def halving_calculator2(self):
-		return self.halving_calculator([],['Current block: 393',f'Current block subsidy: 12.5 {g.coin}'])
+		return self.halving_calculator([],['Current block: 393',f'Current block subsidy: 12.5 {cfg.coin}'])
 
 	def halving_calculator3(self):
 		return self.halving_calculator(['--list'],['33 4950','0'])
@@ -158,7 +156,7 @@ class TestSuiteRegtest(TestSuiteBase):
 		return self.addrbal(
 			args2 + [self.addrs[0]],
 			[
-				f'Balance: 0.357 {g.coin}',
+				f'Balance: 0.357 {cfg.coin}',
 				'2 unspent outputs in 2 blocks',
 				'394','0.123',
 				'395','0.234'
@@ -246,7 +244,7 @@ class TestSuiteRegtest(TestSuiteBase):
 		])
 
 	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)
+		n1,i1,o1,n2,i2,o2 = (2,1,3,6,3,9) if cfg.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}',
@@ -267,7 +265,7 @@ class TestSuiteRegtest(TestSuiteBase):
 			from mmgen.tool.api import tool_api
 			from collections import namedtuple
 
-			t = tool_api()
+			t = tool_api(cfg)
 			t.init_coin(self.proto.coin,self.proto.network)
 			t.addrtype = 'compressed' if self.proto.coin == 'BCH' else 'bech32'
 			wp = namedtuple('wifaddrpair',['wif','addr'])
@@ -384,7 +382,7 @@ class TestSuiteRegtest(TestSuiteBase):
 		return self._feeview([])
 
 	def stop(self):
-		if opt.no_daemon_stop:
+		if cfg.no_daemon_stop:
 			self.spawn('',msg_only=True)
 			msg_r('(leaving daemon running by user request)')
 			return 'ok'

+ 6 - 3
test/unit_tests_d/ut_BlocksInfo.py

@@ -7,6 +7,8 @@ from mmgen.common import *
 from mmgen.exception import *
 from mmgen_node_tools.BlocksInfo import BlocksInfo
 
+from ..include.common import cfg,vmsg
+
 tip = 50000
 range_vecs = (
 	#                  First     Last FromTip nBlocks Step    First      Last    BlockList
@@ -70,12 +72,13 @@ class dummyRPC:
 		class coin_amt:
 			satoshi = 0.00000001
 
-class dummyOpt:
+class dummyCfg:
 	fields = None
 	stats = None
 	miner_info = None
 	header_info = None
 	full_stats = None
+	coin = 'BTC'
 
 class unit_tests:
 
@@ -91,7 +94,7 @@ class unit_tests:
 
 	def parse_rangespec(self,name,ut):
 
-		b = BlocksInfo(0,dummyOpt(),dummyRPC())
+		b = BlocksInfo(dummyCfg(),None,dummyRPC())
 
 		def test(spec,chk,foo):
 			ret = b.parse_rangespec(spec)
@@ -108,8 +111,8 @@ class unit_tests:
 
 		def test(spec,foo,chk):
 			b = BlocksInfo(
+				dummyCfg(),
 				spec if type(spec) == tuple else [spec],
-				dummyOpt(),
 				dummyRPC() )
 			ret = (b.first,b.last,b.block_list)
 			vmsg('{:13} => {}'.format(