mmgen-xmrwallet: new transfer operation

This commit is contained in:
The MMGen Project 2021-06-11 13:20:51 +00:00
commit 4f631a1068
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
3 changed files with 107 additions and 21 deletions

View file

@ -29,9 +29,10 @@ opts_data = {
'desc': """Perform various Monero wallet operations for addresses
in an MMGen XMR key-address file""",
'usage2': [
'[opts] create <xmr_keyaddrfile> [wallets]',
'[opts] sync <xmr_keyaddrfile> [wallets]',
'[opts] sweep <xmr_keyaddrfile> <sweep_spec>',
'[opts] create <xmr_keyaddrfile> [wallets]',
'[opts] sync <xmr_keyaddrfile> [wallets]',
'[opts] transfer <xmr_keyaddrfile> <transfer_spec>',
'[opts] sweep <xmr_keyaddrfile> <sweep_spec>',
],
'options': """
-h, --help Print this help message
@ -61,10 +62,12 @@ may point to a SOCKS proxy, in which case HOST may be a Tor onion address.
SUPPORTED OPERATIONS
create - create wallet for all or specified addresses in key-address file
sync - sync wallet for all or specified addresses in key-address file
sweep - sweep funds in specified wallet:account to new address in same
account or new account in another wallet
create - create wallet for all or specified addresses in key-address file
sync - sync wallet for all or specified addresses in key-address file
transfer - transfer specified XMR amount to specified address from specified
wallet:account
sweep - sweep funds in specified wallet:account to new address in same
account or new account in another wallet
CREATE AND SYNC OPERATION NOTES
@ -75,6 +78,17 @@ key-address file, each corresponding to a Monero wallet to be created or
synced. If omitted, all wallets are operated upon.
TRANSFER OPERATION NOTES
The transfer operation takes a `transfer specifier` arg with the following
format:
SOURCE:ACCOUNT:ADDRESS,AMOUNT
where SOURCE is a wallet index; ACCOUNT the source account index; and ADDRESS
and AMOUNT the destination Monero address and XMR amount, respectively.
SWEEP OPERATION NOTES
The sweep operation takes a `sweep specifier` arg with the following format:
@ -126,7 +140,7 @@ if op in ('create','sync'):
opts.usage()
if cmd_args:
wallets = cmd_args[0]
elif op == 'sweep':
elif op in ('transfer','sweep'):
if len(cmd_args) != 1:
opts.usage()
spec = cmd_args[0]

View file

@ -26,11 +26,13 @@ from .common import *
from .addr import KeyAddrList,AddrIdxList
from .rpc import MoneroRPCClientRaw, MoneroWalletRPCClient
from .daemon import MoneroWalletDaemon
from .protocol import _b58a
xmrwallet_uarg_info = (
lambda e,hp: {
'daemon': e('HOST:PORT', hp),
'tx_relay_daemon': e('HOST:PORT[:PROXY_HOST:PROXY_PORT]', r'({p})(?::({p}))?'.format(p=hp)),
'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+))?'),
})(
namedtuple('uarg_info_entry',['annot','pat']),
@ -39,7 +41,7 @@ xmrwallet_uarg_info = (
class MoneroWalletOps:
ops = ('create','sync','sweep')
ops = ('create','sync','transfer','sweep')
class base:
@ -155,6 +157,20 @@ class MoneroWalletOps:
hl_amt(fee),
))
async def make_transfer_tx(self,account,addr,amt):
res = await self.c.call(
'transfer',
account_index = account,
destinations = [{
'amount': amt.to_unit('atomic'),
'address': addr
}],
do_not_relay = True,
get_tx_metadata = True
)
self.display_tx( res['tx_hash'], res['amount'], res['fee'] )
return res['tx_metadata']
async def make_sweep_tx(self,account,addr):
res = await self.c.call(
'sweep_all',
@ -438,6 +454,11 @@ class MoneroWalletOps:
self.addr_data = list(gen())
self.account = int(m[2])
if self.name == 'transfer':
from mmgen.obj import CoinAddr
self.dest_addr = CoinAddr(self.proto,m[3])
self.amount = self.proto.coin_amt(m[4])
def post_init(self):
if uarg.tx_relay_daemon:
@ -467,7 +488,8 @@ class MoneroWalletOps:
async def process_wallets(self):
gmsg(f'\n{self.desc}ing account #{self.account} of wallet {self.source.idx}' + (
' to new address' if self.dest is None
f': {self.amount} XMR to {self.dest_addr}' if self.name == 'transfer'
else ' to new address' if self.dest == None
else f' to new account in wallet {self.dest.idx}' ))
h = self.rpc(self,self.source)
@ -481,7 +503,9 @@ class MoneroWalletOps:
await h.get_addrs(accts_data,self.account)
if self.dest == None:
if self.name == 'transfer':
new_addr = self.dest_addr
elif self.dest == None:
if keypress_confirm(f'\nCreate new address for account #{self.account}?'):
new_addr = await h.create_new_addr(self.account)
elif keypress_confirm(f'Sweep to last existing address of account #{self.account}?'):
@ -507,12 +531,14 @@ class MoneroWalletOps:
await h2.close_wallet('destination')
await h.open_wallet('source')
msg('\n Creating sweep transaction: balance of wallet {}, account #{} => {}'.format(
self.source.idx,
self.account,
cyan(new_addr),
))
tx_metadata = await h.make_sweep_tx(self.account,new_addr)
msg_r(f'\n Creating {self.name} transaction: wallet {self.source.idx}, account #{self.account}')
if self.name == 'transfer':
msg(f', {self.amount} XMR => {cyan(new_addr)}')
tx_metadata = await h.make_transfer_tx(self.account,new_addr,self.amount)
elif self.name == 'sweep':
msg(f' => {cyan(new_addr)}')
tx_metadata = await h.make_sweep_tx(self.account,new_addr)
if keypress_confirm(f'\nRelay {self.name} transaction?'):
w_desc = 'source'
@ -533,3 +559,10 @@ class MoneroWalletOps:
die(1,'\nExiting at user request')
return True
class transfer(sweep):
name = 'transfer'
desc = 'Transfer'
past = 'transferred'
spec_id = 'transfer_spec'
spec_key = ( (1,'source'), )

View file

@ -61,6 +61,13 @@ class TestSuiteXMRWallet(TestSuiteBase):
('sweep_to_address_noproxy', 'sweeping to new address (via TX relay)'),
('mine_blocks', 'mining blocks'),
('transfer_to_miner_proxy', 'transferring funds to Miner (via TX relay + proxy)'),
('mine_blocks_extra', 'mining blocks'),
('sync_wallet_2', 'syncing Alice’s wallet #2'),
('transfer_to_miner_noproxy', 'transferring funds to Miner (via TX relay)'),
('mine_blocks', 'mining blocks'),
)
def __init__(self,trunner,cfgs,spawn):
@ -197,7 +204,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
'wd_rpc',
])
for user,sid,shift,kal_range in ( # kal_range must be None, a single digit, or a single hyphenated range
('miner', '98831F3A', 130, '1'),
('miner', '98831F3A', 130, '1-2'),
('bob', '1378FC64', 140, None),
('alice', 'FE3C6545', 150, '1-4'),
):
@ -320,6 +327,9 @@ class TestSuiteXMRWallet(TestSuiteBase):
def sync_wallets_selected(self):
return self.sync_wallets(wallets='1,3-4')
def sync_wallet_2(self):
return self.sync_wallets(wallets='2')
def sync_wallets(self,wallets=None):
data = self.users['alice']
dir_opt = [f'--outdir={data.udir}']
@ -376,6 +386,19 @@ class TestSuiteXMRWallet(TestSuiteBase):
self.set_dest('alice',2,1,lambda x: x > 1,'unlocked balance > 1')
return ret
def transfer_to_miner_proxy(self):
addr = read_from_file(self.users['miner'].addrfile_fs.format(2))
amt = '0.135'
ret = self.do_op('transfer','alice',f'2:1:{addr},{amt}',self.tx_relay_daemon_proxy_parm)
self.set_dest('miner',2,0,lambda x: str(x) == amt,f'unlocked balance == {amt}')
return ret
def transfer_to_miner_noproxy(self):
addr = read_from_file(self.users['miner'].addrfile_fs.format(2))
ret = self.do_op('transfer','alice',f'2:1:{addr},0.0995',self.tx_relay_daemon_parm)
self.set_dest('miner',2,0,lambda x: str(x) == '0.2345','unlocked balance == 0.2345')
return ret
# wallet methods
async def open_wallet_user(self,user,wnum):
@ -420,7 +443,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
ret = await self.users['miner'].md_rpc.call('stop_mining')
return self.get_status(ret)
async def mine_blocks(self,random_txs=None):
async def mine_blocks(self,random_txs=None,extra_blocks=None):
"""
- open destination wallet
- optionally create and broadcast random TXs
@ -447,6 +470,18 @@ class TestSuiteXMRWallet(TestSuiteBase):
else:
die(2,'Restart attempt limit exceeded')
async def mine_extra_blocks():
h_start = await get_height()
imsg_r(f'[+{extra_blocks} blocks]: ')
oqmsg_r('|')
while True:
await asyncio.sleep(2)
h = await get_height()
imsg_r(f'{h} ')
oqmsg_r('+')
if h - h_start > extra_blocks:
break
async def send_random_txs():
from mmgen.tool import tool_api
t = tool_api()
@ -480,8 +515,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
self.do_msg(extra_desc=f'+{random_txs} random TXs' if random_txs else None)
if self.dest.user != 'miner':
await self.open_wallet_user(self.dest.user,self.dest.wnum)
await self.open_wallet_user(self.dest.user,self.dest.wnum)
if random_txs:
await send_random_txs()
@ -494,6 +528,8 @@ class TestSuiteXMRWallet(TestSuiteBase):
while True:
ub = await get_balance(self.dest)
if self.dest.test(ub):
if extra_blocks:
await mine_extra_blocks()
imsg('')
oqmsg_r('+')
print_balance(self.dest,ub)
@ -515,6 +551,9 @@ class TestSuiteXMRWallet(TestSuiteBase):
async def mine_blocks_tx(self):
return await self.mine_blocks(random_txs=self.dfl_random_txs)
async def mine_blocks_extra(self):
return await self.mine_blocks(extra_blocks=100) # TODO: 100 is arbitrary value
# util methods
def get_status(self,ret):