3 Commits 907bdc2bdf ... e69dfbe8f3

Author SHA1 Message Date
  The MMGen Project e69dfbe8f3 XMR compat: transaction status support 2 months ago
  The MMGen Project 8f269f1553 xmrwallet.ops: `relay` -> `daemon` 2 months ago
  The MMGen Project e865170484 whitespace, minor fixes 2 months ago

+ 3 - 3
mmgen/autosign.py

@@ -19,7 +19,7 @@ from subprocess import run, PIPE, DEVNULL
 
 
 from .cfg import Config
 from .cfg import Config
 from .util import msg, msg_r, ymsg, rmsg, gmsg, bmsg, die, suf, fmt, fmt_list, is_int, have_sudo, capfirst
 from .util import msg, msg_r, ymsg, rmsg, gmsg, bmsg, die, suf, fmt, fmt_list, is_int, have_sudo, capfirst
-from .color import yellow, red, orange, brown, blue
+from .color import yellow, red, orange, brown, blue, gray
 from .wallet import Wallet, get_wallet_cls
 from .wallet import Wallet, get_wallet_cls
 from .addrlist import AddrIdxList
 from .addrlist import AddrIdxList
 from .filename import find_file_in_dir
 from .filename import find_file_in_dir
@@ -641,7 +641,7 @@ class Autosign:
 			redir = None if verbose else DEVNULL
 			redir = None if verbose else DEVNULL
 			if run(self.mount_cmd.split(), stderr=redir, stdout=redir).returncode == 0:
 			if run(self.mount_cmd.split(), stderr=redir, stdout=redir).returncode == 0:
 				if not silent:
 				if not silent:
-					msg(f'Mounting ‘{self.mountpoint}’')
+					msg(gray(f'Mounting ‘{self.mountpoint}’'))
 			else:
 			else:
 				die(1, f'Unable to mount device ‘{self.dev_label}’ at ‘{self.mountpoint}’')
 				die(1, f'Unable to mount device ‘{self.dev_label}’ at ‘{self.mountpoint}’')
 
 
@@ -652,7 +652,7 @@ class Autosign:
 		if self.mountpoint.is_mount():
 		if self.mountpoint.is_mount():
 			run(['sync'], check=True)
 			run(['sync'], check=True)
 			if not silent:
 			if not silent:
-				msg(f'Unmounting ‘{self.mountpoint}’')
+				msg(gray(f'Unmounting ‘{self.mountpoint}’'))
 			redir = None if verbose else DEVNULL
 			redir = None if verbose else DEVNULL
 			run(self.umount_cmd.split(), stdout=redir, check=True)
 			run(self.umount_cmd.split(), stdout=redir, check=True)
 		if not silent:
 		if not silent:

+ 5 - 11
mmgen/cfg.py

@@ -217,8 +217,7 @@ class Config(Lockable):
 	columns         = 0
 	columns         = 0
 	color = bool(
 	color = bool(
 		(sys.stdout.isatty() and not os.getenv('MMGEN_TEST_SUITE_PEXPECT')) or
 		(sys.stdout.isatty() and not os.getenv('MMGEN_TEST_SUITE_PEXPECT')) or
-		os.getenv('MMGEN_TEST_SUITE_ENABLE_COLOR')
-	)
+		os.getenv('MMGEN_TEST_SUITE_ENABLE_COLOR'))
 
 
 	# miscellaneous features:
 	# miscellaneous features:
 	use_internal_keccak_module     = False
 	use_internal_keccak_module     = False
@@ -280,9 +279,7 @@ class Config(Lockable):
 	_use_cfg_file           = False
 	_use_cfg_file           = False
 	_use_env                = False
 	_use_env                = False
 
 
-	_forbidden_opts = (
-		'data_dir_root',
-	)
+	_forbidden_opts = ('data_dir_root',)
 
 
 	_incompatible_opts = (
 	_incompatible_opts = (
 		('help', 'longhelp'),
 		('help', 'longhelp'),
@@ -290,8 +287,7 @@ class Config(Lockable):
 		('label', 'keep_label'),
 		('label', 'keep_label'),
 		('tx_id', 'info'),
 		('tx_id', 'info'),
 		('tx_id', 'terse_info'),
 		('tx_id', 'terse_info'),
-		('autosign', 'outdir'),
-	)
+		('autosign', 'outdir'))
 
 
 	# proto-specific only: eth_mainnet_chain_names eth_testnet_chain_names
 	# proto-specific only: eth_mainnet_chain_names eth_testnet_chain_names
 	# coin-specific only:  bch_cashaddr (alias of cashaddr)
 	# coin-specific only:  bch_cashaddr (alias of cashaddr)
@@ -376,8 +372,7 @@ class Config(Lockable):
 		'MMGEN_IGNORE_DAEMON_VERSION',
 		'MMGEN_IGNORE_DAEMON_VERSION',
 		'MMGEN_USE_STANDALONE_SCRYPT_MODULE',
 		'MMGEN_USE_STANDALONE_SCRYPT_MODULE',
 		'MMGEN_ENABLE_ERIGON',
 		'MMGEN_ENABLE_ERIGON',
-		'MMGEN_DISABLE_COLOR',
-	)
+		'MMGEN_DISABLE_COLOR')
 
 
 	_infile_opts = (
 	_infile_opts = (
 		'keys_from_file',
 		'keys_from_file',
@@ -385,8 +380,7 @@ class Config(Lockable):
 		'passwd_file',
 		'passwd_file',
 		'keysforaddrs',
 		'keysforaddrs',
 		'comment_file',
 		'comment_file',
-		'contract_data',
-	)
+		'contract_data')
 
 
 	# Auto-typechecked and auto-set opts - first value in list is the default
 	# Auto-typechecked and auto-set opts - first value in list is the default
 	_ov = namedtuple('autoset_opt_info', ['type', 'choices'])
 	_ov = namedtuple('autoset_opt_info', ['type', 'choices'])

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-16.1.dev26
+16.1.dev27

+ 3 - 5
mmgen/proto/xmr/rpc.py

@@ -67,8 +67,7 @@ class MoneroRPCClient(RPCClient):
 				self.daemon_version_str = ver_str
 				self.daemon_version_str = ver_str
 				self.daemon_version = sum(
 				self.daemon_version = sum(
 					int(m) * (1000 ** n) for n, m in
 					int(m) * (1000 ** n) for n, m in
-						enumerate(reversed(re.match(r'(\d+)\.(\d+)\.(\d+)\.(\d+)', ver_str).groups()))
-				)
+						enumerate(reversed(re.match(r'(\d+)\.(\d+)\.(\d+)\.(\d+)', ver_str).groups())))
 				if self.daemon and self.daemon_version > self.daemon.coind_version:
 				if self.daemon and self.daemon_version > self.daemon.coind_version:
 					self.handle_unsupported_daemon_version(
 					self.handle_unsupported_daemon_version(
 						proto.name,
 						proto.name,
@@ -82,8 +81,7 @@ class MoneroRPCClient(RPCClient):
 		return self.process_http_resp(self.backend.run_noasync(
 		return self.process_http_resp(self.backend.run_noasync(
 			payload = {'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': kwargs},
 			payload = {'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': kwargs},
 			timeout = 3600, # allow enough time to sync ≈1,000,000 blocks
 			timeout = 3600, # allow enough time to sync ≈1,000,000 blocks
-			host_path = '/json_rpc'
-		))
+			host_path = '/json_rpc'))
 
 
 	def call_raw(self, method, *params, **kwargs):
 	def call_raw(self, method, *params, **kwargs):
 		assert not params, f'{self.name}.call() accepts keyword arguments only'
 		assert not params, f'{self.name}.call() accepts keyword arguments only'
@@ -91,7 +89,7 @@ class MoneroRPCClient(RPCClient):
 			payload = kwargs,
 			payload = kwargs,
 			timeout = self.timeout,
 			timeout = self.timeout,
 			host_path = f'/{method}'
 			host_path = f'/{method}'
-		), json_rpc=False)
+		), json_rpc = False)
 
 
 	async def do_stop_daemon(self, *, silent=False):
 	async def do_stop_daemon(self, *, silent=False):
 		return self.call_raw('stop_daemon') # unreliable on macOS (daemon stops, but closes connection)
 		return self.call_raw('stop_daemon') # unreliable on macOS (daemon stops, but closes connection)

+ 24 - 3
mmgen/proto/xmr/tx/online.py

@@ -18,9 +18,30 @@ class OnlineSigned(Completed):
 
 
 	async def compat_send(self):
 	async def compat_send(self):
 		from ....xmrwallet import op as xmrwallet_op
 		from ....xmrwallet import op as xmrwallet_op
-		op = xmrwallet_op('submit', self.cfg, self.filename, None, compat_call=True)
-		await op.restart_wallet_daemon()
-		return await op.main()
+		op_name = 'daemon' if self.cfg.status else 'submit'
+		op = xmrwallet_op(op_name, self.cfg, self.filename, None, compat_call=True)
+		if self.cfg.status:
+			from ....util import msg, ymsg, suf
+			txid = self.compat_tx.data.txid
+			if self.cfg.verbose:
+				msg(self.compat_tx.get_info())
+			elif not self.cfg.quiet:
+				from ....obj import CoinTxID
+				msg('TxID: {}'.format(CoinTxID(txid).hl()))
+			res = op.dc.call_raw('get_transactions', txs_hashes=[txid])
+			if res['status'] == 'OK':
+				tx = res['txs'][0]
+				if tx['in_pool']:
+					msg('Transaction is in mempool')
+				else:
+					confs = tx['confirmations']
+					msg('Transaction has {} confirmation{}'.format(confs, suf(confs)))
+			else:
+				ymsg('An RPC error occurred while fetching transaction data')
+				return False
+		else:
+			await op.restart_wallet_daemon()
+			return await op.main()
 
 
 class Sent(OnlineSigned):
 class Sent(OnlineSigned):
 	pass
 	pass

+ 3 - 1
mmgen/xmrwallet/__init__.py

@@ -55,7 +55,8 @@ op_names = {
 	'transfer':            'sweep',
 	'transfer':            'sweep',
 	'sweep':               'sweep',
 	'sweep':               'sweep',
 	'sweep_all':           'sweep',
 	'sweep_all':           'sweep',
-	'relay':               'relay',
+	'daemon':              'daemon',
+	'relay':               'daemon',
 	'txview':              'txview',
 	'txview':              'txview',
 	'txlist':              'txview',
 	'txlist':              'txview',
 	'label':               'label',
 	'label':               'label',
@@ -124,6 +125,7 @@ def op(op, cfg, infile, wallets, *, spec=None, compat_call=False):
 		cfg = Config({
 		cfg = Config({
 			'_clone': cfg,
 			'_clone': cfg,
 			'compat': True,
 			'compat': True,
+			'full_address': cfg.full_address or compat_call,
 			'xmrwallet_compat': True} | ({} if cfg.offline else {
 			'xmrwallet_compat': True} | ({} if cfg.offline else {
 				'no_start_wallet_daemon': cfg.no_start_wallet_daemon or compat_call,
 				'no_start_wallet_daemon': cfg.no_start_wallet_daemon or compat_call,
 				'daemon': cfg.daemon or cfg.monero_daemon,
 				'daemon': cfg.daemon or cfg.monero_daemon,

+ 11 - 5
mmgen/xmrwallet/ops/relay.py → mmgen/xmrwallet/ops/daemon.py

@@ -9,7 +9,7 @@
 #   https://gitlab.com/mmgen/mmgen-wallet
 #   https://gitlab.com/mmgen/mmgen-wallet
 
 
 """
 """
-xmrwallet.ops.relay: Monero wallet ops for the MMGen Suite
+xmrwallet.ops.daemon: Monero wallet ops for the MMGen Suite
 """
 """
 
 
 import time
 import time
@@ -23,7 +23,7 @@ from ..file.tx import MoneroMMGenTX
 
 
 from . import OpBase
 from . import OpBase
 
 
-class OpRelay(OpBase):
+class OpDaemon(OpBase):
 	opts = ('tx_relay_daemon',)
 	opts = ('tx_relay_daemon',)
 
 
 	def __init__(self, cfg, uarg_tuple):
 	def __init__(self, cfg, uarg_tuple):
@@ -32,8 +32,6 @@ class OpRelay(OpBase):
 
 
 		self.mount_removable_device()
 		self.mount_removable_device()
 
 
-		self.tx = MoneroMMGenTX.Signed(self.cfg, Path(self.uargs.infile))
-
 		if self.cfg.tx_relay_daemon:
 		if self.cfg.tx_relay_daemon:
 			m = self.parse_tx_relay_opt()
 			m = self.parse_tx_relay_opt()
 			host, port = m[1].split(':')
 			host, port = m[1].split(':')
@@ -42,7 +40,9 @@ class OpRelay(OpBase):
 		else:
 		else:
 			from ...daemon import CoinDaemon
 			from ...daemon import CoinDaemon
 			md = CoinDaemon(self.cfg, network_id='xmr', test_suite=self.cfg.test_suite)
 			md = CoinDaemon(self.cfg, network_id='xmr', test_suite=self.cfg.test_suite)
-			host, port = ('localhost', md.rpc_port)
+			host, port = (
+				self.cfg.daemon.split(':') if self.cfg.daemon else
+				('localhost', md.rpc_port))
 			proxy = None
 			proxy = None
 
 
 		self.dc = MoneroRPCClient(
 		self.dc = MoneroRPCClient(
@@ -56,6 +56,12 @@ class OpRelay(OpBase):
 			test_connection = host == 'localhost', # avoid extra connections if relay is a public node
 			test_connection = host == 'localhost', # avoid extra connections if relay is a public node
 			proxy  = proxy)
 			proxy  = proxy)
 
 
+class OpRelay(OpDaemon):
+
+	def __init__(self, cfg, uarg_tuple):
+		super().__init__(cfg, uarg_tuple)
+		self.tx = MoneroMMGenTX.Signed(self.cfg, Path(self.uargs.infile))
+
 	async def main(self):
 	async def main(self):
 		msg('\n' + self.tx.get_info(indent='    '))
 		msg('\n' + self.tx.get_info(indent='    '))
 
 

+ 24 - 2
test/cmdtest_d/xmr_autosign.py

@@ -548,7 +548,10 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
 		('alice_txcreate3',          'recreating the transaction'),
 		('alice_txcreate3',          'recreating the transaction'),
 		('wait_loop_start_ltc',      'starting autosign wait loop in XMR compat mode [--coins=ltc,xmr]'),
 		('wait_loop_start_ltc',      'starting autosign wait loop in XMR compat mode [--coins=ltc,xmr]'),
 		('alice_txsend1',            'sending the transaction'),
 		('alice_txsend1',            'sending the transaction'),
+		('alice_txstatus1',          'getting the transaction status (in mempool)'),
 		('mine_blocks_10',           'mining some blocks'),
 		('mine_blocks_10',           'mining some blocks'),
+		('alice_txstatus2',          'getting the transaction status (confirmations)'),
+		('alice_txstatus3',          'getting the transaction status (verbose)'),
 		('alice_twview_chk2',        'viewing Alice’s tracking wallets (check balances)'),
 		('alice_twview_chk2',        'viewing Alice’s tracking wallets (check balances)'),
 		('alice_txcreate_sweep1',    'creating a sweep transaction (account sweep)'),
 		('alice_txcreate_sweep1',    'creating a sweep transaction (account sweep)'),
 		('alice_txsend2',            'sending the transaction'),
 		('alice_txsend2',            'sending the transaction'),
@@ -797,6 +800,22 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
 
 
 	alice_txsend1 = alice_txsend2 = alice_txsend3 = _alice_txsend
 	alice_txsend1 = alice_txsend2 = alice_txsend3 = _alice_txsend
 
 
+	def alice_txstatus1(self):
+		return self._alice_txstatus(expect_str='TxID: .* in mempool')
+
+	def alice_txstatus2(self):
+		return self._alice_txstatus(['--quiet'], expect_str='confirmations')
+
+	def alice_txstatus3(self):
+		return self._alice_txstatus(['--verbose'], expect_str='Info for transaction .* confirmations')
+
+	def _alice_txstatus(self, add_opts=[], expect_str=None):
+		return self._alice_txops(
+			'txsend',
+			opts     = ['--alice', '--status'],
+			add_opts = self.alice_daemon_opts + add_opts,
+			expect_str = expect_str)
+
 	def wait_signed1(self):
 	def wait_signed1(self):
 		self.spawn(msg_only=True)
 		self.spawn(msg_only=True)
 		oqmsg('')
 		oqmsg('')
@@ -815,7 +834,8 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
 			wait_signed = False,
 			wait_signed = False,
 			sweep_type = None,
 			sweep_type = None,
 			sweep_menu = '',
 			sweep_menu = '',
-			signable_desc = 'transaction'):
+			signable_desc = 'transaction',
+			expect_str = None):
 		if wait_signed:
 		if wait_signed:
 			self._wait_signed(signable_desc)
 			self._wait_signed(signable_desc)
 		self.insert_device_online()
 		self.insert_device_online()
@@ -834,8 +854,10 @@ class CmdTestXMRCompat(CmdTestXMRAutosign):
 					t.expect(self.menu_prompt, ch)
 					t.expect(self.menu_prompt, ch)
 				t.expect('to spend from: ', f'{acct_num}\n')
 				t.expect('to spend from: ', f'{acct_num}\n')
 			t.expect('(y/N): ', 'y') # save?
 			t.expect('(y/N): ', 'y') # save?
-		elif op == 'txsend':
+		elif op == 'txsend' and not '--status' in opts:
 			t.expect('(y/N): ', 'y') # view?
 			t.expect('(y/N): ', 'y') # view?
+		if expect_str:
+			t.expect(expect_str, regex=True)
 		t.read() # required!
 		t.read() # required!
 		self.remove_device_online()
 		self.remove_device_online()
 		return t
 		return t