minor fixes and cleanups
This commit is contained in:
parent
4f39e29283
commit
23ae73d908
23 changed files with 110 additions and 93 deletions
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 + ')'),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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: {}'
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
20
mmgen/tw.py
20
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)])
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
19
test/test.py
19
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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue