From 3823e8cdc98acc2c33f7676955b773d5e068aa61 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Thu, 26 May 2022 16:07:20 +0000 Subject: [PATCH] minor fixes and cleanups --- mmgen/addrdata.py | 2 +- mmgen/base_proto/bitcoin/regtest.py | 10 ++--- mmgen/base_proto/bitcoin/rpc.py | 2 +- mmgen/base_proto/bitcoin/tw/txhistory.py | 3 +- mmgen/main_addrimport.py | 57 ++++++++++++------------ mmgen/rpc.py | 1 + mmgen/tool/rpc.py | 3 +- mmgen/tw/common.py | 21 +++++---- mmgen/tw/ctl.py | 10 ++--- mmgen/util.py | 6 +-- test/test_py_d/ts_regtest.py | 8 ++-- 11 files changed, 63 insertions(+), 60 deletions(-) diff --git a/mmgen/addrdata.py b/mmgen/addrdata.py index 3e0f7ca0..326a8fc2 100755 --- a/mmgen/addrdata.py +++ b/mmgen/addrdata.py @@ -94,7 +94,7 @@ class TwAddrData(AddrData,metaclass=AsyncInit): out[al_id].append(AddrListEntry(self.proto,idx=obj.idx,addr=addr_array[0],label=l.comment)) i += 1 - vmsg(f'{i} {g.prog_name} addresses found, {len(twd)} accounts total') + vmsg(f'{i} {g.proj_name} addresses found, {len(twd)} accounts total') for al_id in out: self.add(AddrList(self.proto,al_id=al_id,adata=AddrListData(sorted(out[al_id],key=lambda a: a.idx)))) diff --git a/mmgen/base_proto/bitcoin/regtest.py b/mmgen/base_proto/bitcoin/regtest.py index 79258f66..c7f48421 100755 --- a/mmgen/base_proto/bitcoin/regtest.py +++ b/mmgen/base_proto/bitcoin/regtest.py @@ -56,12 +56,10 @@ def create_hdseed(proto): def cliargs_convert(args): def gen(): for arg in args: - if arg.lower() in ('true','false'): - yield (True,False)[arg.lower() == 'false'] - elif len(str(arg)) < 20 and re.match(r'[0-9]+',arg): - yield int(arg) - else: - yield arg + try: + yield json.loads(arg) # list, dict, bool, int, null + except: + yield arg # arbitrary string return tuple(gen()) diff --git a/mmgen/base_proto/bitcoin/rpc.py b/mmgen/base_proto/bitcoin/rpc.py index 59c75ce3..00292fe8 100755 --- a/mmgen/base_proto/bitcoin/rpc.py +++ b/mmgen/base_proto/bitcoin/rpc.py @@ -258,7 +258,7 @@ class BitcoinRPCClient(RPCClient,metaclass=AsyncInit): 'getmempoolentry', 'getrawtransaction', 'gettransaction', - 'importaddress', + 'importaddress', # address (address or script) label rescan p2sh (Add P2SH version of the script) 'listaccounts', 'listlabels', 'listunspent', diff --git a/mmgen/base_proto/bitcoin/tw/txhistory.py b/mmgen/base_proto/bitcoin/tw/txhistory.py index fb4d2fea..6c113a1e 100755 --- a/mmgen/base_proto/bitcoin/tw/txhistory.py +++ b/mmgen/base_proto/bitcoin/tw/txhistory.py @@ -258,8 +258,7 @@ Actions: [q]uit, r[e]draw: # chain. e.g. 1 would mean the best block hash. Note: this is not used # as a filter, but only affects [lastblock] in the return value # 3. include_watchonly (boolean, optional, default=true for watch-only wallets, otherwise - # false) Include transactions to watch-only addresses (see - # 'importaddress') + # false) Include transactions to watch-only addresses # 4. include_removed (boolean, optional, default=true) Show transactions that were removed # due to a reorg in the "removed" array (not guaranteed to work on # pruned nodes) diff --git a/mmgen/main_addrimport.py b/mmgen/main_addrimport.py index e6150ed7..c4dbeadb 100755 --- a/mmgen/main_addrimport.py +++ b/mmgen/main_addrimport.py @@ -26,31 +26,10 @@ from .common import * from .addrlist import AddrList,KeyAddrList from .tw.common import TwLabel -ai_msgs = lambda k: { - 'rescan': """ -WARNING: You've chosen the '--rescan' option. Rescanning the blockchain is -necessary only if an address you're importing is already in the blockchain, -has a balance and is not in your tracking wallet. Note that the rescanning -process is very slow (>30 min. for each imported address on a low-powered -computer). - """.strip() if opt.rescan else """ -WARNING: If any of the addresses you're importing is already in the blockchain, -has a balance and is not in your tracking wallet, you must exit the program now -and rerun it using the '--rescan' option. -""".strip(), - 'bad_args': f""" -You must specify an {g.proj_name} address file, a single address with the '--address' -option, or a list of non-{g.proj_name} addresses with the '--addrlist' option -""".strip() -}[k] - -# In batch mode, daemon just rescans each address separately anyway, so make -# --batch and --rescan incompatible. - opts_data = { 'text': { 'desc': f'Import addresses into an {g.proj_name} tracking wallet', - 'usage':'[opts] [mmgen address file]', + 'usage':'[opts] [MMGen address file]', 'options': """ -h, --help Print this help message --, --longhelp Print help message for long options (common options) @@ -72,6 +51,23 @@ The --batch and --rescan options cannot be used together. } } +addrimport_msgs = { + 'rescan': """ + WARNING: You’ve chosen the ‘--rescan’ option. Rescanning the blockchain is + necessary only if an address you’re importing is already in an output or + outputs in the blockchain but not all transactions involving the address + are known to the tracking wallet. + + Rescanning is performed via the UTXO method, which is only minimally affected + by the number of addresses imported and typically takes just a few minutes. + """, + 'bad_args': f""" + You must specify either an {g.proj_name} address file, a single address with + the ‘--address’ option, or a flat list of non-{g.proj_name} addresses with + the ‘--addrlist’ option. + """ +} + def parse_cmd_args(rpc,cmd_args): def import_mmgen_list(infile): @@ -96,7 +92,7 @@ def parse_cmd_args(rpc,cmd_args): al = AddrList(proto=proto,addrlist=[opt.address]) infile = 'command line' else: - die(1,ai_msgs('bad_args')) + die(1,addrimport_msgs['bad_args']) return al,infile @@ -105,17 +101,17 @@ def check_opts(tw): rescan = bool(opt.rescan) if rescan and not 'rescan' in tw.caps: - msg(f"'--rescan' ignored: not supported by {type(tw).__name__}") + msg(f"‘--rescan’ ignored: not supported by {type(tw).__name__}") rescan = False if rescan and not opt.quiet: - confirm_or_raise( - message = ai_msgs('rescan'), - action = 'continue', - expect = 'YES' ) + if not keypress_confirm( + '\n{}\n\nContinue?'.format(addrimport_msgs['rescan']), + default_yes = True ): + die(1,'Exiting at user request') if batch and not 'batch' in tw.caps: - msg(f"'--batch' ignored: not supported by {type(tw).__name__}") + msg(f"‘--batch’ ignored: not supported by {type(tw).__name__}") batch = False return batch,rescan @@ -176,6 +172,9 @@ async def main(): from .rpc import rpc_init tw.rpc = await rpc_init(proto) + for k,v in addrimport_msgs.items(): + addrimport_msgs[k] = fmt(v,indent=' ',strip_char='\t').rstrip() + al,infile = parse_cmd_args(tw.rpc,cmd_args) qmsg( diff --git a/mmgen/rpc.py b/mmgen/rpc.py index 2039d8d4..a995c405 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -106,6 +106,7 @@ class RPCBackends: self.timeout = caller.timeout self.http_hdrs = caller.http_hdrs self.make_host_path = caller.make_host_path + self.name = type(self).__name__ class aiohttp(base): """ diff --git a/mmgen/tool/rpc.py b/mmgen/tool/rpc.py index 029f1f90..08aff57a 100755 --- a/mmgen/tool/rpc.py +++ b/mmgen/tool/rpc.py @@ -161,7 +161,8 @@ class tool_cmd(tool_cmd_base): async def remove_address(self,mmgen_or_coin_addr:str): "remove an address from tracking wallet" from ..tw.ctl import TrackingWallet - ret = await (await TrackingWallet(self.proto,mode='w')).remove_address(mmgen_or_coin_addr) # returns None on failure + # returns None on failure: + ret = await (await TrackingWallet(self.proto,mode='w')).remove_address(mmgen_or_coin_addr) if ret: from ..util import msg msg(f'Address {ret!r} deleted from tracking wallet') diff --git a/mmgen/tw/common.py b/mmgen/tw/common.py index db8eda6a..8d6bd8b5 100755 --- a/mmgen/tw/common.py +++ b/mmgen/tw/common.py @@ -221,27 +221,31 @@ class TwCommon: async def view_and_sort(self): from ..opts import opt from ..term import get_char - self.prompt = type(self).prompt.strip() + '\b' + prompt = self.prompt.strip() + '\b' self.no_output = False self.oneshot_msg = None self.interactive = True + immed_chars = ''.join(self.key_mappings.keys()) + + CUR_RIGHT = lambda n: f'\033[{n}C' CUR_HOME = '\033[H' ERASE_ALL = '\033[0J' + self.cursor_to_end_of_prompt = CUR_RIGHT( len(prompt.split('\n')[-1]) - 2 ) + clear_screen = '\n\n' if (opt.no_blank or g.test_suite) else CUR_HOME + ERASE_ALL while True: - msg_r('' if self.no_output else '\n\n' if (opt.no_blank or g.test_suite) else CUR_HOME+ERASE_ALL) reply = get_char( '' if self.no_output else ( - await self.format_squeezed() + clear_screen + + await self.format_squeezed() + '\n' + (self.oneshot_msg or '') - + self.prompt + + prompt ), - immed_chars = ''.join(self.key_mappings.keys()) - ) + immed_chars = immed_chars ) self.no_output = False self.oneshot_msg = '' if self.oneshot_msg else None # tristate, saves previous state - if reply not in self.key_mappings: + if reply not in immed_chars: msg_r('\ninvalid keypress ') await asyncio.sleep(0.3) continue @@ -318,8 +322,7 @@ class TwCommon: def post_view(self,parent): if g.platform == 'linux' and parent.oneshot_msg == None: - CUR_RIGHT = lambda n: f'\033[{n}C' - msg_r(CUR_RIGHT(len(parent.prompt.split('\n')[-1])-2)) + msg_r(parent.cursor_to_end_of_prompt) parent.no_output = True class item_action: diff --git a/mmgen/tw/ctl.py b/mmgen/tw/ctl.py index 48dbb3ea..76a79fb1 100755 --- a/mmgen/tw/ctl.py +++ b/mmgen/tw/ctl.py @@ -234,9 +234,9 @@ class TrackingWallet(MMGenObject,metaclass=AsyncInit): coinaddr = (await TwAddrData(self.proto)).mmaddr2coinaddr(mmaddr) try: - if not is_mmgen_id(self.proto,arg1): - assert coinaddr, f'Invalid coin address for this chain: {arg1}' - assert coinaddr, f'{g.proj_name} address {mmaddr!r} not found in tracking wallet' + assert coinaddr, ( + f'{g.proj_name} address {mmaddr!r} not found in tracking wallet' if mmaddr else + f'Invalid coin address for this chain: {addrspec}' ) assert await self.is_in_wallet(coinaddr), f'Address {coinaddr!r} not found in tracking wallet' except Exception as e: msg(str(e)) @@ -268,8 +268,8 @@ class TrackingWallet(MMGenObject,metaclass=AsyncInit): return False else: desc = '{} address {} in tracking wallet'.format( - mmaddr.type.replace('mmg','MMG'), - mmaddr.replace(self.proto.base_coin.lower()+':','') ) + res.mmaddr.type.replace('mmgen','MMGen'), + res.mmaddr.replace(self.proto.base_coin.lower()+':','') ) if label: msg(f'Added label {label!r} to {desc}') else: diff --git a/mmgen/util.py b/mmgen/util.py index a607e41e..d3667a81 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -176,7 +176,7 @@ def fmt_list(iterable,fmt='dfl',indent=''): 'min': (",", "'", "'"), 'col': ('\n'+indent, indent, '' ), }[fmt] - return lq + sep.join(iterable) + rq + return lq + sep.join(str(i) for i in iterable) + rq def list_gen(*data): """ @@ -464,8 +464,8 @@ def make_full_path(outdir,outfile): return os.path.normpath(os.path.join(outdir, os.path.basename(outfile))) def confirm_or_raise(message,action,expect='YES',exit_msg='Exiting at user request'): - if message.strip(): - msg(message.strip()) + if message: + msg(message) if line_input( (f'{action} ' if action[0].isupper() else f'Are you sure you want to {action}?\n') + f'Type uppercase {expect!r} to confirm: ' diff --git a/test/test_py_d/ts_regtest.py b/test/test_py_d/ts_regtest.py index 2b24d5b2..3df09f20 100755 --- a/test/test_py_d/ts_regtest.py +++ b/test/test_py_d/ts_regtest.py @@ -198,8 +198,8 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): ('bob_bal3', "Bob's balance"), ('bob_pre_import', 'sending to non-imported address'), ('generate', 'mining a block'), - ('bob_import_addr', 'importing non-MMGen address with --rescan'), - ('bob_bal4', "Bob's balance (after import with rescan)"), + ('bob_import_addr', 'importing non-MMGen address'), + ('bob_bal4', "Bob's balance (after import)"), ('bob_import_list', 'importing flat address list'), ('bob_import_list_rescan', 'importing flat address list with --rescan'), ('bob_split2', "splitting Bob's funds"), @@ -421,7 +421,9 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): def bob_import_miner_addr(self): if not self.deterministic: return 'skip' - return self.spawn('mmgen-addrimport', [ '--bob', '--rescan', '--quiet', f'--address={self.miner_addr}' ]) + return self.spawn( + 'mmgen-addrimport', + [ '--bob', '--rescan', '--quiet', f'--address={self.miner_addr}' ] ) def fund_wallet_deterministic(self,user,addr,utxo_nums,skip_passphrase=False): """