diff --git a/mmgen/addrlist.py b/mmgen/addrlist.py index ec8878e4..1732eb04 100755 --- a/mmgen/addrlist.py +++ b/mmgen/addrlist.py @@ -20,7 +20,7 @@ addrlist.py: Address list classes for the MMGen suite """ -from .util import qmsg,qmsg_r,suf,make_chksum_N,Msg +from .util import qmsg,qmsg_r,suf,make_chksum_N,Msg,die from .objmethods import MMGenObject,Hilite,InitErrors from .obj import MMGenListItem,ListItemAttr,MMGenDict,TwComment,WalletPassword from .key import PrivKey diff --git a/mmgen/base_proto/ethereum/__init__.py b/mmgen/base_proto/ethereum/__init__.py index e69de29b..cf187d68 100755 --- a/mmgen/base_proto/ethereum/__init__.py +++ b/mmgen/base_proto/ethereum/__init__.py @@ -0,0 +1,5 @@ +async def erigon_sleep(self): + from ...globalvars import g + if self.proto.network == 'regtest' and g.daemon_id == 'erigon': + import asyncio + await asyncio.sleep(5) diff --git a/mmgen/base_proto/ethereum/contract.py b/mmgen/base_proto/ethereum/contract.py index 345681e9..2c36f1e3 100755 --- a/mmgen/base_proto/ethereum/contract.py +++ b/mmgen/base_proto/ethereum/contract.py @@ -17,12 +17,13 @@ # along with this program. If not, see . """ -altcoins.base_proto.ethereum.contract: Ethereum contract and token classes +altcoins.base_proto.ethereum.contract: Ethereum ERC20 token classes """ from decimal import Decimal from . import rlp +from . import erigon_sleep from ...util import msg,pp_msg from ...globalvars import g from ...base_obj import AsyncInit @@ -33,7 +34,7 @@ from ...amt import ETHAmt def parse_abi(s): return [s[:8]] + [s[8+x*64:8+(x+1)*64] for x in range(len(s[8:])//64)] -class TokenBase(MMGenObject): # ERC20 +class TokenCommon(MMGenObject): def create_method_id(self,sig): return self.keccak_256(sig.encode()).hexdigest()[:8] @@ -51,9 +52,7 @@ class TokenBase(MMGenObject): # ERC20 method_sig, '\n '.join(parse_abi(data)) )) ret = await self.rpc.call('eth_call',{ 'to': '0x'+self.addr, 'data': '0x'+data },'pending') - if self.proto.network == 'regtest' and g.daemon_id == 'erigon': # ERIGON - import asyncio - await asyncio.sleep(5) + await erigon_sleep(self) if toUnit: return int(ret,16) * self.base_unit else: @@ -119,21 +118,25 @@ class TokenBase(MMGenObject): # ERC20 chain_id = None if res == None else int(res,16) tx = Transaction(**tx_in).sign(key,chain_id) - hex_tx = rlp.encode(tx).hex() - coin_txid = CoinTxID(tx.hash.hex()) + if tx.sender.hex() != from_addr: die(3,f'Sender address {from_addr!r} does not match address of key {tx.sender.hex()!r}!') + if g.debug: msg('TOKEN DATA:') pp_msg(tx.to_dict()) msg('PARSED ABI DATA:\n {}'.format( '\n '.join(parse_abi(tx.data.hex())) )) - return hex_tx,coin_txid + + return ( + rlp.encode(tx).hex(), + CoinTxID(tx.hash.hex()) + ) # The following are used for token deployment only: - async def txsend(self,hex_tx): - return (await self.rpc.call('eth_sendRawTransaction','0x'+hex_tx)).replace('0x','',1) + async def txsend(self,txhex): + return (await self.rpc.call('eth_sendRawTransaction','0x'+txhex)).replace('0x','',1) async def transfer( self,from_addr,to_addr,amt,key,start_gas,gasPrice, method_sig='transfer(address,uint256)', @@ -145,10 +148,10 @@ class TokenBase(MMGenObject): # ERC20 nonce = int(await self.rpc.call('eth_getTransactionCount','0x'+from_addr,'pending'),16), method_sig = method_sig, from_addr2 = from_addr2 ) - (hex_tx,coin_txid) = await self.txsign(tx_in,key,from_addr) - return await self.txsend(hex_tx) + (txhex,coin_txid) = await self.txsign(tx_in,key,from_addr) + return await self.txsend(txhex) -class Token(TokenBase): +class Token(TokenCommon): def __init__(self,proto,addr,decimals,rpc=None): if type(self).__name__ == 'Token': @@ -161,7 +164,7 @@ class Token(TokenBase): self.base_unit = Decimal('10') ** -self.decimals self.rpc = rpc -class TokenResolve(TokenBase,metaclass=AsyncInit): +class TokenResolve(TokenCommon,metaclass=AsyncInit): async def __init__(self,proto,rpc,addr): from ...util import get_keccak diff --git a/mmgen/cfg.py b/mmgen/cfg.py index 7dc1dd36..80bf82d5 100755 --- a/mmgen/cfg.py +++ b/mmgen/cfg.py @@ -28,6 +28,7 @@ import sys,os,re from collections import namedtuple from .globalvars import * +from .exception import CfgFileParseError from .util import * def cfg_file(id_str): @@ -192,17 +193,7 @@ class CfgFileSampleSys(CfgFileSample): else: # self.fn is used for error msgs only, so file need not exist on filesystem self.fn = os.path.join(os.path.dirname(__file__),'data',self.fn_base) - # Resource will be unpacked and then cleaned up if necessary, see: - # https://docs.python.org/3/library/importlib.html: - # Note: This module provides functionality similar to pkg_resources Basic - # Resource Access without the performance overhead of that package. - # https://importlib-resources.readthedocs.io/en/latest/migration.html - # https://setuptools.readthedocs.io/en/latest/pkg_resources.html - try: - from importlib.resources import files # Python 3.9 - except ImportError: - from importlib_resources import files - self.data = files('mmgen').joinpath('data',self.fn_base).read_text().splitlines() + self.data = g.get_mmgen_data_file(self.fn_base).splitlines() def make_metadata(self): return [f'# Version {self.cur_ver} {self.computed_chksum}'] diff --git a/mmgen/common.py b/mmgen/common.py index fdf2ba3e..4a640e5a 100755 --- a/mmgen/common.py +++ b/mmgen/common.py @@ -21,7 +21,6 @@ common.py: Common imports for all MMGen scripts """ import sys,os -from .exception import * from .globalvars import * import mmgen.opts as opts from .opts import opt diff --git a/mmgen/daemon.py b/mmgen/daemon.py index 468a082a..25c5ad6b 100755 --- a/mmgen/daemon.py +++ b/mmgen/daemon.py @@ -586,6 +586,15 @@ class bitcoin_core_daemon(CoinDaemon): def stop_cmd(self): return self.cli_cmd('stop') + def set_label_args(self,rpc,coinaddr,lbl): + if 'label_api' in rpc.caps: + return ('setlabel',coinaddr,lbl) + else: + # NOTE: this works because importaddress() removes the old account before + # associating the new account with the address. + # RPC args: addr,label,rescan[=true],p2sh[=none] + return ('importaddress',coinaddr,lbl,False) + class bitcoin_cash_node_daemon(bitcoin_core_daemon): daemon_data = _dd('Bitcoin Cash Node', 24000000, '24.0.0') exec_fn = 'bitcoind-bchn' @@ -598,6 +607,11 @@ class bitcoin_cash_node_daemon(bitcoin_core_daemon): cfg_file_hdr = '# Bitcoin Cash Node config file\n' nonstd_datadir = True + def set_label_args(self,rpc,coinaddr,lbl): + # bitcoin-{abc,bchn} 'setlabel' RPC is broken, so use old 'importaddress' method to set label + # Broken behavior: new label is set OK, but old label gets attached to another address + return ('importaddress',coinaddr,lbl,False) + class litecoin_core_daemon(bitcoin_core_daemon): daemon_data = _dd('Litecoin Core', 180100, '0.18.1') exec_fn = 'litecoind' diff --git a/mmgen/devtools.py b/mmgen/devtools.py index 49192dc2..29ed9d70 100755 --- a/mmgen/devtools.py +++ b/mmgen/devtools.py @@ -49,12 +49,17 @@ if os.getenv('MMGEN_DEBUG') or os.getenv('MMGEN_TEST_SUITE') or os.getenv('MMGEN class MMGenObject(object): + def print_stack_trace(self,*args,**kwargs): + print_stack_trace(*args,**kwargs) + # Pretty-print any object subclassed from MMGenObject, recursing into sub-objects - WIP def pmsg(self): print(self.pfmt()) + def pdie(self): print(self.pfmt()) sys.exit(1) + def pfmt(self,lvl=0,id_list=[]): scalars = (str,int,float,Decimal) def do_list(out,e,lvl=0,is_dict=False): diff --git a/mmgen/fileutil.py b/mmgen/fileutil.py index 2efe8c98..1ac786c3 100755 --- a/mmgen/fileutil.py +++ b/mmgen/fileutil.py @@ -276,7 +276,7 @@ def get_words_from_file(infile,desc,quiet=False): def get_data_from_file(infile,desc='data',dash=False,silent=False,binary=False,quiet=False): from .opts import opt - if not opt.quiet and not silent and not quiet and desc: + if not (opt.quiet or silent or quiet): qmsg(f'Getting {desc} from file {infile!r}') with _open_or_die( @@ -294,8 +294,8 @@ def get_data_from_file(infile,desc='data',dash=False,silent=False,binary=False,q return data -def _mmgen_decrypt_file_maybe(fn,desc='',quiet=False,silent=False): - d = get_data_from_file(fn,desc,binary=True,quiet=quiet,silent=silent) +def _mmgen_decrypt_file_maybe(fn,desc='data',quiet=False,silent=False): + d = get_data_from_file(fn,desc=desc,binary=True,quiet=quiet,silent=silent) from .crypto import mmenc_ext have_enc_ext = get_extension(fn) == mmenc_ext if have_enc_ext or not is_utf8(d): @@ -305,8 +305,8 @@ def _mmgen_decrypt_file_maybe(fn,desc='',quiet=False,silent=False): d = mmgen_decrypt_retry(d,desc) return d -def get_lines_from_file(fn,desc='',trim_comments=False,quiet=False,silent=False): - dec = _mmgen_decrypt_file_maybe(fn,desc,quiet=quiet,silent=silent) +def get_lines_from_file(fn,desc='data',trim_comments=False,quiet=False,silent=False): + dec = _mmgen_decrypt_file_maybe(fn,desc=desc,quiet=quiet,silent=silent) ret = dec.decode().splitlines() if trim_comments: ret = strip_comments(ret) diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index c45aaa01..e1b64cc8 100755 --- a/mmgen/globalvars.py +++ b/mmgen/globalvars.py @@ -53,6 +53,8 @@ class GlobalContext(Lockable): email = '' Cdates = '2013-2022' + is_txprog = prog_name == 'mmgen-regtest' or prog_name.startswith('mmgen-tx') + stdin_tty = sys.stdin.isatty() stdout = sys.stdout stderr = sys.stderr @@ -311,24 +313,28 @@ class GlobalContext(Lockable): if name[:11] == 'MMGEN_DEBUG': os.environ[name] = '1' - def _get_importlib_resources_files(self): + def get_mmgen_data_file(self,filename): """ this is an expensive import, so do only when required """ + # Resource will be unpacked and then cleaned up if necessary, see: + # https://docs.python.org/3/library/importlib.html: + # Note: This module provides functionality similar to pkg_resources Basic + # Resource Access without the performance overhead of that package. + # https://importlib-resources.readthedocs.io/en/latest/migration.html + # https://setuptools.readthedocs.io/en/latest/pkg_resources.html try: from importlib.resources import files # Python 3.9 except ImportError: from importlib_resources import files - return files + return files('mmgen').joinpath('data',filename).read_text() @property def version(self): - files = self._get_importlib_resources_files() - return files('mmgen').joinpath('data','version').read_text().strip() + return self.get_mmgen_data_file('version').strip() @property def release_date(self): - files = self._get_importlib_resources_files() - return files('mmgen').joinpath('data','release_date').read_text().strip() + return self.get_mmgen_data_file('release_date').strip() g = GlobalContext() diff --git a/mmgen/help.py b/mmgen/help.py index dd45a262..6afadac5 100755 --- a/mmgen/help.py +++ b/mmgen/help.py @@ -43,6 +43,10 @@ def help_notes_func(proto,po,k): from .seedsplit import MasterShareIdx return MasterShareIdx + def test_py_log_file(): + from test.test_py_d.common import log_file + return log_file + def tool_help(): from .tool.help import main_help return main_help() diff --git a/mmgen/main_autosign.py b/mmgen/main_autosign.py index 96b6821c..ad6cb9f0 100755 --- a/mmgen/main_autosign.py +++ b/mmgen/main_autosign.py @@ -25,6 +25,7 @@ from subprocess import run,PIPE,DEVNULL from stat import * from .common import * +from .color import red mountpoint = '/mnt/tx' tx_dir = '/mnt/tx/tx' @@ -232,8 +233,9 @@ async def sign(): if signed_txs and not opt.no_summary: print_summary(signed_txs) if fails: - rmsg('\nFailed transactions:') - rmsg(' ' + '\n '.join(sorted(fails)) + '\n') + msg('') + rmsg('Failed transactions:') + msg(' ' + '\n '.join(red(s) for s in sorted(fails)) + '\n') # avoid the 'less' NL color bug return False if fails else True else: msg('No unsigned transactions') diff --git a/mmgen/main_txbump.py b/mmgen/main_txbump.py index 5f9d2584..3a0da938 100755 --- a/mmgen/main_txbump.py +++ b/mmgen/main_txbump.py @@ -42,7 +42,7 @@ opts_data = { -d, --outdir= d Specify an alternate directory 'd' for output -e, --echo-passphrase Print passphrase to screen when typing it -f, --tx-fee= f Transaction fee, as a decimal {cu} amount or as - {fu} (an integer followed by {fl}). + {fu} (an integer followed by {fl!r}). See FEE SPECIFICATION below. -H, --hidden-incog-input-params=f,o Read hidden incognito data from file 'f' at offset 'o' (comma-separated) diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index 29250dd0..5c8f7473 100755 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -40,7 +40,7 @@ opts_data = { -E, --fee-estimate-mode=M Specify the network fee estimate mode. Choices: {fe_all}. Default: {fe_dfl!r} -f, --tx-fee= f Transaction fee, as a decimal {cu} amount or as - {fu} (an integer followed by {fl}). + {fu} (an integer followed by {fl!r}). See FEE SPECIFICATION below. If omitted, fee will be calculated using network fee estimation. -g, --tx-gas= g Specify start gas amount in Wei (ETH only) diff --git a/mmgen/main_txdo.py b/mmgen/main_txdo.py index 0af1b086..45e802c4 100755 --- a/mmgen/main_txdo.py +++ b/mmgen/main_txdo.py @@ -44,7 +44,7 @@ opts_data = { -E, --fee-estimate-mode=M Specify the network fee estimate mode. Choices: {fe_all}. Default: {fe_dfl!r} -f, --tx-fee= f Transaction fee, as a decimal {cu} amount or as - {fu} (an integer followed by {fl}). + {fu} (an integer followed by {fl!r}). See FEE SPECIFICATION below. If omitted, fee will be calculated using network fee estimation. -g, --tx-gas= g Specify start gas amount in Wei (ETH only) diff --git a/mmgen/obj.py b/mmgen/obj.py index b8c9b595..93487e97 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -173,6 +173,7 @@ class MMGenListItem(MMGenObject): valid_attrs = set() valid_attrs_extra = set() invalid_attrs = { + 'print_stack_trace', 'pfmt', 'pmsg', 'pdie', diff --git a/mmgen/opts.py b/mmgen/opts.py index a2eb726e..a17e667b 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -21,7 +21,7 @@ opts.py: MMGen-specific options processing after generic processing by share.Op """ import sys,os,stat -from .exception import UserOptError +from .exception import UserOptError,CfgFileParseError from .globalvars import g from .base_obj import Lockable @@ -419,37 +419,6 @@ def init( return po.cmd_args -# DISABLED -def opt_is_tx_fee(key,val,desc): # 'key' must remain a placeholder - - # contract data or non-standard startgas: disable fee checking - if hasattr(opt,'contract_data') and opt.contract_data: - return - if hasattr(opt,'tx_gas') and opt.tx_gas: - return - - from .tx import MMGenTX - from .protocol import init_proto_from_opts - tx = MMGenTX.New(init_proto_from_opts()) - # Size of 224 is just a ball-park figure to eliminate the most extreme cases at startup - # This check will be performed again once we know the true size - ret = tx.feespec2abs(val,224) - - if ret == False: - raise UserOptError('{!r}: invalid {}\n(not a {} amount or {} specification)'.format( - val, - desc, - tx.proto.coin.upper(), - tx.rel_fee_desc )) - - if ret > tx.proto.max_tx_fee: - raise UserOptError('{!r}: invalid {}\n({} > max_tx_fee ({} {}))'.format( - val, - desc, - ret.fmt(fs='1.1'), - tx.proto.max_tx_fee, - tx.proto.coin.upper() )) - def check_usr_opts(usr_opts): # Raises an exception if any check fails def opt_splits(val,sep,n,desc): diff --git a/mmgen/protocol.py b/mmgen/protocol.py index 60af7df7..0d257baf 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -67,6 +67,10 @@ class CoinProtocol(MMGenObject): 'regtest': '_rt', }[network] + if 'tx' not in self.mmcaps and g.is_txprog: + from .util import die + die(1,f'Command {g.prog_name!r} not supported for coin {self.coin}') + if hasattr(self,'chain_names'): self.chain_name = self.chain_names[0] # first chain name is default else: diff --git a/mmgen/rpc.py b/mmgen/rpc.py index 34412275..0c04a071 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -303,6 +303,7 @@ class RPCClient(MMGenObject): try: socket.create_connection((host,port),timeout=1).close() except: + from .exception import SocketError raise SocketError(f'Unable to connect to {host}:{port}') self.http_hdrs = { 'Content-Type': 'application/json' } @@ -552,7 +553,7 @@ class BitcoinRPCClient(RPCClient,metaclass=AsyncInit): fn = self.get_daemon_cfg_fn() try: - lines = get_lines_from_file(fn,'',silent=not opt.verbose) + lines = get_lines_from_file(fn,'daemon config file',silent=not opt.verbose) except: vmsg(f'Warning: {fn!r} does not exist or is unreadable') return dict((k,None) for k in req_keys) @@ -571,7 +572,7 @@ class BitcoinRPCClient(RPCClient,metaclass=AsyncInit): def get_daemon_auth_cookie(self): fn = self.get_daemon_auth_cookie_fn() - return get_lines_from_file(fn,'')[0] if os.access(fn,os.R_OK) else '' + return get_lines_from_file(fn,'cookie',quiet=True)[0] if os.access(fn,os.R_OK) else '' @staticmethod def make_host_path(wallet): diff --git a/mmgen/twctl.py b/mmgen/twctl.py index 2ecd4807..9cf663a7 100755 --- a/mmgen/twctl.py +++ b/mmgen/twctl.py @@ -240,16 +240,7 @@ class TrackingWallet(MMGenObject,metaclass=AsyncInit): @write_mode async def set_label(self,coinaddr,lbl): - # bitcoin-{abc,bchn} 'setlabel' RPC is broken, so use old 'importaddress' method to set label - # broken behavior: new label is set OK, but old label gets attached to another address - if 'label_api' in self.rpc.caps and self.proto.coin != 'BCH': - args = ('setlabel',coinaddr,lbl) - else: - # NOTE: this works because importaddress() removes the old account before - # associating the new account with the address. - # RPC args: addr,label,rescan[=true],p2sh[=none] - args = ('importaddress',coinaddr,lbl,False) - + args = self.rpc.daemon.set_label_args( self.rpc, coinaddr, lbl ) try: return await self.rpc.call(*args) except Exception as e: diff --git a/mmgen/txfile.py b/mmgen/txfile.py index 7220caf7..70efadf6 100755 --- a/mmgen/txfile.py +++ b/mmgen/txfile.py @@ -87,7 +87,7 @@ class MMGenTxFile: tx.label = MMGenTxLabel(comment) desc = 'number of lines' # four required lines - metadata,tx.hex,inputs_data,outputs_data = tx_data + ( metadata, tx.serialized, inputs_data, outputs_data ) = tx_data assert len(metadata) < 100,'invalid metadata length' # rough check metadata = metadata.split() @@ -123,7 +123,7 @@ class MMGenTxFile: desc = 'transaction file hex data' tx.check_txfile_hex_data() - desc = 'Ethereum transaction file hex or json data' + desc = 'Ethereum RLP or JSON data' tx.parse_txfile_hex_data() desc = 'inputs data' tx.inputs = eval_io_data(inputs_data,'inputs') diff --git a/mmgen/util.py b/mmgen/util.py index 6c47beaf..f39cd5f1 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -164,7 +164,7 @@ def fmt(s,indent='',strip_char=None): "de-indent multiple lines of text, or indent with specified string" return indent + ('\n'+indent).join([l.strip(strip_char) for l in s.strip().splitlines()]) + '\n' -def fmt_list(l,fmt='dfl',indent=''): +def fmt_list(iterable,fmt='dfl',indent=''): "pretty-format a list" sep,lq,rq = { 'utf8': ("“, ”", "“", "”"), @@ -175,7 +175,7 @@ def fmt_list(l,fmt='dfl',indent=''): 'min': (",", "'", "'"), 'col': ('\n'+indent, indent, '' ), }[fmt] - return lq + sep.join(l) + rq + return lq + sep.join(iterable) + rq def list_gen(*data): """ @@ -354,24 +354,22 @@ def capfirst(s): # different from str.capitalize() - doesn't downcase any uc in def decode_timestamp(s): # tz_save = open('/etc/timezone').read().rstrip() os.environ['TZ'] = 'UTC' - ts = time.strptime(s,'%Y%m%d_%H%M%S') - t = time.mktime(ts) # os.environ['TZ'] = tz_save - return int(t) + return int(time.mktime( time.strptime(s,'%Y%m%d_%H%M%S') )) def make_timestamp(secs=None): - t = int(secs) if secs else time.time() - return '{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}'.format(*time.gmtime(t)[:6]) + return '{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}'.format(*time.gmtime( + int(secs) if secs else time.time() )[:6]) def make_timestr(secs=None): - t = int(secs) if secs else time.time() - return '{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}'.format(*time.gmtime(t)[:6]) + return '{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}'.format(*time.gmtime( + int(secs) if secs else time.time() )[:6]) def secs_to_dhms(secs): - dsecs = secs // 3600 + hrs = secs // 3600 return '{}{:02d}:{:02d}:{:02d} h/m/s'.format( - ('{} day{}, '.format(dsecs//24,suf(dsecs//24)) if dsecs > 24 else ''), - dsecs % 24, + ('{} day{}, '.format(hrs//24,suf(hrs//24)) if hrs > 24 else ''), + hrs % 24, (secs // 60) % 60, secs % 60 ) diff --git a/scripts/exec_wrapper.py b/scripts/exec_wrapper.py index c9700760..64d4976b 100755 --- a/scripts/exec_wrapper.py +++ b/scripts/exec_wrapper.py @@ -92,9 +92,8 @@ exec_wrapper_tracemalloc_setup() try: sys.argv.pop(0) exec_wrapper_execed_file = sys.argv[0] - with open(sys.argv[0]) as fp: - text = fp.read() - exec(text) + with open(exec_wrapper_execed_file) as fp: + exec(fp.read()) except SystemExit as e: if e.code != 0 and not os.getenv('EXEC_WRAPPER_NO_TRACEBACK'): exec_wrapper_write_traceback() diff --git a/scripts/tx-v2-to-v3.py b/scripts/tx-v2-to-v3.py index 16a05d56..4225554c 100755 --- a/scripts/tx-v2-to-v3.py +++ b/scripts/tx-v2-to-v3.py @@ -26,7 +26,8 @@ cmd_args = opts.init(opts_data) from mmgen.tx import * -if len(cmd_args) != 1: opts.usage() +if len(cmd_args) != 1: + opts.usage() tx = MMGenTX(cmd_args[0],quiet_open=True) tx.write_to_file(ask_tty=False,ask_overwrite=not opt.quiet,ask_write=not opt.quiet) diff --git a/setup.cfg b/setup.cfg index a8bc151d..bd36f9f4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -41,14 +41,15 @@ install_requires = packages = mmgen - mmgen.share - mmgen.proto - mmgen.tool mmgen.base_proto mmgen.base_proto.ethereum mmgen.base_proto.ethereum.pyethereum mmgen.base_proto.ethereum.rlp mmgen.base_proto.ethereum.rlp.sedes + mmgen.base_proto.ethereum.tx + mmgen.proto + mmgen.share + mmgen.tool scripts = cmds/mmgen-addrgen diff --git a/test/overlay/__init__.py b/test/overlay/__init__.py index 7ddaa588..0db84bdd 100644 --- a/test/overlay/__init__.py +++ b/test/overlay/__init__.py @@ -37,15 +37,15 @@ def overlay_setup(repo_root): shutil.rmtree(overlay_dir,ignore_errors=True) for d in ( 'mmgen', - 'mmgen.data', - 'mmgen.share', - 'mmgen.tool', - 'mmgen.proto', 'mmgen.base_proto', 'mmgen.base_proto.ethereum', 'mmgen.base_proto.ethereum.pyethereum', 'mmgen.base_proto.ethereum.rlp', - 'mmgen.base_proto.ethereum.rlp.sedes' ): + 'mmgen.base_proto.ethereum.rlp.sedes', + 'mmgen.data', + 'mmgen.proto', + 'mmgen.share', + 'mmgen.tool' ): process_srcdir(d) return overlay_dir diff --git a/test/overlay/fakemods/crypto.py b/test/overlay/fakemods/crypto.py index 5305ed31..18ba0cdd 100644 --- a/test/overlay/fakemods/crypto.py +++ b/test/overlay/fakemods/crypto.py @@ -6,6 +6,7 @@ if os.getenv('MMGEN_TEST_SUITE_DETERMINISTIC'): add_user_random_orig = add_user_random import sys + from hashlib import sha256 fake_rand_h = sha256('.'.join(sys.argv).encode()) def fake_urandom(n): diff --git a/test/test.py b/test/test.py index 6c5e08f3..f0d94117 100755 --- a/test/test.py +++ b/test/test.py @@ -74,15 +74,18 @@ import sys,os,time from include.tests_header import repo_root from test.overlay import get_overlay_dir,overlay_setup + overlay_dir = get_overlay_dir(repo_root) sys.path.insert(0,overlay_dir) -try: os.unlink(os.path.join(repo_root,'my.err')) -except: pass +if not (len(sys.argv) == 2 and sys.argv[1] == 'clean'): + 'hack: overlay must be set up before mmgen mods are imported' + overlay_setup(repo_root) from mmgen.common import * -from test.include.common import * -from test.test_py_d.common import * + +try: os.unlink(os.path.join(repo_root,'my.err')) +except: pass g.quiet = False # if 'quiet' was set in config file, disable here os.environ['MMGEN_QUIET'] = '0' # for this script and spawned scripts @@ -92,7 +95,7 @@ opts_data = { 'text': { 'desc': 'Test suite for the MMGen suite', 'usage':'[options] [command(s) or metacommand(s)]', - 'options': f""" + 'options': """ -h, --help Print this help message --, --longhelp Print help message for long options (common options) -A, --no-daemon-autostart Don't start and stop daemons automatically @@ -112,7 +115,7 @@ opts_data = { -g, --list-current-cmd-groups List command groups for current configuration -n, --names Display command names instead of descriptions -N, --no-timings Suppress display of timing information --o, --log Log commands to file {log_file!r} +-o, --log Log commands to file {lf!r} -O, --pexpect-spawn Use pexpect.spawn instead of popen_spawn (much slower, kut does real terminal emulation) -p, --pause Pause between tests, resuming on keypress @@ -136,15 +139,23 @@ opts_data = { If no command is given, the whole test suite is run. """ }, + 'code': { + 'options': lambda proto,help_notes,s: s.format( + lf = help_notes('test_py_log_file') + ) + } } +# we need some opt values before running opts.init, so parse without initializing: +po = opts.init(opts_data,parse_only=True) + +from test.include.common import * +from test.test_py_d.common import * + data_dir = get_data_dir() # include/common.py -# we need some opt values before running opts.init, so parse without initializing: -_uopts = opts.init(opts_data,parse_only=True).user_opts - # step 1: delete data_dir symlink in ./test; -if not ('resume' in _uopts or 'skip_deps' in _uopts): +if not ('resume' in po.user_opts or 'skip_deps' in po.user_opts): try: os.unlink(data_dir) except: pass @@ -773,7 +784,8 @@ class TestSuiteRunner(object): start_test_daemons(network_id,remove_datadir=True) self.daemons_started = True - os.environ['MMGEN_BOGUS_WALLET_DATA'] = '' # zero this here, so test group doesn't have to + os.environ['MMGEN_BOGUS_WALLET_DATA'] = '' # zero this here, so test groups don't have to + self.ts = self.gm.gm_init_group(self,gname,self.spawn_wrapper) self.ts_clsname = type(self.ts).__name__ @@ -796,7 +808,6 @@ class TestSuiteRunner(object): self.start_time = time.time() self.daemons_started = False gname_save = None - overlay_setup(repo_root) if usr_args: for arg in usr_args: if arg in self.gm.cmd_groups: diff --git a/test/test_py_d/ts_ethdev.py b/test/test_py_d/ts_ethdev.py index 4eeca45b..3872bf60 100755 --- a/test/test_py_d/ts_ethdev.py +++ b/test/test_py_d/ts_ethdev.py @@ -842,7 +842,6 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): usr_addrs = [tool_cmd(cmdname='gen_addr',proto=self.proto).gen_addr(addr,dfl_words_file) for addr in usr_mmaddrs] from mmgen.base_proto.ethereum.contract import TokenResolve - from mmgen.base_proto.ethereum.tx import EthereumMMGenTX as etx async def do_transfer(rpc): for i in range(2): tk = await TokenResolve( diff --git a/test/test_py_d/ts_main.py b/test/test_py_d/ts_main.py index a5977ddd..59ad6a80 100755 --- a/test/test_py_d/ts_main.py +++ b/test/test_py_d/ts_main.py @@ -197,6 +197,9 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared): self.tx_fee = {'btc':'0.0001','bch':'0.001','ltc':'0.01'}[self.proto.coin.lower()] self.txbump_fee = {'btc':'123s','bch':'567s','ltc':'12345s'}[self.proto.coin.lower()] + self.unspent_data_file = joinpath('test','trash','unspent.json') + os.environ['MMGEN_BOGUS_WALLET_DATA'] = self.unspent_data_file + def _get_addrfile_checksum(self,display=False): addrfile = self.get_file_with_ext('addrs') silence() @@ -327,16 +330,9 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared): return self.walletchk(wf,pf,wcls=wcls,dfl_wallet=dfl_wallet) def _write_fake_data_to_file(self,d): - unspent_data_file = joinpath(self.tmpdir,'unspent.json') - write_data_to_file(unspent_data_file,d,'Unspent outputs',quiet=True,ignore_opt_outdir=True) - os.environ['MMGEN_BOGUS_WALLET_DATA'] = unspent_data_file - bwd_msg = f'MMGEN_BOGUS_WALLET_DATA={unspent_data_file}' - if opt.print_cmdline: - msg(bwd_msg) - if opt.log: - self.tr.log_fd.write(bwd_msg + ' ') + write_data_to_file(self.unspent_data_file,d,'Unspent outputs',quiet=True,ignore_opt_outdir=True) if opt.verbose or opt.exact_output: - sys.stderr.write(f'Fake transaction wallet data written to file {unspent_data_file!r}\n') + sys.stderr.write(f'Fake transaction wallet data written to file {self.unspent_data_file!r}\n') def _create_fake_unspent_entry(self,coinaddr,al_id=None,idx=None,lbl=None,non_mmgen=False,segwit=False): if 'S' not in self.proto.mmtypes: segwit = False diff --git a/test/test_py_d/ts_misc.py b/test/test_py_d/ts_misc.py index b86639e0..5de9e544 100755 --- a/test/test_py_d/ts_misc.py +++ b/test/test_py_d/ts_misc.py @@ -69,16 +69,15 @@ class TestSuiteHelp(TestSuiteBase): def helpscreens(self,arg='--help',scripts=(),expect='USAGE:.*OPTIONS:'): - scripts = scripts or tuple(s.replace('mmgen-','') for s in os.listdir('cmds')) + scripts = list(scripts) or [s.replace('mmgen-','') for s in os.listdir('cmds')] - if self.test_name.endswith('helpscreens'): - skip = ( - ['regtest','xmrwallet'] if self.proto.base_coin == 'ETH' else - ['regtest'] if self.proto.base_coin == 'XMR' else - [] ) - scripts = sorted( set(scripts) - set(skip) ) + if 'tx' not in self.proto.mmcaps: + scripts = [s for s in scripts if not (s == 'regtest' or s.startswith('tx'))] - for s in scripts: + if self.proto.coin not in ('BTC','XMR') and 'xmrwallet' in scripts: + scripts.remove('xmrwallet') + + for s in sorted(scripts): t = self.spawn(f'mmgen-{s}',[arg],extra_desc=f'(mmgen-{s})') t.expect(expect,regex=True) t.read()