opts.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2019 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. class opt(object): pass
  23. from mmgen.globalvars import g
  24. import mmgen.share.Opts
  25. from mmgen.util import *
  26. def usage(): Die(1,'USAGE: {} {}'.format(g.prog_name,usage_txt))
  27. def die_on_incompatible_opts(incompat_list):
  28. for group in incompat_list:
  29. bad = [k for k in opt.__dict__ if opt.__dict__[k] and k in group]
  30. if len(bad) > 1:
  31. die(1,'Conflicting options: {}'.format(', '.join(map(fmt_opt,bad))))
  32. def fmt_opt(o): return '--' + o.replace('_','-')
  33. def _show_hash_presets():
  34. fs = ' {:<7} {:<6} {:<3} {}'
  35. msg('Available parameters for scrypt.hash():')
  36. msg(fs.format('Preset','N','r','p'))
  37. for i in sorted(g.hash_presets.keys()):
  38. msg(fs.format(i,*g.hash_presets[i]))
  39. msg('N = memory usage (power of two), p = iterations (rounds)')
  40. def opt_preproc_debug(short_opts,long_opts,skipped_opts,uopts,args):
  41. d = (
  42. ('Cmdline', ' '.join(sys.argv)),
  43. ('Short opts', short_opts),
  44. ('Long opts', long_opts),
  45. ('Skipped opts', skipped_opts),
  46. ('User-selected opts', uopts),
  47. ('Cmd args', args),
  48. )
  49. Msg('\n=== opts.py debug ===')
  50. for e in d: Msg(' {:<20}: {}'.format(*e))
  51. def opt_postproc_debug():
  52. a = [k for k in dir(opt) if k[:2] != '__' and getattr(opt,k) != None]
  53. b = [k for k in dir(opt) if k[:2] != '__' and getattr(opt,k) == None]
  54. Msg(' Opts after processing:')
  55. for k in a:
  56. v = getattr(opt,k)
  57. Msg(' {:18}: {!r:<6} [{}]'.format(k,v,type(v).__name__))
  58. Msg(" Opts set to 'None':")
  59. Msg(' {}\n'.format('\n '.join(b)))
  60. Msg(' Global vars:')
  61. for e in [d for d in dir(g) if d[:2] != '__']:
  62. Msg(' {:<20}: {}'.format(e, getattr(g,e)))
  63. Msg('\n=== end opts.py debug ===\n')
  64. def opt_postproc_initializations():
  65. from mmgen.term import set_terminal_vars
  66. set_terminal_vars()
  67. if g.color: # MMGEN_DISABLE_COLOR sets this to False
  68. from mmgen.color import start_mscolor,init_color
  69. if g.platform == 'win':
  70. start_mscolor()
  71. init_color(num_colors=('auto',256)[bool(g.force_256_color)])
  72. g.coin = g.coin.upper() # allow user to use lowercase
  73. g.dcoin = g.coin # the display coin; for ERC20 tokens, g.dcoin is set to the token symbol
  74. def set_data_dir_root():
  75. g.data_dir_root = os.path.normpath(os.path.expanduser(opt.data_dir)) if opt.data_dir else \
  76. os.path.join(g.home_dir,'.'+g.proj_name.lower())
  77. # mainnet and testnet share cfg file, as with Core
  78. g.cfg_file = os.path.join(g.data_dir_root,'{}.cfg'.format(g.proj_name.lower()))
  79. def get_cfg_template_data():
  80. # https://wiki.debian.org/Python:
  81. # Debian (Ubuntu) sys.prefix is '/usr' rather than '/usr/local, so add 'local'
  82. # TODO - test for Windows
  83. # This must match the configuration in setup.py
  84. cfg_template = os.path.join(*([sys.prefix]
  85. + (['share'],['local','share'])[g.platform=='linux']
  86. + [g.proj_name.lower(),os.path.basename(g.cfg_file)]))
  87. try:
  88. return open(cfg_template).read()
  89. except:
  90. msg("WARNING: configuration template not found at '{}'".format(cfg_template))
  91. return ''
  92. def get_data_from_cfg_file():
  93. from mmgen.util import msg,die,check_or_create_dir
  94. check_or_create_dir(g.data_dir_root) # dies on error
  95. template_data = get_cfg_template_data()
  96. data = {}
  97. def copy_template_data(fn):
  98. try:
  99. open(fn,'wb').write(template_data.encode())
  100. os.chmod(fn,0o600)
  101. except:
  102. die(2,"ERROR: unable to write to datadir '{}'".format(g.data_dir))
  103. for k,suf in (('cfg',''),('sample','.sample')):
  104. try:
  105. data[k] = open(g.cfg_file+suf,'rb').read().decode()
  106. except:
  107. if template_data:
  108. copy_template_data(g.cfg_file+suf)
  109. data[k] = template_data
  110. else:
  111. data[k] = ''
  112. if template_data and data['sample'] != template_data:
  113. g.cfg_options_changed = True
  114. copy_template_data(g.cfg_file+'.sample')
  115. return data['cfg']
  116. def override_from_cfg_file(cfg_data):
  117. from mmgen.util import die,strip_comments,set_for_type
  118. import re
  119. from mmgen.protocol import CoinProtocol
  120. for n,l in enumerate(cfg_data.splitlines(),1): # DOS-safe
  121. l = strip_comments(l)
  122. if l == '': continue
  123. m = re.match(r'(\w+)\s+(\S+)$',l)
  124. if not m: die(2,"Parse error in file '{}', line {}".format(g.cfg_file,n))
  125. name,val = m.groups()
  126. if name in g.cfg_file_opts:
  127. pfx,cfg_var = name.split('_',1)
  128. if pfx in CoinProtocol.coins:
  129. tn = False
  130. cv1,cv2 = cfg_var.split('_',1)
  131. if cv1 in ('mainnet','testnet'):
  132. tn,cfg_var = (cv1 == 'testnet'),cv2
  133. cls,attr = CoinProtocol(pfx,tn),cfg_var
  134. else:
  135. cls,attr = g,name
  136. setattr(cls,attr,set_for_type(val,getattr(cls,attr),attr,src=g.cfg_file))
  137. else:
  138. die(2,"'{}': unrecognized option in '{}'".format(name,g.cfg_file))
  139. def override_from_env():
  140. from mmgen.util import set_for_type
  141. for name in g.env_opts:
  142. if name == 'MMGEN_DEBUG_ALL': continue
  143. disable = name[:14] == 'MMGEN_DISABLE_'
  144. val = os.getenv(name) # os.getenv() returns None if env var is unset
  145. if val: # exclude empty string values; string value of '0' or 'false' sets variable to False
  146. gname = name[(6,14)[disable]:].lower()
  147. setattr(g,gname,set_for_type(val,getattr(g,gname),name,disable))
  148. def warn_altcoins(trust_level):
  149. if trust_level > 3:
  150. return
  151. tl = (red('COMPLETELY UNTESTED'),red('LOW'),yellow('MEDIUM'),green('HIGH'))
  152. m = """
  153. Support for coin '{}' is EXPERIMENTAL. The {pn} project assumes no
  154. responsibility for any loss of funds you may incur.
  155. This coin's {pn} testing status: {}
  156. Are you sure you want to continue?
  157. """.strip().format(g.coin,tl[trust_level],pn=g.proj_name)
  158. if g.test_suite:
  159. qmsg(m); return
  160. if not keypress_confirm(m,default_yes=True):
  161. sys.exit(0)
  162. def common_opts_code(s):
  163. from mmgen.protocol import CoinProtocol
  164. return s.format(
  165. pnm=g.proj_name,pn=g.proto.name,dn=g.proto.daemon_name,
  166. cu_dfl=g.coin,
  167. cu_all=' '.join(CoinProtocol.coins) )
  168. common_opts_data = {
  169. # most, but not all, of these set the corresponding global var
  170. 'text': """
  171. --, --accept-defaults Accept defaults at all prompts
  172. --, --coin=c Choose coin unit. Default: {cu_dfl}. Options: {cu_all}
  173. --, --token=t Specify an ERC20 token by address or symbol
  174. --, --color=0|1 Disable or enable color output
  175. --, --force-256-color Force 256-color output when color is enabled
  176. --, --daemon-data-dir=d Specify coin daemon data directory location 'd'
  177. --, --data-dir=d Specify {pnm} data directory location 'd'
  178. --, --no-license Suppress the GPL license prompt
  179. --, --rpc-host=h Communicate with {dn} running on host 'h'
  180. --, --rpc-port=p Communicate with {dn} listening on port 'p'
  181. --, --rpc-user=user Override 'rpcuser' in {pn}.conf
  182. --, --rpc-password=pass Override 'rpcpassword' in {pn}.conf
  183. --, --regtest=0|1 Disable or enable regtest mode
  184. --, --testnet=0|1 Disable or enable testnet
  185. --, --skip-cfg-file Skip reading the configuration file
  186. --, --version Print version information and exit
  187. --, --bob Switch to user "Bob" in MMGen regtest setup
  188. --, --alice Switch to user "Alice" in MMGen regtest setup
  189. """,
  190. 'code': common_opts_code
  191. }
  192. def init(opts_data,add_opts=[],opt_filter=None,parse_only=False):
  193. opts_data['text']['long_options'] = common_opts_data['text']
  194. uopts,args,short_opts,long_opts,skipped_opts = \
  195. mmgen.share.Opts.parse_opts(opts_data,opt_filter=opt_filter,parse_only=parse_only)
  196. if parse_only:
  197. return uopts,args,short_opts,long_opts,skipped_opts
  198. if g.debug_opts: opt_preproc_debug(short_opts,long_opts,skipped_opts,uopts,args)
  199. # Save this for usage()
  200. global usage_txt
  201. usage_txt = opts_data['text']['usage']
  202. # Transfer uopts into opt, setting program's opts + required opts to None if not set by user
  203. for o in ( tuple([s.rstrip('=') for s in long_opts] + add_opts + skipped_opts)
  204. + g.required_opts
  205. + g.common_opts ):
  206. setattr(opt,o,uopts[o] if o in uopts else None)
  207. if opt.version: Die(0,"""
  208. {pn} version {g.version}
  209. Part of the {g.proj_name} suite, an online/offline cryptocoin wallet for the command line.
  210. Copyright (C) {g.Cdates} {g.author} {g.email}
  211. """.format(g=g,pn=g.prog_name.upper()).lstrip('\n').rstrip())
  212. if os.getenv('MMGEN_DEBUG_ALL'):
  213. for name in g.env_opts:
  214. if name[:11] == 'MMGEN_DEBUG':
  215. os.environ[name] = '1'
  216. # === Interaction with global vars begins here ===
  217. # NB: user opt --data-dir is actually g.data_dir_root
  218. # cfg file is in g.data_dir_root, wallet and other data are in g.data_dir
  219. # We must set g.data_dir_root and g.cfg_file from cmdline before processing cfg file
  220. set_data_dir_root()
  221. if not opt.skip_cfg_file:
  222. override_from_cfg_file(get_data_from_cfg_file())
  223. override_from_env()
  224. # User opt sets global var - do these here, before opt is set from g.global_sets_opt
  225. for k in (g.common_opts + g.opt_sets_global):
  226. if hasattr(opt,k):
  227. val = getattr(opt,k)
  228. if val != None:
  229. setattr(g,k,set_for_type(val,getattr(g,k),'--'+k))
  230. if g.regtest: g.testnet = True # These are equivalent for now
  231. g.network = 'testnet' if g.testnet else 'mainnet'
  232. from mmgen.protocol import init_genonly_altcoins,CoinProtocol
  233. altcoin_trust_level = init_genonly_altcoins(opt.coin or 'btc')
  234. # g.testnet is set, so we can set g.proto
  235. g.proto = CoinProtocol(g.coin,g.testnet)
  236. # global sets proto
  237. if g.daemon_data_dir: g.proto.daemon_data_dir = g.daemon_data_dir
  238. # g.proto is set, so we can set g.data_dir
  239. g.data_dir = os.path.normpath(os.path.join(g.data_dir_root,g.proto.data_subdir))
  240. # If user opt is set, convert its type based on value in mmgen.globalvars (g)
  241. # If unset, set it to default value in mmgen.globalvars (g)
  242. setattr(opt,'set_by_user',[])
  243. for k in g.global_sets_opt:
  244. if k in opt.__dict__ and getattr(opt,k) != None:
  245. setattr(opt,k,set_for_type(getattr(opt,k),getattr(g,k),'--'+k))
  246. opt.set_by_user.append(k)
  247. else:
  248. setattr(opt,k,g.__dict__[k])
  249. if opt.show_hash_presets:
  250. _show_hash_presets()
  251. sys.exit(0)
  252. if opt.verbose: opt.quiet = None
  253. die_on_incompatible_opts(g.incompatible_opts)
  254. opt_postproc_initializations()
  255. if opts_data['do_help']: # print help screen only after global vars are initialized
  256. if not 'code' in opts_data:
  257. opts_data['code'] = {}
  258. opts_data['code']['long_options'] = common_opts_data['code']
  259. if g.debug_utf8:
  260. for k in opts_data:
  261. if type(opts_data[k]) == str:
  262. opts_data[k] += '-α'
  263. mmgen.share.Opts.print_help(opts_data,opt_filter) # exits
  264. if g.bob or g.alice:
  265. g.testnet = True
  266. g.regtest = True
  267. g.proto = CoinProtocol(g.coin,g.testnet)
  268. g.rpc_host = 'localhost'
  269. g.data_dir = os.path.join(g.data_dir_root,'regtest',g.coin.lower(),('alice','bob')[g.bob])
  270. from mmgen.regtest import MMGenRegtest
  271. g.rpc_user = MMGenRegtest.rpc_user
  272. g.rpc_password = MMGenRegtest.rpc_password
  273. g.rpc_port = MMGenRegtest(g.coin).d.rpc_port
  274. check_or_create_dir(g.data_dir) # g.data_dir is finalized, so now we can do this
  275. if g.regtest and hasattr(g.proto,'bech32_hrp_rt'):
  276. g.proto.bech32_hrp = g.proto.bech32_hrp_rt
  277. # Check user-set opts without modifying them
  278. if not check_opts(uopts):
  279. die(1,'Options checking failed')
  280. # Check user-set opts against g.opt_values, setting opt if unset:
  281. if not check_opts2(uopts):
  282. die(1,'Options checking failed')
  283. if hasattr(g,'cfg_options_changed'):
  284. ymsg("Warning: config file options have changed! See '{}' for details".format(g.cfg_file+'.sample'))
  285. from mmgen.util import my_raw_input
  286. my_raw_input('Hit ENTER to continue: ')
  287. if g.debug and g.prog_name != 'test.py':
  288. opt.verbose,opt.quiet = (True,None)
  289. if g.debug_opts: opt_postproc_debug()
  290. warn_altcoins(altcoin_trust_level)
  291. # We don't need this data anymore
  292. del mmgen.share.Opts, opts_data
  293. return args
  294. def opt_is_tx_fee(val,desc):
  295. from mmgen.tx import MMGenTX
  296. tx = MMGenTX(offline=True)
  297. # TODO: size is just a guess; do this check after parsing tx file
  298. ret = tx.process_fee_spec(val,224,on_fail='return')
  299. # Non-standard startgas: disable fee checking
  300. if hasattr(opt,'contract_data') and opt.contract_data: ret = None
  301. if hasattr(opt,'tx_gas') and opt.tx_gas: ret = None
  302. if ret == False:
  303. msg("'{}': invalid {}\n(not a {} amount or {} specification)".format(
  304. val,desc,g.coin.upper(),tx.rel_fee_desc))
  305. elif ret != None and ret > g.proto.max_tx_fee:
  306. msg("'{}': invalid {}\n({} > max_tx_fee ({} {}))".format(
  307. val,desc,ret.fmt(fs='1.1'),g.proto.max_tx_fee,g.coin.upper()))
  308. else:
  309. return True
  310. return False
  311. def check_opts2(usr_opts): # Returns false if any check fails
  312. for key in [e for e in opt.__dict__ if not e.startswith('__')]:
  313. if key in g.opt_values:
  314. val = getattr(opt,key)
  315. d = g.opt_values[key]
  316. if d[0] == 'nocase_str':
  317. if val == None:
  318. setattr(opt,key,d[1][0])
  319. elif val.lower() not in d[1]:
  320. m = "{!r}: invalid parameter for option --{} (valid choices: '{}')"
  321. msg(m.format(val,key.replace('_','-'),"', '".join(d[1])))
  322. return False
  323. return True
  324. def check_opts(usr_opts): # Returns false if any check fails
  325. def opt_splits(val,sep,n,desc):
  326. sepword = 'comma' if sep == ',' else 'colon' if sep == ':' else "'{}'".format(sep)
  327. try: l = val.split(sep)
  328. except:
  329. msg("'{}': invalid {} (not {}-separated list)".format(val,desc,sepword))
  330. return False
  331. if len(l) == n: return True
  332. else:
  333. msg("'{}': invalid {} ({} {}-separated items required)".format(val,desc,n,sepword))
  334. return False
  335. def opt_compares(val,op_str,target,desc,what=''):
  336. import operator as o
  337. op_f = { '<':o.lt, '<=':o.le, '>':o.gt, '>=':o.ge, '=':o.eq }[op_str]
  338. if what: what += ' '
  339. if not op_f(val,target):
  340. msg('{}: invalid {} ({}not {} {})'.format(val,desc,what,op_str,target))
  341. return False
  342. return True
  343. def opt_is_int(val,desc):
  344. try: int(val)
  345. except:
  346. msg("'{}': invalid {} (not an integer)".format(val,desc))
  347. return False
  348. return True
  349. def opt_is_float(val,desc):
  350. try: float(val)
  351. except:
  352. msg("'{}': invalid {} (not a floating-point number)".format(val,desc))
  353. return False
  354. return True
  355. def opt_is_in_list(val,lst,desc):
  356. if val not in lst:
  357. q,sep = (('',','),("'","','"))[type(lst[0]) == str]
  358. fs = '{q}{v}{q}: invalid {w}\nValid choices: {q}{o}{q}'
  359. msg(fs.format(v=val,w=desc,q=q,o=sep.join(map(str,sorted(lst)))))
  360. return False
  361. return True
  362. def opt_unrecognized(key,val,desc):
  363. msg("'{}': unrecognized {} for option '{}'".format(val,desc,fmt_opt(key)))
  364. return False
  365. def opt_display(key,val='',beg='For selected',end=':\n'):
  366. s = '{}={}'.format(fmt_opt(key),val) if val else fmt_opt(key)
  367. msg_r("{} option '{}'{}".format(beg,s,end))
  368. global opt
  369. for key,val in [(k,getattr(opt,k)) for k in usr_opts]:
  370. desc = "parameter for '{}' option".format(fmt_opt(key))
  371. from mmgen.util import check_infile,check_outfile,check_outdir
  372. # Check for file existence and readability
  373. if key in ('keys_from_file','mmgen_keys_from_file',
  374. 'passwd_file','keysforaddrs','comment_file'):
  375. check_infile(val) # exits on error
  376. continue
  377. if key == 'outdir':
  378. check_outdir(val) # exits on error
  379. # # NEW
  380. elif key in ('in_fmt','out_fmt'):
  381. from mmgen.seed import SeedSource,IncogWallet,Brainwallet,IncogWalletHidden
  382. sstype = SeedSource.fmt_code_to_type(val)
  383. if not sstype:
  384. return opt_unrecognized(key,val,'format code')
  385. if key == 'out_fmt':
  386. p = 'hidden_incog_output_params'
  387. if sstype == IncogWalletHidden and not getattr(opt,p):
  388. m1 = 'Hidden incog format output requested. '
  389. m2 = "You must supply a file and offset with the '{}' option"
  390. die(1,m1+m2.format(fmt_opt(p)))
  391. if issubclass(sstype,IncogWallet) and opt.old_incog_fmt:
  392. opt_display(key,val,beg='Selected',end=' ')
  393. opt_display('old_incog_fmt',beg='conflicts with',end=':\n')
  394. die(1,'Export to old incog wallet format unsupported')
  395. elif issubclass(sstype,Brainwallet):
  396. die(1,'Output to brainwallet format unsupported')
  397. elif key in ('hidden_incog_input_params','hidden_incog_output_params'):
  398. a = val.split(',')
  399. if len(a) < 2:
  400. opt_display(key,val)
  401. msg('Option requires two comma-separated arguments')
  402. return False
  403. fn,ofs = ','.join(a[:-1]),a[-1] # permit comma in filename
  404. if not opt_is_int(ofs,desc): return False
  405. if key == 'hidden_incog_input_params':
  406. check_infile(fn,blkdev_ok=True)
  407. key2 = 'in_fmt'
  408. else:
  409. try: os.stat(fn)
  410. except:
  411. b = os.path.dirname(fn)
  412. if b: check_outdir(b)
  413. else: check_outfile(fn,blkdev_ok=True)
  414. key2 = 'out_fmt'
  415. if hasattr(opt,key2):
  416. val2 = getattr(opt,key2)
  417. from mmgen.seed import IncogWalletHidden
  418. if val2 and val2 not in IncogWalletHidden.fmt_codes:
  419. fs = 'Option conflict:\n {}, with\n {}={}'
  420. die(1,fs.format(fmt_opt(key),fmt_opt(key2),val2))
  421. elif key == 'seed_len':
  422. if not opt_is_int(val,desc): return False
  423. if not opt_is_in_list(int(val),g.seed_lens,desc): return False
  424. elif key == 'hash_preset':
  425. if not opt_is_in_list(val,list(g.hash_presets.keys()),desc): return False
  426. elif key == 'brain_params':
  427. a = val.split(',')
  428. if len(a) != 2:
  429. opt_display(key,val)
  430. msg('Option requires two comma-separated arguments')
  431. return False
  432. d = 'seed length ' + desc
  433. if not opt_is_int(a[0],d): return False
  434. if not opt_is_in_list(int(a[0]),g.seed_lens,d): return False
  435. d = 'hash preset ' + desc
  436. if not opt_is_in_list(a[1],list(g.hash_presets.keys()),d): return False
  437. elif key == 'usr_randchars':
  438. if val == 0: continue
  439. if not opt_is_int(val,desc): return False
  440. if not opt_compares(val,'>=',g.min_urandchars,desc): return False
  441. if not opt_compares(val,'<=',g.max_urandchars,desc): return False
  442. elif key == 'tx_fee':
  443. if not opt_is_tx_fee(val,desc): return False
  444. elif key == 'tx_confs':
  445. if not opt_is_int(val,desc): return False
  446. if not opt_compares(val,'>=',1,desc): return False
  447. elif key == 'vsize_adj':
  448. if not opt_is_float(val,desc): return False
  449. ymsg('Adjusting transaction vsize by a factor of {:1.2f}'.format(float(val)))
  450. elif key == 'key_generator':
  451. if not opt_compares(val,'<=',len(g.key_generators),desc): return False
  452. if not opt_compares(val,'>',0,desc): return False
  453. elif key == 'coin':
  454. from mmgen.protocol import CoinProtocol
  455. if not opt_is_in_list(val.lower(),list(CoinProtocol.coins.keys()),'coin'): return False
  456. elif key == 'rbf':
  457. if not g.proto.cap('rbf'):
  458. msg('--rbf requested, but {} does not support replace-by-fee transactions'.format(g.coin))
  459. return False
  460. elif key in ('bob','alice'):
  461. m = "Regtest (Bob and Alice) mode not set up yet. Run '{}-regtest setup' to initialize."
  462. from mmgen.regtest import MMGenRegtest
  463. try: os.stat(os.path.join(MMGenRegtest(g.coin).d.datadir,'regtest','debug.log'))
  464. except: die(1,m.format(g.proj_name.lower()))
  465. elif key == 'locktime':
  466. if not opt_is_int(val,desc): return False
  467. if not opt_compares(int(val),'>',0,desc): return False
  468. elif key == 'token':
  469. if not 'token' in g.proto.caps:
  470. msg("Coin '{}' does not support the --token option".format(g.coin))
  471. return False
  472. elif len(val) == 40 and is_hex_str(val):
  473. pass
  474. elif len(val) > 20 or not all(s.isalnum() for s in val):
  475. msg("u'{}: invalid parameter for --token option".format(val))
  476. return False
  477. elif key == 'contract_data':
  478. check_infile(val)
  479. else:
  480. if g.debug: Msg("check_opts(): No test for opt '{}'".format(key))
  481. return True