Browse Source

mmgen-xmrwallet: new ‘sweep_all’ operation

This operation sweeps balances of all addresses in an account, as opposed to
‘sweep’, which sweeps the balance of a single randomly chosen address.

For accounts having only one address with a balance, the two operations are
identical.
The MMGen Project 1 year ago
parent
commit
fc7e3c8302
5 changed files with 37 additions and 45 deletions
  1. 1 1
      mmgen/data/version
  2. 8 5
      mmgen/help/xmrwallet.py
  3. 2 2
      mmgen/main_xmrwallet.py
  4. 16 9
      mmgen/xmrwallet.py
  5. 10 28
      test/cmdtest_py_d/ct_xmrwallet.py

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-14.1.dev27
+14.1.dev28

+ 8 - 5
mmgen/help/xmrwallet.py

@@ -48,7 +48,8 @@ new       - create a new account in a wallet, or a new address in an account
 transfer  - transfer specified XMR amount from specified wallet:account to
 transfer  - transfer specified XMR amount from specified wallet:account to
             specified address
             specified address
 sweep     - sweep funds in specified wallet:account to new address in same
 sweep     - sweep funds in specified wallet:account to new address in same
-            account or new account in another wallet
+            account, or new or specified account in another wallet
+sweep_all - same as above, but sweep balances of all addresses in the account
 relay     - relay a transaction from a transaction file created using ‘sweep’
 relay     - relay a transaction from a transaction file created using ‘sweep’
             or ‘transfer’ with the --no-relay option
             or ‘transfer’ with the --no-relay option
 submit    - submit an autosigned transaction to a wallet and the network
 submit    - submit an autosigned transaction to a wallet and the network
@@ -102,9 +103,10 @@ where SOURCE is a wallet number, ACCOUNT the source account index, ADDRESS
 the destination Monero address and AMOUNT the XMR amount to be sent.
 the destination Monero address and AMOUNT the XMR amount to be sent.
 
 
 
 
-                           ‘SWEEP’ OPERATION NOTES
+                    ‘SWEEP’ AND ‘SWEEP_ALL’ OPERATION NOTES
 
 
-The sweep operation takes a SWEEP_SPEC arg with the following format:
+The sweep and sweep_all operations take a SWEEP_SPEC arg with the following
+format:
 
 
     {sweep_spec}
     {sweep_spec}
 
 
@@ -123,8 +125,9 @@ swept into ACCOUNT of DEST.
 
 
 The user is prompted before addresses are created or funds transferred.
 The user is prompted before addresses are created or funds transferred.
 
 
-Note that multiple sweep operations may be required to sweep all the funds
-in an account.
+With ‘sweep’, if the source account has more than one address with a balance,
+the balance of a single randomly chosen address will be swept.  To sweep the
+balances of all addresses in an account, use ‘sweep_all’.
 
 
 
 
                     ‘SUBMIT’ AND ‘RELAY’ OPERATION NOTES
                     ‘SUBMIT’ AND ‘RELAY’ OPERATION NOTES

+ 2 - 2
mmgen/main_xmrwallet.py

@@ -45,7 +45,7 @@ opts_data = {
 			'[opts] label    [xmr_keyaddrfile] LABEL_SPEC',
 			'[opts] label    [xmr_keyaddrfile] LABEL_SPEC',
 			'[opts] new      [xmr_keyaddrfile] NEW_ADDRESS_SPEC',
 			'[opts] new      [xmr_keyaddrfile] NEW_ADDRESS_SPEC',
 			'[opts] transfer [xmr_keyaddrfile] TRANSFER_SPEC',
 			'[opts] transfer [xmr_keyaddrfile] TRANSFER_SPEC',
-			'[opts] sweep    [xmr_keyaddrfile] SWEEP_SPEC',
+			'[opts] sweep | sweep_all [xmr_keyaddrfile] SWEEP_SPEC',
 			'[opts] submit   [TX_file]',
 			'[opts] submit   [TX_file]',
 			'[opts] relay    <TX_file>',
 			'[opts] relay    <TX_file>',
 			'[opts] resubmit | abort (for use with --autosign only)',
 			'[opts] resubmit | abort (for use with --autosign only)',
@@ -139,7 +139,7 @@ elif op in ('create','sync','list','view','listview','dump','restore'): # kafile
 	if len(cmd_args) > 1:
 	if len(cmd_args) > 1:
 		cfg._opts.usage()
 		cfg._opts.usage()
 	wallets = cmd_args.pop(0) if cmd_args else None
 	wallets = cmd_args.pop(0) if cmd_args else None
-elif op in ('new','transfer','sweep','label'):
+elif op in ('new', 'transfer', 'sweep', 'sweep_all', 'label'):
 	if len(cmd_args) != 1:
 	if len(cmd_args) != 1:
 		cfg._opts.usage()
 		cfg._opts.usage()
 	spec = cmd_args[0]
 	spec = cmd_args[0]

+ 16 - 9
mmgen/xmrwallet.py

@@ -217,7 +217,7 @@ class MoneroMMGenTX:
 			'amount',
 			'amount',
 			'fee',
 			'fee',
 			'blob' }
 			'blob' }
-		oneline_fs = '{a:7} {b:8} {c:19} {d:13} {e:8} {f:6} {x:2} {g:6} {h:17} {j}'
+		oneline_fs = '{a:7} {b:8} {c:19} {d:13} {e:9} {f:6} {x:2} {g:6} {h:17} {j}'
 		chksum_nchars = 6
 		chksum_nchars = 6
 		xmrwallet_tx_data = namedtuple('xmrwallet_tx_data',[
 		xmrwallet_tx_data = namedtuple('xmrwallet_tx_data',[
 			'op',
 			'op',
@@ -254,7 +254,7 @@ class MoneroMMGenTX:
 					b = d.seed_id.hl(),
 					b = d.seed_id.hl(),
 					c = make_timestr(d.submit_time if d.submit_time is not None else d.create_time),
 					c = make_timestr(d.submit_time if d.submit_time is not None else d.create_time),
 					d = orange(self.file_id),
 					d = orange(self.file_id),
-					e = purple(capfirst(d.op.ljust(8))),
+					e = purple(d.op.replace('_', ' ').title().ljust(9)),
 					f = red('{}:{}'.format(d.source.wallet,d.source.account).ljust(6)),
 					f = red('{}:{}'.format(d.source.wallet,d.source.account).ljust(6)),
 					g = red('{}:{}'.format(d.dest.wallet,d.dest.account).ljust(6)) if d.dest else cyan('ext   '),
 					g = red('{}:{}'.format(d.dest.wallet,d.dest.account).ljust(6)) if d.dest else cyan('ext   '),
 					h = d.amount.fmt( color=True, iwidth=4, prec=12 ),
 					h = d.amount.fmt( color=True, iwidth=4, prec=12 ),
@@ -640,6 +640,7 @@ class MoneroWalletOps:
 		'new',
 		'new',
 		'transfer',
 		'transfer',
 		'sweep',
 		'sweep',
+		'sweep_all',
 		'relay',
 		'relay',
 		'txview',
 		'txview',
 		'txlist',
 		'txlist',
@@ -663,6 +664,7 @@ class MoneroWalletOps:
 		'new',
 		'new',
 		'transfer',
 		'transfer',
 		'sweep',
 		'sweep',
+		'sweep_all',
 		'dump',
 		'dump',
 		'restore' )
 		'restore' )
 
 
@@ -1138,11 +1140,13 @@ class MoneroWalletOps:
 					unsigned_txset = res['unsigned_txset'] if self.cfg.watch_only else None,
 					unsigned_txset = res['unsigned_txset'] if self.cfg.watch_only else None,
 				)
 				)
 
 
-			def make_sweep_tx(self,account,dest_acct,dest_addr_idx,addr):
+			def make_sweep_tx(self, account, dest_acct, dest_addr_idx, addr, addrs_data):
 				res = self.c.call(
 				res = self.c.call(
 					'sweep_all',
 					'sweep_all',
 					address = addr,
 					address = addr,
 					account_index = account,
 					account_index = account,
+					subaddr_indices = list(range(len(addrs_data[account]['addresses'])))
+						if self.parent.name == 'sweep_all' else [],
 					priority = self.cfg.priority or None,
 					priority = self.cfg.priority or None,
 					do_not_relay = True,
 					do_not_relay = True,
 					get_tx_hex = True,
 					get_tx_hex = True,
@@ -1546,7 +1550,7 @@ class MoneroWalletOps:
 				else:
 				else:
 					return s # None or empty string
 					return s # None or empty string
 
 
-			if self.name == 'sweep':
+			if self.name in ('sweep', 'sweep_all'):
 				self.dest_acct = None if m[4] is None else int(m[4])
 				self.dest_acct = None if m[4] is None else int(m[4])
 			elif self.name == 'transfer':
 			elif self.name == 'transfer':
 				self.dest_addr = CoinAddr(self.proto,m[3])
 				self.dest_addr = CoinAddr(self.proto,m[3])
@@ -1592,7 +1596,7 @@ class MoneroWalletOps:
 				cfg    = self.cfg,
 				cfg    = self.cfg,
 				daemon = wd2 )
 				daemon = wd2 )
 
 
-		def create_tx(self, h, accts_data):
+		def create_tx(self, h, accts_data, addrs_data):
 
 
 			def create_new_addr_maybe(h, account, label=None):
 			def create_new_addr_maybe(h, account, label=None):
 				if keypress_confirm(self.cfg, f'\nCreate new address for account #{account}?'):
 				if keypress_confirm(self.cfg, f'\nCreate new address for account #{account}?'):
@@ -1657,7 +1661,7 @@ class MoneroWalletOps:
 			assert dest_addr_chk in (None, dest_addr), 'dest_addr_chk'
 			assert dest_addr_chk in (None, dest_addr), 'dest_addr_chk'
 
 
 			msg(f'\n    Creating {self.name} transaction...')
 			msg(f'\n    Creating {self.name} transaction...')
-			return (h, h.make_sweep_tx(self.account, dest_acct, dest_addr_idx, dest_addr))
+			return (h, h.make_sweep_tx(self.account, dest_acct, dest_addr_idx, dest_addr, addrs_data))
 
 
 		@property
 		@property
 		def add_desc(self):
 		def add_desc(self):
@@ -1682,13 +1686,13 @@ class MoneroWalletOps:
 			h = self.rpc(self,self.source)
 			h = self.rpc(self,self.source)
 
 
 			h.open_wallet('source')
 			h.open_wallet('source')
-			accts_data = h.get_accts()[0]
+			accts_data, addrs_data = h.get_accts()
 
 
 			self.check_account_exists(accts_data, self.account)
 			self.check_account_exists(accts_data, self.account)
 
 
 			h.print_addrs(accts_data,self.account)
 			h.print_addrs(accts_data,self.account)
 
 
-			h, new_tx = self.create_tx(h, accts_data)
+			h, new_tx = self.create_tx(h, accts_data, addrs_data)
 
 
 			msg('\n' + new_tx.get_info(indent='    '))
 			msg('\n' + new_tx.get_info(indent='    '))
 
 
@@ -1715,6 +1719,9 @@ class MoneroWalletOps:
 			else:
 			else:
 				die(1,'\nExiting at user request')
 				die(1,'\nExiting at user request')
 
 
+	class sweep_all(sweep):
+		stem = 'sweep'
+
 	class transfer(sweep):
 	class transfer(sweep):
 		stem    = 'transferr'
 		stem    = 'transferr'
 		spec_id = 'transfer_spec'
 		spec_id = 'transfer_spec'
@@ -1724,7 +1731,7 @@ class MoneroWalletOps:
 		def add_desc(self):
 		def add_desc(self):
 			return f': {self.amount} XMR to {self.dest_addr}'
 			return f': {self.amount} XMR to {self.dest_addr}'
 
 
-		def create_tx(self, h, accts_data):
+		def create_tx(self, h, accts_data, addrs_data):
 			msg(f'\n    Creating {self.name} transaction...')
 			msg(f'\n    Creating {self.name} transaction...')
 			return (h, h.make_transfer_tx(self.account, self.dest_addr, self.amount))
 			return (h, h.make_transfer_tx(self.account, self.dest_addr, self.amount))
 
 

+ 10 - 28
test/cmdtest_py_d/ct_xmrwallet.py

@@ -528,7 +528,6 @@ class CmdTestXMRWallet(CmdTestBase):
 			arg2,
 			arg2,
 			tx_relay_parm = None,
 			tx_relay_parm = None,
 			no_relay      = False,
 			no_relay      = False,
-			return_amt    = False,
 			use_existing  = False,
 			use_existing  = False,
 			add_opts      = [],
 			add_opts      = [],
 			add_desc      = None,
 			add_desc      = None,
@@ -558,7 +557,7 @@ class CmdTestXMRWallet(CmdTestBase):
 		if op == 'sign':
 		if op == 'sign':
 			return t
 			return t
 
 
-		if op == 'sweep':
+		if op in ('sweep', 'sweep_all'):
 			desc = 'address' if re.match(r'.*:\d+$', arg2) else 'account'
 			desc = 'address' if re.match(r'.*:\d+$', arg2) else 'account'
 			t.expect(
 			t.expect(
 				rf'Create new {desc} .* \(y/N\): ',
 				rf'Create new {desc} .* \(y/N\): ',
@@ -567,9 +566,6 @@ class CmdTestXMRWallet(CmdTestBase):
 			if use_existing:
 			if use_existing:
 				t.expect(rf'to last existing {desc} .* \(y/N\): ', 'y', regex=True)
 				t.expect(rf'to last existing {desc} .* \(y/N\): ', 'y', regex=True)
 
 
-		if return_amt:
-			amt = XMRAmt(strip_ansi_escapes(t.expect_getend('Amount: ')).replace('XMR','').strip())
-
 		dtype = 'unsigned' if data.autosign else 'signed'
 		dtype = 'unsigned' if data.autosign else 'signed'
 		t.expect(f'Save {dtype} transaction? (y/N): ','y')
 		t.expect(f'Save {dtype} transaction? (y/N): ','y')
 		t.written_to_file(f'{dtype.capitalize()} transaction')
 		t.written_to_file(f'{dtype.capitalize()} transaction')
@@ -580,7 +576,7 @@ class CmdTestXMRWallet(CmdTestBase):
 
 
 		t.read()
 		t.read()
 
 
-		return t if do_ret else amt if return_amt else t.ok()
+		return t if do_ret else t.ok()
 
 
 	def sweep_to_wallet(self):
 	def sweep_to_wallet(self):
 		self.do_op('sweep', 'alice', '1:0,2')
 		self.do_op('sweep', 'alice', '1:0,2')
@@ -657,29 +653,15 @@ class CmdTestXMRWallet(CmdTestBase):
 		return await self.mine_chk('alice',2,1,lambda x: x.ub > 0.9,'unlocked balance > 0.9')
 		return await self.mine_chk('alice',2,1,lambda x: x.ub > 0.9,'unlocked balance > 0.9')
 
 
 	async def sweep_create_and_send(self):
 	async def sweep_create_and_send(self):
-		bal = XMRAmt('0')
-		min_bal = XMRAmt('0.9')
+		get_file_with_ext(self.users['alice'].udir, 'sigtx', delete_all=True)
+
+		self.do_op('sweep_all', 'alice', '2:1,3', no_relay=True, use_existing=True)
+		ok()
+
+		self.relay_tx(f'--tx-relay-daemon={self.tx_relay_daemon_parm}')
 
 
-		for i in range(4):
-			if i:
-				ok()
-			get_file_with_ext(self.users['alice'].udir,'sigtx',delete_all=True)
-			send_amt = self.do_op(
-				'sweep','alice','2:1,3', # '2:1,3'
-				no_relay     = True,
-				use_existing = True,
-				add_desc     = f'TX #{i+1}',
-				return_amt   = True )
-			ok()
-			self.relay_tx(f'--tx-relay-daemon={self.tx_relay_daemon_parm}',add_desc=f'send amt: {send_amt} XMR')
-			await self.mine_chk('alice',2,1,lambda x: 'chk_bal_chg','balance has changed')
-			ok()
-			bal_info = await self.mine_chk('alice',3,0,lambda x,y=bal: x.ub > y, f'bal > {bal}',return_bal=True)
-			bal += bal_info.ub
-			if bal >= min_bal:
-				return 'ok'
-
-		return False
+		min_bal = XMRAmt('0.9')
+		return await self.mine_chk('alice', 3, 0, lambda x: x.ub > min_bal, f'bal > {min_bal}')
 
 
 	# wallet methods
 	# wallet methods