opts.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2021 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.py: MMGen-specific options processing after generic processing by share.Opts
  20. """
  21. import sys,os,stat
  22. from .exception import UserOptError
  23. from .globalvars import g
  24. from .base_obj import Lockable
  25. import mmgen.share.Opts
  26. class UserOpts(Lockable):
  27. _set_ok = ('usr_randchars',)
  28. _reset_ok = ('quiet','verbose','yes')
  29. opt = UserOpts()
  30. from .util import *
  31. def usage():
  32. from mmgen.share import Opts
  33. Die(1,Opts.make_usage_str(g.prog_name,'user',usage_data))
  34. def version():
  35. Die(0,fmt("""
  36. {pn} version {g.version}
  37. Part of the {g.proj_name} suite, an online/offline cryptocurrency wallet for the
  38. command line. Copyright (C){g.Cdates} {g.author} {g.email}
  39. """.format(g=g,pn=g.prog_name.upper()),indent=' ').rstrip())
  40. def print_help(po,opts_data,opt_filter):
  41. if not 'code' in opts_data:
  42. opts_data['code'] = {}
  43. from .protocol import init_proto_from_opts
  44. proto = init_proto_from_opts()
  45. if getattr(opt,'longhelp',None):
  46. opts_data['code']['long_options'] = common_opts_data['code']
  47. def remove_unneeded_long_opts():
  48. d = opts_data['text']['long_options']
  49. if g.prog_name != 'mmgen-tool':
  50. d = '\n'.join(''+i for i in d.split('\n') if not '--monero-wallet' in i)
  51. if proto.base_proto != 'Ethereum':
  52. d = '\n'.join(''+i for i in d.split('\n') if not '--token' in i)
  53. opts_data['text']['long_options'] = d
  54. remove_unneeded_long_opts()
  55. mmgen.share.Opts.print_help( # exits
  56. proto,
  57. po,
  58. opts_data,
  59. opt_filter )
  60. def fmt_opt(o):
  61. return '--' + o.replace('_','-')
  62. def die_on_incompatible_opts(incompat_list):
  63. for group in incompat_list:
  64. bad = [k for k in opt.__dict__ if k in group and getattr(opt,k) != None]
  65. if len(bad) > 1:
  66. die(1,'Conflicting options: {}'.format(', '.join(map(fmt_opt,bad))))
  67. def _show_hash_presets():
  68. fs = ' {:<7} {:<6} {:<3} {}'
  69. msg('Available parameters for scrypt.hash():')
  70. msg(fs.format('Preset','N','r','p'))
  71. for i in sorted(g.hash_presets.keys()):
  72. msg(fs.format(i,*g.hash_presets[i]))
  73. msg('N = memory usage (power of two), p = iterations (rounds)')
  74. sys.exit(0)
  75. def opt_preproc_debug(po):
  76. d = (
  77. ('Cmdline', ' '.join(sys.argv)),
  78. ('Opts', po.opts),
  79. ('Skipped opts', po.skipped_opts),
  80. ('User-selected opts', po.user_opts),
  81. ('Cmd args', po.cmd_args),
  82. )
  83. Msg('\n=== opts.py debug ===')
  84. for e in d:
  85. Msg(' {:<20}: {}'.format(*e))
  86. def opt_postproc_debug():
  87. a = [k for k in dir(opt) if k[:2] != '__' and getattr(opt,k) != None]
  88. b = [k for k in dir(opt) if k[:2] != '__' and getattr(opt,k) == None]
  89. Msg(' Opts after processing:')
  90. for k in a:
  91. v = getattr(opt,k)
  92. Msg(' {:18}: {!r:<6} [{}]'.format(k,v,type(v).__name__))
  93. Msg(" Opts set to 'None':")
  94. Msg(' {}\n'.format('\n '.join(b)))
  95. Msg(' Global vars:')
  96. for e in [d for d in dir(g) if d[:2] != '__']:
  97. Msg(' {:<20}: {}'.format(e, getattr(g,e)))
  98. Msg('\n=== end opts.py debug ===\n')
  99. def override_globals_from_cfg_file(ucfg):
  100. from .protocol import CoinProtocol,init_proto
  101. for d in ucfg.get_lines():
  102. if d.name in g.cfg_file_opts:
  103. ns = d.name.split('_')
  104. if ns[0] in CoinProtocol.coins:
  105. nse,tn = (
  106. (ns[2:],ns[1]=='testnet') if len(ns) > 2 and ns[1] in ('mainnet','testnet') else
  107. (ns[1:],False)
  108. )
  109. cls = type(init_proto(ns[0],tn)) # no instance yet, so override _class_ attr
  110. attr = '_'.join(nse)
  111. else:
  112. cls = g # g is "singleton" instance, so override _instance_ attr
  113. attr = d.name
  114. refval = getattr(cls,attr)
  115. val = ucfg.parse_value(d.value,refval)
  116. if not val:
  117. raise CfgFileParseError(f'Parse error in file {ucfg.fn!r}, line {d.lineno}')
  118. val_conv = set_for_type(val,refval,attr,src=ucfg.fn)
  119. setattr(cls,attr,val_conv)
  120. else:
  121. raise CfgFileParseError(f'{d.name!r}: unrecognized option in {ucfg.fn!r}, line {d.lineno}')
  122. def override_globals_and_set_opts_from_env(opt):
  123. for name in g.env_opts:
  124. if name == 'MMGEN_DEBUG_ALL':
  125. continue
  126. disable = name[:14] == 'MMGEN_DISABLE_'
  127. val = os.getenv(name) # os.getenv() returns None if env var is unset
  128. if val: # exclude empty string values; string value of '0' or 'false' sets variable to False
  129. gname = name[(6,14)[disable]:].lower()
  130. if hasattr(g,gname):
  131. setattr(g,gname,set_for_type(val,getattr(g,gname),name,disable))
  132. elif hasattr(opt,gname):
  133. if getattr(opt,gname) is None: # env must not override cmdline!
  134. setattr(opt,gname,val)
  135. else:
  136. raise ValueError(f'Name {gname} not present in globals or opts')
  137. def show_common_opts_diff():
  138. def common_opts_data_to_list():
  139. for l in common_opts_data['text'].splitlines():
  140. if l.startswith('--,'):
  141. yield l.split()[1].split('=')[0][2:].replace('-','_')
  142. def do_fmt(set_data):
  143. return fmt_list(['--'+s.replace('_','-') for s in set_data],fmt='col',indent=' ')
  144. a = set(g.common_opts)
  145. b = set(common_opts_data_to_list())
  146. m1 = 'g.common_opts - common_opts_data:\n {}\n'
  147. msg(m1.format(do_fmt(a-b) if a-b else 'None'))
  148. m2 = 'common_opts_data - g.common_opts (these do not set global var):\n{}\n'
  149. msg(m2.format(do_fmt(b-a)))
  150. m3 = 'common_opts_data ^ g.common_opts (these set global var):\n{}\n'
  151. msg(m3.format(do_fmt(b.intersection(a))))
  152. sys.exit(0)
  153. common_opts_data = {
  154. # Most but not all of these set the corresponding global var
  155. # View differences with show_common_opts_diff()
  156. 'text': """
  157. --, --accept-defaults Accept defaults at all prompts
  158. --, --coin=c Choose coin unit. Default: BTC. Current choice: {cu_dfl}
  159. --, --token=t Specify an ERC20 token by address or symbol
  160. --, --color=0|1 Disable or enable color output
  161. --, --force-256-color Force 256-color output when color is enabled
  162. --, --data-dir=path Specify {pnm} data directory location
  163. --, --daemon-data-dir=path Specify {dn} data directory location
  164. --, --daemon-id=ID Specify the coin daemon ID
  165. --, --ignore-daemon-version Ignore {dn} version check
  166. --, --http-timeout=t Set HTTP timeout in seconds for JSON-RPC connections
  167. --, --no-license Suppress the GPL license prompt
  168. --, --rpc-host=host Communicate with {dn} running on host 'host'
  169. --, --rpc-port=port Communicate with {dn} listening on port 'port'
  170. --, --rpc-user=user Authenticate to {dn} using username 'user'
  171. --, --rpc-password=pass Authenticate to {dn} using password 'pass'
  172. --, --rpc-backend=backend Use backend 'backend' for JSON-RPC communications
  173. --, --aiohttp-rpc-queue-len=N Use 'N' simultaneous RPC connections with aiohttp
  174. --, --monero-wallet-rpc-host=host Specify Monero wallet daemon host
  175. --, --monero-wallet-rpc-user=user Specify Monero wallet daemon username
  176. --, --monero-wallet-rpc-password=pass Specify Monero wallet daemon password
  177. --, --regtest=0|1 Disable or enable regtest mode
  178. --, --testnet=0|1 Disable or enable testnet
  179. --, --skip-cfg-file Skip reading the configuration file
  180. --, --version Print version information and exit
  181. --, --bob Switch to user "Bob" in MMGen regtest setup
  182. --, --alice Switch to user "Alice" in MMGen regtest setup
  183. """,
  184. 'code': lambda help_notes,proto,s: s.format(
  185. pnm = g.proj_name,
  186. dn = help_notes('coind_exec'),
  187. cu_dfl = proto.coin,
  188. )
  189. }
  190. opts_data_dfl = {
  191. 'text': {
  192. 'desc': '',
  193. 'usage':'',
  194. 'options': """
  195. -h, --help Print this help message
  196. --, --longhelp Print help message for long (common) options
  197. """
  198. }
  199. }
  200. def init(opts_data=None,add_opts=None,init_opts=None,opt_filter=None,parse_only=False):
  201. if opts_data is None:
  202. opts_data = opts_data_dfl
  203. opts_data['text']['long_options'] = common_opts_data['text']
  204. # po: (user_opts,cmd_args,opts,skipped_opts)
  205. po = mmgen.share.Opts.parse_opts(opts_data,opt_filter=opt_filter,parse_only=parse_only)
  206. if init_opts: # allow programs to preload user opts
  207. for uopt,val in init_opts.items():
  208. if uopt not in po.user_opts:
  209. po.user_opts[uopt] = val
  210. if parse_only:
  211. return po
  212. if g.debug_opts:
  213. opt_preproc_debug(po)
  214. # Copy parsed opts to opt, setting values to None if not set by user
  215. for o in set(
  216. po.opts
  217. + po.skipped_opts
  218. + tuple(add_opts or [])
  219. + tuple(init_opts or [])
  220. + g.required_opts
  221. + g.common_opts ):
  222. setattr(opt,o,po.user_opts[o] if o in po.user_opts else None)
  223. # Make this available to usage()
  224. global usage_data
  225. usage_data = opts_data['text'].get('usage2') or opts_data['text']['usage']
  226. if opt.version:
  227. version() # exits
  228. # === begin global var initialization === #
  229. """
  230. NB: user opt --data-dir is actually data_dir_root
  231. - data_dir is data_dir_root plus optionally 'regtest' or 'testnet', so for mainnet
  232. data_dir == data_dir_root
  233. - As with Bitcoin Core, cfg file is in data_dir_root, wallets and other data are
  234. in data_dir
  235. - Since cfg file is in data_dir_root, data_dir_root must be finalized before we
  236. can process cfg file
  237. - Since data_dir depends on the values of g.testnet and g.regtest, these must be
  238. finalized before setting data_dir
  239. """
  240. if opt.data_dir:
  241. g.data_dir_root = os.path.normpath(os.path.expanduser(opt.data_dir))
  242. elif os.getenv('MMGEN_TEST_SUITE'):
  243. from test.include.common import get_data_dir
  244. g.data_dir_root = get_data_dir()
  245. else:
  246. g.data_dir_root = os.path.join(g.home_dir,'.'+g.proj_name.lower())
  247. check_or_create_dir(g.data_dir_root)
  248. from .term import init_term
  249. init_term()
  250. if not opt.skip_cfg_file:
  251. from .cfg import cfg_file
  252. # check for changes in system template file - term must be initialized
  253. cfg_file('sample')
  254. override_globals_from_cfg_file(cfg_file('usr'))
  255. override_globals_and_set_opts_from_env(opt)
  256. # Set globals from opts, setting type from original global value
  257. # Do here, before opts are set from globals below
  258. for k in (g.common_opts + g.opt_sets_global):
  259. if hasattr(opt,k):
  260. val = getattr(opt,k)
  261. if val != None and hasattr(g,k):
  262. setattr(g,k,set_for_type(val,getattr(g,k),'--'+k))
  263. """
  264. g.color is finalized, so initialize color
  265. """
  266. if g.color: # MMGEN_DISABLE_COLOR sets this to False
  267. from .color import init_color
  268. init_color(num_colors=('auto',256)[bool(g.force_256_color)])
  269. """
  270. g.testnet and g.regtest are finalized, so we can set g.data_dir
  271. """
  272. g.data_dir = os.path.normpath(os.path.join(
  273. g.data_dir_root,
  274. ('regtest' if g.regtest else 'testnet' if g.testnet else '') ))
  275. # Set user opts from globals:
  276. # - if opt is unset, set it to global value
  277. # - if opt is set, convert its type to that of global value
  278. for k in g.global_sets_opt:
  279. if hasattr(opt,k) and getattr(opt,k) != None:
  280. setattr(opt,k,set_for_type(getattr(opt,k),getattr(g,k),'--'+k))
  281. else:
  282. setattr(opt,k,getattr(g,k))
  283. if opt.show_hash_presets: # exits
  284. _show_hash_presets()
  285. g.coin = g.coin.upper() or 'BTC'
  286. g.token = g.token.upper() or None
  287. if g.prog_name == 'mmgen-regtest' or g.bob or g.alice:
  288. g.regtest = True
  289. g.rpc_host = 'localhost'
  290. g.data_dir = os.path.join(g.data_dir_root,'regtest',g.coin.lower(),('alice','bob')[g.bob])
  291. from .regtest import MMGenRegtest
  292. g.rpc_user = MMGenRegtest.rpc_user
  293. g.rpc_password = MMGenRegtest.rpc_password
  294. g.rpc_port = MMGenRegtest(g.coin).d.rpc_port
  295. from .protocol import init_genonly_altcoins
  296. altcoin_trust_level = init_genonly_altcoins(
  297. g.coin,
  298. testnet = g.testnet or g.regtest )
  299. # === end global var initialization === #
  300. # print help screen only after global vars are initialized:
  301. if getattr(opt,'help',None) or getattr(opt,'longhelp',None):
  302. print_help(po,opts_data,opt_filter) # exits
  303. warn_altcoins(g.coin,altcoin_trust_level)
  304. die_on_incompatible_opts(g.incompatible_opts)
  305. check_or_create_dir(g.data_dir) # g.data_dir is finalized, so we can create it
  306. # Check user-set opts without modifying them
  307. check_usr_opts(po.user_opts)
  308. # Check all opts against g.autoset_opts, setting if unset
  309. check_and_set_autoset_opts()
  310. set_auto_typeset_opts()
  311. if opt.verbose:
  312. opt.quiet = None
  313. if g.debug and g.prog_name != 'test.py':
  314. opt.verbose,opt.quiet = (True,None)
  315. if g.debug_opts:
  316. opt_postproc_debug()
  317. # We don't need this data anymore
  318. del mmgen.share.Opts
  319. for k in ('text','notes','code'):
  320. if k in opts_data:
  321. del opts_data[k]
  322. g.lock()
  323. opt.lock()
  324. return po.cmd_args
  325. # DISABLED
  326. def opt_is_tx_fee(key,val,desc): # 'key' must remain a placeholder
  327. # contract data or non-standard startgas: disable fee checking
  328. if hasattr(opt,'contract_data') and opt.contract_data:
  329. return
  330. if hasattr(opt,'tx_gas') and opt.tx_gas:
  331. return
  332. from .tx import MMGenTX
  333. from .protocol import init_proto_from_opts
  334. tx = MMGenTX.New(init_proto_from_opts())
  335. # Size of 224 is just a ball-park figure to eliminate the most extreme cases at startup
  336. # This check will be performed again once we know the true size
  337. ret = tx.feespec2abs(val,224)
  338. if ret == False:
  339. raise UserOptError('{!r}: invalid {}\n(not a {} amount or {} specification)'.format(
  340. val,desc,tx.proto.coin.upper(),tx.rel_fee_desc))
  341. if ret > tx.proto.max_tx_fee:
  342. raise UserOptError('{!r}: invalid {}\n({} > max_tx_fee ({} {}))'.format(
  343. val,desc,ret.fmt(fs='1.1'),tx.proto.max_tx_fee,tx.proto.coin.upper()))
  344. def check_usr_opts(usr_opts): # Raises an exception if any check fails
  345. def opt_splits(val,sep,n,desc):
  346. sepword = 'comma' if sep == ',' else 'colon' if sep == ':' else repr(sep)
  347. try:
  348. l = val.split(sep)
  349. except:
  350. raise UserOptError('{!r}: invalid {} (not {}-separated list)'.format(val,desc,sepword))
  351. if len(l) != n:
  352. raise UserOptError('{!r}: invalid {} ({} {}-separated items required)'.format(val,desc,n,sepword))
  353. def opt_compares(val,op_str,target,desc,desc2=''):
  354. import operator as o
  355. op_f = { '<':o.lt, '<=':o.le, '>':o.gt, '>=':o.ge, '=':o.eq }[op_str]
  356. if not op_f(val,target):
  357. d2 = desc2 + ' ' if desc2 else ''
  358. raise UserOptError('{}: invalid {} ({}not {} {})'.format(val,desc,d2,op_str,target))
  359. def opt_is_int(val,desc):
  360. if not is_int(val):
  361. raise UserOptError('{!r}: invalid {} (not an integer)'.format(val,desc))
  362. def opt_is_float(val,desc):
  363. try:
  364. float(val)
  365. except:
  366. raise UserOptError('{!r}: invalid {} (not a floating-point number)'.format(val,desc))
  367. def opt_is_in_list(val,tlist,desc):
  368. if val not in tlist:
  369. q,sep = (('',','),("'","','"))[type(tlist[0]) == str]
  370. fs = '{q}{v}{q}: invalid {w}\nValid choices: {q}{o}{q}'
  371. raise UserOptError(fs.format(v=val,w=desc,q=q,o=sep.join(map(str,sorted(tlist)))))
  372. def opt_unrecognized(key,val,desc='value'):
  373. raise UserOptError('{!r}: unrecognized {} for option {!r}'.format(val,desc,fmt_opt(key)))
  374. def opt_display(key,val='',beg='For selected',end=':\n'):
  375. s = '{}={}'.format(fmt_opt(key),val) if val else fmt_opt(key)
  376. msg_r('{} option {!r}{}'.format(beg,s,end))
  377. def chk_in_fmt(key,val,desc):
  378. from .wallet import Wallet,IncogWallet,Brainwallet,IncogWalletHidden
  379. sstype = Wallet.fmt_code_to_type(val)
  380. if not sstype:
  381. opt_unrecognized(key,val)
  382. if key == 'out_fmt':
  383. p = 'hidden_incog_output_params'
  384. if sstype == IncogWalletHidden and not getattr(opt,p):
  385. m1 = 'Hidden incog format output requested. '
  386. m2 = 'You must supply a file and offset with the {!r} option'
  387. raise UserOptError(m1+m2.format(fmt_opt(p)))
  388. if issubclass(sstype,IncogWallet) and opt.old_incog_fmt:
  389. opt_display(key,val,beg='Selected',end=' ')
  390. opt_display('old_incog_fmt',beg='conflicts with',end=':\n')
  391. raise UserOptError('Export to old incog wallet format unsupported')
  392. elif issubclass(sstype,Brainwallet):
  393. raise UserOptError('Output to brainwallet format unsupported')
  394. chk_out_fmt = chk_in_fmt
  395. def chk_hidden_incog_input_params(key,val,desc):
  396. a = val.rsplit(',',1) # permit comma in filename
  397. if len(a) != 2:
  398. opt_display(key,val)
  399. raise UserOptError('Option requires two comma-separated arguments')
  400. fn,offset = a
  401. opt_is_int(offset,desc)
  402. if key == 'hidden_incog_input_params':
  403. check_infile(fn,blkdev_ok=True)
  404. key2 = 'in_fmt'
  405. else:
  406. try: os.stat(fn)
  407. except:
  408. b = os.path.dirname(fn)
  409. if b: check_outdir(b)
  410. else:
  411. check_outfile(fn,blkdev_ok=True)
  412. key2 = 'out_fmt'
  413. if hasattr(opt,key2):
  414. val2 = getattr(opt,key2)
  415. from .wallet import IncogWalletHidden
  416. if val2 and val2 not in IncogWalletHidden.fmt_codes:
  417. fs = 'Option conflict:\n {}, with\n {}={}'
  418. raise UserOptError(fs.format(fmt_opt(key),fmt_opt(key2),val2))
  419. chk_hidden_incog_output_params = chk_hidden_incog_input_params
  420. def chk_seed_len(key,val,desc):
  421. opt_is_int(val,desc)
  422. opt_is_in_list(int(val),g.seed_lens,desc)
  423. def chk_hash_preset(key,val,desc):
  424. opt_is_in_list(val,list(g.hash_presets.keys()),desc)
  425. def chk_brain_params(key,val,desc):
  426. a = val.split(',')
  427. if len(a) != 2:
  428. opt_display(key,val)
  429. raise UserOptError('Option requires two comma-separated arguments')
  430. opt_is_int(a[0],'seed length '+desc)
  431. opt_is_in_list(int(a[0]),g.seed_lens,'seed length '+desc)
  432. opt_is_in_list(a[1],list(g.hash_presets.keys()),'hash preset '+desc)
  433. def chk_usr_randchars(key,val,desc):
  434. if val == 0:
  435. return
  436. opt_is_int(val,desc)
  437. opt_compares(val,'>=',g.min_urandchars,desc)
  438. opt_compares(val,'<=',g.max_urandchars,desc)
  439. def chk_tx_fee(key,val,desc):
  440. pass
  441. # opt_is_tx_fee(key,val,desc) # TODO: move this check elsewhere
  442. def chk_tx_confs(key,val,desc):
  443. opt_is_int(val,desc)
  444. opt_compares(val,'>=',1,desc)
  445. def chk_vsize_adj(key,val,desc):
  446. opt_is_float(val,desc)
  447. ymsg('Adjusting transaction vsize by a factor of {:1.2f}'.format(float(val)))
  448. def chk_key_generator(key,val,desc):
  449. opt_compares(val,'<=',len(g.key_generators),desc)
  450. opt_compares(val,'>',0,desc)
  451. def chk_coin(key,val,desc):
  452. from .protocol import CoinProtocol
  453. opt_is_in_list(val.lower(),CoinProtocol.coins,'coin')
  454. # TODO: move this check elsewhere
  455. # def chk_rbf(key,val,desc):
  456. # if not proto.cap('rbf'):
  457. # m = '--rbf requested, but {} does not support replace-by-fee transactions'
  458. # raise UserOptError(m.format(proto.coin))
  459. # def chk_bob(key,val,desc):
  460. # m = "Regtest (Bob and Alice) mode not set up yet. Run '{}-regtest setup' to initialize."
  461. # from .regtest import MMGenRegtest
  462. # try:
  463. # os.stat(os.path.join(MMGenRegtest(g.coin).d.datadir,'regtest','debug.log'))
  464. # except:
  465. # raise UserOptError(m.format(g.proj_name.lower()))
  466. #
  467. # chk_alice = chk_bob
  468. def chk_locktime(key,val,desc):
  469. opt_is_int(val,desc)
  470. opt_compares(int(val),'>',0,desc)
  471. # TODO: move this check elsewhere
  472. # def chk_token(key,val,desc):
  473. # if not 'token' in proto.caps:
  474. # raise UserOptError('Coin {!r} does not support the --token option'.format(tx.coin))
  475. # if len(val) == 40 and is_hex_str(val):
  476. # return
  477. # if len(val) > 20 or not all(s.isalnum() for s in val):
  478. # raise UserOptError('{!r}: invalid parameter for --token option'.format(val))
  479. cfuncs = { k:v for k,v in locals().items() if k.startswith('chk_') }
  480. for key in usr_opts:
  481. val = getattr(opt,key)
  482. desc = 'parameter for {!r} option'.format(fmt_opt(key))
  483. if key in g.infile_opts:
  484. check_infile(val) # file exists and is readable - dies on error
  485. elif key == 'outdir':
  486. check_outdir(val) # dies on error
  487. elif 'chk_'+key in cfuncs:
  488. cfuncs['chk_'+key](key,val,desc)
  489. elif g.debug:
  490. Msg('check_usr_opts(): No test for opt {!r}'.format(key))
  491. def set_auto_typeset_opts():
  492. for key,ref_type in g.auto_typeset_opts.items():
  493. if hasattr(opt,key):
  494. val = getattr(opt,key)
  495. if val is not None: # typeset only if opt is set
  496. setattr(opt,key,ref_type(val))
  497. def check_and_set_autoset_opts(): # Raises exception if any check fails
  498. def nocase_str(key,val,asd):
  499. try:
  500. return asd.choices.index(val)
  501. except:
  502. return 'one of'
  503. def nocase_pfx(key,val,asd):
  504. cs = [s.startswith(val.lower()) for s in asd.choices]
  505. if cs.count(True) == 1:
  506. return cs.index(True)
  507. else:
  508. return 'unique substring of'
  509. for key,asd in g.autoset_opts.items():
  510. if hasattr(opt,key):
  511. val = getattr(opt,key)
  512. if val is None:
  513. setattr(opt,key,asd.choices[0])
  514. else:
  515. ret = locals()[asd.type](key,val,asd)
  516. if type(ret) is str:
  517. m = '{!r}: invalid parameter for option --{} (not {}: {})'
  518. raise UserOptError(m.format(val,key.replace('_','-'),ret,fmt_list(asd.choices)))
  519. elif ret is True:
  520. setattr(opt,key,val)
  521. else:
  522. setattr(opt,key,asd.choices[ret])