txsend: improve options handling; tx.online: reimplement send()
This commit is contained in:
parent
8b27679202
commit
5e326df10f
10 changed files with 139 additions and 100 deletions
|
|
@ -1 +1 @@
|
|||
16.3.0dev22
|
||||
16.3.0dev23
|
||||
|
|
|
|||
|
|
@ -75,7 +75,6 @@ class SwapCfgValueError(Exception): mmcode = 2
|
|||
# 3: yellow hl, 'MMGen Error' + exception + message
|
||||
class RPCFailure(Exception): mmcode = 3
|
||||
class RPCChainMismatch(Exception): mmcode = 3
|
||||
class TxIDMismatch(Exception): mmcode = 3
|
||||
class BadTxSizeEstimate(Exception): mmcode = 3
|
||||
class MaxInputSizeExceeded(Exception): mmcode = 3
|
||||
class MaxFileSizeExceeded(Exception): mmcode = 3
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ async def main():
|
|||
TxKeys(cfg, tx2, seedfiles=seedfiles, keylist=kl, keyaddrlist=kal).keys):
|
||||
tx4 = await OnlineSignedTX(cfg=cfg, data=tx3.__dict__)
|
||||
tx4.file.write(ask_write=False)
|
||||
sys.exit(await tx4.send(cfg, asi if cfg.autosign else None))
|
||||
sys.exit(await tx4.send(cfg, 'rpc_send', None, asi if cfg.autosign else None))
|
||||
else:
|
||||
die(2, 'Transaction could not be signed')
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ async def main():
|
|||
if tx3:
|
||||
tx3.file.write(ask_write=False)
|
||||
tx4 = await SentTX(cfg=cfg, data=tx3.__dict__)
|
||||
return await tx4.send(cfg, asi=None)
|
||||
return await tx4.send(cfg, 'rpc_send', None, asi=None)
|
||||
else:
|
||||
die(2, 'Transaction could not be signed')
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network
|
|||
|
||||
import sys
|
||||
|
||||
from .cfg import gc, Config
|
||||
from .cfg import gc, Config, fmt_opt
|
||||
from .util import msg, async_run, die
|
||||
|
||||
opts_data = {
|
||||
|
|
@ -97,14 +97,30 @@ if cfg.autosign and cfg.outdir:
|
|||
if cfg.mark_sent and not cfg.autosign:
|
||||
die(1, '--mark-sent is used only in combination with --autosign')
|
||||
|
||||
if cfg.test and cfg.dump_hex:
|
||||
die(1, '--test cannot be used in combination with --dump-hex')
|
||||
opt_ops = (
|
||||
'test',
|
||||
'dump_hex',
|
||||
'status',
|
||||
'receipt',
|
||||
'mark_sent',
|
||||
'abort')
|
||||
|
||||
if cfg.dump_hex and cfg.dump_hex != '-':
|
||||
match [op for op in opt_ops if getattr(cfg, op) is not None]:
|
||||
case []:
|
||||
op = 'proxy_send' if cfg.tx_proxy else 'rpc_send'
|
||||
op_val = None
|
||||
case [usr_op]:
|
||||
op = ('proxy_test' if cfg.tx_proxy else 'rpc_test') if usr_op == 'test' else usr_op
|
||||
op_val = getattr(cfg, usr_op)
|
||||
case usr_ops:
|
||||
die(1, 'Only one of the following options may be specified: {}'.format(
|
||||
', '.join(fmt_opt(op) for op in usr_ops)))
|
||||
|
||||
if op == 'dump_hex' and op_val != '-':
|
||||
from .fileutil import check_outfile_dir
|
||||
check_outfile_dir(cfg.dump_hex)
|
||||
check_outfile_dir(op_val)
|
||||
|
||||
post_send_op = cfg.status or cfg.receipt
|
||||
post_send_op = op in ('status', 'receipt')
|
||||
asi, tx_range = (None, None)
|
||||
|
||||
def init_autosign(arg):
|
||||
|
|
@ -114,7 +130,7 @@ def init_autosign(arg):
|
|||
from .tx.online import SentTXRange
|
||||
asi = mount_removable_device(cfg)
|
||||
si = Signable.automount_transaction(asi)
|
||||
if cfg.abort:
|
||||
if op == 'abort':
|
||||
si.shred_abortable() # prompts user, then raises exception or exits
|
||||
elif post_send_op and (si.unsent or si.unsigned):
|
||||
die(1, 'Transaction is {}'.format('unsent' if si.unsent else 'unsigned'))
|
||||
|
|
@ -139,7 +155,7 @@ match cfg._args:
|
|||
case _:
|
||||
cfg._usage()
|
||||
|
||||
if not cfg.status:
|
||||
if not post_send_op:
|
||||
from .ui import do_license_msg
|
||||
do_license_msg(cfg)
|
||||
|
||||
|
|
@ -170,7 +186,7 @@ async def process_tx(tx):
|
|||
|
||||
txcfg = Config({'_clone': cfg, 'proto': tx.proto, 'coin': tx.proto.coin})
|
||||
|
||||
if (not post_send_op) and cfg.tx_proxy:
|
||||
if op == 'proxy_send':
|
||||
from .tx.tx_proxy import check_client
|
||||
check_client(txcfg)
|
||||
|
||||
|
|
@ -178,10 +194,11 @@ async def process_tx(tx):
|
|||
from .rpc import rpc_init
|
||||
tx.rpc = await rpc_init(txcfg)
|
||||
|
||||
if op == 'mark_sent':
|
||||
await tx.post_send(asi)
|
||||
return 0
|
||||
|
||||
if not post_send_op:
|
||||
if cfg.mark_sent:
|
||||
await tx.post_send(asi)
|
||||
return 0
|
||||
|
||||
if tx.is_swap or tx.is_refreq:
|
||||
tx.check_swap_network()
|
||||
|
|
@ -198,7 +215,7 @@ async def process_tx(tx):
|
|||
if tx.has_comment and tx.add_comment() and not cfg.autosign:
|
||||
tx.file.write(ask_write_default_yes=True)
|
||||
|
||||
return await tx.send(txcfg, asi, batch=batch)
|
||||
return await tx.send(txcfg, op, op_val, asi, batch=batch)
|
||||
|
||||
async def main():
|
||||
if cfg.autosign and post_send_op:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from ....util2 import format_elapsed_hr
|
|||
|
||||
class Status(TxBase.Status):
|
||||
|
||||
async def display(self, *, idx=''):
|
||||
async def display(self, *, idx=0):
|
||||
|
||||
def do_return(exitval, message):
|
||||
if message:
|
||||
|
|
@ -112,3 +112,5 @@ class Status(TxBase.Status):
|
|||
for txid, mp_entry in zip(r.replacing_txs, d):
|
||||
msg(f' {txid}' + (' in mempool' if 'height' in mp_entry else ''))
|
||||
return do_return(0, '')
|
||||
else:
|
||||
return do_return(3, 'Transaction is in an unknown state')
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from ....util import msg, suf, capfirst
|
|||
|
||||
class Status(TxBase.Status):
|
||||
|
||||
async def display(self, *, idx=''):
|
||||
async def display(self, *, idx=0):
|
||||
|
||||
def do_return(exitval, message):
|
||||
if message:
|
||||
|
|
@ -23,8 +23,9 @@ class Status(TxBase.Status):
|
|||
return exitval
|
||||
|
||||
tx = self.tx
|
||||
coin_txid = '0x' + getattr(tx, f'coin_txid{idx}')
|
||||
tx_desc = 'transaction' + (f' {idx}' if idx else '')
|
||||
sfx = idx + 1 if idx else ''
|
||||
coin_txid = '0x' + getattr(tx, f'coin_txid{sfx}')
|
||||
tx_desc = 'transaction' + (f' {sfx}' if sfx else '')
|
||||
|
||||
async def is_in_mempool():
|
||||
if not 'full_node' in tx.rpc.caps:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from ....tx import status as TxBase
|
|||
|
||||
class Status(TxBase.Status):
|
||||
|
||||
async def display(self, *, idx=''):
|
||||
async def display(self, *, idx=0):
|
||||
|
||||
try:
|
||||
self.tx.rpc.tx_info(self.tx.coin_txid)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from ....xmrwallet import op as xmrwallet_op
|
|||
|
||||
class Status(TxBase.Status):
|
||||
|
||||
async def display(self, *, idx=''):
|
||||
async def display(self, *, idx=0):
|
||||
tx = self.tx
|
||||
if tx.cfg.verbose:
|
||||
msg(f'{tx.proto.coin} TxID: {tx.coin_txid.hl()}')
|
||||
|
|
|
|||
|
|
@ -18,6 +18,13 @@ from ..color import pink, yellow
|
|||
|
||||
from .signed import Signed, AutomountSigned
|
||||
|
||||
def test_txid(txid, txid_chk):
|
||||
if txid == txid_chk:
|
||||
return 0
|
||||
else:
|
||||
ymsg(f'Warning: txid mismatch (after sending) ({txid} != {txid_chk})')
|
||||
return 1
|
||||
|
||||
class SentTXRange(MMGenRange):
|
||||
min_idx = 0
|
||||
max_idx = 1_000_000
|
||||
|
|
@ -26,6 +33,9 @@ class CreatedTXRange(SentTXRange):
|
|||
pass
|
||||
|
||||
class OnlineSigned(Signed):
|
||||
"""
|
||||
send_op_*() methods return an integer exit value to the system
|
||||
"""
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
|
|
@ -78,94 +88,104 @@ class OnlineSigned(Signed):
|
|||
ask_overwrite = False,
|
||||
ask_write = False)
|
||||
|
||||
async def send(self, cfg, asi, batch=False):
|
||||
async def send_op_status(self, cfg, idx, txid, txhex, op_val):
|
||||
if cfg.verbose:
|
||||
await self.post_network_send(txid)
|
||||
else:
|
||||
cfg._util.qmsg(f'{self.proto.coin} TxID: {txid.hl()}')
|
||||
return await self.status.display(idx=idx)
|
||||
|
||||
async def send_op_receipt(self, cfg, idx, txid, txhex, op_val):
|
||||
if res := await self.get_receipt(txid, receipt_only=True):
|
||||
import json
|
||||
Msg(json.dumps(res, indent=4))
|
||||
return 0
|
||||
else:
|
||||
msg(f'Unable to get receipt for TX {txid.hl()}')
|
||||
return 1
|
||||
|
||||
async def send_op_dump_hex(self, cfg, idx, txid, txhex, op_val):
|
||||
from ..fileutil import write_data_to_file
|
||||
sfx = idx + 1 if idx else ''
|
||||
write_data_to_file(
|
||||
cfg,
|
||||
('-' if op_val == '-' else f'{op_val}{sfx}'),
|
||||
txhex + '\n',
|
||||
desc = 'serialized transaction hex data',
|
||||
ask_overwrite = False,
|
||||
ask_tty = False)
|
||||
return 0
|
||||
|
||||
async def send_op_rpc_send(self, cfg, idx, txid, txhex, op_val):
|
||||
msg(f'Sending TX: {txid.hl()}')
|
||||
if cfg.test_suite_bogus_send:
|
||||
msg(f'BOGUS transaction NOT sent: {txid.hl()}')
|
||||
return 0
|
||||
else:
|
||||
if idx:
|
||||
await asyncio.sleep(1)
|
||||
ret = await self.rpc_send(txhex)
|
||||
msg(f'Transaction sent: {txid.hl()}')
|
||||
return test_txid(ret, txid)
|
||||
|
||||
async def send_op_rpc_test(self, cfg, idx, txid, txhex, op_val):
|
||||
if await self.test_sendable(txhex):
|
||||
gmsg('Transaction can be sent')
|
||||
return 0
|
||||
else:
|
||||
ymsg('Transaction cannot be sent')
|
||||
return 1
|
||||
|
||||
async def send_op_proxy_send(self, cfg, idx, txid, txhex, op_val, *, test=False):
|
||||
if idx and not cfg.test_suite:
|
||||
await asyncio.sleep(2)
|
||||
from .tx_proxy import send_tx
|
||||
msg('{} TX: {}'.format('Testing' if test else 'Sending', txid.hl()))
|
||||
ret = send_tx(cfg, txhex, test=test)
|
||||
return 0 if test else test_txid(ret, txid)
|
||||
|
||||
async def send_op_proxy_test(self, cfg, idx, txid, txhex, op_val):
|
||||
return await self.send_op_proxy_send(cfg, idx, txid, txhex, op_val, test=True)
|
||||
|
||||
async def send(self, cfg, op, op_val, asi, batch=False):
|
||||
"""
|
||||
returns an integer exit value to the system
|
||||
"""
|
||||
|
||||
status_exitval = None
|
||||
sent_status = None
|
||||
all_ok = True
|
||||
idxs = ['', '2']
|
||||
sending = op in ('rpc_send', 'proxy_send')
|
||||
idxs = (
|
||||
[int(cfg.txhex_idx) - 1] if cfg.txhex_idx else
|
||||
[0, 1] if getattr(self, 'coin_txid2', None) else
|
||||
[0])
|
||||
ret = []
|
||||
post_network_ret = []
|
||||
|
||||
if cfg.txhex_idx and getattr(self, 'coin_txid2', None):
|
||||
idxs = ['' if cfg.txhex_idx == '1' else cfg.txhex_idx]
|
||||
|
||||
if not (cfg.status or cfg.receipt or cfg.dump_hex or cfg.test):
|
||||
if sending:
|
||||
self.confirm_send(idxs)
|
||||
|
||||
for idx in idxs:
|
||||
if coin_txid := getattr(self, f'coin_txid{idx}', None):
|
||||
txhex = getattr(self, f'serialized{idx}')
|
||||
if cfg.status:
|
||||
if cfg.verbose:
|
||||
await self.post_network_send(coin_txid)
|
||||
else:
|
||||
cfg._util.qmsg(f'{self.proto.coin} TxID: {coin_txid.hl()}')
|
||||
status_exitval = await self.status.display(idx=idx)
|
||||
elif cfg.receipt:
|
||||
if res := await self.get_receipt(coin_txid, receipt_only=True):
|
||||
import json
|
||||
Msg(json.dumps(res, indent=4))
|
||||
else:
|
||||
msg(f'Unable to get receipt for TX {coin_txid.hl()}')
|
||||
elif cfg.dump_hex:
|
||||
from ..fileutil import write_data_to_file
|
||||
write_data_to_file(
|
||||
cfg,
|
||||
cfg.dump_hex + idx,
|
||||
txhex + '\n',
|
||||
desc = 'serialized transaction hex data',
|
||||
ask_overwrite = False,
|
||||
ask_tty = False)
|
||||
elif cfg.tx_proxy:
|
||||
if idx != '' and not cfg.test_suite:
|
||||
await asyncio.sleep(2)
|
||||
from .tx_proxy import send_tx
|
||||
msg('{} TX: {}'.format('Testing' if cfg.test else 'Sending', coin_txid.hl()))
|
||||
if ret := send_tx(cfg, txhex, test=cfg.test):
|
||||
if ret != coin_txid:
|
||||
ymsg(f'Warning: txid mismatch (after sending) ({ret} != {coin_txid})')
|
||||
sent_status = 'confirm_post_send'
|
||||
if cfg.test:
|
||||
break
|
||||
elif cfg.test:
|
||||
if await self.test_sendable(txhex):
|
||||
gmsg('Transaction can be sent')
|
||||
else:
|
||||
ymsg('Transaction cannot be sent')
|
||||
else: # node send
|
||||
msg(f'Sending TX: {coin_txid.hl()}')
|
||||
if cfg.test_suite_bogus_send:
|
||||
msg(f'BOGUS transaction NOT sent: {coin_txid.hl()}')
|
||||
else:
|
||||
if idx != '':
|
||||
await asyncio.sleep(1)
|
||||
ret = await self.rpc_send(txhex)
|
||||
msg(f'Transaction sent: {coin_txid.hl()}')
|
||||
if ret != coin_txid:
|
||||
die('TxIDMismatch', f'txid mismatch (after sending) ({ret} != {coin_txid})')
|
||||
sent_status = 'no_confirm_post_send'
|
||||
sfx = idx + 1 if idx else ''
|
||||
txid = getattr(self, f'coin_txid{sfx}')
|
||||
txhex = getattr(self, f'serialized{sfx}')
|
||||
ret.append(await getattr(self, f'send_op_{op}')(cfg, idx, txid, txhex, op_val))
|
||||
if op == 'proxy_send' and cfg.test:
|
||||
continue
|
||||
if sending and cfg.wait:
|
||||
post_network_ret.append(await self.post_network_send(txid))
|
||||
|
||||
if cfg.wait and sent_status:
|
||||
res = await self.post_network_send(coin_txid)
|
||||
if all_ok:
|
||||
all_ok = res
|
||||
|
||||
if not cfg.txhex_idx and sent_status and all_ok:
|
||||
if sending and all(post_network_ret) and not cfg.txhex_idx:
|
||||
from ..ui import keypress_confirm
|
||||
if sent_status == 'no_confirm_post_send' or not asi or keypress_confirm(
|
||||
if op == 'rpc_send' or not asi or keypress_confirm(
|
||||
cfg, 'Mark transaction as sent on removable device?'):
|
||||
await self.post_send(asi)
|
||||
|
||||
if status_exitval is not None:
|
||||
if cfg.verbose:
|
||||
if batch:
|
||||
self.info.view(pause=False, terse=True)
|
||||
else:
|
||||
self.info.view_with_prompt('View transaction details?', pause=False)
|
||||
return status_exitval
|
||||
return 0
|
||||
if op == 'status' and cfg.verbose:
|
||||
if batch:
|
||||
self.info.view(pause=False, terse=True)
|
||||
else:
|
||||
self.info.view_with_prompt('View transaction details?', pause=False)
|
||||
|
||||
return max(ret)
|
||||
|
||||
class AutomountOnlineSigned(AutomountSigned, OnlineSigned):
|
||||
pass
|
||||
|
|
|
|||
Loading…
Reference in a new issue