mmgen-txsend --status: support transaction ranges
Example (assumes --autosign):
# Display status of last four sent transactions:
$ mmgen-txsend -s 0-3
Testing/demo:
$ test/cmdtest.py -e -X alice_txstatus9 autosign_automount
This commit is contained in:
parent
74acc44a2c
commit
48edcf412c
6 changed files with 71 additions and 35 deletions
|
|
@ -334,22 +334,25 @@ class Signable:
|
||||||
shred_file(self.cfg, fn, iterations=15)
|
shred_file(self.cfg, fn, iterations=15)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
async def get_last_sent(self, *, idx=0):
|
async def get_last_sent(self, *, tx_range=None):
|
||||||
return await self.get_last_created(
|
return await self.get_last_created(
|
||||||
# compat fallback - ‘sent_timestamp’ attr is missing in some old TX files:
|
# compat fallback - ‘sent_timestamp’ attr is missing in some old TX files:
|
||||||
sort_key = lambda x: x.sent_timestamp or x.timestamp,
|
sort_key = lambda x: x.sent_timestamp or x.timestamp,
|
||||||
idx = idx)
|
tx_range = tx_range)
|
||||||
|
|
||||||
async def get_last_created(self, *, sort_key=lambda x: x.timestamp, idx=0):
|
async def get_last_created(self, *, sort_key=lambda x: x.timestamp, tx_range=None):
|
||||||
from .tx import CompletedTX
|
from .tx import CompletedTX
|
||||||
fns = [f for f in self.dir.iterdir() if f.name.endswith(self.subext)]
|
fns = [f for f in self.dir.iterdir() if f.name.endswith(self.subext)]
|
||||||
files = sorted(
|
files = sorted(
|
||||||
[await CompletedTX(cfg=self.cfg, filename=str(txfile), quiet_open=True)
|
[await CompletedTX(cfg=self.cfg, filename=str(txfile), quiet_open=True)
|
||||||
for txfile in fns],
|
for txfile in fns],
|
||||||
key = sort_key)
|
key = sort_key)
|
||||||
if not (0 <= idx < len(files)):
|
if files:
|
||||||
die(2, f'{idx}: invalid transaction index (must be less than {len(files)})')
|
return (
|
||||||
return files[-1 - idx]
|
files[-1] if tx_range is None else
|
||||||
|
files[len(files) - 1 - tx_range.last:len(files) - tx_range.first])
|
||||||
|
else:
|
||||||
|
die(1, 'No sent automount transactions!')
|
||||||
|
|
||||||
class xmr_signable: # mixin class
|
class xmr_signable: # mixin class
|
||||||
automount = True
|
automount = True
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
January 2026
|
February 2026
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
16.1.dev28
|
16.1.dev29
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .cfg import gc, Config
|
from .cfg import gc, Config
|
||||||
from .util import async_run, die, is_int
|
from .util import msg, async_run, die
|
||||||
|
|
||||||
opts_data = {
|
opts_data = {
|
||||||
'sets': [
|
'sets': [
|
||||||
|
|
@ -35,7 +35,7 @@ opts_data = {
|
||||||
'usage2': [
|
'usage2': [
|
||||||
'[opts] <signed transaction file>',
|
'[opts] <signed transaction file>',
|
||||||
'[opts] --autosign',
|
'[opts] --autosign',
|
||||||
'[opts] --autosign (--status | --receipt) [IDX]',
|
'[opts] --autosign (--status | --receipt) [index or range]',
|
||||||
],
|
],
|
||||||
'options': """
|
'options': """
|
||||||
-h, --help Print this help message
|
-h, --help Print this help message
|
||||||
|
|
@ -71,10 +71,13 @@ opts_data = {
|
||||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||||
""",
|
""",
|
||||||
'notes': """
|
'notes': """
|
||||||
With --autosign, combined with --status or --receipt, the optional IDX arg
|
With --autosign, combined with --status or --receipt, the optional index or
|
||||||
represents an index into the list of sent transaction files on the removable
|
range arg represents an index or range into the list of sent transaction files
|
||||||
device, in reverse chronological order. ‘0’ (the default) specifies the
|
on the removable device, in reverse chronological order. ‘0’ (the default)
|
||||||
last sent transaction, ‘1’ the next-to-last, and so on.
|
specifies the last sent transaction, ‘1’ the next-to-last, and so on. Hyphen-
|
||||||
|
separated ranges are also supported. For example, specifying a range ‘0-3’
|
||||||
|
would output data for the last four sent transactions, beginning with the most
|
||||||
|
recent.
|
||||||
"""
|
"""
|
||||||
},
|
},
|
||||||
'code': {
|
'code': {
|
||||||
|
|
@ -99,12 +102,12 @@ if cfg.dump_hex and cfg.dump_hex != '-':
|
||||||
check_outfile_dir(cfg.dump_hex)
|
check_outfile_dir(cfg.dump_hex)
|
||||||
|
|
||||||
post_send_op = cfg.status or cfg.receipt
|
post_send_op = cfg.status or cfg.receipt
|
||||||
|
asi, tx_range = (None, None)
|
||||||
asi = None
|
|
||||||
|
|
||||||
def init_autosign(arg):
|
def init_autosign(arg):
|
||||||
global asi, si, infile, tx_idx
|
global asi, si, infile, tx_range
|
||||||
from .tx.util import mount_removable_device
|
from .tx.util import mount_removable_device
|
||||||
|
from .tx.online import SentTXRange
|
||||||
from .autosign import Signable
|
from .autosign import Signable
|
||||||
asi = mount_removable_device(cfg)
|
asi = mount_removable_device(cfg)
|
||||||
si = Signable.automount_transaction(asi)
|
si = Signable.automount_transaction(asi)
|
||||||
|
|
@ -113,9 +116,11 @@ def init_autosign(arg):
|
||||||
elif post_send_op and (si.unsent or si.unsigned):
|
elif post_send_op and (si.unsent or si.unsigned):
|
||||||
die(1, 'Transaction is {}'.format('unsent' if si.unsent else 'unsigned'))
|
die(1, 'Transaction is {}'.format('unsent' if si.unsent else 'unsigned'))
|
||||||
elif post_send_op:
|
elif post_send_op:
|
||||||
if not is_int(arg):
|
try:
|
||||||
die(2, f'{arg}: invalid transaction index (must be a non-negative integer)')
|
tx_range = SentTXRange(arg)
|
||||||
tx_idx = int(arg)
|
except:
|
||||||
|
die(2, f'{arg}: invalid transaction index arg '
|
||||||
|
'(must be a non-negative integer or hyphen-separated range)')
|
||||||
else:
|
else:
|
||||||
infile = si.get_unsent()
|
infile = si.get_unsent()
|
||||||
cfg._util.qmsg(f'Got signed transaction file ‘{infile}’')
|
cfg._util.qmsg(f'Got signed transaction file ‘{infile}’')
|
||||||
|
|
@ -124,7 +129,7 @@ match cfg._args:
|
||||||
case [arg] if cfg.autosign and post_send_op:
|
case [arg] if cfg.autosign and post_send_op:
|
||||||
init_autosign(arg)
|
init_autosign(arg)
|
||||||
case [] if cfg.autosign:
|
case [] if cfg.autosign:
|
||||||
init_autosign(0)
|
init_autosign('0')
|
||||||
case [infile]:
|
case [infile]:
|
||||||
from .fileutil import check_infile
|
from .fileutil import check_infile
|
||||||
check_infile(infile)
|
check_infile(infile)
|
||||||
|
|
@ -137,8 +142,15 @@ if not cfg.status:
|
||||||
|
|
||||||
from .tx import OnlineSignedTX
|
from .tx import OnlineSignedTX
|
||||||
|
|
||||||
|
batch = tx_range and (tx_range.last != tx_range.first)
|
||||||
|
|
||||||
|
def do_sep():
|
||||||
|
if batch:
|
||||||
|
msg('-' * 74)
|
||||||
|
|
||||||
async def process_tx(tx):
|
async def process_tx(tx):
|
||||||
|
|
||||||
|
do_sep()
|
||||||
cfg._util.vmsg(f'Getting {tx.desc} ‘{tx.infile}’')
|
cfg._util.vmsg(f'Getting {tx.desc} ‘{tx.infile}’')
|
||||||
|
|
||||||
if tx.is_compat:
|
if tx.is_compat:
|
||||||
|
|
@ -168,11 +180,14 @@ async def process_tx(tx):
|
||||||
if not cfg.autosign:
|
if not cfg.autosign:
|
||||||
tx.file.write(ask_write_default_yes=True)
|
tx.file.write(ask_write_default_yes=True)
|
||||||
|
|
||||||
return await tx.send(txcfg, asi)
|
return await tx.send(txcfg, asi, batch=batch)
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
if cfg.autosign and post_send_op:
|
if cfg.autosign and post_send_op:
|
||||||
return await process_tx(await si.get_last_sent(idx=tx_idx))
|
exitvals = [await process_tx(tx)
|
||||||
|
for tx in reversed(await si.get_last_sent(tx_range=tx_range))]
|
||||||
|
do_sep()
|
||||||
|
return max(exitvals)
|
||||||
else:
|
else:
|
||||||
return await process_tx(await OnlineSignedTX(
|
return await process_tx(await OnlineSignedTX(
|
||||||
cfg = cfg,
|
cfg = cfg,
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,16 @@ tx.online: online signed transaction class
|
||||||
|
|
||||||
import time, asyncio
|
import time, asyncio
|
||||||
|
|
||||||
|
from ..obj import MMGenRange
|
||||||
from ..util import msg, Msg, gmsg, ymsg, make_timestr, die
|
from ..util import msg, Msg, gmsg, ymsg, make_timestr, die
|
||||||
from ..color import pink, yellow
|
from ..color import pink, yellow
|
||||||
|
|
||||||
from .signed import Signed, AutomountSigned
|
from .signed import Signed, AutomountSigned
|
||||||
|
|
||||||
|
class SentTXRange(MMGenRange):
|
||||||
|
min_idx = 0
|
||||||
|
max_idx = 1_000_000
|
||||||
|
|
||||||
class OnlineSigned(Signed):
|
class OnlineSigned(Signed):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -62,7 +67,7 @@ class OnlineSigned(Signed):
|
||||||
ask_overwrite = False,
|
ask_overwrite = False,
|
||||||
ask_write = False)
|
ask_write = False)
|
||||||
|
|
||||||
async def send(self, cfg, asi):
|
async def send(self, cfg, asi, batch=False):
|
||||||
"""
|
"""
|
||||||
returns integer exit val to system
|
returns integer exit val to system
|
||||||
"""
|
"""
|
||||||
|
|
@ -149,7 +154,10 @@ class OnlineSigned(Signed):
|
||||||
|
|
||||||
if status_exitval is not None:
|
if status_exitval is not None:
|
||||||
if cfg.verbose:
|
if cfg.verbose:
|
||||||
self.info.view_with_prompt('View transaction details?', pause=False)
|
if batch:
|
||||||
|
self.info.view(pause=False, terse=True)
|
||||||
|
else:
|
||||||
|
self.info.view_with_prompt('View transaction details?', pause=False)
|
||||||
return status_exitval
|
return status_exitval
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,9 +82,10 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
|
||||||
('alice_txbump5', 'bumping the transaction (new outputs)'),
|
('alice_txbump5', 'bumping the transaction (new outputs)'),
|
||||||
('alice_txsend5', 'sending the bumped transaction'),
|
('alice_txsend5', 'sending the bumped transaction'),
|
||||||
('alice_txstatus5', 'getting transaction status (in mempool)'),
|
('alice_txstatus5', 'getting transaction status (in mempool)'),
|
||||||
('alice_txstatus6', 'getting transaction status (idx=0, in mempool)'),
|
('alice_txstatus6', 'getting transaction status (tx_range=0, in mempool)'),
|
||||||
('alice_txstatus7', 'getting transaction status (idx=1, replaced)'),
|
('alice_txstatus7', 'getting transaction status (tx_range=1, replaced)'),
|
||||||
('alice_txstatus8', 'getting transaction status (idx=3, 2 confirmations)'),
|
('alice_txstatus8', 'getting transaction status (tx_range=3, 2 confirmations)'),
|
||||||
|
('alice_txstatus9', 'getting transaction status (tx_range=0-3)'),
|
||||||
('generate', 'mining a block'),
|
('generate', 'mining a block'),
|
||||||
('alice_bal2', 'checking Alice’s balance'),
|
('alice_bal2', 'checking Alice’s balance'),
|
||||||
('wait_loop_kill', 'stopping autosign wait loop'),
|
('wait_loop_kill', 'stopping autosign wait loop'),
|
||||||
|
|
@ -229,8 +230,9 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
|
||||||
expect,
|
expect,
|
||||||
exit_val = None,
|
exit_val = None,
|
||||||
need_rbf = False,
|
need_rbf = False,
|
||||||
idx = None,
|
tx_range = None,
|
||||||
verbose = True):
|
verbose = True,
|
||||||
|
batch = False):
|
||||||
|
|
||||||
if need_rbf and not self.proto.cap('rbf'):
|
if need_rbf and not self.proto.cap('rbf'):
|
||||||
return 'skip'
|
return 'skip'
|
||||||
|
|
@ -240,11 +242,11 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
|
||||||
'mmgen-txsend',
|
'mmgen-txsend',
|
||||||
['--alice', '--autosign', '--status']
|
['--alice', '--autosign', '--status']
|
||||||
+ (['--verbose'] if verbose else [])
|
+ (['--verbose'] if verbose else [])
|
||||||
+ ([] if idx is None else [str(idx)]),
|
+ ([] if tx_range is None else [tx_range]),
|
||||||
no_passthru_opts = ['coin'],
|
no_passthru_opts = ['coin'],
|
||||||
exit_val = exit_val)
|
exit_val = exit_val)
|
||||||
t.expect(expect, regex=True)
|
t.expect(expect, regex=True)
|
||||||
if not exit_val:
|
if not (exit_val or batch):
|
||||||
t.expect('view: ', 'n')
|
t.expect('view: ', 'n')
|
||||||
t.read()
|
t.read()
|
||||||
self.remove_device_online()
|
self.remove_device_online()
|
||||||
|
|
@ -267,13 +269,21 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
|
||||||
return self._alice_txstatus('in mempool', need_rbf=True)
|
return self._alice_txstatus('in mempool', need_rbf=True)
|
||||||
|
|
||||||
def alice_txstatus6(self):
|
def alice_txstatus6(self):
|
||||||
return self._alice_txstatus('in mempool', need_rbf=True, idx=0)
|
return self._alice_txstatus('in mempool', need_rbf=True, tx_range='0')
|
||||||
|
|
||||||
def alice_txstatus7(self):
|
def alice_txstatus7(self):
|
||||||
return self._alice_txstatus('replaced', need_rbf=True, idx=1)
|
return self._alice_txstatus('replaced', need_rbf=True, tx_range='1')
|
||||||
|
|
||||||
def alice_txstatus8(self):
|
def alice_txstatus8(self):
|
||||||
return self._alice_txstatus('2 confirmations', need_rbf=True, idx=3)
|
return self._alice_txstatus('2 confirmations', need_rbf=True, tx_range='3')
|
||||||
|
|
||||||
|
def alice_txstatus9(self):
|
||||||
|
return self._alice_txstatus(
|
||||||
|
'in mempool.*replaced.*replaced.*2 confirmations',
|
||||||
|
need_rbf = True,
|
||||||
|
tx_range = '0-3',
|
||||||
|
verbose = False,
|
||||||
|
batch = True)
|
||||||
|
|
||||||
def alice_txsend_bad_no_unsent(self):
|
def alice_txsend_bad_no_unsent(self):
|
||||||
self.insert_device_online()
|
self.insert_device_online()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue