From 92fc9fd4627f8cf89073b41a8a1916989f62d46c Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sat, 15 Feb 2025 09:54:19 +0000 Subject: [PATCH] fixes and cleanups throughout --- mmgen/main_txcreate.py | 6 +++--- mmgen/main_txdo.py | 6 +++--- mmgen/proto/btc/tx/info.py | 14 ++++++++++---- mmgen/proto/btc/tx/new.py | 12 +++++++----- mmgen/proto/btc/tx/new_swap.py | 2 +- mmgen/proto/eth/tx/info.py | 2 +- mmgen/tx/base.py | 16 +++++++--------- mmgen/tx/file.py | 6 ++---- mmgen/tx/info.py | 12 +++++++----- mmgen/tx/new_swap.py | 2 +- mmgen/util2.py | 4 ++-- test/cmdtest_d/ct_regtest.py | 21 +++++++-------------- test/include/common.py | 8 ++++++++ test/test-release.sh | 2 +- 14 files changed, 60 insertions(+), 53 deletions(-) diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index b7d433a8..59aaa46e 100755 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -69,10 +69,10 @@ opts_data = { -- -q, --quiet Suppress warnings; overwrite files without prompting bt -R, --no-rbf Make transaction non-replaceable (non-replace-by-fee + according to BIP 125) + -s -s, --swap-proto Swap protocol to use (Default: {x_dfl}, + + Choices: {x_all}) -- -v, --verbose Produce more verbose output b- -V, --vsize-adj= f Adjust transaction's estimated vsize by factor 'f' - -s -x, --swap-proto Swap protocol to use (Default: {x_dfl}, - + Choices: {x_all}) -- -y, --yes Answer 'yes' to prompts, suppress non-essential output e- -X, --cached-balances Use cached balances """, @@ -101,7 +101,7 @@ opts_data = { cfg = Config(opts_data=opts_data) -if not cfg.info and len(cfg._args) < {'tx': 1, 'swaptx': 2}[target]: +if not (cfg.info or cfg.contract_data) and len(cfg._args) < {'tx': 1, 'swaptx': 2}[target]: cfg._usage() async def main(): diff --git a/mmgen/main_txdo.py b/mmgen/main_txdo.py index 5ceb7af1..9db5f559 100755 --- a/mmgen/main_txdo.py +++ b/mmgen/main_txdo.py @@ -83,16 +83,16 @@ opts_data = { -- -p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p' + for password hashing (default: '{gc.dfl_hash_preset}') -- -P, --passwd-file= f Get {pnm} wallet passphrase from file 'f' + -- -q, --quiet Suppress warnings; overwrite files without prompting bt -R, --no-rbf Make transaction non-replaceable (non-replace-by-fee + according to BIP 125) - -- -q, --quiet Suppress warnings; overwrite files without prompting + -s -s, --swap-proto Swap protocol to use (Default: {x_dfl}, + + Choices: {x_all}) -- -u, --subseeds= n The number of subseed pairs to scan for (default: {ss}, + maximum: {ss_max}). Only the default or first supplied + wallet is scanned for subseeds. -- -v, --verbose Produce more verbose output b- -V, --vsize-adj= f Adjust transaction's estimated vsize by factor 'f' - -s -x, --swap-proto Swap protocol to use (Default: {x_dfl}, - + Choices: {x_all}) e- -X, --cached-balances Use cached balances -- -y, --yes Answer 'yes' to prompts, suppress non-essential output -- -z, --show-hash-presets Show information on available hash presets diff --git a/mmgen/proto/btc/tx/info.py b/mmgen/proto/btc/tx/info.py index 09536cce..574ea076 100755 --- a/mmgen/proto/btc/tx/info.py +++ b/mmgen/proto/btc/tx/info.py @@ -14,12 +14,12 @@ proto.btc.tx.info: Bitcoin transaction info class from ....tx.info import TxInfo from ....util import fmt, die -from ....color import red, green, pink, blue +from ....color import red, green, blue, pink from ....addr import MMGenID class TxInfo(TxInfo): sort_orders = ('addr', 'raw') - txinfo_hdr_fs = 'TRANSACTION DATA\n\nID={i} ({a} {c}) RBF={r} Sig={s} Locktime={l}\n' + txinfo_hdr_fs = '{hdr}\n ID={i} ({a} {c}) RBF={r} Sig={s} Locktime={l}\n' txinfo_hdr_fs_short = 'TX {i} ({a} {c}) RBF={r} Sig={s} Locktime={l}\n' txinfo_ftr_fs = fmt(""" Input amount: {i} {d} @@ -64,7 +64,10 @@ class TxInfo(TxInfo): append_chars=('', ' (chg)')[bool(not is_input and e.is_chg and terse)], append_color='green') else: - return MMGenID.fmtc(nonmm_str, width=max_mmwid, color=True) + return MMGenID.fmtc( + nonmm_str, + width = max_mmwid, + color = True) def format_io(desc): io = getattr(tx, desc) @@ -134,7 +137,10 @@ class TxInfo(TxInfo): vp1 = 0 return ( - 'Displaying inputs and outputs in {} sort order'.format({'raw':'raw', 'addr':'address'}[sort]) + 'Inputs/Outputs sort order: {}'.format({ + 'raw': pink('UNSORTED'), + 'addr': pink('ADDRESS') + }[sort]) + ('\n\n', '\n')[terse] + ''.join(format_io('inputs')) + ''.join(format_io('outputs'))) diff --git a/mmgen/proto/btc/tx/new.py b/mmgen/proto/btc/tx/new.py index 89efb2c1..6f0ad80a 100755 --- a/mmgen/proto/btc/tx/new.py +++ b/mmgen/proto/btc/tx/new.py @@ -125,18 +125,19 @@ class New(Base, TxNew): def final_inputs_ok_msg(self, funds_left): return 'Transaction produces {} {} in change'.format(funds_left.hl(), self.coin) - def check_chg_addr_is_wallet_addr(self): - if len([o for o in self.outputs if not o.data]) > 1 and not self.chg_output.mmid: + def check_chg_addr_is_wallet_addr(self, message='Change address is not an MMGen wallet address!'): + def do_err(): from ....ui import confirm_or_raise confirm_or_raise( cfg = self.cfg, - message = yellow('Change address is not an MMGen wallet address!'), + message = yellow(message), action = 'Are you sure this is what you want?') + if len(self.outputs) > 1 and not self.chg_output.mmid: + do_err() async def create_serialized(self, locktime=None, bump=None): - if not (bump or self.is_swap): - self.inputs.sort_bip69() + if not bump: # Set all sequence numbers to the same value, in conformity with the behavior of most modern wallets: do_rbf = self.proto.cap('rbf') and not self.cfg.no_rbf seqnum_val = self.proto.max_int - (2 if do_rbf else 1 if locktime else 0) @@ -144,6 +145,7 @@ class New(Base, TxNew): i.sequence = seqnum_val if not self.is_swap: + self.inputs.sort_bip69() self.outputs.sort_bip69() inputs_list = [{ diff --git a/mmgen/proto/btc/tx/new_swap.py b/mmgen/proto/btc/tx/new_swap.py index 8e62b92b..401368d3 100755 --- a/mmgen/proto/btc/tx/new_swap.py +++ b/mmgen/proto/btc/tx/new_swap.py @@ -18,6 +18,6 @@ from .new import New class NewSwap(New, TxNewSwap): desc = 'Bitcoin swap transaction' - async def process_swap_cmdline_args(self, cmd_args): + async def process_swap_cmdline_args(self, cmd_args, addrfile_args): import sys sys.exit(0) diff --git a/mmgen/proto/eth/tx/info.py b/mmgen/proto/eth/tx/info.py index 99abb8f8..dd13e902 100755 --- a/mmgen/proto/eth/tx/info.py +++ b/mmgen/proto/eth/tx/info.py @@ -18,7 +18,7 @@ from ....color import pink, yellow, blue from ....addr import MMGenID class TxInfo(TxInfo): - txinfo_hdr_fs = 'TRANSACTION DATA\n\nID={i} ({a} {c}) Sig={s} Locktime={l}\n' + txinfo_hdr_fs = '{hdr}\n ID={i} ({a} {c}) Sig={s} Locktime={l}\n' txinfo_hdr_fs_short = 'TX {i} ({a} {c}) Sig={s} Locktime={l}\n' txinfo_ftr_fs = fmt(""" Total in account: {i} {d} diff --git a/mmgen/tx/base.py b/mmgen/tx/base.py index 196b08e9..9349ac43 100755 --- a/mmgen/tx/base.py +++ b/mmgen/tx/base.py @@ -98,8 +98,8 @@ class Base(MMGenObject): tw_copy_attrs = {'scriptPubKey', 'vout', 'amt', 'comment', 'mmid', 'addr', 'confs', 'txid'} class Output(MMGenTxIO): - is_chg = ListItemAttr(bool, typeconv=False) - data = ListItemAttr(None, typeconv=False) # placeholder + is_chg = ListItemAttr(bool, typeconv=False) + data = ListItemAttr(None, typeconv=False) # placeholder class InputList(MMGenTxIOList): desc = 'transaction inputs' @@ -146,12 +146,10 @@ class Base(MMGenObject): return self.proto.coin_amt('0') return sum(e.amt for e in olist) - def _chg_output_ops(self, op): - is_chgs = [x.is_chg for x in self.outputs] + def _chg_output_ops(self, op, attr): + is_chgs = [getattr(x, attr) for x in self.outputs] if is_chgs.count(True) == 1: - return ( - is_chgs.index(True) if op == 'idx' else - self.outputs[is_chgs.index(True)]) + return is_chgs.index(True) if op == 'idx' else self.outputs[is_chgs.index(True)] elif is_chgs.count(True) == 0: return None else: @@ -159,11 +157,11 @@ class Base(MMGenObject): @property def chg_idx(self): - return self._chg_output_ops('idx') + return self._chg_output_ops('idx', 'is_chg') @property def chg_output(self): - return self._chg_output_ops('output') + return self._chg_output_ops('output', 'is_chg') def add_timestamp(self): self.timestamp = make_timestamp() diff --git a/mmgen/tx/file.py b/mmgen/tx/file.py index aa52e826..663332ae 100755 --- a/mmgen/tx/file.py +++ b/mmgen/tx/file.py @@ -64,15 +64,13 @@ class MMGenTxFile(MMGenObject): 'send_amt': 'skip', 'timestamp': None, 'blockcount': None, - 'serialized': None, - } + 'serialized': None} extra_attrs = { 'locktime': None, 'comment': MMGenTxComment, 'coin_txid': CoinTxID, 'sent_timestamp': None, - 'is_swap': False, - } + 'is_swap': None} def __init__(self, tx): self.tx = tx diff --git a/mmgen/tx/info.py b/mmgen/tx/info.py index a4079eb6..7ddf3335 100755 --- a/mmgen/tx/info.py +++ b/mmgen/tx/info.py @@ -15,7 +15,7 @@ tx.info: transaction info class import importlib from ..cfg import gc -from ..color import red, green, orange +from ..color import red, green, cyan, orange from ..util import msg, msg_r, decode_timestamp, make_timestr from ..util2 import format_elapsed_hr @@ -51,6 +51,7 @@ class TxInfo: def gen_view(): yield (self.txinfo_hdr_fs_short if terse else self.txinfo_hdr_fs).format( + hdr = cyan('TRANSACTION DATA'), i = tx.txid.hl(), a = tx.send_amt.hl(), c = tx.dcoin, @@ -63,19 +64,19 @@ class TxInfo: for attr, label in [('timestamp', 'Created:'), ('sent_timestamp', 'Sent:')]: if (val := getattr(tx, attr)) is not None: _ = decode_timestamp(val) - yield f'{label:8} {make_timestr(_)} ({format_elapsed_hr(_)})\n' + yield f' {label:8} {make_timestr(_)} ({format_elapsed_hr(_)})\n' if tx.chain != 'mainnet': # if mainnet has a coin-specific name, display it - yield green(f'Chain: {tx.chain.upper()}') + '\n' + yield green(f' Chain: {tx.chain.upper()}') + '\n' if tx.coin_txid: - yield f'{tx.coin} TxID: {tx.coin_txid.hl()}\n' + yield f' {tx.coin} TxID: {tx.coin_txid.hl()}\n' enl = ('\n', '')[bool(terse)] yield enl if tx.comment: - yield f'Comment: {tx.comment.hl()}\n{enl}' + yield f' Comment: {tx.comment.hl()}\n{enl}' yield self.format_body( blockcount, @@ -124,6 +125,7 @@ class TxInfo: from ..ui import do_pager do_pager(o) else: + msg('') msg_r(o) from ..term import get_char if pause: diff --git a/mmgen/tx/new_swap.py b/mmgen/tx/new_swap.py index c03f1560..5218b353 100755 --- a/mmgen/tx/new_swap.py +++ b/mmgen/tx/new_swap.py @@ -18,5 +18,5 @@ class NewSwap(New): desc = 'swap transaction' is_swap = True - async def process_swap_cmdline_args(self, cmd_args): + async def process_swap_cmdline_args(self, cmd_args, addrfile_args): raise NotImplementedError(f'Swap not implemented for protocol {self.proto.__name__}') diff --git a/mmgen/util2.py b/mmgen/util2.py index f374d202..e92d6260 100755 --- a/mmgen/util2.py +++ b/mmgen/util2.py @@ -135,14 +135,14 @@ def format_elapsed_days_hr(t, now=None, cached={}): cached[e] = f'{days} day{suf(days)} ' + ('ago' if e > 0 else 'in the future') return cached[e] -def format_elapsed_hr(t, now=None, cached={}, rel_now=True, show_secs=False): +def format_elapsed_hr(t, now=None, cached={}, rel_now=True, show_secs=False, future_msg='in the future'): e = int((now or time.time()) - t) key = f'{e}:{rel_now}:{show_secs}' if not key in cached: def add_suffix(): return ( ((' ago' if rel_now else '') if e > 0 else - (' in the future' if rel_now else ' (negative elapsed)')) + (f' {future_msg}' if rel_now else ' (negative elapsed)')) if (abs_e if show_secs else abs_e // 60) else ('just now' if rel_now else ('0 ' + ('seconds' if show_secs else 'minutes'))) ) diff --git a/test/cmdtest_d/ct_regtest.py b/test/cmdtest_d/ct_regtest.py index 9cd88a83..63700723 100755 --- a/test/cmdtest_d/ct_regtest.py +++ b/test/cmdtest_d/ct_regtest.py @@ -44,8 +44,9 @@ from ..include.common import ( cmp_or_die, strip_ansi_escapes, gr_uc, - getrandhex -) + getrandhex, + make_burn_addr) + from .common import ( ok_msg, get_file_with_ext, @@ -53,8 +54,8 @@ from .common import ( tw_comment_lat_cyr_gr, tw_comment_zh, tx_comment_jp, - get_env_without_debug_vars -) + get_env_without_debug_vars) + from .ct_base import CmdTestBase from .ct_shared import CmdTestShared @@ -161,14 +162,6 @@ rt_data = { } } -def make_burn_addr(proto, mmtype='compressed'): - from mmgen.tool.coin import tool_cmd - return tool_cmd( - cfg = cfg, - cmdname = 'pubhash2addr', - proto = proto, - mmtype = mmtype).pubhash2addr('00'*20) - class CmdTestRegtest(CmdTestBase, CmdTestShared): 'transacting and tracking wallet operations via regtest mode' networks = ('btc', 'ltc', 'bch') @@ -1185,9 +1178,9 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared): txfile = self.get_file_with_ext(ext, delete=False, no_dot=True) return self.user_txbump('bob', self.tmpdir, txfile, rtFee[2], add_args=['--send']) - def generate(self, num_blocks=1): + def generate(self, num_blocks=1, add_opts=[]): int(num_blocks) - t = self.spawn('mmgen-regtest', ['generate', str(num_blocks)]) + t = self.spawn('mmgen-regtest', add_opts + ['generate', str(num_blocks)]) t.expect(f'Mined {num_blocks} block') return t diff --git a/test/include/common.py b/test/include/common.py index 878fb362..4717ba44 100755 --- a/test/include/common.py +++ b/test/include/common.py @@ -350,6 +350,14 @@ def in_nix_environment(): return True return False +def make_burn_addr(proto, mmtype='compressed', hexdata=None): + from mmgen.tool.coin import tool_cmd + return tool_cmd( + cfg = cfg, + cmdname = 'pubhash2addr', + proto = proto, + mmtype = mmtype).pubhash2addr(hexdata or '00'*20) + def VirtBlockDevice(img_path, size): if sys.platform == 'linux': return VirtBlockDeviceLinux(img_path, size) diff --git a/test/test-release.sh b/test/test-release.sh index 2d76cfe8..31a58670 100755 --- a/test/test-release.sh +++ b/test/test-release.sh @@ -380,7 +380,7 @@ done in_nix_environment && parity --help >/dev/null 2>&1 || SKIP_PARITY=1 -[ "$MMGEN_DISABLE_COLOR" ] || { +[ "$MMGEN_DISABLE_COLOR" -o ! -t 1 ] || { RED="\e[31;1m" GREEN="\e[32;1m" YELLOW="\e[33;1m" BLUE="\e[34;1m" MAGENTA="\e[35;1m" CYAN="\e[36;1m" RESET="\e[0m" }