From 34f30fbf358f51a65056f8572a2ba5a288392e4f Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Fri, 3 Jun 2022 12:32:33 +0000 Subject: [PATCH] mmgen-xmrwallet: add 'new' operation for new address creation --- mmgen/data/release_date | 2 +- mmgen/data/version | 2 +- mmgen/main_xmrwallet.py | 22 ++++++++++++++- mmgen/xmrwallet.py | 49 +++++++++++++++++++++++++++++----- test/test_py_d/ts_xmrwallet.py | 45 +++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 10 deletions(-) diff --git a/mmgen/data/release_date b/mmgen/data/release_date index e3ed941d..021e426d 100644 --- a/mmgen/data/release_date +++ b/mmgen/data/release_date @@ -1 +1 @@ -May 2022 +June 2022 diff --git a/mmgen/data/version b/mmgen/data/version index 6082c61e..5b10a639 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -13.2.dev4 +13.2.dev5 diff --git a/mmgen/main_xmrwallet.py b/mmgen/main_xmrwallet.py index e8519dbe..e236aa5d 100755 --- a/mmgen/main_xmrwallet.py +++ b/mmgen/main_xmrwallet.py @@ -31,6 +31,7 @@ opts_data = { 'usage2': [ '[opts] create [wallets]', '[opts] sync [wallets]', + '[opts] new NEW_ADDRESS_SPEC', '[opts] transfer TRANSFER_SPEC', '[opts] sweep SWEEP_SPEC', '[opts] relay ', @@ -75,6 +76,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 +new - create a new account in a wallet, or a new address in an account transfer - transfer specified XMR amount to specified address from specified wallet:account sweep - sweep funds in specified wallet:account to new address in same @@ -91,6 +93,18 @@ in the specified key-address file, each corresponding to a Monero wallet to be created or synced. If omitted, all wallets are operated upon. + 'NEW' OPERATION NOTES + +This operation takes a NEW_ADDRESS_SPEC arg with the following format: + + WALLET[:ACCOUNT][,"label text"] + +where WALLET is a wallet number and ACCOUNT an account index. If ACCOUNT is +omitted, a new account will be created in the wallet, otherwise a new address +will be created in the specified account. An optional label text may be +appended to the spec following a comma. + + 'TRANSFER' OPERATION NOTES The transfer operation takes a TRANSFER_SPEC arg with the following format: @@ -167,6 +181,12 @@ $ mmgen-xmrwallet --do-not-relay sweep *.akeys.mmenc 2:0 Relay the created sweep transaction via a host on the Tor network: $ mmgen-xmrwallet --tx-relay-daemon=abcdefghijklmnop.onion:127.0.0.1:9050 relay *XMR*.sigtx + +Create a new account in wallet 2: +$ mmgen-xmrwallet new *.akeys.mmenc 2 + +Create a new address in account 1 of wallet 2, with label: +$ mmgen-xmrwallet new *.akeys.mmenc 2:1,"from ABC exchange" """ }, 'code': { @@ -199,7 +219,7 @@ elif op in ('create','sync'): opts.usage() if cmd_args: wallets = cmd_args[0] -elif op in ('transfer','sweep'): +elif op in ('new','transfer','sweep'): if len(cmd_args) != 1: opts.usage() spec = cmd_args[0] diff --git a/mmgen/xmrwallet.py b/mmgen/xmrwallet.py index df362b92..d1900bc2 100755 --- a/mmgen/xmrwallet.py +++ b/mmgen/xmrwallet.py @@ -38,6 +38,7 @@ xmrwallet_uarg_info = ( lambda e,hp: { 'daemon': e('HOST:PORT', hp), 'tx_relay_daemon': e('HOST:PORT[:PROXY_HOST:PROXY_PORT]', rf'({hp})(?::({hp}))?'), + '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+))?'), })( @@ -221,7 +222,7 @@ class MoneroMMGenTX: class MoneroWalletOps: - ops = ('create','sync','transfer','sweep','relay') + ops = ('create','sync','new','transfer','sweep','relay') opts = ( 'wallet_dir', 'daemon', @@ -455,12 +456,14 @@ class MoneroWalletOps: self.print_accts(data,addrs_data) return ( data, addrs_data ) - async def create_acct(self): + async def create_acct(self,label=None): msg('\n Creating new account...') ret = await self.c.call( 'create_account', - label = f'Sweep from {self.parent.source.idx}:{self.parent.account} [{make_timestr()}]' - ) + label = label or 'Sweep from {}:{} [{}]'.format( + self.parent.source.idx, + self.parent.account, + make_timestr() )) msg(' Index: {}'.format( pink(str(ret['account_index'])) )) msg(' Address: {}'.format( cyan(ret['address']) )) return (ret['account_index'], ret['address']) @@ -488,12 +491,12 @@ class MoneroWalletOps: e['used'] )) - async def create_new_addr(self,account): + async def create_new_addr(self,account,label=None): msg_r('\n Creating new address: ') ret = await self.c.call( 'create_address', account_index = account, - label = f'Sweep from this account [{make_timestr()}]', + label = label or f'Sweep from this account [{make_timestr()}]', ) msg(cyan(ret['address'])) return ret['address'] @@ -732,11 +735,13 @@ class MoneroWalletOps: yield res self.addr_data = list(gen()) - self.account = int(m[2]) + self.account = None if m[2] is None else int(m[2]) if self.name == 'transfer': self.dest_addr = CoinAddr(self.proto,m[3]) self.amount = self.proto.coin_amt(m[4]) + elif self.name == 'new': + self.label = m[3] def init_tx_relay_daemon(self): @@ -847,6 +852,36 @@ class MoneroWalletOps: spec_id = 'transfer_spec' spec_key = ( (1,'source'), ) + class new(sweep): + name = 'new' + desc = 'New' + past = 'address created' + spec_id = 'newaddr_spec' + spec_key = ( (1,'source'), ) + + async def main(self): + h = self.rpc(self,self.source) + await h.open_wallet('Monero',refresh=True) + label = '{a} [{b}]'.format( + a = self.label or f"xmrwallet new {'account' if self.account == None else 'address'}", + b = make_timestr() ) + if self.account == None: + acct,addr = await h.create_acct(label=label) + else: + msg_r('\n Account index: {}'.format( pink(str(self.account)) )) + addr = await h.create_new_addr(self.account,label=label) + + accts_data = (await h.get_accts())[0] + + if self.account != None: + await h.print_addrs(accts_data,self.account) + + # wallet must be left open: otherwise the 'stop_wallet' RPC call used to stop the daemon will fail + if uopt.no_stop_wallet_daemon: + await h.close_wallet('Monero') + + msg('') + class relay(base): name = 'relay' desc = 'Relay' diff --git a/test/test_py_d/ts_xmrwallet.py b/test/test_py_d/ts_xmrwallet.py index 4564cad9..f490f9a8 100755 --- a/test/test_py_d/ts_xmrwallet.py +++ b/test/test_py_d/ts_xmrwallet.py @@ -53,6 +53,10 @@ class TestSuiteXMRWallet(TestSuiteBase): ('fund_alice', 'sending funds'), ('sync_wallets_all', 'syncing all wallets'), + ('new_account_alice', 'creating a new account (Alice)'), + ('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)'), ('sync_wallets_selected', 'syncing selected wallets'), ('sweep_to_address_proxy', 'sweeping to new address (via TX relay + proxy)'), @@ -304,6 +308,47 @@ class TestSuiteXMRWallet(TestSuiteBase): t.expect('Address: ') return t + def new_addr_alice(self,spec,cfg,expect): + data = self.users['alice'] + t = self.spawn( + 'mmgen-xmrwallet', + self.extra_opts + + [f'--wallet-dir={data.udir}'] + + [f'--daemon=localhost:{data.md.rpc_port}'] + + (['--no-start-wallet-daemon'] if cfg in ('continue','stop') else []) + + (['--no-stop-wallet-daemon'] if cfg in ('start','continue') else []) + + [ '--alice', 'new', data.kafile, spec ] ) + res = strip_ansi_escapes(t.read()).replace('\r','') + m = re.search(expect,res,re.DOTALL) + assert m, m + return t + + na_idx = 1 + + def new_account_alice(self): + return self.new_addr_alice( + '4', + 'start', + fr'Creating new account.*Index:\s+{self.na_idx}\s') + + def new_account_alice_label(self): + return self.new_addr_alice( + '4,Alice’s new account', + 'continue', + fr'Creating new account.*Index:\s+{self.na_idx+1}\s.*Alice’s new account') + + def new_address_alice(self): + return self.new_addr_alice( + '4:2', + 'continue', + fr'Account index:\s+2\s+Creating new address' ) + + def new_address_alice_label(self): + return self.new_addr_alice( + '4:2,Alice’s new address', + 'stop', + fr'Account index:\s+2\s+Creating new address.*Alice’s new address' ) + async def mine_initial_coins(self): await self.open_wallet_user('miner',1) return await self.mine_chk('miner',1,0,lambda x: x > 20,'unlocked balance > 20')