mmgen-txsend: add --dump-hex and --mark-sent options
Use --dump-hex to dump the serialized transaction hex to file or standard output instead of sending the transaction. With --autosign, use --mark-sent to mark the transaction as sent on the removable device after a successful out-of-band send.
This commit is contained in:
parent
886f8e3029
commit
6967456f8f
8 changed files with 157 additions and 17 deletions
|
|
@ -109,6 +109,10 @@ def check_infile(f, *, blkdev_ok=False):
|
|||
def check_outfile(f, *, blkdev_ok=False):
|
||||
return _check_file_type_and_access(f, 'output file', blkdev_ok=blkdev_ok)
|
||||
|
||||
def check_outfile_dir(fn, *, blkdev_ok=False):
|
||||
return _check_file_type_and_access(
|
||||
os.path.dirname(os.path.abspath(fn)), 'output directory', blkdev_ok=blkdev_ok)
|
||||
|
||||
def check_outdir(f):
|
||||
return _check_file_type_and_access(f, 'output directory')
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,11 @@ opts_data = {
|
|||
--autosign’ and delete it from the removable device. The
|
||||
transaction may be signed or unsigned.
|
||||
-d, --outdir= d Specify an alternate directory 'd' for output
|
||||
-H, --dump-hex=F Instead of sending to the network, dump the transaction hex
|
||||
to file ‘F’. Use filename ‘-’ to dump to standard output.
|
||||
-m, --mark-sent Mark the transaction as sent by adding it to the removable
|
||||
device. Used in combination with --autosign when a trans-
|
||||
action has been successfully sent out-of-band.
|
||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||
-s, --status Get status of a sent transaction (or current transaction,
|
||||
whether sent or unsent, when used with --autosign)
|
||||
|
|
@ -58,6 +63,13 @@ cfg = Config(opts_data=opts_data)
|
|||
if cfg.autosign and cfg.outdir:
|
||||
die(1, '--outdir cannot be used in combination with --autosign')
|
||||
|
||||
if cfg.mark_sent and not cfg.autosign:
|
||||
die(1, '--mark-sent is used only in combination with --autosign')
|
||||
|
||||
if cfg.dump_hex and cfg.dump_hex != '-':
|
||||
from .fileutil import check_outfile_dir
|
||||
check_outfile_dir(cfg.dump_hex)
|
||||
|
||||
if len(cfg._args) == 1:
|
||||
infile = cfg._args[0]
|
||||
from .fileutil import check_infile
|
||||
|
|
@ -110,6 +122,10 @@ async def main():
|
|||
|
||||
cfg._util.vmsg(f'Getting {tx.desc} ‘{tx.infile}’')
|
||||
|
||||
if cfg.mark_sent:
|
||||
await post_send(tx)
|
||||
sys.exit(0)
|
||||
|
||||
if cfg.status:
|
||||
if tx.coin_txid:
|
||||
cfg._util.qmsg(f'{tx.proto.coin} txid: {tx.coin_txid.hl()}')
|
||||
|
|
@ -127,7 +143,22 @@ async def main():
|
|||
if not cfg.autosign:
|
||||
tx.file.write(ask_write_default_yes=True)
|
||||
|
||||
if await tx.send():
|
||||
if cfg.dump_hex:
|
||||
from .fileutil import write_data_to_file
|
||||
write_data_to_file(
|
||||
cfg,
|
||||
cfg.dump_hex,
|
||||
tx.serialized + '\n',
|
||||
desc = 'serialized transaction hex data',
|
||||
ask_overwrite = False,
|
||||
ask_tty = False)
|
||||
if cfg.autosign:
|
||||
from .ui import keypress_confirm
|
||||
if keypress_confirm(cfg, 'Mark transaction as sent on removable device?'):
|
||||
await post_send(tx)
|
||||
else:
|
||||
await post_send(tx)
|
||||
elif await tx.send():
|
||||
await post_send(tx)
|
||||
|
||||
async_run(main())
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ def get_file_with_ext(
|
|||
no_dot = False,
|
||||
return_list = False,
|
||||
delete_all = False,
|
||||
subdir = None,
|
||||
substr = False):
|
||||
|
||||
dot = '' if no_dot else '.'
|
||||
|
|
@ -118,6 +119,9 @@ def get_file_with_ext(
|
|||
or fn.endswith(dot + ext)
|
||||
or (substr and ext in fn))
|
||||
|
||||
if subdir:
|
||||
tdir = os.path.join(tdir, subdir)
|
||||
|
||||
# Don’t use os.scandir here - it returns broken paths under Windows/MSYS2
|
||||
flist = [os.path.join(tdir, name) for name in os.listdir(tdir) if have_match(name)]
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
|
|||
('alice_txcreate4', 'creating a transaction'),
|
||||
('alice_txbump1', 'bumping the unsigned transaction (error)'),
|
||||
('alice_txbump2', 'bumping the unsent transaction (error)'),
|
||||
('alice_txsend2', 'sending the transaction'),
|
||||
('alice_txsend2_dump_hex', 'dumping the transaction to hex'),
|
||||
('alice_txsend2_cli', 'sending the transaction via cli'),
|
||||
('alice_txsend2_mark_sent', 'marking the transaction sent'),
|
||||
('alice_txbump3', 'bumping the transaction'),
|
||||
('alice_txsend3', 'sending the bumped transaction'),
|
||||
('alice_txbump4', 'bumping the transaction (new outputs, fee too low)'),
|
||||
|
|
@ -143,10 +145,18 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
|
|||
return self.run_setup(mn_type='default', use_dfl_wallet=True, passwd=rt_pw)
|
||||
|
||||
def alice_txsend1(self):
|
||||
return self._user_txsend('alice', 'This one’s worth a comment', no_wait=True)
|
||||
return self._user_txsend('alice', comment='This one’s worth a comment', no_wait=True)
|
||||
|
||||
def alice_txsend2(self):
|
||||
return self._user_txsend('alice', need_rbf=True)
|
||||
def alice_txsend2_dump_hex(self):
|
||||
return self._user_txsend('alice', need_rbf=True, dump_hex=True)
|
||||
|
||||
def alice_txsend2_cli(self):
|
||||
if not self.proto.cap('rbf'):
|
||||
return 'skip'
|
||||
return self._user_dump_hex_send_cli('alice')
|
||||
|
||||
def alice_txsend2_mark_sent(self):
|
||||
return self._user_txsend('alice', need_rbf=True, mark_sent=True)
|
||||
|
||||
def alice_txsend3(self):
|
||||
return self._user_txsend('alice', need_rbf=True)
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ class CmdTestAutosignBase(CmdTestBase):
|
|||
atexit.register(self._macOS_eject_disk, self.asi.dev_label)
|
||||
|
||||
self.opts = ['--coins='+','.join(self.coins)]
|
||||
self.txhex_file = f'{self.tmpdir}/tx_dump.hex'
|
||||
|
||||
if not self.live:
|
||||
self.spawn_env['MMGEN_TEST_SUITE_ROOT_PFX'] = self.tmpdir
|
||||
|
|
@ -492,7 +493,15 @@ class CmdTestAutosignThreaded(CmdTestAutosignBase):
|
|||
|
||||
return do_return()
|
||||
|
||||
def _user_txsend(self, user, comment=None, no_wait=False, need_rbf=False):
|
||||
def _user_txsend(
|
||||
self,
|
||||
user,
|
||||
*,
|
||||
comment = None,
|
||||
no_wait = False,
|
||||
need_rbf = False,
|
||||
dump_hex = False,
|
||||
mark_sent = False):
|
||||
|
||||
if need_rbf and not self.proto.cap('rbf'):
|
||||
return 'skip'
|
||||
|
|
@ -500,12 +509,26 @@ class CmdTestAutosignThreaded(CmdTestAutosignBase):
|
|||
if not no_wait:
|
||||
self._wait_signed('transaction')
|
||||
|
||||
extra_opt = (
|
||||
[f'--dump-hex={self.txhex_file}'] if dump_hex
|
||||
else ['--mark-sent'] if mark_sent
|
||||
else [])
|
||||
|
||||
self.insert_device_online()
|
||||
t = self.spawn('mmgen-txsend', [f'--{user}', '--quiet', '--autosign'])
|
||||
t.view_tx('t')
|
||||
t.do_comment(comment)
|
||||
self._do_confirm_send(t, quiet=True)
|
||||
t.written_to_file('Sent automount transaction')
|
||||
t = self.spawn('mmgen-txsend', [f'--{user}', '--quiet', '--autosign'] + extra_opt)
|
||||
|
||||
if mark_sent:
|
||||
t.written_to_file('Sent automount transaction')
|
||||
else:
|
||||
t.view_tx('t')
|
||||
t.do_comment(comment)
|
||||
if dump_hex:
|
||||
t.written_to_file('Serialized transaction hex data')
|
||||
t.expect('(y/N): ', 'n') # mark as sent?
|
||||
else:
|
||||
self._do_confirm_send(t, quiet=True)
|
||||
t.written_to_file('Sent automount transaction')
|
||||
|
||||
t.read()
|
||||
self.remove_device_online()
|
||||
return t
|
||||
|
|
|
|||
|
|
@ -76,8 +76,9 @@ class CmdTestBase:
|
|||
def get_file_with_ext(self, ext, **kwargs):
|
||||
return get_file_with_ext(self.tmpdir, ext, **kwargs)
|
||||
|
||||
def read_from_tmpfile(self, fn, binary=False):
|
||||
return read_from_file(os.path.join(self.tmpdir, fn), binary=binary)
|
||||
def read_from_tmpfile(self, fn, binary=False, subdir=None):
|
||||
tdir = os.path.join(self.tmpdir, subdir) if subdir else self.tmpdir
|
||||
return read_from_file(os.path.join(tdir, fn), binary=binary)
|
||||
|
||||
def write_to_tmpfile(self, fn, data, binary=False):
|
||||
return write_to_file(os.path.join(self.tmpdir, fn), data, binary=binary)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from mmgen.proto.btc.regtest import MMGenRegtest
|
|||
from mmgen.proto.bch.cashaddr import b32a
|
||||
from mmgen.proto.btc.common import b58a
|
||||
from mmgen.color import yellow
|
||||
from mmgen.util import msg_r, die, gmsg, capfirst, suf, fmt_list
|
||||
from mmgen.util import msg_r, die, gmsg, capfirst, suf, fmt_list, is_hex_str
|
||||
from mmgen.protocol import init_proto
|
||||
from mmgen.addrlist import AddrList
|
||||
from mmgen.wallet import Wallet, get_wallet_cls
|
||||
|
|
@ -189,6 +189,7 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
|
|||
('subgroup.view', ['label']),
|
||||
('subgroup._auto_chg_deps', ['twexport', 'label']),
|
||||
('subgroup.auto_chg', ['_auto_chg_deps']),
|
||||
('subgroup.dump_hex', ['fund_users']),
|
||||
('stop', 'stopping regtest daemon'),
|
||||
)
|
||||
cmd_subgroups = {
|
||||
|
|
@ -458,6 +459,16 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
|
|||
'(no unused addresses)'),
|
||||
('carol_delete_wallet', 'unloading and deleting Carol’s tracking wallet'),
|
||||
),
|
||||
'dump_hex': (
|
||||
'sending from dumped hex',
|
||||
('bob_dump_hex_create', 'dump_hex transaction - creating'),
|
||||
('bob_dump_hex_sign', 'dump_hex transaction - signing'),
|
||||
('bob_dump_hex_dump_stdout', 'dump_hex transaction - dumping tx hex to stdout'),
|
||||
('bob_dump_hex_dump', 'dump_hex transaction - dumping tx hex to file'),
|
||||
('bob_dump_hex_send_cli', 'dump_hex transaction - sending via cli'),
|
||||
('generate', 'mining a block'),
|
||||
('bob_bal7', 'Bob’s balance'),
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, trunner, cfgs, spawn):
|
||||
|
|
@ -494,6 +505,7 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
|
|||
self.burn_addr = make_burn_addr(self.proto)
|
||||
self.user_sids = {}
|
||||
self.protos = (self.proto,)
|
||||
self.dump_hex_subdir = os.path.join(self.tmpdir, 'nochg_tx')
|
||||
|
||||
def _add_comments_to_addr_file(self, proto, addrfile, outfile, use_comments=False):
|
||||
silence()
|
||||
|
|
@ -2185,6 +2197,58 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
|
|||
'L',
|
||||
'contains no unused addresses of address type')
|
||||
|
||||
def bob_dump_hex_create(self):
|
||||
if not os.path.exists(self.dump_hex_subdir):
|
||||
os.mkdir(self.dump_hex_subdir)
|
||||
autochg_arg = self._user_sid('bob') + ':C'
|
||||
return self.txcreate_ui_common(
|
||||
self.spawn('mmgen-txcreate',
|
||||
[
|
||||
'-d',
|
||||
self.dump_hex_subdir,
|
||||
'-B',
|
||||
'--bob',
|
||||
'--fee=0.00009713',
|
||||
autochg_arg
|
||||
]),
|
||||
auto_chg_addr = autochg_arg)
|
||||
|
||||
def bob_dump_hex_sign(self):
|
||||
txfile = get_file_with_ext(self.dump_hex_subdir, 'rawtx')
|
||||
return self.txsign_ui_common(
|
||||
self.spawn('mmgen-txsign', ['-d', self.dump_hex_subdir, '--bob', txfile]),
|
||||
do_passwd = True,
|
||||
passwd = rt_pw)
|
||||
|
||||
def _bob_dump_hex_dump(self, file):
|
||||
txfile = get_file_with_ext(self.dump_hex_subdir, 'sigtx')
|
||||
t = self.spawn('mmgen-txsend', ['-d', self.dump_hex_subdir, f'--dump-hex={file}', '--bob', txfile])
|
||||
t.expect('view: ', '\n')
|
||||
t.expect('(y/N): ', '\n') # add comment?
|
||||
t.written_to_file('Sent transaction')
|
||||
return t
|
||||
|
||||
def bob_dump_hex_dump(self):
|
||||
return self._bob_dump_hex_dump('tx_dump.hex')
|
||||
|
||||
def bob_dump_hex_dump_stdout(self):
|
||||
return self._bob_dump_hex_dump('-')
|
||||
|
||||
def _user_dump_hex_send_cli(self, user, *, subdir=None):
|
||||
txhex = self.read_from_tmpfile('tx_dump.hex', subdir=subdir).strip()
|
||||
t = self.spawn('mmgen-cli', [f'--{user}', 'sendrawtransaction', txhex])
|
||||
txid = t.read().splitlines()[0]
|
||||
assert is_hex_str(txid) and len(txid) == 64
|
||||
return t
|
||||
|
||||
def bob_dump_hex_send_cli(self):
|
||||
return self._user_dump_hex_send_cli('bob', subdir='nochg_tx')
|
||||
|
||||
def bob_bal7(self):
|
||||
if not self.coin == 'btc':
|
||||
return 'skip'
|
||||
return self._user_bal_cli('bob', chks=['499.99990287', '46.51845565'])
|
||||
|
||||
def stop(self):
|
||||
self.spawn('', msg_only=True)
|
||||
if cfg.no_daemon_stop:
|
||||
|
|
|
|||
|
|
@ -134,15 +134,18 @@ class CmdTestShared:
|
|||
ni = False,
|
||||
save = True,
|
||||
do_passwd = False,
|
||||
passwd = None,
|
||||
has_label = False):
|
||||
|
||||
txdo = (caller or self.test_name)[:4] == 'txdo'
|
||||
|
||||
if do_passwd:
|
||||
t.passphrase('MMGen wallet', self.wpasswd)
|
||||
if do_passwd and txdo:
|
||||
t.passphrase('MMGen wallet', passwd or self.wpasswd)
|
||||
|
||||
if not ni and not txdo:
|
||||
if not (ni or txdo):
|
||||
t.view_tx(view)
|
||||
if do_passwd:
|
||||
t.passphrase('MMGen wallet', passwd or self.wpasswd)
|
||||
t.do_comment(add_comment, has_label=has_label)
|
||||
t.expect('(Y/n): ', ('n', 'y')[save])
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue