Browse Source

autosign,xmrwallet: various fixes and cleanups

The MMGen Project 1 year ago
parent
commit
ca8657d4cd
7 changed files with 82 additions and 33 deletions
  1. 17 11
      mmgen/autosign.py
  2. 3 1
      mmgen/cfg.py
  3. 1 1
      mmgen/rpc.py
  4. 33 15
      mmgen/xmrwallet.py
  5. 8 4
      test/test_py_d/ts_autosign.py
  6. 3 1
      test/test_py_d/ts_base.py
  7. 17 0
      test/test_py_d/ts_xmrwallet.py

+ 17 - 11
mmgen/autosign.py

@@ -188,9 +188,9 @@ class Autosign:
 		if 'coin' in cfg._uopts:
 			die(1,'--coin option not supported with this command.  Use --coins instead')
 
-		if cfg.coins:
-			self.coins = cfg.coins.upper().split(',')
-		else:
+		self.coins = cfg.coins.upper().split(',') if cfg.coins else []
+
+		if not self.coins:
 			ymsg('Warning: no coins specified, defaulting to BTC')
 			self.coins = ['BTC']
 
@@ -227,6 +227,16 @@ class Autosign:
 
 	def do_mount(self):
 
+		from stat import S_ISDIR,S_IWUSR,S_IRUSR
+
+		def check_dir(cdir):
+			try:
+				ds = os.stat(cdir)
+				assert S_ISDIR(ds.st_mode), f'{cdir!r} is not a directory!'
+				assert ds.st_mode & S_IWUSR|S_IRUSR == S_IWUSR|S_IRUSR, f'{cdir!r} is not read/write for this user!'
+			except:
+				die(1,f'{cdir!r} missing or not read/writable by user!')
+
 		if not os.path.isdir(self.mountpoint):
 			def do_die(m):
 				die(1,'\n' + yellow(fmt(m.strip(),indent='  ')))
@@ -241,14 +251,10 @@ class Autosign:
 
 		self.have_msg_dir = os.path.isdir(self.msg_dir)
 
-		from stat import S_ISDIR,S_IWUSR,S_IRUSR
-		for cdir in [self.tx_dir] + ([self.msg_dir] if self.have_msg_dir else []):
-			try:
-				ds = os.stat(cdir)
-				assert S_ISDIR(ds.st_mode), f'{cdir!r} is not a directory!'
-				assert ds.st_mode & S_IWUSR|S_IRUSR == S_IWUSR|S_IRUSR, f'{cdir!r} is not read/write for this user!'
-			except:
-				die(1,f'{cdir!r} missing or not read/writable by user!')
+		check_dir(self.tx_dir)
+
+		if self.have_msg_dir:
+			check_dir(self.msg_dir)
 
 	def do_umount(self):
 		if os.path.ismount(self.mountpoint):

+ 3 - 1
mmgen/cfg.py

@@ -405,7 +405,9 @@ class Config(Lockable):
 		# Step 1: get user-supplied configuration data from a) command line, or b) first argument
 		#         to constructor; save to self._uopts:
 		if opts_data or parsed_opts or process_opts:
-			assert cfg is None
+			assert cfg is None, (
+				'Config(): ‘cfg’ cannot be used simultaneously with ' +
+				'‘opts_data’, ‘parsed_opts’ or ‘process_opts’' )
 			from mmgen.opts import UserOpts
 			UserOpts(
 				cfg          = self,

+ 1 - 1
mmgen/rpc.py

@@ -25,7 +25,7 @@ from decimal import Decimal
 from collections import namedtuple
 
 from .cfg import gc
-from .util import msg,die,fmt,fmt_list,pp_fmt
+from .util import msg,die,fmt,fmt_list,pp_fmt,oneshot_warning
 from .base_obj import AsyncInit
 from .obj import NonNegativeInt
 from .objmethods import Hilite,InitErrors,MMGenObject

+ 33 - 15
mmgen/xmrwallet.py

@@ -115,6 +115,9 @@ class MoneroMMGenTX:
 
 	class Base:
 
+		def __init__(self):
+			self.name = type(self).__name__
+
 		def make_chksum(self,keys=None):
 			res = json.dumps(
 				dict( (k,v) for k,v in self.data._asdict().items() if (not keys or k in keys) ),
@@ -195,7 +198,7 @@ class MoneroMMGenTX:
 					pmid = pink(pmid.hex()) if pmid else None
 				)
 
-		def write(self,delete_metadata=False):
+		def write(self,delete_metadata=False,ask_write=True,ask_overwrite=True):
 			dict_data = self.data._asdict()
 			if delete_metadata:
 				dict_data['metadata'] = None
@@ -224,16 +227,16 @@ class MoneroMMGenTX:
 				outfile               = fn,
 				data                  = out,
 				desc                  = self.desc,
-				ask_write             = True,
-				ask_write_default_yes = False )
+				ask_write             = ask_write,
+				ask_write_default_yes = not ask_write,
+				ask_overwrite         = ask_overwrite )
 
-	class NewSigned(Base):
-		signed = True
-		desc = 'signed transaction data'
-		ext = 'sigtx'
+	class New(Base):
 
 		def __init__(self,*args,**kwargs):
 
+			super().__init__()
+
 			assert not args, 'Non-keyword args not permitted'
 
 			d = namedtuple('kwargs_tuple',kwargs)(**kwargs)
@@ -259,11 +262,17 @@ class MoneroMMGenTX:
 				metadata       = d.metadata,
 			)
 
+	class NewSigned(New):
+		desc = 'signed transaction data'
+		ext = 'sigtx'
+		signed = True
+
 	class Completed(Base):
-		name = 'completed'
 
 		def __init__(self,cfg,fn):
 
+			super().__init__()
+
 			self.cfg = cfg
 			self.fn = fn
 
@@ -276,6 +285,10 @@ class MoneroMMGenTX:
 
 			d = self.xmrwallet_tx_data(**d_wrap['data'])
 
+			if self.name != 'Completed':
+				assert fn.endswith('.'+self.ext), 'TX filename {fn!r} has incorrect extension (not {self.ext!r})'
+				assert getattr(d,self.req_field), f'{self.name} TX missing required field {self.req_field!r}'
+
 			proto = init_proto( cfg, 'xmr', network=d.network, need_amt=True )
 
 			self.data = self.xmrwallet_tx_data(
@@ -293,13 +306,17 @@ class MoneroMMGenTX:
 				blob           = d.blob,
 				metadata       = d.metadata,
 			)
+
 			for k in ('base_chksum','full_chksum'):
 				a = getattr(self,k)
 				b = d_wrap[k]
 				assert a == b, f'{k} mismatch: {a} != {b}'
 
 	class Signed(Completed):
-		name = 'signed'
+		desc = 'signed transaction'
+		ext = 'sigtx'
+		signed = True
+		req_field = 'blob'
 
 class MoneroWalletOps:
 
@@ -505,6 +522,7 @@ class MoneroWalletOps:
 
 			def __init__(self,parent,d):
 				self.parent = parent
+				self.cfg = parent.cfg
 				self.c = parent.c
 				self.d = d
 				self.fn = parent.get_wallet_fn(d)
@@ -637,7 +655,7 @@ class MoneroWalletOps:
 					get_tx_metadata = True
 				)
 				return MoneroMMGenTX.NewSigned(
-					cfg            = self.parent.cfg,
+					cfg            = self.cfg,
 					op             = self.parent.name,
 					network        = self.parent.proto.network,
 					seed_id        = self.parent.kal.al_id.sid,
@@ -665,7 +683,7 @@ class MoneroWalletOps:
 					die(3,'More than one TX required.  Cannot perform this sweep')
 
 				return MoneroMMGenTX.NewSigned(
-					cfg            = self.parent.cfg,
+					cfg            = self.cfg,
 					op             = self.parent.name,
 					network        = self.parent.proto.network,
 					seed_id        = self.parent.kal.al_id.sid,
@@ -858,7 +876,7 @@ class MoneroWalletOps:
 					else:
 						idx = int(m[i])
 						try:
-							res = [d for d in self.kal.data if d.idx == idx][0]
+							res = self.kal.entry(idx)
 						except:
 							die(1,'Supplied key-address file does not contain address {}:{}'.format(
 								self.kal.al_id.sid,
@@ -1095,6 +1113,8 @@ class MoneroWalletOps:
 
 			super().__init__(cfg,uarg_tuple)
 
+			self.tx = MoneroMMGenTX.Signed( self.cfg, uarg.infile )
+
 			if self.cfg.tx_relay_daemon:
 				m = re.fullmatch(
 					uarg_info['tx_relay_daemon'].pat,
@@ -1120,8 +1140,6 @@ class MoneroWalletOps:
 				test_connection = False, # relay is presumably a public node, so avoid extra connections
 				proxy  = proxy )
 
-			self.tx = MoneroMMGenTX.Signed( self.cfg, uarg.infile )
-
 		async def main(self):
 			msg('\n' + self.tx.get_info())
 
@@ -1150,6 +1168,6 @@ class MoneroWalletOps:
 				'\n'.join(
 					tx.get_info() for tx in
 					sorted(
-						(MoneroMMGenTX.Signed( self.cfg, fn ) for fn in uarg.infile),
+						(MoneroMMGenTX.Completed( self.cfg, fn ) for fn in uarg.infile),
 						key = lambda x: x.data.sign_time )
 			))

+ 8 - 4
test/test_py_d/ts_autosign.py

@@ -97,6 +97,7 @@ class TestSuiteAutosignBase(TestSuiteBase):
 
 		self.asi = Autosign(
 			AutosignConfig({
+				'coins': ','.join(self.coins),
 				'mountpoint': (
 					None if self.live else
 					os.path.join(self.tmpdir,self.mountpoint_basename)
@@ -298,20 +299,23 @@ class TestSuiteAutosignBase(TestSuiteBase):
 	def do_sign(self,args,have_msg=False):
 		t = self.spawn('mmgen-autosign', self.opts + args )
 		t.expect(
-			f'{self.tx_count} transactions signed' if self.tx_count else
+			f'{self.tx_count} transaction{suf(self.tx_count)} signed' if self.tx_count else
 			'No unsigned transactions' )
 
 		if self.bad_tx_count:
-			t.expect(f'{self.bad_tx_count} transactions failed to sign')
+			t.expect(f'{self.bad_tx_count} transaction{suf(self.bad_tx_count)} failed to sign')
 			t.req_exit_val = 1
 
 		if have_msg:
 			t.expect(
-				f'{self.good_msg_count} message files{{0,1}} signed' if self.good_msg_count else
+				f'{self.good_msg_count} message file{suf(self.good_msg_count)}{{0,1}} signed'
+					if self.good_msg_count else
 				'No unsigned message files', regex=True )
 
 			if self.bad_msg_count:
-				t.expect(f'{self.bad_msg_count} message files{{0,1}} failed to sign', regex=True)
+				t.expect(
+					f'{self.bad_msg_count} message file{suf(self.bad_msg_count)}{{0,1}} failed to sign',
+					regex = True )
 				t.req_exit_val = 1
 
 		if 'wait' in args:

+ 3 - 1
test/test_py_d/ts_base.py

@@ -25,7 +25,7 @@ from mmgen.cfg import gc
 from ..include.common import *
 from .common import *
 
-class TestSuiteBase(object):
+class TestSuiteBase:
 	'initializer class for the test.py test suite'
 	base_passthru_opts = ('data_dir','skip_cfg_file')
 	passthru_opts = ()
@@ -36,6 +36,8 @@ class TestSuiteBase(object):
 	need_daemon = False
 
 	def __init__(self,trunner,cfgs,spawn):
+		if hasattr(self,'tr'): # init will be called multiple times for classes with multiple inheritance
+			return
 		self.proto = cfg._proto
 		self.tr = trunner
 		self.cfgs = cfgs

+ 17 - 0
test/test_py_d/ts_xmrwallet.py

@@ -615,6 +615,23 @@ class TestSuiteXMRWallet(TestSuiteBase):
 
 	# mining methods
 
+	async def mine5(self):
+		return await self.mine(5)
+
+	async def mine10(self):
+		return await self.mine(10)
+
+	async def mine60(self):
+		return await self.mine(60)
+
+	async def mine(self,nsecs):
+		imsg_r(f'Mining for {nsecs} seconds...')
+		await self.start_mining()
+		await asyncio.sleep(nsecs)
+		ret = await self.stop_mining()
+		imsg('done')
+		return ret
+
 	async def start_mining(self):
 		data = self.users['miner']
 		addr = read_from_file(data.addrfile_fs.format(1)) # mine to wallet #1, account 0