From 7b6717d85a10bf7554a6156a63a32b0786f37862 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Mon, 24 Feb 2025 11:27:44 +0000 Subject: [PATCH] fixes and cleanups throughout --- mmgen/main_txbump.py | 18 +++++------------- mmgen/proto/eth/tx/bump.py | 4 ++-- mmgen/tx/bump.py | 23 ++++++++++++++++++++--- mmgen/tx/new.py | 31 ++++++++++++++++++++----------- scripts/exec_wrapper.py | 22 ++++++++++++---------- test/cmdtest.py | 5 +++-- test/cmdtest_d/ct_automount.py | 7 ++++--- test/cmdtest_d/ct_autosign.py | 11 +++++++---- test/cmdtest_d/ct_regtest.py | 7 ++++--- test/cmdtest_d/ct_swap.py | 9 +-------- test/test-release.sh | 4 +--- 11 files changed, 79 insertions(+), 62 deletions(-) diff --git a/mmgen/main_txbump.py b/mmgen/main_txbump.py index dc0ee1f9..8dead1e1 100755 --- a/mmgen/main_txbump.py +++ b/mmgen/main_txbump.py @@ -164,34 +164,26 @@ async def main(): kal = kl = sign_and_send = None else: orig_tx = await CompletedTX(cfg=cfg, filename=tx_file) + kal = get_keyaddrlist(cfg, orig_tx.proto) + kl = get_keylist(cfg) + sign_and_send = any([seed_files, kl, kal]) if not silent: msg(green('ORIGINAL TRANSACTION')) msg(orig_tx.info.format(terse=True)) - if not cfg.autosign: - kal = get_keyaddrlist(cfg, orig_tx.proto) - kl = get_keylist(cfg) - sign_and_send = any([seed_files, kl, kal]) - from .tw.ctl import TwCtl tx = await BumpTX( cfg = cfg, data = orig_tx.__dict__, automount = cfg.autosign, check_sent = cfg.autosign or sign_and_send, + new_outputs = bool(cfg._args), twctl = await TwCtl(cfg, orig_tx.proto) if orig_tx.proto.tokensym else None) - tx.orig_rel_fee = tx.get_orig_rel_fee() - - if cfg._args: - tx.new_outputs = True - tx.is_swap = False - tx.outputs = tx.OutputList(tx) - tx.cfg = cfg # NB: with --automount, must use current cfg opts, not those from orig_tx + if tx.new_outputs: await tx.create(cfg._args, caller='txdo' if sign_and_send else 'txcreate') else: - tx.new_outputs = False await tx.create_feebump(silent=silent) if not silent: diff --git a/mmgen/proto/eth/tx/bump.py b/mmgen/proto/eth/tx/bump.py index 0b97e8f6..a68981c1 100755 --- a/mmgen/proto/eth/tx/bump.py +++ b/mmgen/proto/eth/tx/bump.py @@ -21,8 +21,8 @@ from .new import New, TokenNew class Bump(Completed, New, TxBase.Bump): desc = 'fee-bumped transaction' - def get_orig_rel_fee(self): # disable this check for ETH - return 0 + def get_orig_rel_fee(self): + return self.txobj['gasPrice'].to_unit('Gwei') @property def min_fee(self): diff --git a/mmgen/tx/bump.py b/mmgen/tx/bump.py index 85f6cf00..8fd36976 100755 --- a/mmgen/tx/bump.py +++ b/mmgen/tx/bump.py @@ -15,16 +15,29 @@ tx.bump: transaction bump class from .new import New from .completed import Completed from ..util import msg, ymsg, is_int, die +from ..color import pink class Bump(Completed, New): desc = 'fee-bumped transaction' ext = 'rawtx' bump_output_idx = None is_bump = True + swap_attrs = ('is_swap',) - def __init__(self, check_sent, *args, **kwargs): + def __init__(self, *, check_sent, new_outputs, **kwargs): - super().__init__(*args, **kwargs) + super().__init__(**kwargs) + + self.new_outputs = new_outputs + self.orig_rel_fee = self.get_orig_rel_fee() + + if new_outputs: + from .base import Base + if self.is_swap: + for attr in self.swap_attrs: + setattr(self, attr, getattr(Base, attr)) + self.outputs = self.OutputList(self) + self.cfg = kwargs['cfg'] # must use current cfg opts, not those from orig_tx if not self.is_replaceable(): die(1, f'Transaction {self.txid} is not replaceable') @@ -60,7 +73,11 @@ class Bump(Completed, New): output_idx = self.choose_output() if not silent: - msg(f'Minimum fee for new transaction: {self.min_fee.hl()} {self.proto.coin}') + msg('Minimum fee for new transaction: {} {} ({} {})'.format( + self.min_fee.hl(), + self.proto.coin, + pink(self.fee_abs2rel(self.min_fee)), + self.rel_fee_disp)) self.usr_fee = self.get_usr_fee_interactive(fee=self.cfg.fee, desc='User-selected') diff --git a/mmgen/tx/new.py b/mmgen/tx/new.py index 6e736c69..14b42904 100755 --- a/mmgen/tx/new.py +++ b/mmgen/tx/new.py @@ -83,7 +83,9 @@ class New(Base): _funds_available = namedtuple('funds_available', ['is_positive', 'amt']) def __init__(self, *args, target=None, **kwargs): - self.is_swap = target == 'swaptx' + if target == 'swaptx': + self.is_swap = True + self.swap_proto = kwargs['cfg'].swap_proto super().__init__(*args, **kwargs) def warn_insufficient_funds(self, amt, coin): @@ -183,6 +185,8 @@ class New(Base): arg, amt = arg_in.split(',', 1) if ',' in arg_in else (arg_in, None) + coin_addr, mmid = (None, None) + if mmid := get_obj(MMGenID, proto=proto, id_str=arg, silent=True): coin_addr = mmaddr2coinaddr(self.cfg, arg, ad_w, ad_f, proto) elif is_coin_addr(proto, arg): @@ -191,7 +195,6 @@ class New(Base): if proto.base_proto_coin != 'BTC': die(2, f'Change addresses not supported for {proto.name} protocol') self.chg_autoselected = True - coin_addr = None else: die(2, f'{arg_in}: invalid command-line argument') @@ -219,7 +222,7 @@ class New(Base): parsed_args = [self.parse_cmdline_arg(self.proto, arg, ad_f, ad_w) for arg in cmd_args] - chg_args = [a for a in parsed_args if not ((a.amt and a.addr) or a.data)] + chg_args = [a for a in parsed_args if not (a.amt or a.data)] if len(chg_args) > 1: desc = 'requested' if self.chg_autoselected else 'listed' @@ -229,13 +232,15 @@ class New(Base): if a.data: self.add_output(None, self.proto.coin_amt('0'), data=a.data) else: - exclude = [a.mmid for a in parsed_args if a.mmid] self.add_output( coinaddr = a.addr or ( - await self.get_autochg_addr(self.proto, a.arg, exclude=exclude, desc='change address') - ).addr, - amt = self.proto.coin_amt(a.amt or '0'), - is_chg = not a.amt) + await self.get_autochg_addr( + self.proto, + a.arg, + exclude = [a.mmid for a in parsed_args if a.mmid], + desc = 'change address')).addr, + amt = self.proto.coin_amt(a.amt or '0'), + is_chg = not a.amt) if self.chg_idx is None: die(2, @@ -387,13 +392,14 @@ class New(Base): sel_unspent, outputs_sum): return False + self.copy_inputs_from_tw(sel_unspent) # makes self.inputs return True - async def get_fee(self, fee, outputs_sum): + async def get_fee(self, fee, outputs_sum, start_fee_desc): if fee: - self.usr_fee = self.get_usr_fee_interactive(fee, 'User-selected') + self.usr_fee = self.get_usr_fee_interactive(fee, start_fee_desc) else: fee_per_kb, fe_type = await self.get_rel_fee_from_network() self.usr_fee = self.get_usr_fee_interactive( @@ -468,7 +474,10 @@ class New(Base): fee_hint = None if self.is_swap: fee_hint = self.update_vault_output(self.vault_output.amt or self.sum_inputs()) - if funds_left := await self.get_fee(fee_hint or self.cfg.fee, outputs_sum): + if funds_left := await self.get_fee( + self.cfg.fee or fee_hint, + outputs_sum, + 'User-selected' if self.cfg.fee else 'Recommended' if fee_hint else None): break self.check_non_mmgen_inputs(caller) diff --git a/scripts/exec_wrapper.py b/scripts/exec_wrapper.py index 702b964f..55c262a3 100755 --- a/scripts/exec_wrapper.py +++ b/scripts/exec_wrapper.py @@ -123,6 +123,15 @@ def exec_wrapper_tracemalloc_log(): s = sum(stat.size for stat in stats) / 1024, w = col1w)) +def exec_wrapper_do_exit(e, exit_val): + if exit_val != 0: + exec_wrapper_write_traceback(e, exit_val) + else: + if exec_wrapper_os.getenv('MMGEN_TRACEMALLOC'): + exec_wrapper_tracemalloc_log() + exec_wrapper_end_msg() + exec_wrapper_sys.exit(exit_val) + import sys as exec_wrapper_sys import os as exec_wrapper_os import time as exec_wrapper_time @@ -145,17 +154,10 @@ try: with open(exec_wrapper_execed_file) as fp: exec(fp.read()) except SystemExit as e: - if e.code != 0: - exec_wrapper_write_traceback(e, e.code) - else: - if exec_wrapper_os.getenv('MMGEN_TRACEMALLOC'): - exec_wrapper_tracemalloc_log() - exec_wrapper_end_msg() - exec_wrapper_sys.exit(e.code) + exec_wrapper_do_exit(e, e.code) except Exception as e: - exit_val = e.mmcode if hasattr(e, 'mmcode') else e.code if hasattr(e, 'code') else 1 - exec_wrapper_write_traceback(e, exit_val) - exec_wrapper_sys.exit(exit_val) + exec_wrapper_do_exit( + e, e.mmcode if hasattr(e, 'mmcode') else e.code if hasattr(e, 'code') else 1) if exec_wrapper_os.getenv('MMGEN_TRACEMALLOC'): exec_wrapper_tracemalloc_log() diff --git a/test/cmdtest.py b/test/cmdtest.py index 1955b36a..a2982778 100755 --- a/test/cmdtest.py +++ b/test/cmdtest.py @@ -447,7 +447,7 @@ class CmdGroupMgr: yield ' {} - {}'.format( yellow(name.ljust(13)), (cls.__doc__.strip() if cls.__doc__ else cls.__name__)) - if hasattr(cls, 'cmd_subgroups'): + if 'cmd_subgroups' in cls.__dict__: subgroups = {k:v for k, v in cls.cmd_subgroups.items() if not k.startswith('_')} max_w = max(len(k) for k in subgroups) for k, v in subgroups.items(): @@ -762,7 +762,8 @@ class CmdTestRunner: if isinstance(e, KeyError) and e.args[0] == cmdname: ret = getattr(self.tg, cmdname)() if type(ret).__name__ == 'coroutine': - asyncio.run(ret) + ret = asyncio.run(ret) + self.process_retval(cmdname, ret) else: raise do_between() diff --git a/test/cmdtest_d/ct_automount.py b/test/cmdtest_d/ct_automount.py index 4eff5b6f..e9e8127f 100755 --- a/test/cmdtest_d/ct_automount.py +++ b/test/cmdtest_d/ct_automount.py @@ -14,14 +14,15 @@ test.cmdtest_d.ct_automount: autosigning with automount tests for the cmdtest.py import time from .ct_autosign import CmdTestAutosignThreaded -from .ct_regtest import CmdTestRegtestBDBWallet, rt_pw +from .ct_regtest import CmdTestRegtest, rt_pw from ..include.common import cfg, gr_uc -class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtestBDBWallet): +class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest): 'automounted transacting operations via regtest mode' networks = ('btc', 'bch', 'ltc') tmpdir_nums = [49] + bdb_wallet = True rt_data = { 'rtFundAmt': {'btc':'500', 'bch':'500', 'ltc':'5500'}, @@ -78,7 +79,7 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtestBDBWallet) self.coins = [cfg.coin.lower()] CmdTestAutosignThreaded.__init__(self, trunner, cfgs, spawn) - CmdTestRegtestBDBWallet.__init__(self, trunner, cfgs, spawn) + CmdTestRegtest.__init__(self, trunner, cfgs, spawn) if trunner is None: return diff --git a/test/cmdtest_d/ct_autosign.py b/test/cmdtest_d/ct_autosign.py index 78e7019c..e2d13ccb 100755 --- a/test/cmdtest_d/ct_autosign.py +++ b/test/cmdtest_d/ct_autosign.py @@ -441,19 +441,22 @@ class CmdTestAutosignThreaded(CmdTestAutosignBase): no_insert_check = False threaded = True - def _wait_loop_start(self): + def _wait_loop_start(self, add_opts=[]): t = self.spawn( 'mmgen-autosign', - self.opts + ['--full-summary', 'wait'], + self.opts + add_opts + ['--full-summary', 'wait'], direct_exec = True, no_passthru_opts = True, spawn_env_override = self.spawn_env | {'EXEC_WRAPPER_DO_RUNTIME_MSG': ''}) self.write_to_tmpfile('autosign_thread_pid', str(t.ep.pid)) return t - def wait_loop_start(self): + def wait_loop_start(self, add_opts=[]): import threading - threading.Thread(target=self._wait_loop_start, name='Autosign wait loop').start() + threading.Thread( + target = self._wait_loop_start, + kwargs = {'add_opts': add_opts}, + name = 'Autosign wait loop').start() time.sleep(0.1) # try to ensure test output is displayed before next test starts return 'silent' diff --git a/test/cmdtest_d/ct_regtest.py b/test/cmdtest_d/ct_regtest.py index 79ae48f0..2d553cdb 100755 --- a/test/cmdtest_d/ct_regtest.py +++ b/test/cmdtest_d/ct_regtest.py @@ -280,8 +280,8 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared): ('bob_split1', 'splitting Bob’s funds'), ('generate', 'mining a block'), ('bob_bal2', 'Bob’s balance'), - ('bob_rbf_1output_create', 'creating RBF tx with one output'), - ('bob_rbf_1output_bump', 'bumping RBF tx with one output'), + ('bob_rbf_1output_create', 'creating RBF TX with one output'), + ('bob_rbf_1output_bump', 'creating replacement TX with one output'), ('bob_bal2a', 'Bob’s balance (age_fmt=confs)'), ('bob_bal2b', 'Bob’s balance (showempty=1)'), ('bob_bal2c', 'Bob’s balance (showempty=1 minconf=2 age_fmt=days)'), @@ -1122,7 +1122,7 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared): one_output = True) def bob_send_maybe_rbf(self): - outputs_cl = self._create_tx_outputs('alice', (('L', 1, ', 60'), ('C', 1, ', 40'))) + outputs_cl = self._create_tx_outputs('alice', (('L', 1, ',60'), ('C', 1, ',40'))) outputs_cl += [self._user_sid('bob')+':'+rtBobOp3] return self.user_txdo( user = 'bob', @@ -2174,4 +2174,5 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared): return 'ok' class CmdTestRegtestBDBWallet(CmdTestRegtest): + 'transacting and tracking wallet operations via regtest mode (legacy BDB wallet)' bdb_wallet = True diff --git a/test/cmdtest_d/ct_swap.py b/test/cmdtest_d/ct_swap.py index 23d70ff3..9917671a 100755 --- a/test/cmdtest_d/ct_swap.py +++ b/test/cmdtest_d/ct_swap.py @@ -13,15 +13,8 @@ test.cmdtest_d.ct_swap: asset swap tests for the cmdtest.py test suite """ from mmgen.protocol import init_proto - from ..include.common import gr_uc - -from .ct_regtest import ( - CmdTestRegtest, - rt_data, - dfl_wcls, - rt_pw, - cfg) +from .ct_regtest import CmdTestRegtest, rt_data, dfl_wcls, rt_pw, cfg sample1 = gr_uc[:24] sample2 = '00010203040506' diff --git a/test/test-release.sh b/test/test-release.sh index 6c0cb696..560043e0 100755 --- a/test/test-release.sh +++ b/test/test-release.sh @@ -217,9 +217,7 @@ do_reexec() { install_secp256k1_mod_maybe() { if [[ "$repo" =~ ^mmgen[-_]wallet ]]; then - [ -e mmgen/proto/secp256k1/secp256k1*$(python3 --version | sed 's/.* //;s/\.//;s/\..*//')* ] || { - eval "python3 setup.py build_ext --inplace $STDOUT_DEVNULL" - } + eval "python3 setup.py build_ext --inplace $STDOUT_DEVNULL" fi }