offline transaction signing with automount for BTC, BCH, LTC and ETH/ERC20
Previously supported only for XMR, offline transaction autosigning with no
filename arguments and automatic mounting/unmounting of the removable device
on the online machine is now available for all coins MMGen Wallet supports
transacting with. To activate, invoke ‘mmgen-txcreate’ and ‘mmgen-txsend’
with the --autosign option.
Be aware that transactions must be created, signed and sent one at a time when
using this feature. For bulk transaction signing, you must use the old manual
mounting method.
Example create-sign-send workflow for BTC:
$ mmgen-txcreate --autosign bc1qxmymxf8p5ckvlxkmkwgw8ap5t2xuaffmrpexap,0.00123 B
(remove device - insert offline - wait for signing - remove - insert online)
$ mmgen-txsend --autosign
Unsigned or unsent transactions may be aborted as follows:
$ mmgen-txsend --abort
And sent RBF transactions may be fee-bumped:
$ mmgen-txbump --autosign
You can check the status of the current transaction, whether sent or unsent,
with the following command:
$ mmgen-txsend --status
That’s all there is to it!
Testing (add the -e option to see script output):
$ test/cmdtest.py autosign_automount
$ test/cmdtest.py --coin=eth autosign_eth
This commit is contained in:
parent
a33dea177d
commit
1c5c3319d4
27 changed files with 719 additions and 45 deletions
|
|
@ -28,6 +28,7 @@ class Signable:
|
|||
|
||||
non_xmr_signables = (
|
||||
'transaction',
|
||||
'automount_transaction',
|
||||
'message')
|
||||
|
||||
xmr_signables = ( # order is important!
|
||||
|
|
@ -62,6 +63,13 @@ class Signable:
|
|||
def unsubmitted(self):
|
||||
return self._unprocessed( '_unsubmitted', self.sigext, self.subext )
|
||||
|
||||
@property
|
||||
def unsubmitted_raw(self):
|
||||
return self._unprocessed( '_unsubmitted_raw', self.rawext, self.subext )
|
||||
|
||||
unsent = unsubmitted
|
||||
unsent_raw = unsubmitted_raw
|
||||
|
||||
def _unprocessed(self,attrname,rawext,sigext):
|
||||
if not hasattr(self,attrname):
|
||||
dirlist = sorted(self.dir.iterdir())
|
||||
|
|
@ -91,20 +99,45 @@ class Signable:
|
|||
e = f'in ‘{getattr(self.parent, self.dir_name)}’' if show_dir else 'on removable device',
|
||||
))
|
||||
|
||||
def check_create_ok(self):
|
||||
if len(self.unsigned):
|
||||
self.die_wrong_num_txs('unsigned', msg='Cannot create transaction')
|
||||
|
||||
def get_unsubmitted(self, tx_type='unsubmitted'):
|
||||
if len(self.unsubmitted) == 1:
|
||||
return self.unsubmitted[0]
|
||||
else:
|
||||
self.die_wrong_num_txs(tx_type)
|
||||
|
||||
def get_unsent(self):
|
||||
return self.get_unsubmitted('unsent')
|
||||
|
||||
def get_submitted(self):
|
||||
if len(self.submitted) == 0:
|
||||
self.die_wrong_num_txs('submitted')
|
||||
else:
|
||||
return self.submitted
|
||||
|
||||
def get_abortable(self):
|
||||
if len(self.unsent_raw) != 1:
|
||||
self.die_wrong_num_txs('unsent_raw', desc='unsent')
|
||||
if len(self.unsent) > 1:
|
||||
self.die_wrong_num_txs('unsent')
|
||||
if self.unsent:
|
||||
if self.unsent[0].stem != self.unsent_raw[0].stem:
|
||||
die(1, f'{self.unsent[0]}, {self.unsent_raw[0]}: file mismatch')
|
||||
return self.unsent_raw + self.unsent
|
||||
|
||||
async def get_last_created(self):
|
||||
from .tx import CompletedTX
|
||||
ext = '.' + Signable.automount_transaction.subext
|
||||
files = [f for f in self.dir.iterdir() if f.name.endswith(ext)]
|
||||
return sorted(
|
||||
[await CompletedTX(cfg=self.cfg, filename=str(txfile), quiet_open=True) for txfile in files],
|
||||
key = lambda x: x.timestamp)[-1]
|
||||
|
||||
class transaction(base):
|
||||
desc = 'transaction'
|
||||
desc = 'non-automount transaction'
|
||||
rawext = 'rawtx'
|
||||
sigext = 'sigtx'
|
||||
dir_name = 'tx_dir'
|
||||
|
|
@ -112,7 +145,10 @@ class Signable:
|
|||
|
||||
async def sign(self,f):
|
||||
from .tx import UnsignedTX
|
||||
tx1 = UnsignedTX( cfg=self.cfg, filename=f )
|
||||
tx1 = UnsignedTX(
|
||||
cfg = self.cfg,
|
||||
filename = f,
|
||||
automount = self.name=='automount_transaction')
|
||||
if tx1.proto.sign_mode == 'daemon':
|
||||
from .rpc import rpc_init
|
||||
tx1.rpc = await rpc_init( self.cfg, tx1.proto, ignore_wallet=True )
|
||||
|
|
@ -168,6 +204,14 @@ class Signable:
|
|||
for f in bad_files:
|
||||
yield red(f.name)
|
||||
|
||||
class automount_transaction(transaction):
|
||||
desc = 'automount transaction'
|
||||
dir_name = 'txauto_dir'
|
||||
rawext = 'arawtx'
|
||||
sigext = 'asigtx'
|
||||
subext = 'asubtx'
|
||||
multiple_ok = False
|
||||
|
||||
class xmr_signable(transaction): # mixin class
|
||||
|
||||
def need_daemon_restart(self,m,new_idx):
|
||||
|
|
@ -278,6 +322,7 @@ class Autosign:
|
|||
|
||||
non_xmr_dirs = {
|
||||
'tx_dir': 'tx',
|
||||
'txauto_dir': 'txauto',
|
||||
'msg_dir': 'msg',
|
||||
}
|
||||
xmr_dirs = {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
14.1.dev14
|
||||
14.1.dev15
|
||||
|
|
|
|||
|
|
@ -87,9 +87,7 @@ the status LED indicates whether the program is busy or in standby mode, i.e.
|
|||
ready for device insertion or removal.
|
||||
|
||||
The removable device must have a partition labeled MMGEN_TX with a user-
|
||||
writable root directory and a directory named ‘/tx’, where unsigned MMGen
|
||||
transactions are placed. Optionally, the directory ‘/msg’ may be created
|
||||
and unsigned message files produced by ‘mmgen-msg’ placed there.
|
||||
writable root directory.
|
||||
|
||||
On both the signing and online machines the mountpoint ‘{asi.mountpoint}’
|
||||
(as currently configured) must exist and ‘/etc/fstab’ must contain the
|
||||
|
|
|
|||
|
|
@ -33,10 +33,17 @@ opts_data = {
|
|||
creating a new transaction, and optionally sign and send the
|
||||
new transaction
|
||||
""",
|
||||
'usage': f'[opts] <{gc.proj_name} TX file> [seed source] ...',
|
||||
'usage': f'[opts] [{gc.proj_name} TX file] [seed source] ...',
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
--, --longhelp Print help message for long options (common options)
|
||||
-a, --autosign Bump the most recent transaction created and sent with
|
||||
the --autosign option. The removable device is mounted
|
||||
and unmounted automatically. The transaction file
|
||||
argument must be omitted. Note that only sent trans-
|
||||
actions may be bumped with this option. To redo an
|
||||
unsent --autosign transaction, first delete it using
|
||||
‘mmgen-txsend --abort’ and then create a new one
|
||||
-b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for
|
||||
brainwallet input
|
||||
-c, --comment-file= f Source the transaction's comment from file 'f'
|
||||
|
|
@ -103,10 +110,10 @@ FMT CODES:
|
|||
|
||||
cfg = Config(opts_data=opts_data)
|
||||
|
||||
tx_file = cfg._args.pop(0)
|
||||
|
||||
from .fileutil import check_infile
|
||||
check_infile(tx_file)
|
||||
if not cfg.autosign:
|
||||
tx_file = cfg._args.pop(0)
|
||||
from .fileutil import check_infile
|
||||
check_infile(tx_file)
|
||||
|
||||
from .tx import CompletedTX, BumpTX, UnsignedTX, OnlineSignedTX
|
||||
from .tx.sign import txsign,get_seed_files,get_keyaddrlist,get_keylist
|
||||
|
|
@ -120,20 +127,37 @@ silent = cfg.yes and cfg.fee is not None and cfg.output_to_reduce is not None
|
|||
|
||||
async def main():
|
||||
|
||||
orig_tx = await CompletedTX(cfg=cfg,filename=tx_file)
|
||||
if cfg.autosign:
|
||||
from .tx.util import init_removable_device
|
||||
from .autosign import Signable
|
||||
asi = init_removable_device(cfg)
|
||||
asi.do_mount()
|
||||
si = Signable.automount_transaction(asi)
|
||||
if si.unsigned or si.unsent:
|
||||
state = 'unsigned' if si.unsigned else 'unsent'
|
||||
die(1,
|
||||
'Only sent transactions can be bumped with --autosign. Instead of bumping\n'
|
||||
f'your {state} transaction, abort it with ‘mmgen-txsend --abort’ and create\n'
|
||||
'a new one.')
|
||||
orig_tx = await si.get_last_created()
|
||||
kal = kl = sign_and_send = None
|
||||
else:
|
||||
orig_tx = await CompletedTX(cfg=cfg, filename=tx_file)
|
||||
|
||||
if not silent:
|
||||
msg(green('ORIGINAL TRANSACTION'))
|
||||
msg(orig_tx.info.format(terse=True))
|
||||
|
||||
kal = get_keyaddrlist(cfg,orig_tx.proto)
|
||||
kl = get_keylist(cfg)
|
||||
sign_and_send = bool(seed_files or kl or kal)
|
||||
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,
|
||||
twctl = await TwCtl(cfg,orig_tx.proto) if orig_tx.proto.tokensym else None )
|
||||
|
||||
|
|
@ -181,6 +205,7 @@ async def main():
|
|||
die(2,'Transaction could not be signed')
|
||||
else:
|
||||
tx.file.write(
|
||||
outdir = asi.txauto_dir if cfg.autosign else None,
|
||||
ask_write = not cfg.yes,
|
||||
ask_write_default_yes = False,
|
||||
ask_overwrite = not cfg.yes)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ opts_data = {
|
|||
'options': """
|
||||
-h, --help Print this help message
|
||||
--, --longhelp Print help message for long options (common options)
|
||||
-a, --autosign Create a transaction for offline autosigning (see
|
||||
‘mmgen-autosign’). The removable device is mounted and
|
||||
unmounted automatically
|
||||
-A, --fee-adjust= f Adjust transaction fee by factor 'f' (see below)
|
||||
-B, --no-blank Don't blank screen before displaying unspent outputs
|
||||
-c, --comment-file=f Source the transaction's comment from file 'f'
|
||||
|
|
@ -83,6 +86,13 @@ cfg = Config(opts_data=opts_data)
|
|||
|
||||
async def main():
|
||||
|
||||
if cfg.autosign:
|
||||
from .tx.util import init_removable_device
|
||||
from .autosign import Signable
|
||||
asi = init_removable_device(cfg)
|
||||
asi.do_mount()
|
||||
Signable.automount_transaction(asi).check_create_ok()
|
||||
|
||||
from .tx import NewTX
|
||||
tx1 = await NewTX(cfg=cfg,proto=cfg._proto)
|
||||
|
||||
|
|
@ -95,6 +105,7 @@ async def main():
|
|||
do_info = cfg.info )
|
||||
|
||||
tx2.file.write(
|
||||
outdir = asi.txauto_dir if cfg.autosign else None,
|
||||
ask_write = not cfg.yes,
|
||||
ask_overwrite = not cfg.yes,
|
||||
ask_write_default_yes = False)
|
||||
|
|
|
|||
|
|
@ -23,19 +23,31 @@ mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network
|
|||
import sys
|
||||
|
||||
from .cfg import gc,Config
|
||||
from .util import async_run
|
||||
from .util import async_run, msg, suf, die, fmt_list
|
||||
from .fileutil import shred_file
|
||||
|
||||
opts_data = {
|
||||
'sets': [('yes', True, 'quiet', True)],
|
||||
'sets': [
|
||||
('yes', True, 'quiet', True),
|
||||
('abort', True, 'autosign', True),
|
||||
],
|
||||
'text': {
|
||||
'desc': f'Send a signed {gc.proj_name} cryptocoin transaction',
|
||||
'usage': '[opts] <signed transaction file>',
|
||||
'usage': '[opts] [signed transaction file]',
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
--, --longhelp Print help message for long options (common options)
|
||||
-a, --autosign Send an autosigned transaction created by ‘mmgen-txcreate
|
||||
--autosign’. The removable device is mounted and unmounted
|
||||
automatically. The transaction file argument must be omitted
|
||||
when using this option
|
||||
-A, --abort Abort an unsent transaction created by ‘mmgen-txcreate
|
||||
--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
|
||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||
-s, --status Get status of a sent transaction
|
||||
-s, --status Get status of a sent transaction (or the current transaction,
|
||||
whether sent or unsent, when used with --autosign)
|
||||
-v, --verbose Be more verbose
|
||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||
"""
|
||||
|
|
@ -44,10 +56,41 @@ opts_data = {
|
|||
|
||||
cfg = Config(opts_data=opts_data)
|
||||
|
||||
if cfg.autosign and cfg.outdir:
|
||||
die(1, '--outdir cannot be used in combination with --autosign')
|
||||
|
||||
if len(cfg._args) == 1:
|
||||
infile = cfg._args[0]
|
||||
from .fileutil import check_infile
|
||||
check_infile(infile)
|
||||
elif not cfg._args and cfg.autosign:
|
||||
from .tx.util import init_removable_device
|
||||
from .autosign import Signable
|
||||
asi = init_removable_device(cfg)
|
||||
asi.do_mount()
|
||||
si = Signable.automount_transaction(asi)
|
||||
if cfg.abort:
|
||||
files = si.get_abortable() # raises AutosignTXError if no unsent TXs available
|
||||
from .ui import keypress_confirm
|
||||
if keypress_confirm(
|
||||
cfg,
|
||||
'The following file{} will be securely deleted:\n{}\nOK?'.format(
|
||||
suf(files),
|
||||
fmt_list(map(str, files), fmt='col', indent=' '))):
|
||||
for f in files:
|
||||
msg(f'Shredding file ‘{f}’')
|
||||
shred_file(f)
|
||||
sys.exit(0)
|
||||
else:
|
||||
die(1, 'Exiting at user request')
|
||||
elif cfg.status:
|
||||
if si.unsent:
|
||||
die(1, 'Transaction is unsent')
|
||||
if si.unsigned:
|
||||
die(1, 'Transaction is unsigned')
|
||||
else:
|
||||
infile = si.get_unsent()
|
||||
cfg._util.qmsg(f'Got signed transaction file ‘{infile}’')
|
||||
else:
|
||||
cfg._opts.usage()
|
||||
|
||||
|
|
@ -59,10 +102,14 @@ async def main():
|
|||
|
||||
from .tx import OnlineSignedTX, SentTX
|
||||
|
||||
tx = await OnlineSignedTX(
|
||||
cfg = cfg,
|
||||
filename = infile,
|
||||
quiet_open = True)
|
||||
if cfg.status and cfg.autosign:
|
||||
tx = await si.get_last_created()
|
||||
else:
|
||||
tx = await OnlineSignedTX(
|
||||
cfg = cfg,
|
||||
filename = infile,
|
||||
automount = cfg.autosign,
|
||||
quiet_open = True)
|
||||
|
||||
from .rpc import rpc_init
|
||||
tx.rpc = await rpc_init(cfg,tx.proto)
|
||||
|
|
@ -78,11 +125,13 @@ async def main():
|
|||
if not cfg.yes:
|
||||
tx.info.view_with_prompt('View transaction details?')
|
||||
if tx.add_comment(): # edits an existing comment, returns true if changed
|
||||
tx.file.write(ask_write_default_yes=True)
|
||||
if not cfg.autosign:
|
||||
tx.file.write(ask_write_default_yes=True)
|
||||
|
||||
if await tx.send():
|
||||
tx2 = await SentTX(cfg=cfg, data=tx.__dict__)
|
||||
tx2 = await SentTX(cfg=cfg, data=tx.__dict__, automount=cfg.autosign)
|
||||
tx2.file.write(
|
||||
outdir = asi.txauto_dir if cfg.autosign else None,
|
||||
ask_overwrite = False,
|
||||
ask_write = False)
|
||||
tx2.print_contract_addr()
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from ....tx import bump as TxBase
|
|||
from ....util import msg
|
||||
from .new import New
|
||||
from .completed import Completed
|
||||
from .unsigned import AutomountUnsigned
|
||||
|
||||
class Bump(Completed,New,TxBase.Bump):
|
||||
desc = 'fee-bumped transaction'
|
||||
|
|
@ -52,3 +53,8 @@ class Bump(Completed,New,TxBase.Bump):
|
|||
c = self.coin ))
|
||||
return False
|
||||
return ret
|
||||
|
||||
class AutomountBump(Bump):
|
||||
desc = 'unsigned fee-bumped automount transaction'
|
||||
ext = AutomountUnsigned.ext
|
||||
automount = AutomountUnsigned.automount
|
||||
|
|
|
|||
|
|
@ -77,3 +77,9 @@ class OnlineSigned(Signed,TxBase.OnlineSigned):
|
|||
|
||||
class Sent(TxBase.Sent, OnlineSigned):
|
||||
pass
|
||||
|
||||
class AutomountOnlineSigned(TxBase.AutomountOnlineSigned, OnlineSigned):
|
||||
pass
|
||||
|
||||
class AutomountSent(TxBase.AutomountSent, AutomountOnlineSigned):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -30,3 +30,6 @@ class Signed(Completed,TxBase.Signed):
|
|||
Your transaction fee estimates will be inaccurate
|
||||
Please re-create and re-sign the transaction using the option --vsize-adj={1/ratio:1.2f}
|
||||
""").strip())
|
||||
|
||||
class AutomountSigned(TxBase.AutomountSigned, Signed):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class Unsigned(Completed,TxBase.Unsigned):
|
|||
try:
|
||||
self.update_serialized(ret['hex'])
|
||||
from ....tx import SignedTX
|
||||
new = await SignedTX(cfg=self.cfg,data=self.__dict__)
|
||||
new = await SignedTX(cfg=self.cfg, data=self.__dict__, automount=self.automount)
|
||||
tx_decoded = await self.rpc.call( 'decoderawtransaction', ret['hex'] )
|
||||
new.compare_size_and_estimated_size(tx_decoded)
|
||||
new.coin_txid = CoinTxID(self.deserialized.txid)
|
||||
|
|
@ -81,3 +81,6 @@ class Unsigned(Completed,TxBase.Unsigned):
|
|||
import sys,traceback
|
||||
ymsg( '\n' + ''.join(traceback.format_exception(*sys.exc_info())) )
|
||||
return False
|
||||
|
||||
class AutomountUnsigned(TxBase.AutomountUnsigned, Unsigned):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -33,3 +33,9 @@ class Bump(Completed,New,TxBase.Bump):
|
|||
|
||||
class TokenBump(TokenCompleted,TokenNew,Bump):
|
||||
desc = 'fee-bumped transaction'
|
||||
|
||||
class AutomountBump(Bump):
|
||||
pass
|
||||
|
||||
class TokenAutomountBump(TokenBump):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -77,3 +77,15 @@ class Sent(TxBase.Sent, OnlineSigned):
|
|||
|
||||
class TokenSent(TxBase.Sent, TokenOnlineSigned):
|
||||
pass
|
||||
|
||||
class AutomountOnlineSigned(TxBase.AutomountOnlineSigned, OnlineSigned):
|
||||
pass
|
||||
|
||||
class AutomountSent(TxBase.AutomountSent, AutomountOnlineSigned):
|
||||
pass
|
||||
|
||||
class TokenAutomountOnlineSigned(TxBase.AutomountOnlineSigned, TokenOnlineSigned):
|
||||
pass
|
||||
|
||||
class TokenAutomountSent(TxBase.AutomountSent, TokenAutomountOnlineSigned):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -54,3 +54,9 @@ class TokenSigned(TokenCompleted,Signed):
|
|||
def parse_txfile_serialized_data(self):
|
||||
raise NotImplementedError(
|
||||
'Signed transaction files cannot be parsed offline, because tracking wallet is required!')
|
||||
|
||||
class AutomountSigned(TxBase.AutomountSigned, Signed):
|
||||
pass
|
||||
|
||||
class TokenAutomountSigned(TxBase.AutomountSigned, TokenSigned):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ class Unsigned(Completed,TxBase.Unsigned):
|
|||
await self.do_sign(keys[0].sec.wif)
|
||||
msg('OK')
|
||||
from ....tx import SignedTX
|
||||
return await SignedTX(cfg=self.cfg,data=self.__dict__)
|
||||
return await SignedTX(cfg=self.cfg, data=self.__dict__, automount=self.automount)
|
||||
except Exception as e:
|
||||
msg(f'{e}: transaction signing failed!')
|
||||
return False
|
||||
|
|
@ -107,3 +107,9 @@ class TokenUnsigned(TokenCompleted,Unsigned):
|
|||
gasPrice = o['gasPrice'],
|
||||
nonce = o['nonce'])
|
||||
(self.serialized,self.coin_txid) = await t.txsign(tx_in,wif,o['from'],chain_id=o['chainId'])
|
||||
|
||||
class AutomountUnsigned(TxBase.AutomountUnsigned, Unsigned):
|
||||
pass
|
||||
|
||||
class TokenAutomountUnsigned(TxBase.AutomountUnsigned, TokenUnsigned):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -52,6 +52,11 @@ def _get_cls_info(clsname,modname,args,kwargs):
|
|||
|
||||
kwargs['proto'] = proto
|
||||
|
||||
if 'automount' in kwargs:
|
||||
if kwargs['automount']:
|
||||
clsname = 'Automount' + clsname
|
||||
del kwargs['automount']
|
||||
|
||||
return ( kwargs['cfg'], proto, clsname, modname, kwargs )
|
||||
|
||||
|
||||
|
|
@ -74,7 +79,9 @@ async def _get_obj_async( _clsname, _modname, *args, **kwargs ):
|
|||
if proto and proto.tokensym and clsname in (
|
||||
'New',
|
||||
'OnlineSigned',
|
||||
'Sent'):
|
||||
'AutomountOnlineSigned',
|
||||
'Sent',
|
||||
'AutomountSent'):
|
||||
from ..tw.ctl import TwCtl
|
||||
kwargs['twctl'] = await TwCtl(cfg,proto)
|
||||
|
||||
|
|
|
|||
|
|
@ -54,15 +54,17 @@ class Completed(Base):
|
|||
"""
|
||||
see twctl:import_token()
|
||||
"""
|
||||
from .unsigned import Unsigned
|
||||
from .online import Sent
|
||||
for cls in (Unsigned, Sent):
|
||||
from .unsigned import Unsigned, AutomountUnsigned
|
||||
from .online import Sent, AutomountSent
|
||||
for cls in (Unsigned, AutomountUnsigned, Sent, AutomountSent):
|
||||
if ext == getattr(cls, 'ext'):
|
||||
return cls
|
||||
|
||||
if proto.tokensym:
|
||||
from .online import OnlineSigned as Signed
|
||||
from .online import AutomountOnlineSigned as AutomountSigned
|
||||
else:
|
||||
from .signed import Signed
|
||||
if ext == Signed.ext:
|
||||
return Signed
|
||||
from .signed import Signed, AutomountSigned
|
||||
for cls in (Signed, AutomountSigned):
|
||||
if ext == getattr(cls, 'ext'):
|
||||
return cls
|
||||
|
|
|
|||
|
|
@ -436,7 +436,7 @@ class New(Base):
|
|||
self.cfg._util.qmsg('Transaction successfully created')
|
||||
|
||||
from . import UnsignedTX
|
||||
new = UnsignedTX(cfg=self.cfg,data=self.__dict__)
|
||||
new = UnsignedTX(cfg=self.cfg, data=self.__dict__, automount=self.cfg.autosign)
|
||||
|
||||
if not self.cfg.yes:
|
||||
new.info.view_with_prompt('View transaction details?')
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
tx.online: online signed transaction class
|
||||
"""
|
||||
|
||||
from .signed import Signed
|
||||
from .signed import Signed, AutomountSigned
|
||||
|
||||
class OnlineSigned(Signed):
|
||||
|
||||
|
|
@ -31,6 +31,13 @@ class OnlineSigned(Signed):
|
|||
expect = 'YES' if self.cfg.quiet or self.cfg.yes else 'YES, I REALLY WANT TO DO THIS' )
|
||||
msg('Sending transaction')
|
||||
|
||||
class AutomountOnlineSigned(AutomountSigned, OnlineSigned):
|
||||
pass
|
||||
|
||||
class Sent(OnlineSigned):
|
||||
desc = 'sent transaction'
|
||||
ext = 'subtx'
|
||||
|
||||
class AutomountSent(AutomountOnlineSigned):
|
||||
desc = 'sent automount transaction'
|
||||
ext = 'asubtx'
|
||||
|
|
|
|||
|
|
@ -112,8 +112,8 @@ def _pop_matching_fns(args,cmplist): # strips found args
|
|||
return list(reversed([args.pop(args.index(a)) for a in reversed(args) if get_extension(a) in cmplist]))
|
||||
|
||||
def get_tx_files(cfg, args):
|
||||
from .unsigned import Unsigned
|
||||
ret = _pop_matching_fns(args,[Unsigned.ext])
|
||||
from .unsigned import Unsigned, AutomountUnsigned
|
||||
ret = _pop_matching_fns(args, [(AutomountUnsigned if cfg.autosign else Unsigned).ext])
|
||||
if not ret:
|
||||
die(1,'You must specify a raw transaction file!')
|
||||
return ret
|
||||
|
|
|
|||
|
|
@ -18,3 +18,7 @@ class Signed(Completed):
|
|||
desc = 'signed transaction'
|
||||
ext = 'sigtx'
|
||||
signed = True
|
||||
|
||||
class AutomountSigned(Signed):
|
||||
desc = 'signed automount transaction'
|
||||
ext = 'asigtx'
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from ..util import remove_dups
|
|||
class Unsigned(Completed):
|
||||
desc = 'unsigned transaction'
|
||||
ext = 'rawtx'
|
||||
automount = False
|
||||
|
||||
def delete_attrs(self,desc,attr):
|
||||
for e in getattr(self,desc):
|
||||
|
|
@ -28,3 +29,8 @@ class Unsigned(Completed):
|
|||
return remove_dups(
|
||||
(e.mmid.sid for e in getattr(self,desc) if e.mmid),
|
||||
quiet = True )
|
||||
|
||||
class AutomountUnsigned(Unsigned):
|
||||
desc = 'unsigned automount transaction'
|
||||
ext = 'arawtx'
|
||||
automount = True
|
||||
|
|
|
|||
35
mmgen/tx/util.py
Executable file
35
mmgen/tx/util.py
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
|
||||
# Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen-wallet
|
||||
# https://gitlab.com/mmgen/mmgen-wallet
|
||||
|
||||
"""
|
||||
tx.util: transaction utilities
|
||||
"""
|
||||
|
||||
def get_autosign_obj(cfg):
|
||||
from ..cfg import Config
|
||||
from ..autosign import Autosign
|
||||
return Autosign(
|
||||
Config({
|
||||
'mountpoint': cfg.autosign_mountpoint,
|
||||
'test_suite': cfg.test_suite,
|
||||
'test_suite_root_pfx': cfg.test_suite_root_pfx,
|
||||
'coins': cfg.coin,
|
||||
'online': True, # used only in online environment (txcreate, txsend)
|
||||
})
|
||||
)
|
||||
|
||||
def init_removable_device(cfg):
|
||||
asi = get_autosign_obj(cfg)
|
||||
if not asi.get_insert_status():
|
||||
from ..util import die
|
||||
die(1, 'Removable device not present!')
|
||||
import atexit
|
||||
atexit.register(lambda: asi.do_umount())
|
||||
return asi
|
||||
|
|
@ -34,6 +34,8 @@ cmd_groups_dfl = {
|
|||
'output': ('CmdTestOutput',{'modname':'misc','full_data':True}),
|
||||
'autosign_clean': ('CmdTestAutosignClean', {'modname':'autosign'}),
|
||||
'autosign': ('CmdTestAutosign',{}),
|
||||
'autosign_automount': ('CmdTestAutosignAutomount', {'modname':'automount'}),
|
||||
'autosign_eth': ('CmdTestAutosignETH', {'modname':'automount_eth'}),
|
||||
'regtest': ('CmdTestRegtest',{}),
|
||||
# 'chainsplit': ('CmdTestChainsplit',{}),
|
||||
'ethdev': ('CmdTestEthdev',{}),
|
||||
|
|
@ -221,6 +223,8 @@ cfgs = { # addr_idx_lists (except 31,32,33,34) must contain exactly 8 addresses
|
|||
'39': {}, # xmr_autosign
|
||||
'40': {}, # cfgfile
|
||||
'41': {}, # opts
|
||||
'49': {}, # autosign_automount
|
||||
'59': {}, # autosign_eth
|
||||
'99': {}, # dummy
|
||||
}
|
||||
|
||||
|
|
|
|||
259
test/cmdtest_py_d/ct_automount.py
Executable file
259
test/cmdtest_py_d/ct_automount.py
Executable file
|
|
@ -0,0 +1,259 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
|
||||
# Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen-wallet
|
||||
# https://gitlab.com/mmgen/mmgen-wallet
|
||||
|
||||
"""
|
||||
test.cmdtest_py_d.ct_automount: autosigning with automount tests for the cmdtest.py test suite
|
||||
"""
|
||||
import os, time
|
||||
from pathlib import Path
|
||||
|
||||
from .ct_autosign import CmdTestAutosignThreaded
|
||||
from .ct_regtest import CmdTestRegtest, rt_pw
|
||||
from .common import get_file_with_ext
|
||||
from ..include.common import cfg
|
||||
|
||||
class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
|
||||
'automounted transacting operations via regtest mode'
|
||||
|
||||
networks = ('btc', 'bch', 'ltc')
|
||||
tmpdir_nums = [49]
|
||||
|
||||
rtFundAmt = None # pylint
|
||||
rt_data = {
|
||||
'rtFundAmt': {'btc':'500', 'bch':'500', 'ltc':'5500'},
|
||||
}
|
||||
|
||||
cmd_group = (
|
||||
('setup', 'regtest mode setup'),
|
||||
('walletgen_alice', 'wallet generation (Alice)'),
|
||||
('addrgen_alice', 'address generation (Alice)'),
|
||||
('addrimport_alice', 'importing Alice’s addresses'),
|
||||
('fund_alice', 'funding Alice’s wallet'),
|
||||
('generate', 'mining a block'),
|
||||
('alice_bal1', 'checking Alice’s balance'),
|
||||
('alice_txcreate1', 'creating a transaction'),
|
||||
('alice_txcreate_bad_have_unsigned', 'creating the transaction again (error)'),
|
||||
('copy_wallet', 'copying Alice’s wallet'),
|
||||
('alice_run_autosign_setup', 'running ‘autosign setup’ (with default wallet)'),
|
||||
('autosign_start_thread', 'starting autosign wait loop'),
|
||||
('alice_txstatus1', 'getting transaction status (unsigned)'),
|
||||
('alice_txstatus2', 'getting transaction status (unsent)'),
|
||||
('alice_txsend1', 'sending a transaction, editing comment'),
|
||||
('alice_txstatus3', 'getting transaction status (in mempool)'),
|
||||
('alice_txsend_bad_no_unsent', 'sending the transaction again (error)'),
|
||||
('generate', 'mining a block'),
|
||||
('alice_txstatus4', 'getting transaction status (one confirmation)'),
|
||||
('alice_txcreate2', 'creating a transaction'),
|
||||
('alice_txsend_abort1', 'aborting the transaction (raw only)'),
|
||||
('alice_txsend_abort2', 'aborting the transaction again (error)'),
|
||||
('alice_txcreate3', 'creating a transaction'),
|
||||
('alice_txsend_abort3', 'aborting the transaction (user exit)'),
|
||||
('alice_txsend_abort4', 'aborting the transaction (raw + signed)'),
|
||||
('alice_txsend_abort5', 'aborting the transaction again (error)'),
|
||||
('generate', 'mining a block'),
|
||||
('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_txbump3', 'bumping the transaction'),
|
||||
('alice_txsend3', 'sending the bumped transaction'),
|
||||
('autosign_kill_thread', 'stopping autosign wait loop'),
|
||||
('stop', 'stopping regtest daemon'),
|
||||
('txview', 'viewing transactions'),
|
||||
)
|
||||
|
||||
def __init__(self, trunner, cfgs, spawn):
|
||||
|
||||
self.coins = [cfg.coin.lower()]
|
||||
|
||||
CmdTestAutosignThreaded.__init__(self, trunner, cfgs, spawn)
|
||||
CmdTestRegtest.__init__(self, trunner, cfgs, spawn)
|
||||
|
||||
if trunner == None:
|
||||
return
|
||||
|
||||
self.opts.append('--alice')
|
||||
|
||||
def _alice_txcreate(self, chg_addr, opts=[], exit_val=0):
|
||||
self.insert_device_online()
|
||||
sid = self._user_sid('alice')
|
||||
t = self.spawn(
|
||||
'mmgen-txcreate',
|
||||
opts
|
||||
+ ['--alice', '--autosign']
|
||||
+ [f'{self.burn_addr},1.23456', f'{sid}:{chg_addr}'])
|
||||
if exit_val:
|
||||
t.read()
|
||||
self.remove_device_online()
|
||||
t.req_exit_val = exit_val
|
||||
return t
|
||||
t = self.txcreate_ui_common(
|
||||
t,
|
||||
inputs = '1',
|
||||
interactive_fee = '32s',
|
||||
file_desc = 'Unsigned automount transaction')
|
||||
t.read()
|
||||
self.remove_device_online()
|
||||
return t
|
||||
|
||||
def alice_txcreate1(self):
|
||||
return self._alice_txcreate(chg_addr='C:5')
|
||||
|
||||
def alice_txcreate2(self):
|
||||
return self._alice_txcreate(chg_addr='L:5')
|
||||
|
||||
alice_txcreate3 = alice_txcreate2
|
||||
|
||||
def alice_txcreate4(self):
|
||||
if cfg.coin == 'BCH':
|
||||
return 'skip'
|
||||
return self._alice_txcreate(chg_addr='L:4')
|
||||
|
||||
def _alice_txsend_abort(self, err=False, user_exit=False, del_expect=[]):
|
||||
self.insert_device_online()
|
||||
t = self.spawn('mmgen-txsend', ['--quiet', '--abort'])
|
||||
if err:
|
||||
t.expect('No unsent transactions')
|
||||
t.req_exit_val = 2
|
||||
else:
|
||||
t.expect('(y/N): ', 'n' if user_exit else 'y')
|
||||
if user_exit:
|
||||
t.expect('Exiting at user request')
|
||||
t.req_exit_val = 1
|
||||
else:
|
||||
for pat in del_expect:
|
||||
t.expect(pat, regex=True)
|
||||
self.remove_device_online()
|
||||
return t
|
||||
|
||||
def alice_txsend_abort1(self):
|
||||
return self._alice_txsend_abort(del_expect=['Shredding .*arawtx'])
|
||||
|
||||
def alice_txsend_abort2(self):
|
||||
return self._alice_txsend_abort(err=True)
|
||||
|
||||
def alice_txsend_abort3(self):
|
||||
return self._alice_txsend_abort(user_exit=True)
|
||||
|
||||
def alice_txsend_abort4(self):
|
||||
self._wait_signed('transaction')
|
||||
return self._alice_txsend_abort(del_expect=[r'Shredding .*arawtx', r'Shredding .*asigtx'])
|
||||
|
||||
alice_txsend_abort5 = alice_txsend_abort2
|
||||
|
||||
def alice_txcreate_bad_have_unsigned(self):
|
||||
return self._alice_txcreate(chg_addr='C:5', exit_val=2)
|
||||
|
||||
def copy_wallet(self):
|
||||
self.spawn('', msg_only=True)
|
||||
if cfg.coin == 'BTC':
|
||||
return 'skip_msg'
|
||||
src = Path(self.tr.data_dir, 'regtest', cfg.coin.lower(), 'alice')
|
||||
dest = Path(self.tr.data_dir, 'regtest', 'btc', 'alice')
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
wf = Path(get_file_with_ext(src, 'mmdat')).absolute()
|
||||
link_path = dest / wf.name
|
||||
if not link_path.exists():
|
||||
link_path.symlink_to(wf)
|
||||
return 'ok'
|
||||
|
||||
def alice_run_autosign_setup(self):
|
||||
self.insert_device()
|
||||
t = self.run_setup(mn_type='default', use_dfl_wallet=True, passwd=rt_pw)
|
||||
t.read()
|
||||
self.remove_device()
|
||||
return t
|
||||
|
||||
def alice_txsend1(self):
|
||||
return self._alice_txsend('This one’s worth a comment', no_wait=True)
|
||||
|
||||
def alice_txsend2(self):
|
||||
if cfg.coin == 'BCH':
|
||||
return 'skip'
|
||||
return self._alice_txsend()
|
||||
|
||||
def alice_txsend3(self):
|
||||
if cfg.coin == 'BCH':
|
||||
return 'skip'
|
||||
return self._alice_txsend()
|
||||
|
||||
def _alice_txstatus(self, expect, exit_val=None):
|
||||
self.insert_device_online()
|
||||
t = self.spawn('mmgen-txsend', ['--alice', '--autosign', '--status', '--verbose'])
|
||||
t.expect(expect)
|
||||
self.remove_device_online()
|
||||
if exit_val:
|
||||
t.req_exit_val = exit_val
|
||||
return t
|
||||
|
||||
def alice_txstatus1(self):
|
||||
return self._alice_txstatus('unsigned', 1)
|
||||
|
||||
def alice_txstatus2(self):
|
||||
self._wait_signed('transaction')
|
||||
return self._alice_txstatus('unsent', 1)
|
||||
|
||||
def alice_txstatus3(self):
|
||||
return self._alice_txstatus('in mempool')
|
||||
|
||||
def alice_txstatus4(self):
|
||||
return self._alice_txstatus('1 confirmation')
|
||||
|
||||
def _alice_txsend(self, comment=None, no_wait=False):
|
||||
if not no_wait:
|
||||
self._wait_signed('transaction')
|
||||
self.insert_device_online()
|
||||
t = self.spawn('mmgen-txsend', ['--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.read()
|
||||
self.remove_device_online()
|
||||
return t
|
||||
|
||||
def alice_txsend_bad_no_unsent(self):
|
||||
self.insert_device_online()
|
||||
t = self.spawn('mmgen-txsend', ['--quiet', '--autosign'])
|
||||
t.expect('No unsent transactions')
|
||||
t.read()
|
||||
t.req_exit_val = 2
|
||||
self.remove_device_online()
|
||||
return t
|
||||
|
||||
def _alice_txbump(self, bad_tx_desc=None):
|
||||
if cfg.coin == 'BCH':
|
||||
return 'skip'
|
||||
self.insert_device_online()
|
||||
t = self.spawn('mmgen-txbump', ['--autosign'])
|
||||
if bad_tx_desc:
|
||||
t.expect('Only sent transactions')
|
||||
t.expect(bad_tx_desc)
|
||||
t.req_exit_val = 1
|
||||
else:
|
||||
t.expect(f'to deduct the fee from .* change output\): ', '\n', regex=True)
|
||||
t.expect(r'(Y/n): ', 'y') # output OK?
|
||||
t.expect('transaction fee: ', '200s\n')
|
||||
t.expect(r'(Y/n): ', 'y') # fee OK?
|
||||
t.expect(r'(y/N): ', '\n') # add comment?
|
||||
t.expect(r'(y/N): ', 'y') # save?
|
||||
t.read()
|
||||
self.remove_device_online()
|
||||
return t
|
||||
|
||||
def alice_txbump1(self):
|
||||
return self._alice_txbump(bad_tx_desc='unsigned transaction')
|
||||
|
||||
def alice_txbump2(self):
|
||||
self._wait_signed('transaction')
|
||||
return self._alice_txbump(bad_tx_desc='unsent transaction')
|
||||
|
||||
def alice_txbump3(self):
|
||||
return self._alice_txbump()
|
||||
141
test/cmdtest_py_d/ct_automount_eth.py
Executable file
141
test/cmdtest_py_d/ct_automount_eth.py
Executable file
|
|
@ -0,0 +1,141 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
|
||||
# Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen-wallet
|
||||
# https://gitlab.com/mmgen/mmgen-wallet
|
||||
|
||||
"""
|
||||
test.cmdtest_py_d.ct_automount_eth: Ethereum automount autosigning tests for the cmdtest.py test suite
|
||||
"""
|
||||
import os, re
|
||||
|
||||
from .ct_autosign import CmdTestAutosignThreaded
|
||||
from .ct_ethdev import CmdTestEthdev, parity_devkey_fn
|
||||
from .common import dfl_words_file
|
||||
from ..include.common import cfg
|
||||
|
||||
class CmdTestAutosignETH(CmdTestAutosignThreaded, CmdTestEthdev):
|
||||
'automounted transacting operations for Ethereum via ethdev'
|
||||
|
||||
networks = ('eth', 'etc')
|
||||
tmpdir_nums = [59]
|
||||
|
||||
cmd_group = (
|
||||
('setup', f'dev mode tests for coin {cfg.coin} (start daemon)'),
|
||||
('addrgen', 'generating addresses'),
|
||||
('addrimport', 'importing addresses'),
|
||||
('addrimport_dev_addr', "importing dev faucet address 'Ox00a329c..'"),
|
||||
('fund_dev_address', 'funding the default (Parity dev) address'),
|
||||
('fund_mmgen_address', 'funding an MMGen address'),
|
||||
('create_tx', 'creating a transaction'),
|
||||
('run_autosign_setup', 'running ‘autosign setup’'),
|
||||
('autosign_start_thread', 'starting autosign wait loop'),
|
||||
('send_tx', 'sending the transaction'),
|
||||
('token_compile1', 'compiling ERC20 token #1'),
|
||||
('token_deploy1a', 'deploying ERC20 token #1 (SafeMath)'),
|
||||
('token_deploy1b', 'deploying ERC20 token #1 (Owned)'),
|
||||
('token_deploy1c', 'deploying ERC20 token #1 (Token)'),
|
||||
('tx_status2', 'getting the transaction status'),
|
||||
('token_fund_user', 'transferring token funds from dev to user'),
|
||||
('token_addrgen_addr1', 'generating token addresses'),
|
||||
('token_addrimport_addr1', 'importing token addresses using token address (MM1)'),
|
||||
('token_bal1', f'the {cfg.coin} balance and token balance'),
|
||||
('create_token_tx', 'creating a token transaction'),
|
||||
('send_token_tx', 'sending a token transaction'),
|
||||
('token_bal2', f'the {cfg.coin} balance and token balance'),
|
||||
('autosign_kill_thread', 'stopping autosign wait loop'),
|
||||
('txview', 'viewing transactions'),
|
||||
('stop', 'stopping daemon'),
|
||||
)
|
||||
|
||||
def __init__(self, trunner, cfgs, spawn):
|
||||
|
||||
self.coins = [cfg.coin.lower()]
|
||||
|
||||
CmdTestAutosignThreaded.__init__(self, trunner, cfgs, spawn)
|
||||
CmdTestEthdev.__init__(self, trunner, cfgs, spawn)
|
||||
|
||||
if trunner == None:
|
||||
return
|
||||
|
||||
self.opts.append('--alice')
|
||||
|
||||
def fund_mmgen_address(self):
|
||||
keyfile = os.path.join(self.tmpdir, parity_devkey_fn)
|
||||
t = self.spawn(
|
||||
'mmgen-txdo',
|
||||
['--quiet']
|
||||
+ [f'--keys-from-file={keyfile}']
|
||||
+ ['--fee=40G', '98831F3A:E:1,123.456', dfl_words_file],
|
||||
)
|
||||
t.expect('efresh balance:\b', 'q')
|
||||
t.expect('from: ', '10')
|
||||
t.expect('(Y/n): ', 'y')
|
||||
t.expect('(Y/n): ', 'y')
|
||||
t.expect('(y/N): ', 'n')
|
||||
t.expect('view: ', 'n')
|
||||
t.expect('confirm: ', 'YES')
|
||||
return t
|
||||
|
||||
def create_tx(self):
|
||||
self.insert_device_online()
|
||||
t = self.txcreate(
|
||||
args = ['--autosign', '98831F3A:E:11,54.321'],
|
||||
menu = [],
|
||||
acct = '1')
|
||||
t.read()
|
||||
self.remove_device_online()
|
||||
return t
|
||||
|
||||
def run_autosign_setup(self):
|
||||
self.insert_device()
|
||||
t = self.run_setup(mn_type='bip39', mn_file='test/ref/98831F3A.bip39', use_dfl_wallet=None)
|
||||
t.read()
|
||||
self.remove_device()
|
||||
return t
|
||||
|
||||
def send_tx(self, add_args=[]):
|
||||
self._wait_signed('transaction')
|
||||
self.insert_device_online()
|
||||
t = self.spawn('mmgen-txsend', ['--quiet', '--autosign'] + add_args)
|
||||
t.view_tx('t')
|
||||
t.expect('(y/N): ', 'n')
|
||||
self._do_confirm_send(t, quiet=True)
|
||||
t.written_to_file('Sent automount transaction')
|
||||
self.remove_device_online()
|
||||
return t
|
||||
|
||||
def token_fund_user(self):
|
||||
return self.token_transfer_ops(op='do_transfer', num_tokens=1)
|
||||
|
||||
def token_addrgen_addr1(self):
|
||||
return self.token_addrgen(num_tokens=1)
|
||||
|
||||
def token_bal1(self):
|
||||
return self.token_bal(pat=r':E:11\s+1000\s+54\.321\s+')
|
||||
|
||||
def token_bal2(self):
|
||||
return self.token_bal(pat=r':E:11\s+998.76544\s+54.318\d+\s+.*:E:12\s+1\.23456\s+')
|
||||
|
||||
def token_bal(self, pat):
|
||||
t = self.spawn('mmgen-tool', ['--quiet', '--token=mm1', 'twview', 'wide=1'])
|
||||
text = t.read(strip_color=True)
|
||||
assert re.search(pat, text, re.DOTALL), f'output failed to match regex {pat}'
|
||||
return t
|
||||
|
||||
def create_token_tx(self):
|
||||
self.insert_device_online()
|
||||
t = self.token_txcreate(
|
||||
args = ['--autosign', '98831F3A:E:12,1.23456'],
|
||||
token = 'MM1',
|
||||
file_desc = 'Unsigned automount transaction')
|
||||
t.read()
|
||||
self.remove_device_online()
|
||||
return t
|
||||
|
||||
def send_token_tx(self):
|
||||
return self.send_tx(add_args=['--token=MM1'])
|
||||
|
|
@ -209,6 +209,16 @@ class CmdTestAutosignClean(CmdTestAutosignBase):
|
|||
):
|
||||
(self.asi.tx_dir / fn).touch()
|
||||
|
||||
for fn in (
|
||||
'a.arawtx', 'a.asigtx', 'a.asubtx',
|
||||
'b.arawtx', 'b.asigtx',
|
||||
'c.asubtx',
|
||||
'd.arawtx', 'd.asubtx',
|
||||
'e.arawtx',
|
||||
'f.asigtx', 'f.asubtx',
|
||||
):
|
||||
(self.asi.txauto_dir / fn).touch()
|
||||
|
||||
for fn in (
|
||||
'a.rawmsg.json', 'a.sigmsg.json',
|
||||
'b.rawmsg.json',
|
||||
|
|
@ -270,6 +280,7 @@ class CmdTestAutosignClean(CmdTestAutosignBase):
|
|||
|
||||
chk_non_xmr = """
|
||||
tx: a.sigtx b.sigtx c.rawtx d.sigtx
|
||||
txauto: a.asubtx b.asigtx c.asubtx d.asubtx e.arawtx f.asubtx
|
||||
msg: a.sigmsg.json b.rawmsg.json c.sigmsg.json d.sigmsg.json
|
||||
"""
|
||||
chk_xmr = """
|
||||
|
|
@ -281,10 +292,10 @@ class CmdTestAutosignClean(CmdTestAutosignBase):
|
|||
shred_count = 0
|
||||
|
||||
if not self.asi.xmr_only:
|
||||
for k in ('tx_dir','msg_dir'):
|
||||
for k in ('tx_dir', 'txauto_dir', 'msg_dir'):
|
||||
shutil.rmtree(getattr(self.asi, k))
|
||||
chk += chk_non_xmr.rstrip()
|
||||
shred_count += 4
|
||||
shred_count += 9
|
||||
|
||||
if self.asi.have_xmr:
|
||||
shutil.rmtree(self.asi.xmr_dir)
|
||||
|
|
@ -371,6 +382,21 @@ class CmdTestAutosignThreaded(CmdTestAutosignBase):
|
|||
def do_umount_online(self, *args, **kwargs):
|
||||
return self._mount_ops('asi_online', 'do_umount', *args, **kwargs)
|
||||
|
||||
async def txview(self):
|
||||
self.spawn('', msg_only=True)
|
||||
self.do_mount()
|
||||
src = Path(self.asi.txauto_dir)
|
||||
from mmgen.tx import CompletedTX
|
||||
txs = sorted(
|
||||
[await CompletedTX(cfg=cfg, filename=path, quiet_open=True) for path in sorted(src.iterdir())],
|
||||
key = lambda x: x.timestamp)
|
||||
for tx in txs:
|
||||
imsg(blue(f'\nViewing ‘{tx.infile.name}’:'))
|
||||
out = tx.info.format(terse=True)
|
||||
imsg(indent(out, indent=' '))
|
||||
self.do_umount()
|
||||
return 'ok'
|
||||
|
||||
class CmdTestAutosign(CmdTestAutosignBase):
|
||||
'autosigning transactions for all supported coins'
|
||||
coins = ['btc','bch','ltc','eth']
|
||||
|
|
@ -754,9 +780,9 @@ class CmdTestAutosignLive(CmdTestAutosignBTC):
|
|||
|
||||
def prompt_insert_sign(t):
|
||||
omsg(orange(insert_msg))
|
||||
t.expect(f'{self.tx_count} transactions signed')
|
||||
t.expect(f'{self.tx_count} non-automount transactions signed')
|
||||
if self.bad_tx_count:
|
||||
t.expect(f'{self.bad_tx_count} transactions failed to sign')
|
||||
t.expect(f'{self.bad_tx_count} non-automount transactions failed to sign')
|
||||
t.expect('Waiting')
|
||||
|
||||
if led_opts:
|
||||
|
|
|
|||
|
|
@ -153,8 +153,15 @@ init_tests() {
|
|||
d_etc="operations for Ethereum Classic using devnet"
|
||||
t_etc="parity $cmdtest_py --coin=etc ethdev"
|
||||
|
||||
d_autosign="transaction and message autosigning"
|
||||
t_autosign="- $cmdtest_py autosign autosign_clean"
|
||||
d_autosign="transaction autosigning with automount"
|
||||
t_autosign="
|
||||
- $cmdtest_py autosign autosign_clean autosign_automount
|
||||
- $cmdtest_py --coin=bch autosign_automount
|
||||
s $cmdtest_py --coin=ltc autosign_automount
|
||||
- $cmdtest_py --coin=eth autosign_eth
|
||||
s $cmdtest_py --coin=etc autosign_eth
|
||||
"
|
||||
[ "$FAST" ] && t_autosign_skip='s'
|
||||
|
||||
d_autosign_btc="transaction and message autosigning (Bitcoin only)"
|
||||
t_autosign_btc="- $cmdtest_py autosign_btc"
|
||||
|
|
@ -164,7 +171,7 @@ init_tests() {
|
|||
|
||||
d_btc="overall operations with emulated RPC data (Bitcoin)"
|
||||
t_btc="
|
||||
- $cmdtest_py --exclude regtest,autosign,autosign_clean,ref_altcoin
|
||||
- $cmdtest_py --exclude regtest,autosign,autosign_clean,autosign_automount,ref_altcoin
|
||||
- $cmdtest_py --segwit
|
||||
- $cmdtest_py --segwit-random
|
||||
- $cmdtest_py --bech32
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue