opts.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2023 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. opts: MMGen-specific options processing after generic processing by share.Opts
  20. """
  21. import sys,os,stat
  22. from .globalvars import g
  23. from .base_obj import Lockable
  24. import mmgen.share.Opts
  25. class UserOpts(Lockable):
  26. _autolock = False
  27. _default_to_none = True
  28. _set_ok = ('usr_randchars',)
  29. _reset_ok = ('quiet','verbose','yes')
  30. opt = UserOpts()
  31. def usage():
  32. from .util import Die
  33. Die(1,mmgen.share.Opts.make_usage_str(g.prog_name,'user',usage_data))
  34. def version():
  35. from .util import Die,fmt
  36. Die(0,fmt(f"""
  37. {g.prog_name.upper()} version {g.version}
  38. Part of the {g.proj_name} suite, an online/offline cryptocurrency wallet for the
  39. command line. Copyright (C){g.Cdates} {g.author} {g.email}
  40. """,indent=' ').rstrip())
  41. def delete_data(opts_data):
  42. for k in ('text','notes','code'):
  43. if k in opts_data:
  44. del opts_data[k]
  45. del mmgen.share.Opts.make_help
  46. del mmgen.share.Opts.process_uopts
  47. del mmgen.share.Opts.parse_opts
  48. def post_init():
  49. global opts_data_save,opt_filter_save
  50. if opt.help or opt.longhelp:
  51. print_help(opt,opts_data_save,opt_filter_save)
  52. else:
  53. delete_data(opts_data_save)
  54. del opts_data_save,opt_filter_save
  55. def print_help(opt,opts_data,opt_filter):
  56. if not 'code' in opts_data:
  57. opts_data['code'] = {}
  58. from .protocol import init_proto_from_opts
  59. proto = init_proto_from_opts(need_amt=True)
  60. if getattr(opt,'longhelp',None):
  61. opts_data['code']['long_options'] = common_opts_data['code']
  62. def remove_unneeded_long_opts():
  63. d = opts_data['text']['long_options']
  64. if proto.base_proto != 'Ethereum':
  65. d = '\n'.join(''+i for i in d.split('\n') if not '--token' in i)
  66. opts_data['text']['long_options'] = d
  67. remove_unneeded_long_opts()
  68. from .ui import do_pager
  69. do_pager(
  70. mmgen.share.Opts.make_help(
  71. proto,
  72. opt,
  73. opts_data,
  74. opt_filter ))
  75. sys.exit(0)
  76. def fmt_opt(o):
  77. return '--' + o.replace('_','-')
  78. def die_on_incompatible_opts(incompat_list):
  79. for group in incompat_list:
  80. bad = [k for k in opt.__dict__ if k in group and getattr(opt,k) != None]
  81. if len(bad) > 1:
  82. from .util import die
  83. die(1,'Conflicting options: {}'.format(', '.join(map(fmt_opt,bad))))
  84. def _show_hash_presets():
  85. fs = ' {:<7} {:<6} {:<3} {}'
  86. from .util import msg
  87. from .crypto import hash_presets
  88. msg('Available parameters for scrypt.hash():')
  89. msg(fs.format('Preset','N','r','p'))
  90. for i in sorted(hash_presets.keys()):
  91. msg(fs.format(i,*hash_presets[i]))
  92. msg('N = memory usage (power of two), p = iterations (rounds)')
  93. sys.exit(0)
  94. def opt_preproc_debug(po):
  95. d = (
  96. ('Cmdline', ' '.join(sys.argv)),
  97. ('Opts', po.opts),
  98. ('Skipped opts', po.skipped_opts),
  99. ('User-selected opts', po.user_opts),
  100. ('Cmd args', po.cmd_args),
  101. )
  102. from .util import Msg
  103. Msg('\n=== opts.py debug ===')
  104. for e in d:
  105. Msg(' {:<20}: {}'.format(*e))
  106. def opt_postproc_debug():
  107. a = [k for k in dir(opt) if k[:2] != '__' and getattr(opt,k) != None]
  108. b = [k for k in dir(opt) if k[:2] != '__' and getattr(opt,k) == None]
  109. from .util import Msg
  110. Msg(' Opts after processing:')
  111. for k in a:
  112. v = getattr(opt,k)
  113. Msg(' {:18}: {!r:<6} [{}]'.format(k,v,type(v).__name__))
  114. Msg(" Opts set to 'None':")
  115. Msg(' {}\n'.format('\n '.join(b)))
  116. Msg(' Global vars:')
  117. for e in [d for d in dir(g) if d[:2] != '__']:
  118. Msg(' {:<20}: {}'.format(e, getattr(g,e)))
  119. Msg('\n=== end opts.py debug ===\n')
  120. def set_for_type(val,refval,desc,invert_bool=False,src=None):
  121. if type(refval) == bool:
  122. v = str(val).lower()
  123. ret = (
  124. True if v in ('true','yes','1','on') else
  125. False if v in ('false','no','none','0','off','') else
  126. None
  127. )
  128. if ret is not None:
  129. return not ret if invert_bool else ret
  130. else:
  131. try:
  132. return type(refval)(not val if invert_bool else val)
  133. except:
  134. pass
  135. from .util import die
  136. die(1,'{!r}: invalid value for {!r}{} (must be of type {!r})'.format(
  137. val,
  138. desc,
  139. ' in {!r}'.format(src) if src else '',
  140. type(refval).__name__) )
  141. def override_globals_from_cfg_file(ucfg,autoset_opts,need_proto):
  142. if need_proto:
  143. from .protocol import init_proto
  144. for d in ucfg.get_lines():
  145. if d.name in g.cfg_file_opts:
  146. ns = d.name.split('_')
  147. if ns[0] in g.core_coins:
  148. if not need_proto:
  149. continue
  150. nse,tn = (
  151. (ns[2:],ns[1]=='testnet') if len(ns) > 2 and ns[1] in ('mainnet','testnet') else
  152. (ns[1:],False)
  153. )
  154. cls = type(init_proto( ns[0], tn, need_amt=True )) # no instance yet, so override _class_ attr
  155. attr = '_'.join(nse)
  156. else:
  157. cls = g # g is "singleton" instance, so override _instance_ attr
  158. attr = d.name
  159. refval = getattr(cls,attr)
  160. val = ucfg.parse_value(d.value,refval)
  161. if not val:
  162. from .util import die
  163. die( 'CfgFileParseError', f'Parse error in file {ucfg.fn!r}, line {d.lineno}' )
  164. val_conv = set_for_type(val,refval,attr,src=ucfg.fn)
  165. setattr(cls,attr,val_conv)
  166. elif d.name in g.autoset_opts:
  167. autoset_opts[d.name] = d.value
  168. else:
  169. from .util import die
  170. die( 'CfgFileParseError', f'{d.name!r}: unrecognized option in {ucfg.fn!r}, line {d.lineno}' )
  171. def override_globals_and_set_opts_from_env(opt):
  172. for name in g.env_opts:
  173. if name == 'MMGEN_DEBUG_ALL':
  174. continue
  175. disable = name[:14] == 'MMGEN_DISABLE_'
  176. val = os.getenv(name) # os.getenv() returns None if env var is unset
  177. if val: # exclude empty string values; string value of '0' or 'false' sets variable to False
  178. gname = name[(6,14)[disable]:].lower()
  179. if hasattr(g,gname):
  180. setattr(g,gname,set_for_type(val,getattr(g,gname),name,disable))
  181. elif hasattr(opt,gname):
  182. if getattr(opt,gname) is None: # env must not override cmdline!
  183. setattr(opt,gname,val)
  184. else:
  185. raise ValueError(f'Name {gname} not present in globals or opts')
  186. def show_common_opts_diff():
  187. def common_opts_data_to_list():
  188. for l in common_opts_data['text'].splitlines():
  189. if l.startswith('--,'):
  190. yield l.split()[1].split('=')[0][2:].replace('-','_')
  191. def do_fmt(set_data):
  192. from .util import fmt_list
  193. return fmt_list(['--'+s.replace('_','-') for s in set_data],fmt='col',indent=' ')
  194. a = g.common_opts
  195. b = list(common_opts_data_to_list())
  196. a_minus_b = [e for e in a if e not in b]
  197. b_minus_a = [e for e in b if e not in a]
  198. a_and_b = [e for e in a if e in b]
  199. from .util import msg
  200. msg(f'g.common_opts - common_opts_data:\n {do_fmt(a_minus_b) if a_minus_b else "None"}\n')
  201. msg(f'common_opts_data - g.common_opts (these do not set global var):\n{do_fmt(b_minus_a)}\n')
  202. msg(f'common_opts_data ^ g.common_opts (these set global var):\n{do_fmt(a_and_b)}\n')
  203. sys.exit(0)
  204. common_opts_data = {
  205. # Most but not all of these set the corresponding global var
  206. # View differences with show_common_opts_diff()
  207. 'text': """
  208. --, --accept-defaults Accept defaults at all prompts
  209. --, --coin=c Choose coin unit. Default: BTC. Current choice: {cu_dfl}
  210. --, --token=t Specify an ERC20 token by address or symbol
  211. --, --color=0|1 Disable or enable color output (enabled by default)
  212. --, --columns=N Force N columns of output with certain commands
  213. --, --scroll Use the curses-like scrolling interface for
  214. tracking wallet views
  215. --, --force-256-color Force 256-color output when color is enabled
  216. --, --pager Pipe output of certain commands to pager (WIP)
  217. --, --data-dir=path Specify {pnm} data directory location
  218. --, --daemon-data-dir=path Specify coin daemon data directory location
  219. --, --daemon-id=ID Specify the coin daemon ID
  220. --, --ignore-daemon-version Ignore coin daemon version check
  221. --, --http-timeout=t Set HTTP timeout in seconds for JSON-RPC connections
  222. --, --no-license Suppress the GPL license prompt
  223. --, --rpc-host=HOST Communicate with coin daemon running on host HOST
  224. --, --rpc-port=PORT Communicate with coin daemon listening on port PORT
  225. --, --rpc-user=USER Authenticate to coin daemon using username USER
  226. --, --rpc-password=PASS Authenticate to coin daemon using password PASS
  227. --, --rpc-backend=backend Use backend 'backend' for JSON-RPC communications
  228. --, --aiohttp-rpc-queue-len=N Use N simultaneous RPC connections with aiohttp
  229. --, --regtest=0|1 Disable or enable regtest mode
  230. --, --testnet=0|1 Disable or enable testnet
  231. --, --skip-cfg-file Skip reading the configuration file
  232. --, --version Print version information and exit
  233. --, --bob Specify user “Bob” in MMGen regtest mode
  234. --, --alice Specify user “Alice” in MMGen regtest mode
  235. --, --carol Specify user “Carol” in MMGen regtest mode
  236. """,
  237. 'code': lambda help_notes,proto,s: s.format(
  238. pnm = g.proj_name,
  239. cu_dfl = proto.coin,
  240. )
  241. }
  242. opts_data_dfl = {
  243. 'text': {
  244. 'desc': '',
  245. 'usage':'',
  246. 'options': """
  247. -h, --help Print this help message
  248. --, --longhelp Print help message for long (common) options
  249. """
  250. }
  251. }
  252. def init(
  253. opts_data = None,
  254. add_opts = None,
  255. init_opts = None,
  256. opt_filter = None,
  257. parse_only = False,
  258. parsed_opts = None,
  259. need_proto = True,
  260. do_post_init = False,
  261. return_parsed = False ):
  262. if opts_data is None:
  263. opts_data = opts_data_dfl
  264. opts_data['text']['long_options'] = common_opts_data['text']
  265. # Make this available to usage()
  266. global usage_data
  267. usage_data = opts_data['text'].get('usage2') or opts_data['text']['usage']
  268. # po: (user_opts,cmd_args,opts,skipped_opts)
  269. po = parsed_opts or mmgen.share.Opts.parse_opts(opts_data,opt_filter=opt_filter,parse_only=parse_only)
  270. if init_opts: # allow programs to preload user opts
  271. for uopt,val in init_opts.items():
  272. if uopt not in po.user_opts:
  273. po.user_opts[uopt] = val
  274. if parse_only and not any(k in po.user_opts for k in ('version','help','longhelp')):
  275. return po
  276. if g.debug_opts:
  277. opt_preproc_debug(po)
  278. # Copy parsed opts to opt, setting values to None if not set by user
  279. for o in set(
  280. po.opts
  281. + po.skipped_opts
  282. + tuple(add_opts or [])
  283. + tuple(init_opts or [])
  284. + g.init_opts
  285. + g.common_opts ):
  286. setattr(opt,o,po.user_opts[o] if o in po.user_opts else None)
  287. if opt.version:
  288. version() # exits
  289. # === begin global var initialization === #
  290. """
  291. NB: user opt --data-dir is actually data_dir_root
  292. - data_dir is data_dir_root plus optionally 'regtest' or 'testnet', so for mainnet
  293. data_dir == data_dir_root
  294. - As with Bitcoin Core, cfg file is in data_dir_root, wallets and other data are
  295. in data_dir
  296. - Since cfg file is in data_dir_root, data_dir_root must be finalized before we
  297. can process cfg file
  298. - Since data_dir depends on the values of g.testnet and g.regtest, these must be
  299. finalized before setting data_dir
  300. """
  301. if opt.data_dir:
  302. g.data_dir_root = os.path.normpath(os.path.abspath(opt.data_dir))
  303. elif os.getenv('MMGEN_TEST_SUITE'):
  304. from test.include.common import get_test_data_dir
  305. g.data_dir_root = get_test_data_dir()
  306. else:
  307. g.data_dir_root = os.path.join(g.home_dir,'.'+g.proj_name.lower())
  308. from .fileutil import check_or_create_dir
  309. check_or_create_dir(g.data_dir_root)
  310. from .term import init_term
  311. init_term()
  312. from .util import wrap_ripemd160
  313. wrap_ripemd160() # ripemd160 used by cfg_file()
  314. cfgfile_autoset_opts = {}
  315. if not (opt.skip_cfg_file or opt.bob or opt.alice or g.prog_name == 'mmgen-regtest'):
  316. from .cfg import cfg_file
  317. # check for changes in system template file - term must be initialized
  318. # workaround: g.test_suite is needed by cfg.py, but g is not set from environment yet
  319. g.test_suite = False if os.getenv('MMGEN_TEST_SUITE') in (None,'','false','0') else True
  320. cfg_file('sample')
  321. override_globals_from_cfg_file( cfg_file('usr'), cfgfile_autoset_opts, need_proto )
  322. override_globals_and_set_opts_from_env(opt)
  323. # Set globals from opts, setting type from original global value
  324. # Do here, before opts are set from globals below
  325. for k in (g.common_opts + g.opt_sets_global):
  326. if hasattr(opt,k):
  327. val = getattr(opt,k)
  328. if val != None and hasattr(g,k):
  329. setattr(g,k,set_for_type(val,getattr(g,k),'--'+k))
  330. """
  331. g.color is finalized, so initialize color
  332. """
  333. if g.color: # MMGEN_DISABLE_COLOR sets this to False
  334. from .color import init_color
  335. init_color(num_colors=('auto',256)[bool(g.force_256_color)])
  336. """
  337. g.testnet and g.regtest are finalized, so we can set g.data_dir
  338. """
  339. g.data_dir = os.path.normpath(os.path.join(
  340. g.data_dir_root,
  341. ('regtest' if g.regtest else 'testnet' if g.testnet else '') ))
  342. # Set user opts from globals:
  343. # - if opt is unset, set it to global value
  344. # - if opt is set, convert its type to that of global value
  345. for k in g.global_sets_opt:
  346. if hasattr(opt,k) and getattr(opt,k) != None:
  347. setattr(opt,k,set_for_type(getattr(opt,k),getattr(g,k),'--'+k))
  348. else:
  349. setattr(opt,k,getattr(g,k))
  350. if opt.show_hash_presets: # exits
  351. _show_hash_presets()
  352. g.coin = g.coin.upper() or 'BTC'
  353. g.token = g.token.upper() or None
  354. if g.bob or g.alice or g.carol or g.prog_name == 'mmgen-regtest':
  355. g.regtest_user = 'bob' if g.bob else 'alice' if g.alice else 'carol' if g.carol else None
  356. g.regtest = True
  357. g.data_dir = os.path.join(
  358. g.data_dir_root,
  359. 'regtest',
  360. g.coin.lower(),
  361. (g.regtest_user or 'none') )
  362. # === end global var initialization === #
  363. if need_proto:
  364. from .protocol import warn_trustlevel
  365. warn_trustlevel(g.coin)
  366. die_on_incompatible_opts(g.incompatible_opts)
  367. check_or_create_dir(g.data_dir) # g.data_dir is finalized, so we can create it
  368. # Check user-set opts without modifying them
  369. check_usr_opts(po.user_opts)
  370. # Check autoset opts, setting if unset
  371. for key in g.autoset_opts:
  372. if hasattr(opt,key):
  373. if getattr(opt,key) is not None:
  374. setattr(opt, key, get_autoset_opt(key,getattr(opt,key),src='cmdline'))
  375. elif key in cfgfile_autoset_opts:
  376. setattr(opt, key, get_autoset_opt(key,cfgfile_autoset_opts[key],src='cfgfile'))
  377. else:
  378. setattr(opt, key, g.autoset_opts[key].choices[0])
  379. set_auto_typeset_opts()
  380. if opt.verbose:
  381. opt.quiet = None
  382. if g.debug and g.prog_name != 'test.py':
  383. opt.verbose,opt.quiet = (True,None)
  384. if g.debug_opts:
  385. opt_postproc_debug()
  386. g.lock()
  387. opt.lock()
  388. # print help screen only after globals and opts initialized and locked:
  389. if opt.help or opt.longhelp:
  390. if not do_post_init:
  391. print_help(opt,opts_data,opt_filter) # exits
  392. if do_post_init:
  393. global opts_data_save,opt_filter_save
  394. opts_data_save = opts_data
  395. opt_filter_save = opt_filter
  396. else:
  397. delete_data(opts_data)
  398. return po if return_parsed else po.cmd_args
  399. def check_usr_opts(usr_opts): # Raises an exception if any check fails
  400. def opt_splits(val,sep,n,desc):
  401. sepword = 'comma' if sep == ',' else 'colon' if sep == ':' else repr(sep)
  402. try:
  403. l = val.split(sep)
  404. except:
  405. die( 'UserOptError', f'{val!r}: invalid {desc} (not {sepword}-separated list)' )
  406. if len(l) != n:
  407. die( 'UserOptError', f'{val!r}: invalid {desc} ({n} {sepword}-separated items required)' )
  408. def opt_compares(val,op_str,target,desc,desc2=''):
  409. import operator as o
  410. op_f = { '<':o.lt, '<=':o.le, '>':o.gt, '>=':o.ge, '=':o.eq }[op_str]
  411. if not op_f(val,target):
  412. d2 = desc2 + ' ' if desc2 else ''
  413. die( 'UserOptError', f'{val}: invalid {desc} ({d2}not {op_str} {target})' )
  414. def opt_is_int(val,desc):
  415. if not is_int(val):
  416. die( 'UserOptError', f'{val!r}: invalid {desc} (not an integer)' )
  417. def opt_is_float(val,desc):
  418. try:
  419. float(val)
  420. except:
  421. die( 'UserOptError', f'{val!r}: invalid {desc} (not a floating-point number)' )
  422. def opt_is_in_list(val,tlist,desc):
  423. if val not in tlist:
  424. q,sep = (('',','),("'","','"))[type(tlist[0]) == str]
  425. die( 'UserOptError', '{q}{v}{q}: invalid {w}\nValid choices: {q}{o}{q}'.format(
  426. v = val,
  427. w = desc,
  428. q = q,
  429. o = sep.join(map(str,sorted(tlist))) ))
  430. def opt_unrecognized(key,val,desc='value'):
  431. die( 'UserOptError', f'{val!r}: unrecognized {desc} for option {fmt_opt(key)!r}' )
  432. def opt_display(key,val='',beg='For selected',end=':\n'):
  433. from .util import msg_r
  434. msg_r('{} option {!r}{}'.format(
  435. beg,
  436. f'{fmt_opt(key)}={val}' if val else fmt_opt(key),
  437. end ))
  438. def chk_in_fmt(key,val,desc):
  439. from .wallet import get_wallet_data
  440. wd = get_wallet_data(fmt_code=val)
  441. if not wd:
  442. opt_unrecognized(key,val)
  443. if key == 'out_fmt':
  444. p = 'hidden_incog_output_params'
  445. if wd.type == 'incog_hidden' and not getattr(opt,p):
  446. die( 'UserOptError',
  447. 'Hidden incog format output requested. ' +
  448. f'You must supply a file and offset with the {fmt_opt(p)!r} option' )
  449. if wd.base_type == 'incog_base' and opt.old_incog_fmt:
  450. opt_display(key,val,beg='Selected',end=' ')
  451. opt_display('old_incog_fmt',beg='conflicts with',end=':\n')
  452. die( 'UserOptError', 'Export to old incog wallet format unsupported' )
  453. elif wd.type == 'brain':
  454. die( 'UserOptError', 'Output to brainwallet format unsupported' )
  455. chk_out_fmt = chk_in_fmt
  456. def chk_hidden_incog_input_params(key,val,desc):
  457. a = val.rsplit(',',1) # permit comma in filename
  458. if len(a) != 2:
  459. opt_display(key,val)
  460. die( 'UserOptError', 'Option requires two comma-separated arguments' )
  461. fn,offset = a
  462. opt_is_int(offset,desc)
  463. from .fileutil import check_infile,check_outdir,check_outfile
  464. if key == 'hidden_incog_input_params':
  465. check_infile(fn,blkdev_ok=True)
  466. key2 = 'in_fmt'
  467. else:
  468. try: os.stat(fn)
  469. except:
  470. b = os.path.dirname(fn)
  471. if b:
  472. check_outdir(b)
  473. else:
  474. check_outfile(fn,blkdev_ok=True)
  475. key2 = 'out_fmt'
  476. if hasattr(opt,key2):
  477. val2 = getattr(opt,key2)
  478. from .wallet import get_wallet_data
  479. wd = get_wallet_data('incog_hidden')
  480. if val2 and val2 not in wd.fmt_codes:
  481. die( 'UserOptError', f'Option conflict:\n {fmt_opt(key)}, with\n {fmt_opt(key2)}={val2}' )
  482. chk_hidden_incog_output_params = chk_hidden_incog_input_params
  483. def chk_subseeds(key,val,desc):
  484. from .subseed import SubSeedIdxRange
  485. opt_is_int(val,desc)
  486. opt_compares(int(val),'>=',SubSeedIdxRange.min_idx,desc)
  487. opt_compares(int(val),'<=',SubSeedIdxRange.max_idx,desc)
  488. def chk_seed_len(key,val,desc):
  489. from .seed import Seed
  490. opt_is_int(val,desc)
  491. opt_is_in_list(int(val),Seed.lens,desc)
  492. def chk_hash_preset(key,val,desc):
  493. from .crypto import hash_presets
  494. opt_is_in_list(val,list(hash_presets.keys()),desc)
  495. def chk_brain_params(key,val,desc):
  496. from .seed import Seed
  497. from .crypto import hash_presets
  498. a = val.split(',')
  499. if len(a) != 2:
  500. opt_display(key,val)
  501. die( 'UserOptError', 'Option requires two comma-separated arguments' )
  502. opt_is_int(a[0],'seed length '+desc)
  503. opt_is_in_list(int(a[0]),Seed.lens,'seed length '+desc)
  504. opt_is_in_list(a[1],list(hash_presets.keys()),'hash preset '+desc)
  505. def chk_usr_randchars(key,val,desc):
  506. if val == 0:
  507. return
  508. opt_is_int(val,desc)
  509. opt_compares(val,'>=',g.min_urandchars,desc)
  510. opt_compares(val,'<=',g.max_urandchars,desc)
  511. def chk_tx_fee(key,val,desc):
  512. pass
  513. # opt_is_tx_fee(key,val,desc) # TODO: move this check elsewhere
  514. def chk_tx_confs(key,val,desc):
  515. opt_is_int(val,desc)
  516. opt_compares(val,'>=',1,desc)
  517. def chk_vsize_adj(key,val,desc):
  518. opt_is_float(val,desc)
  519. from .util import ymsg
  520. ymsg(f'Adjusting transaction vsize by a factor of {float(val):1.2f}')
  521. def chk_daemon_id(key,val,desc):
  522. from .daemon import CoinDaemon
  523. opt_is_in_list(val,CoinDaemon.all_daemon_ids(),desc)
  524. # TODO: move this check elsewhere
  525. # def chk_rbf(key,val,desc):
  526. # if not proto.cap('rbf'):
  527. # die( 'UserOptError', f'--rbf requested, but {proto.coin} does not support replace-by-fee transactions' )
  528. # def chk_bob(key,val,desc):
  529. # from .proto.btc.regtest import MMGenRegtest
  530. # try:
  531. # os.stat(os.path.join(MMGenRegtest(g.coin).d.datadir,'regtest','debug.log'))
  532. # except:
  533. # die( 'UserOptError',
  534. # 'Regtest (Bob and Alice) mode not set up yet. ' +
  535. # f"Run '{g.proj_name.lower()}-regtest setup' to initialize." )
  536. #
  537. # chk_alice = chk_bob
  538. def chk_locktime(key,val,desc):
  539. opt_is_int(val,desc)
  540. opt_compares(int(val),'>',0,desc)
  541. def chk_columns(key,val,desc):
  542. opt_compares(int(val),'>',10,desc)
  543. # TODO: move this check elsewhere
  544. # def chk_token(key,val,desc):
  545. # if not 'token' in proto.caps:
  546. # die( 'UserOptError', f'Coin {tx.coin!r} does not support the --token option' )
  547. # if len(val) == 40 and is_hex_str(val):
  548. # return
  549. # if len(val) > 20 or not all(s.isalnum() for s in val):
  550. # die( 'UserOptError', f'{val!r}: invalid parameter for --token option' )
  551. from .util import is_int,die,Msg
  552. cfuncs = { k:v for k,v in locals().items() if k.startswith('chk_') }
  553. for key in usr_opts:
  554. val = getattr(opt,key)
  555. desc = f'parameter for {fmt_opt(key)!r} option'
  556. if key in g.infile_opts:
  557. from .fileutil import check_infile
  558. check_infile(val) # file exists and is readable - dies on error
  559. elif key == 'outdir':
  560. from .fileutil import check_outdir
  561. check_outdir(val) # dies on error
  562. elif 'chk_'+key in cfuncs:
  563. cfuncs['chk_'+key](key,val,desc)
  564. elif g.debug:
  565. Msg(f'check_usr_opts(): No test for opt {key!r}')
  566. def set_auto_typeset_opts():
  567. for key,ref_type in g.auto_typeset_opts.items():
  568. if hasattr(opt,key):
  569. val = getattr(opt,key)
  570. if val is not None: # typeset only if opt is set
  571. setattr(opt,key,ref_type(val))
  572. def get_autoset_opt(key,val,src):
  573. def die_on_err(desc):
  574. from .util import fmt_list,die
  575. die(
  576. 'UserOptError',
  577. '{a!r}: invalid {b} (not {c}: {d})'.format(
  578. a = val,
  579. b = {
  580. 'cmdline': 'parameter for option --{}'.format(key.replace('_','-')),
  581. 'cfgfile': 'value for cfg file option {!r}'.format(key)
  582. }[src],
  583. c = desc,
  584. d = fmt_list(data.choices) ))
  585. class opt_type:
  586. def nocase_str():
  587. if val.lower() in data.choices:
  588. return val.lower()
  589. else:
  590. die_on_err('one of')
  591. def nocase_pfx():
  592. cs = [s for s in data.choices if s.startswith(val.lower())]
  593. if len(cs) == 1:
  594. return cs[0]
  595. else:
  596. die_on_err('unique substring of')
  597. data = g.autoset_opts[key]
  598. return getattr(opt_type,data.type)()