From 333cbcf3ac60229fefca6647f02f9e8b0cdc6a4a Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sun, 26 Mar 2023 12:48:45 +0000 Subject: [PATCH] mmgen-xmrwallet: new `label` operation --- mmgen/data/version | 2 +- mmgen/main_xmrwallet.py | 14 ++++++- mmgen/xmrwallet.py | 69 +++++++++++++++++++++++++++++++++- test/test_py_d/ts_xmrwallet.py | 23 ++++++++++++ 4 files changed, 105 insertions(+), 3 deletions(-) diff --git a/mmgen/data/version b/mmgen/data/version index 19046775..2bf0a3fb 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -13.3.dev39 +13.3.dev40 diff --git a/mmgen/main_xmrwallet.py b/mmgen/main_xmrwallet.py index e032e920..a7504517 100755 --- a/mmgen/main_xmrwallet.py +++ b/mmgen/main_xmrwallet.py @@ -32,6 +32,7 @@ opts_data = { '[opts] create [wallets]', '[opts] sync [wallets]', '[opts] list [wallets]', + '[opts] label LABEL_SPEC', '[opts] new NEW_ADDRESS_SPEC', '[opts] transfer TRANSFER_SPEC', '[opts] sweep SWEEP_SPEC', @@ -80,6 +81,7 @@ plain HTTP is not supported. create - create wallet for all or specified addresses in key-address file sync - sync wallet for all or specified addresses in key-address file list - same as 'sync', but also list detailed address info for accounts +label - set a label for an address 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 specified address @@ -99,6 +101,16 @@ in the specified key-address file, each corresponding to a Monero wallet to be created, synced or listed. If omitted, all wallets are operated upon. + 'LABEL' OPERATION NOTES + +This operation takes a LABEL_SPEC arg with the following format: + + WALLET:ACCOUNT:ADDRESS,"label text" + +where WALLET is a wallet number, ACCOUNT an account index, and ADDRESS an +address index. + + 'NEW' OPERATION NOTES This operation takes a NEW_ADDRESS_SPEC arg with the following format: @@ -231,7 +243,7 @@ elif op in ('create','sync','list'): opts.usage() if cmd_args: wallets = cmd_args[0] -elif op in ('new','transfer','sweep'): +elif op in ('new','transfer','sweep','label'): if len(cmd_args) != 1: opts.usage() spec = cmd_args[0] diff --git a/mmgen/xmrwallet.py b/mmgen/xmrwallet.py index aeaf4bfa..6ca3a75c 100755 --- a/mmgen/xmrwallet.py +++ b/mmgen/xmrwallet.py @@ -42,6 +42,7 @@ xmrwallet_uarg_info = ( 'newaddr_spec': e('WALLET_NUM[:ACCOUNT][,"label text"]', rf'(\d+)(?::(\d+))?(?:,(.*))?'), 'transfer_spec': e('SOURCE_WALLET_NUM:ACCOUNT:ADDRESS,AMOUNT', rf'(\d+):(\d+):([{b58a}]+),([0-9.]+)'), 'sweep_spec': e('SOURCE_WALLET_NUM:ACCOUNT[,DEST_WALLET_NUM]', r'(\d+):(\d+)(?:,(\d+))?'), + 'label_spec': e('WALLET_NUM:ACCOUNT:ADDRESS,"label text"', rf'(\d+):(\d+):(\d+),(.*)'), })( namedtuple('uarg_info_entry',['annot','pat']), r'(?:[^:]+):(?:\d+)' @@ -237,7 +238,7 @@ class MoneroMMGenTX: class MoneroWalletOps: - ops = ('create','sync','list','new','transfer','sweep','relay','txview') + ops = ('create','sync','list','new','transfer','sweep','relay','txview','label') opts = ( 'wallet_dir', 'daemon', @@ -508,6 +509,7 @@ class MoneroWalletOps: e['label'], e['used'] )) + return ret def create_new_addr(self,account,label=None): msg_r('\n Creating new address: ') @@ -531,6 +533,13 @@ class MoneroWalletOps: msg(' ' + cyan(addr)) return ( addr, len(ret) - 1 ) + def set_label(self,account,address_idx,label): + return self.c.call( + 'label_address', + index = { 'major': account, 'minor': address_idx }, + label = label + ) + def make_transfer_tx(self,account,addr,amt): res = self.c.call( 'transfer', @@ -788,6 +797,9 @@ class MoneroWalletOps: self.amount = self.proto.coin_amt(m[4]) elif self.name == 'new': self.label = strip_quotes(m[3]) + elif self.name == 'label': + self.address_idx = int(m[3]) + self.label = strip_quotes(m[4]) class sweep(spec): name = 'sweep' @@ -933,6 +945,61 @@ class MoneroWalletOps: msg('') + class label(spec): + name = 'label' + spec_id = 'label_spec' + spec_key = ( (1,'source'), ) + opts = () + + async def main(self): + + gmsg('\n{} label for wallet {}, account #{}, address #{}'.format( + 'Setting' if self.label else 'Removing', + self.source.idx, + self.account, + self.address_idx + )) + h = self.rpc(self,self.source) + + h.open_wallet('source') + accts_data = h.get_accts()[0] + + max_acct = len(accts_data['subaddress_accounts']) - 1 + if self.account > max_acct: + die(1,f'{self.account}: requested account index out of bounds (>{max_acct})') + + ret = h.print_addrs(accts_data,self.account) + + if self.address_idx > len(ret['addresses']) - 1: + die(1,'{}: requested address index out of bounds (>{})'.format( + self.account, + len(ret['addresses']) - 1 )) + + addr = ret['addresses'][self.address_idx] + + msg('\n {} {}\n {} {}\n {} {}'.format( + 'Address: ', + cyan(addr['address'][:15] + '...'), + 'Existing label:', + pink(addr['label']) if addr['label'] else '[none]', + 'New label: ', + pink(self.label) if self.label else '[none]' )) + + if addr['label'] == self.label: + ymsg('\nLabel is unchanged, operation cancelled') + elif keypress_confirm(' {} label?'.format('Set' if self.label else 'Remove')): + h.set_label( self.account, self.address_idx, self.label ) + accts_data = h.get_accts(print=False)[0] + ret = h.print_addrs(accts_data,self.account) + new_label = ret['addresses'][self.address_idx]['label'] + if new_label != self.label: + ymsg(f'Warning: new label {new_label!r} does not match requested value!') + return False + else: + msg(cyan('\nLabel successfully {}'.format('set' if self.label else 'removed'))) + else: + ymsg('\nOperation cancelled by user request') + class relay(base): name = 'relay' opts = ('tx_relay_daemon',) diff --git a/test/test_py_d/ts_xmrwallet.py b/test/test_py_d/ts_xmrwallet.py index 9790abe4..80425d83 100755 --- a/test/test_py_d/ts_xmrwallet.py +++ b/test/test_py_d/ts_xmrwallet.py @@ -49,6 +49,7 @@ class TestSuiteXMRWallet(TestSuiteBase): ('daemon_version', 'checking daemon version'), ('gen_kafiles', 'generating key-address files'), ('create_wallets_miner', 'creating Monero wallets (Miner)'), + ('set_label_miner', 'setting an address label (Miner, primary account)'), ('mine_initial_coins', 'mining initial coins'), ('create_wallets_alice', 'creating Monero wallets (Alice)'), ('fund_alice', 'sending funds'), @@ -58,6 +59,8 @@ class TestSuiteXMRWallet(TestSuiteBase): ('new_account_alice_label', 'creating a new account (Alice, with label)'), ('new_address_alice', 'creating a new address (Alice)'), ('new_address_alice_label', 'creating a new address (Alice, with label)'), + ('remove_label_alice', 'removing an address label (Alice, subaddress)'), + ('set_label_alice', 'setting an address label (Alice, subaddress)'), ('sync_wallets_selected', 'syncing selected wallets'), ('sweep_to_address_proxy', 'sweeping to new address (via TX relay + proxy)'), @@ -374,6 +377,26 @@ class TestSuiteXMRWallet(TestSuiteBase): random_txs = self.dfl_random_txs ) + def set_label_miner(self): + return self.set_label_user( 'miner', '1:0:0,"Miner’s new primary account label [1:0:0]"' ) + + def remove_label_alice(self): + return self.set_label_user( 'alice', '4:2:2,""' ) + + def set_label_alice(self): + return self.set_label_user( 'alice', '4:2:2,"Alice’s new subaddress label [4:2:2]"' ) + + def set_label_user(self,user,label_spec): + data = self.users[user] + cmd_opts = [f'--wallet-dir={data.udir}', f'--daemon=localhost:{data.md.rpc_port}'] + t = self.spawn( + 'mmgen-xmrwallet', + self.extra_opts + cmd_opts + ['label', data.kafile, label_spec] + ) + t.expect('(y/N): ','y') + t.expect(['successfully set','successfully removed']) + return t + def sync_wallets_all(self): return self.sync_wallets('alice',add_opts=['--rescan-blockchain'])