From 23ae73d9081e35de55c321d75f4f250fb4789e47 Mon Sep 17 00:00:00 2001 From: MMGen Date: Wed, 3 Jul 2019 17:30:51 +0000 Subject: [PATCH] minor fixes and cleanups --- mmgen/addr.py | 5 +++-- mmgen/altcoin.py | 1 - mmgen/altcoins/eth/contract.py | 31 ++++++++++++++++--------------- mmgen/altcoins/eth/tw.py | 4 ++-- mmgen/altcoins/eth/tx.py | 22 +++++++++++----------- mmgen/exception.py | 5 +++++ mmgen/main.py | 1 + mmgen/main_autosign.py | 6 ++---- mmgen/main_wallet.py | 2 +- mmgen/obj.py | 3 ++- mmgen/opts.py | 6 ++++-- mmgen/protocol.py | 3 +-- mmgen/regtest.py | 2 +- mmgen/rpc.py | 4 ++-- mmgen/seed.py | 10 +++++++--- mmgen/tool.py | 28 +++++++++++++++------------- mmgen/tw.py | 20 ++++++++++++++------ mmgen/tx.py | 9 +++++---- mmgen/txsign.py | 6 +++--- mmgen/util.py | 4 ++-- scripts/traceback_run.py | 10 ++++++---- test/test.py | 19 ++++++------------- test/test_py_d/ts_autosign.py | 2 +- 23 files changed, 110 insertions(+), 93 deletions(-) diff --git a/mmgen/addr.py b/mmgen/addr.py index f6bcefad..ac2cb850 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -556,7 +556,7 @@ Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file pop_list.append(n) for n in reversed(pop_list): self.data.pop(n) if pop_list: - vmsg(self.msgs['removed_dup_keys'].format(len(pop_list),suf(removed,'s'))) + vmsg(self.msgs['removed_dup_keys'].format(len(pop_list),suf(removed))) def add_wifs(self,key_list): if not key_list: return @@ -900,7 +900,8 @@ re-import your addresses. def __init__(self,source=None): self.al_ids = {} - if source == 'tw': self.add_tw_data() + if source == 'tw': + self.add_tw_data() def seed_ids(self): return list(self.al_ids.keys()) diff --git a/mmgen/altcoin.py b/mmgen/altcoin.py index 024ab760..c0b70ddd 100755 --- a/mmgen/altcoin.py +++ b/mmgen/altcoin.py @@ -431,7 +431,6 @@ class CoinInfo(object): line[n] = re.sub(r"'0x(....)'",r'0x\1',line[n]) line[n] = re.sub(r' ',r'',line[n]) + ('',',')[n != len(line)-1] - from mmgen.util import pmsg,pdie if trust != -1: if sym in tt: src = tt[sym] diff --git a/mmgen/altcoins/eth/contract.py b/mmgen/altcoins/eth/contract.py index c1ebfa3b..71082b66 100755 --- a/mmgen/altcoins/eth/contract.py +++ b/mmgen/altcoins/eth/contract.py @@ -26,7 +26,7 @@ from . import rlp from mmgen.globalvars import g from mmgen.common import * from mmgen.obj import MMGenObject,CoinAddr,TokenAddr,CoinTxID,ETHAmt -from mmgen.util import msg,pmsg +from mmgen.util import msg try: assert not g.use_internal_keccak_module @@ -60,17 +60,16 @@ class Token(MMGenObject): # ERC20 def do_call(self,method_sig,method_args='',toUnit=False): data = create_method_id(method_sig) + method_args if g.debug: - msg('{}: {}'.format(method_sig,'\n '.join(parse_abi(data)))) + msg('ETH_CALL {}: {}'.format(method_sig,'\n '.join(parse_abi(data)))) ret = g.rpch.eth_call({ 'to': '0x'+self.addr, 'data': '0x'+data }) return int(ret,16) * self.base_unit if toUnit else ret def balance(self,acct_addr): - return self.do_call('balanceOf(address)',acct_addr.rjust(64,'0'),toUnit=True) + return ETHAmt(self.do_call('balanceOf(address)',acct_addr.rjust(64,'0'),toUnit=True)) def strip(self,s): return ''.join([chr(b) for b in s if 32 <= b <= 127]).strip() - def total_supply(self): return self.do_call('totalSupply()',toUnit=True) def decimals(self): ret = self.do_call('decimals()') try: @@ -79,8 +78,15 @@ class Token(MMGenObject): # ERC20 except: "RPC call to decimals() failed (returned '{}')".format(ret) return int(b,16) if b else None - def name(self): return self.strip(bytes.fromhex(self.do_call('name()')[2:])) - def symbol(self): return self.strip(bytes.fromhex(self.do_call('symbol()')[2:])) + + def name(self): + return self.strip(bytes.fromhex(self.do_call('name()')[2:])) + + def symbol(self): + return self.strip(bytes.fromhex(self.do_call('symbol()')[2:])) + + def total_supply(self): + return self.do_call('totalSupply()',toUnit=True) def info(self): fs = '{:15}{}\n' * 5 @@ -108,8 +114,8 @@ class Token(MMGenObject): # ERC20 'startgas': start_gas.toWei(), 'gasprice': gasPrice.toWei(), 'value': 0, - 'nonce': nonce, - 'data': bytes.fromhex(data) } + 'nonce': nonce, + 'data': bytes.fromhex(data) } def txsign(self,tx_in,key,from_addr,chain_id=None): @@ -125,8 +131,9 @@ class Token(MMGenObject): # ERC20 m = "Sender address '{}' does not match address of key '{}'!" die(3,m.format(from_addr,tx.sender.hex())) if g.debug: - msg('{}'.format('\n '.join(parse_abi(data)))) + msg('TOKEN DATA:') pmsg(tx.to_dict()) + msg('PARSED ABI DATA:\n {}'.format('\n '.join(parse_abi(tx.data.hex())))) return hex_tx,coin_txid # The following are used for token deployment only: @@ -145,9 +152,3 @@ class Token(MMGenObject): # ERC20 from_addr2=from_addr2) (hex_tx,coin_txid) = self.txsign(tx_in,key,from_addr) return self.txsend(hex_tx) - - def transfer_from(self,from_addr,to_addr,amt,key,start_gas,gasPrice): - raise NotImplementedError('method not implemented') - return self.transfer( from_addr,to_addr,amt,key,start_gas,gasPrice, - method_sig='transferFrom(address,address,uint256)', - from_addr2=from_addr) diff --git a/mmgen/altcoins/eth/tw.py b/mmgen/altcoins/eth/tw.py index 90dce27b..c0138847 100755 --- a/mmgen/altcoins/eth/tw.py +++ b/mmgen/altcoins/eth/tw.py @@ -233,11 +233,11 @@ class EthereumTwAddrList(TwAddrList): rpc_init() if g.token: self.token = Token(g.token) - tw = TrackingWallet().mmid_ordered_dict() + tw_dict = TrackingWallet().mmid_ordered_dict() self.total = g.proto.coin_amt('0') from mmgen.obj import CoinAddr - for mmid,d in list(tw.items()): + for mmid,d in list(tw_dict.items()): # if d['confirmations'] < minconf: continue # cannot get confirmations for eth account label = TwLabel(mmid+' '+d['comment'],on_fail='raise') if usr_addr_list and (label.mmid not in usr_addr_list): continue diff --git a/mmgen/altcoins/eth/tx.py b/mmgen/altcoins/eth/tx.py index f759f65e..9cec55a0 100755 --- a/mmgen/altcoins/eth/tx.py +++ b/mmgen/altcoins/eth/tx.py @@ -44,7 +44,7 @@ class EthereumMMGenTX(MMGenTX): usr_rel_fee = None # not in MMGenTX disable_fee_check = False txobj = None # "" - data = HexStr('') + usr_contract_data = HexStr('') def __init__(self,*args,**kwargs): super(EthereumMMGenTX,self).__init__(*args,**kwargs) @@ -53,7 +53,7 @@ class EthereumMMGenTX(MMGenTX): if hasattr(opt,'contract_data') and opt.contract_data: m = "'--contract-data' option may not be used with token transaction" assert not 'Token' in type(self).__name__, m - self.data = HexStr(open(opt.contract_data).read().strip()) + self.usr_contract_data = HexStr(open(opt.contract_data).read().strip()) self.disable_fee_check = True @classmethod @@ -120,7 +120,7 @@ class EthereumMMGenTX(MMGenTX): 'chainId': Int(d['chainId']), 'data': HexStr(d['data']) } self.tx_gas = o['startGas'] # approximate, but better than nothing - self.data = o['data'] + self.usr_contract_data = o['data'] if o['data'] and not o['to']: self.disable_fee_check = True self.fee = self.fee_rel2abs(o['gasPrice'].toWei()) self.txobj = o @@ -139,7 +139,7 @@ class EthereumMMGenTX(MMGenTX): 'startGas': self.start_gas, 'nonce': self.get_nonce(), 'chainId': Int(g.rpch.request(chain_id_method),16), - 'data': self.data, + 'data': self.usr_contract_data, } # Instead of serializing tx data as with BTC, just create a JSON dump. @@ -147,7 +147,7 @@ class EthereumMMGenTX(MMGenTX): # thus removing an attack vector def create_raw(self): assert len(self.inputs) == 1,'Transaction has more than one input!' - o_ok = (0,1) if self.data else (1,) + o_ok = (0,1) if self.usr_contract_data else (1,) o_num = len(self.outputs) assert o_num in o_ok,'Transaction has invalid number of outputs!'.format(o_num) self.make_txobj() @@ -166,7 +166,7 @@ class EthereumMMGenTX(MMGenTX): def process_cmd_args(self,cmd_args,ad_f,ad_w): lc = len(cmd_args) - if lc == 0 and self.data and not 'Token' in type(self).__name__: return + if lc == 0 and self.usr_contract_data and not 'Token' in type(self).__name__: return if lc != 1: fs = '{} output{} specified, but Ethereum transactions must have exactly one' die(1,fs.format(lc,suf(lc))) @@ -264,7 +264,7 @@ class EthereumMMGenTX(MMGenTX): def format_view_abs_fee(self): fee = self.fee_rel2abs(self.txobj['gasPrice'].toWei()) - note = ' (max)' if self.data else '' + note = ' (max)' if self.usr_contract_data else '' return fee.hl() + note def format_view_rel_fee(self,terse): return '' @@ -337,7 +337,7 @@ class EthereumMMGenTX(MMGenTX): confs = self.is_in_wallet() if confs is not False: - if self.data: + if self.usr_contract_data: exec_status = type(self).get_exec_status(self.coin_txid) if exec_status == 0: msg('Contract failed to execute!') @@ -451,12 +451,12 @@ class EthereumTokenMMGenTX(EthereumMMGenTX): o['token_addr'] = TokenAddr(d['token_addr']) o['decimals'] = Int(d['decimals']) t = Token(o['token_addr'],o['decimals']) - self.data = o['data'] = t.create_data(o['to'],o['amt']) + self.usr_contract_data = o['data'] = t.create_data(o['to'],o['amt']) def format_view_body(self,*args,**kwargs): - if self.data: + if self.usr_contract_data: from .contract import Token - self.txobj['token_to'] = Token.transferdata2sendaddr(self.data) + self.txobj['token_to'] = Token.transferdata2sendaddr(self.usr_contract_data) return 'Token: {d} {c}\n{r}'.format( d=self.txobj['token_addr'].hl(), c=blue('(' + g.dcoin + ')'), diff --git a/mmgen/exception.py b/mmgen/exception.py index 207daed8..5da32adb 100755 --- a/mmgen/exception.py +++ b/mmgen/exception.py @@ -24,10 +24,15 @@ mmgen.exception: Exception classes for the MMGen suite class UserNonConfirmation(Exception): mmcode = 1 class BadAgeFormat(Exception): mmcode = 1 class BadFilename(Exception): mmcode = 1 +class SocketError(Exception): mmcode = 1 +class UserAddressNotInWallet(Exception): mmcode = 1 # 2: yellow hl, message only +class InvalidTokenAddress(Exception): mmcode = 2 class UnrecognizedTokenSymbol(Exception): mmcode = 2 class TokenNotInBlockchain(Exception): mmcode = 2 +class TokenNotInWallet(Exception): mmcode = 2 +class BadTwComment(Exception): mmcode = 2 # 3: yellow hl, 'MMGen Error' + exception + message class RPCFailure(Exception): mmcode = 3 diff --git a/mmgen/main.py b/mmgen/main.py index 90707745..a791717f 100755 --- a/mmgen/main.py +++ b/mmgen/main.py @@ -39,6 +39,7 @@ def launch(mod): __import__('mmgen.main_' + mod) except KeyboardInterrupt: sys.stderr.write('\nUser interrupt\n') + sys.exit(1) # must exit normally so exit handlers will be called except EOFError: sys.stderr.write('\nEnd of file\n') except Exception as e: diff --git a/mmgen/main_autosign.py b/mmgen/main_autosign.py index 2675b572..03247d11 100755 --- a/mmgen/main_autosign.py +++ b/mmgen/main_autosign.py @@ -170,16 +170,14 @@ def do_umount(): def sign_tx_file(txfile,signed_txs): try: - g.testnet = False - g.coin = 'BTC' + init_coin('BTC',testnet=False) tmp_tx = mmgen.tx.MMGenTX(txfile,metadata_only=True) init_coin(tmp_tx.coin) if tmp_tx.chain != 'mainnet': if tmp_tx.chain == 'testnet' or ( hasattr(g.proto,'chain_name') and tmp_tx.chain != g.proto.chain_name): - g.testnet = True - init_coin(tmp_tx.coin) + init_coin(tmp_tx.coin,testnet=True) if hasattr(g.proto,'chain_name'): m = 'Chains do not match! tx file: {}, proto: {}' diff --git a/mmgen/main_wallet.py b/mmgen/main_wallet.py index c23a2348..a5bebb36 100755 --- a/mmgen/main_wallet.py +++ b/mmgen/main_wallet.py @@ -157,7 +157,7 @@ if invoked_as in ('conv','passchg','subgen'): gmsg('Processing output wallet') if invoked_as == 'subgen': - ss_out = SeedSource(seed=ss_in.seed.subseed(ss_idx,print_msg=True).data) + ss_out = SeedSource(seed_bin=ss_in.seed.subseed(ss_idx,print_msg=True).data) else: ss_out = SeedSource(ss=ss_in,passchg=invoked_as=='passchg') diff --git a/mmgen/obj.py b/mmgen/obj.py index 47079b16..910c02bb 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -491,7 +491,8 @@ class BTCAmt(Decimal,Hilite,InitErrors): def __str__(self,color=False): # format simply, no exponential notation return self.colorize( - str(int(self)) if int(self) == self else self.normalize().__format__('f'), + str(int(self)) if int(self) == self else + self.normalize().__format__('f'), color=color) def __repr__(self): diff --git a/mmgen/opts.py b/mmgen/opts.py index 1696575a..c8d031b3 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -348,13 +348,15 @@ def init(opts_data,add_opts=[],opt_filter=None,parse_only=False): def opt_is_tx_fee(val,desc): from mmgen.tx import MMGenTX - ret = MMGenTX().process_fee_spec(val,224,on_fail='return') + tx = MMGenTX() + # TODO: size is just a guess; do this check after parsing tx file + ret = tx.process_fee_spec(val,224,on_fail='return') # Non-standard startgas: disable fee checking if hasattr(opt,'contract_data') and opt.contract_data: ret = None if hasattr(opt,'tx_gas') and opt.tx_gas: ret = None if ret == False: msg("'{}': invalid {}\n(not a {} amount or {} specification)".format( - val,desc,g.coin.upper(),MMGenTX().rel_fee_desc)) + val,desc,g.coin.upper(),tx.rel_fee_desc)) elif ret != None and ret > g.proto.max_tx_fee: msg("'{}': invalid {}\n({} > max_tx_fee ({} {}))".format( val,desc,ret.fmt(fs='1.1'),g.proto.max_tx_fee,g.coin.upper())) diff --git a/mmgen/protocol.py b/mmgen/protocol.py index 9ea3796e..e3d2a7c4 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -21,7 +21,7 @@ protocol.py: Coin protocol functions, classes and methods """ import sys,os,hashlib -from mmgen.util import msg,pmsg,ymsg,Msg,pdie,ydie +from mmgen.util import msg,ymsg,Msg,ydie from mmgen.obj import MMGenObject,BTCAmt,LTCAmt,BCHAmt,B2XAmt,ETHAmt from mmgen.globalvars import g import mmgen.bech32 as bech32 @@ -520,7 +520,6 @@ def make_init_genonly_altcoins_str(data): if proto+'Protocol' in globals(): continue if coin.lower() in CoinProtocol.coins: continue out += fs.format(coin.lower(),proto,('None',proto+'TestnetProtocol')[coin in tn_coins]) -# print out return out def init_coin(coin,testnet=None): diff --git a/mmgen/regtest.py b/mmgen/regtest.py index 8051acef..708c9e8e 100755 --- a/mmgen/regtest.py +++ b/mmgen/regtest.py @@ -333,4 +333,4 @@ def generate(blocks=1,silent=False): if not out or len(literal_eval(out)) != blocks: rdie(1,'Error generating blocks') p.wait() - gmsg('Mined {} block{}'.format(blocks,suf(blocks,'s'))) + gmsg('Mined {} block{}'.format(blocks,suf(blocks))) diff --git a/mmgen/rpc.py b/mmgen/rpc.py index 6d7c6a5c..74a0de58 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -28,7 +28,7 @@ from decimal import Decimal def dmsg_rpc(s): if g.debug_rpc: msg(s) -class CoinDaemonRPCConnection(object): +class CoinDaemonRPCConnection(MMGenObject): auth = True db_fs = ' host [{h}] port [{p}] user [{u}] passwd [{pw}] auth_cookie [{c}]\n' @@ -42,7 +42,7 @@ class CoinDaemonRPCConnection(object): try: socket.create_connection((host,port),timeout=3).close() except: - die(1,'Unable to connect to {}:{}'.format(host,port)) + raise SocketError('Unable to connect to {}:{}'.format(host,port)) if not self.auth: pass diff --git a/mmgen/seed.py b/mmgen/seed.py index 8285d086..2678aeae 100755 --- a/mmgen/seed.py +++ b/mmgen/seed.py @@ -72,6 +72,10 @@ class SeedBase(MMGenObject): def hexdata(self): return self.data.hex() + @property + def fn_stem(self): + return self.sid + class SubSeedList(MMGenObject): have_short = True nonce_start = 0 @@ -612,7 +616,7 @@ class SeedSourceUnenc(SeedSource): def _filename(self): s = self.seed return '{}[{}]{x}.{}'.format( - s.sid, + s.fn_stem, s.bitlen, self.ext, x='-α' if g.debug_utf8 else '') @@ -1121,7 +1125,7 @@ class Wallet (SeedSourceEnc): s = self.seed d = self.ssdata return '{}-{}[{},{}]{x}.{}'.format( - s.sid, + s.fn_stem, d.key_id, s.bitlen, d.hash_preset, @@ -1248,7 +1252,7 @@ to exit and re-run the program with the '--old-incog-fmt' option. s = self.seed d = self.ssdata return '{}-{}-{}[{},{}]{x}.{}'.format( - s.sid, + s.fn_stem, d.key_id, d.iv_id, s.bitlen, diff --git a/mmgen/tool.py b/mmgen/tool.py index c86cb507..ab7131c2 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -111,7 +111,7 @@ def _process_args(cmd,cmd_args): if flag != 'VAR_ARGS': if len(cmd_args) < len(c_args): m1 = 'Command requires exactly {} non-keyword argument{}' - msg(m1.format(len(c_args),suf(c_args,'s'))) + msg(m1.format(len(c_args),suf(c_args))) _usage(cmd) u_args = cmd_args[:len(c_args)] @@ -459,25 +459,25 @@ class MMGenToolCmdMnemonic(MMGenToolCmdBase): IMPORTANT NOTE: Though MMGen mnemonics use the Electrum wordlist, they're computed using a different algorithm and are NOT Electrum-compatible! """ - def _do_random_mn(self,nbytes:int,wordlist_id:str): + def _do_random_mn(self,nbytes:int,wordlist:str): assert nbytes in (16,24,32), 'nbytes must be 16, 24 or 32' hexrand = get_random(nbytes).hex() Vmsg('Seed: {}'.format(hexrand)) - return self.hex2mn(hexrand,wordlist_id=wordlist_id) + return self.hex2mn(hexrand,wordlist=wordlist) def mn_rand128(self,wordlist=dfl_wl_id): - "generate random 128-bit mnemonic" + "generate random 128-bit mnemonic seed phrase" return self._do_random_mn(16,wordlist) def mn_rand192(self,wordlist=dfl_wl_id): - "generate random 192-bit mnemonic" + "generate random 192-bit mnemonic seed phrase" return self._do_random_mn(24,wordlist) def mn_rand256(self,wordlist=dfl_wl_id): - "generate random 256-bit mnemonic" + "generate random 256-bit mnemonic seed phrase" return self._do_random_mn(32,wordlist) - def hex2mn(self,hexstr:'sstr',wordlist_id=dfl_wl_id): + def hex2mn(self,hexstr:'sstr',wordlist=dfl_wl_id): "convert a 16, 24 or 32-byte hexadecimal number to a mnemonic" opt.out_fmt = 'words' from mmgen.seed import SeedSource @@ -553,7 +553,9 @@ class MMGenToolCmdFile(MMGenToolCmdBase): flist.sort_by_age(key=file_sort) # in-place sort sep = '—'*77+'\n' - return sep.join([MMGenTX(fn).format_view(terse=terse,sort=tx_sort) for fn in flist.names()]).rstrip() + return sep.join( + [MMGenTX(fn).format_view(terse=terse,sort=tx_sort) for fn in flist.names()] + ).rstrip() class MMGenToolCmdFileCrypt(MMGenToolCmdBase): """ @@ -787,11 +789,11 @@ class MMGenToolCmdRPC(MMGenToolCmdBase): "view tracking wallet" rpc_init() from mmgen.tw import TwUnspentOutputs - tw = TwUnspentOutputs(minconf=minconf) - tw.do_sort(sort,reverse=reverse) - tw.age_fmt = age_fmt - tw.show_mmid = show_mmid - return tw.format_for_printing(color=True) if wide else tw.format_for_display() + twuo = TwUnspentOutputs(minconf=minconf) + twuo.do_sort(sort,reverse=reverse) + twuo.age_fmt = age_fmt + twuo.show_mmid = show_mmid + return twuo.format_for_printing(color=True) if wide else twuo.format_for_display() def add_label(self,mmgen_or_coin_addr:str,label:str): "add descriptive label for address in tracking wallet" diff --git a/mmgen/tw.py b/mmgen/tw.py index e701d742..41d43ef0 100755 --- a/mmgen/tw.py +++ b/mmgen/tw.py @@ -108,6 +108,16 @@ watch-only wallet using '{}-addrimport' and then re-run this program. return sum(i.amt for i in self.unspent) def get_unspent_rpc(self): + # bitcoin-cli help listunspent: + # Arguments: + # 1. minconf (numeric, optional, default=1) The minimum confirmations to filter + # 2. maxconf (numeric, optional, default=9999999) The maximum confirmations to filter + # 3. addresses (json array, optional, default=empty array) A json array of bitcoin addresses + # 4. include_unsafe (boolean, optional, default=true) Include outputs that are not safe to spend + # 5. query_options (json object, optional) JSON with query options + + # for now, self.addrs is just an empty list for Bitcoin and friends + add_args = (9999999,self.addrs) if self.addrs else () return g.rpch.listunspent(self.minconf) def get_unspent_data(self): @@ -115,8 +125,6 @@ watch-only wallet using '{}-addrimport' and then re-run this program. us_rpc = eval(get_data_from_file(g.bogus_wallet_data)) # testing, so ok else: us_rpc = self.get_unspent_rpc() -# write_data_to_file('bogus_unspent.json', repr(us), 'bogus unspent data') -# sys.exit(0) if not us_rpc: die(0,self.wmsg['no_spendable_outputs']) confs_per_day = 60*60*24 // g.proto.secs_per_block @@ -288,7 +296,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program. return self.fmt_print def display_total(self): - fs = '\nTotal unspent: {} {} ({} outputs)' + fs = '\nTotal unspent: {} {} ({} output%s)' % suf(self.unspent) msg(fs.format(self.total.hl(),g.dcoin,len(self.unspent))) def get_idx_from_user(self,get_label=False): @@ -469,7 +477,7 @@ class TwAddrList(MMGenDict): if age_fmt not in age_fmts: raise BadAgeFormat("'{}': invalid age format (must be one of {!r})".format(age_fmt,age_fmts)) out = ['Chain: '+green(g.chain.upper())] if g.chain != 'mainnet' else [] - fs = '{{mid}}{} {{cmt}} {{amt}}{}'.format(('',' {addr}')[showbtcaddrs],('',' {age}')[show_age]) + fs = '{mid}' + ('',' {addr}')[showbtcaddrs] + ' {cmt} {amt}' + ('',' {age}')[show_age] mmaddrs = [k for k in self.keys() if k.type == 'mmgen'] max_mmid_len = max(len(k) for k in mmaddrs) + 2 if mmaddrs else 10 max_cmt_width = max(max(v['lbl'].comment.screen_width for v in self.values()),7) @@ -483,7 +491,7 @@ class TwAddrList(MMGenDict): cmt=TwComment.fmtc('COMMENT',width=max_cmt_width+1), amt='BALANCE'.ljust(max_fp_len+4), age=('CONFS','DAYS')[age_fmt=='days'], - )] + ).rstrip()] def sort_algo(j): if sort and 'age' in sort: @@ -515,7 +523,7 @@ class TwAddrList(MMGenDict): cmt=e['lbl'].comment.fmt(width=max_cmt_width,color=True,nullrepl='-'), amt=e['amt'].fmt('4.{}'.format(max(max_fp_len,3)),color=True), age=mmid.confs // (1,confs_per_day)[age_fmt=='days'] if hasattr(mmid,'confs') and mmid.confs != None else '-' - )) + ).rstrip()) return '\n'.join(out + ['\nTOTAL: {} {}'.format(self.total.hl(color=True),g.dcoin)]) diff --git a/mmgen/tx.py b/mmgen/tx.py index 36696a33..76ee5eaa 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -735,7 +735,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam self.check_pubkey_scripts() - qmsg('Passing {} key{} to {}'.format(len(keys),suf(keys,'s'),g.proto.daemon_name)) + qmsg('Passing {} key{} to {}'.format(len(keys),suf(keys),g.proto.daemon_name)) if self.has_segwit_inputs(): from mmgen.addr import KeyGenerator,AddrGenerator @@ -781,12 +781,12 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam msg('OK') return True except Exception as e: - if g.traceback: - import traceback - ymsg('\n'+''.join(traceback.format_exception(*sys.exc_info()))) try: m = '{}'.format(e.args[0]) except: m = repr(e.args[0]) msg('\n'+yellow(m)) + if g.traceback: + import traceback + ymsg('\n'+''.join(traceback.format_exception(*sys.exc_info()))) return False def mark_raw(self): @@ -1548,6 +1548,7 @@ class MMGenBumpTX(MMGenTX): return False return ret +# NOT MAINTAINED class MMGenSplitTX(MMGenTX): def get_outputs_from_cmdline(self,mmid): # TODO: check that addr is empty diff --git a/mmgen/txsign.py b/mmgen/txsign.py index 03f1dbd3..6c785745 100755 --- a/mmgen/txsign.py +++ b/mmgen/txsign.py @@ -70,7 +70,7 @@ def get_seed_for_seed_id(sid,infiles,saved_seeds): def generate_kals_for_mmgen_addrs(need_keys,infiles,saved_seeds): mmids = [e.mmid for e in need_keys] sids = {i.sid for i in mmids} - vmsg('Need seed{}: {}'.format(suf(sids,'s'),' '.join(sids))) + vmsg('Need seed{}: {}'.format(suf(sids),' '.join(sids))) d = MMGenList() from mmgen.addr import KeyAddrList for sid in sids: @@ -104,7 +104,7 @@ def add_keys(tx,src,infiles=None,saved_seeds=None,keyaddr_list=None): else: die(3,wmsg['mapping_error'].format(m1,mmid,f.addr,'tx file:',e.mmid,e.addr)) if new_keys: - vmsg('Added {} wif key{} from {}'.format(len(new_keys),suf(new_keys,'s'),desc)) + vmsg('Added {} wif key{} from {}'.format(len(new_keys),suf(new_keys),desc)) return new_keys def _pop_and_return(args,cmplist): # strips found args @@ -167,6 +167,6 @@ def txsign(tx,seed_files,kl,kal,tx_num_str=''): extra_sids = set(saved_seeds) - tx.get_input_sids() - tx.get_output_sids() if extra_sids: - msg('Unused Seed ID{}: {}'.format(suf(extra_sids,'s'),' '.join(extra_sids))) + msg('Unused Seed ID{}: {}'.format(suf(extra_sids),' '.join(extra_sids))) return tx.sign(tx_num_str,keys) # returns True or False diff --git a/mmgen/util.py b/mmgen/util.py index f0d3752f..930442b2 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -184,7 +184,7 @@ def dmsg(s): if opt.debug: msg(s) def suf(arg,suf_type='s'): - suf_types = { 's': ('s',''), 'es': ('es',''), 'y': ('ies','y') } + suf_types = { 's': '', 'es': '', 'ies': 'y' } assert suf_type in suf_types t = type(arg) if t == int: @@ -193,7 +193,7 @@ def suf(arg,suf_type='s'): n = len(arg) else: die(2,'{}: invalid parameter for suf()'.format(arg)) - return suf_types[suf_type][n==1] + return suf_types[suf_type] if n == 1 else suf_type def get_extension(f): a,b = os.path.splitext(f) diff --git a/scripts/traceback_run.py b/scripts/traceback_run.py index eadaed79..e59590cd 100755 --- a/scripts/traceback_run.py +++ b/scripts/traceback_run.py @@ -12,6 +12,7 @@ def traceback_run_init(): if 'TMUX' in os.environ: del os.environ['TMUX'] os.environ['MMGEN_TRACEBACK'] = '1' + os.environ['PYTHONPATH'] = '.' of = 'my.err' try: os.unlink(of) @@ -28,11 +29,11 @@ def traceback_run_process_exception(): exc = l.pop() if exc[:11] == 'SystemExit:': l.pop() - if os.getenv('MMGEN_DISABLE_COLOR'): + if False: # was: if os.getenv('MMGEN_DISABLE_COLOR'): sys.stdout.write('{}{}'.format(''.join(l),exc)) else: - red = lambda s: '\033[31;1m{}\033[0m'.format(s) - yellow = lambda s: '\033[33;1m{}\033[0m'.format(s) + def red(s): return '\033[31;1m{}\033[0m'.format(s) + def yellow(s): return '\033[33;1m{}\033[0m'.format(s) sys.stdout.write('{}{}'.format(yellow(''.join(l)),red(exc))) open(traceback_run_outfile,'w').write(''.join(l+[exc])) @@ -50,7 +51,8 @@ except SystemExit as e: sys.exit(e.code) except Exception as e: traceback_run_process_exception() - sys.exit(e.mmcode if hasattr(e,'mmcode') else e.code if hasattr(e,'code') else 1) + retval = e.mmcode if hasattr(e,'mmcode') else e.code if hasattr(e,'code') else 1 + sys.exit(retval) blue = lambda s: s if os.getenv('MMGEN_DISABLE_COLOR') else '\033[34;1m{}\033[0m'.format(s) sys.stdout.write(blue('Runtime: {:0.5f} secs\n'.format(time.time() - traceback_run_tstart))) diff --git a/test/test.py b/test/test.py index 46971bef..a2afd9b7 100755 --- a/test/test.py +++ b/test/test.py @@ -69,6 +69,7 @@ def create_shm_dir(data_dir,trash_dir): import sys,os,time +os.environ['MMGEN_TEST_SUITE'] = '1' repo_root = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),os.pardir))) os.chdir(repo_root) sys.path.__setitem__(0,repo_root) @@ -386,7 +387,7 @@ def clean(usr_dirs=None): else: die(1,'{}: invalid directory number'.format(d)) if dirlist: - iqmsg(green('Cleaned tmp director{} {}'.format(suf(dirlist,'y'),' '.join(dirlist)))) + iqmsg(green('Cleaned tmp director{} {}'.format(suf(dirlist,'ies'),' '.join(dirlist)))) cleandir(data_dir) cleandir(trash_dir) iqmsg(green("Cleaned directories '{}'".format("' '".join([data_dir,trash_dir])))) @@ -429,8 +430,6 @@ def set_environ_for_spawned_scripts(): os.environ['MMGEN_NO_LICENSE'] = '1' os.environ['MMGEN_MIN_URANDCHARS'] = '3' os.environ['MMGEN_BOGUS_SEND'] = '1' - # Tell spawned programs they're running in the test suite - os.environ['MMGEN_TEST_SUITE'] = '1' def set_restore_term_at_exit(): import termios,atexit @@ -880,23 +879,17 @@ try: tr = TestSuiteRunner(data_dir,trash_dir) tr.run_tests(usr_args) except KeyboardInterrupt: - die(1,'\nExiting at user request') + die(1,'\ntest.py exiting at user request') except TestSuiteException as e: ydie(1,e.args[0]) except TestSuiteFatalException as e: rdie(1,e.args[0]) except Exception: if opt.traceback: + msg(blue('Spawned script exited with error')) + else: import traceback print(''.join(traceback.format_exception(*sys.exc_info()))) - try: - os.stat('my.err') - t = open('my.err').readlines() - if t: - msg_r('\n'+yellow(''.join(t[:-1]))+red(t[-1])) - except: pass - die(1,blue('Test script exited with error')) - else: - raise + msg(blue('Test script exited with error')) except: raise diff --git a/test/test_py_d/ts_autosign.py b/test/test_py_d/ts_autosign.py index 6f365711..9412f83d 100755 --- a/test/test_py_d/ts_autosign.py +++ b/test/test_py_d/ts_autosign.py @@ -155,8 +155,8 @@ class TestSuiteAutosign(TestSuiteBase): def do_autosign(opts,mountpoint): make_wallet(opts) - copy_files(mountpoint,include_bad_tx=True) + t = self.spawn('mmgen-autosign',opts+['--full-summary','wait'],extra_desc='(sign - full summary)') t.expect('{} transactions signed'.format(txcount)) t.expect('2 transactions failed to sign')