Asynchronous HTTP significantly speeds up operations involving multiple
JSON-RPC calls to the server, such as tracking wallet views for wallets
with a large number of outputs.
This patch adds base-level asyncio infrastructure plus aiohttp support to all
applicable MMGen commands.
The aiohttp package is not currently supported by MSYS2, so Windows users will
have to choose one of the other backends ('curl' is the default).
Tested on: Linux, Armbian, Windows; Python 3.6, 3.7, 3.8
New user features:
- configurable RPC backends via the 'rpc_backend' option. Supported
options are 'aiohttp' (Linux-only), 'httplib', 'requests' and 'curl'
- configurable RPC queue size via the 'aiohttp_rpc_queue_len' option
The patch also includes a rewrite/redesign of large parts of the MMGen code
base, most importantly:
- rpc.py - full rewrite of RPC library, new RPCBackends class
- main_addrimport.py - full rewrite
- main_autosign.py - LED code now handled by new LEDControl class
- eth/tw.py, eth/tx.py - reworked logic for resolving token symbols and
addresses
- eth/tx.py - separate classes for signed and unsigned transactions
Testing:
# Set a backend (choose one):
$ export MMGEN_RPC_BACKEND='aiohttp' # Linux-only
$ export MMGEN_RPC_BACKEND='curl' # Windows
$ export MMGEN_RPC_BACKEND='httplib' # compare performance with 'aiohttp'
# Bitcoin:
$ test/unit_tests.py rpc btc
$ test/test.py main regtest autosign
# Ethereum:
$ test/unit_tests.py rpc eth
$ test/tooltest2.py --coin=eth --testnet=1 txview
$ test/test.py --coin=eth ethdev
# Monero wallet:
$ test/unit_tests.py rpc xmr_wallet
$ test/test-release.sh -F xmr
929 lines
35 KiB
Python
Executable file
929 lines
35 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
#
|
|
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
|
# Copyright (C)2013-2020 The MMGen Project <mmgen@tuta.io>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""
|
|
ts_ethdev.py: Ethdev tests for the test.py test suite
|
|
"""
|
|
|
|
import sys,os,re,shutil
|
|
from decimal import Decimal
|
|
from subprocess import run,PIPE,DEVNULL
|
|
|
|
from mmgen.globalvars import g
|
|
from mmgen.opts import opt
|
|
from mmgen.util import die
|
|
from mmgen.exception import *
|
|
from ..include.common import *
|
|
from .common import *
|
|
|
|
del_addrs = ('4','1')
|
|
dfl_sid = '98831F3A'
|
|
|
|
# The Parity dev address with lots of coins. Create with "ethkey -b info ''":
|
|
dfl_addr = '00a329c0648769a73afac7f9381e08fb43dbea72'
|
|
dfl_addr_chk = '00a329c0648769A73afAc7F9381E08FB43dBEA72'
|
|
dfl_privkey = '4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7'
|
|
burn_addr = 'deadbeef'*5
|
|
amt1 = '999999.12345689012345678'
|
|
amt2 = '888.111122223333444455'
|
|
|
|
parity_pid_fn = 'parity.pid'
|
|
parity_key_fn = 'parity.devkey'
|
|
|
|
# Token sends require varying amounts of gas, depending on compiler version
|
|
def get_solc_ver():
|
|
try: cp = run(['solc','--version'],stdout=PIPE)
|
|
except: return None
|
|
|
|
if cp.returncode:
|
|
return None
|
|
|
|
line = cp.stdout.decode().splitlines()[1]
|
|
m = re.search(r'Version:\s*(\d+)\.(\d+)\.(\d+)',line)
|
|
return '.'.join(m.groups()) if m else None
|
|
|
|
solc_ver = get_solc_ver()
|
|
|
|
if solc_ver == '0.5.1':
|
|
vbal1 = '1.2288337'
|
|
vbal1a = 'TODO'
|
|
vbal2 = '99.997085083'
|
|
vbal3 = '1.23142165'
|
|
vbal4 = '127.0287837'
|
|
else: # 0.5.3 or precompiled 0.5.3
|
|
vbal1 = '1.2288487'
|
|
vbal1a = '1.22627465'
|
|
vbal2 = '99.997092733'
|
|
vbal3 = '1.23142915'
|
|
vbal4 = '127.0287987'
|
|
|
|
bals = {
|
|
'1': [ ('98831F3A:E:1','123.456')],
|
|
'2': [ ('98831F3A:E:1','123.456'),('98831F3A:E:11','1.234')],
|
|
'3': [ ('98831F3A:E:1','123.456'),('98831F3A:E:11','1.234'),('98831F3A:E:21','2.345')],
|
|
'4': [ ('98831F3A:E:1','100'),
|
|
('98831F3A:E:2','23.45495'),
|
|
('98831F3A:E:11','1.234'),
|
|
('98831F3A:E:21','2.345')],
|
|
'5': [ ('98831F3A:E:1','100'),
|
|
('98831F3A:E:2','23.45495'),
|
|
('98831F3A:E:11','1.234'),
|
|
('98831F3A:E:21','2.345'),
|
|
(burn_addr + '\s+Non-MMGen',amt1)],
|
|
'8': [ ('98831F3A:E:1','0'),
|
|
('98831F3A:E:2','23.45495'),
|
|
('98831F3A:E:11',vbal1,'a1'),
|
|
('98831F3A:E:12','99.99895'),
|
|
('98831F3A:E:21','2.345'),
|
|
(burn_addr + '\s+Non-MMGen',amt1)],
|
|
'9': [ ('98831F3A:E:1','0'),
|
|
('98831F3A:E:2','23.45495'),
|
|
('98831F3A:E:11',vbal1,'a1'),
|
|
('98831F3A:E:12',vbal2),
|
|
('98831F3A:E:21','2.345'),
|
|
(burn_addr + '\s+Non-MMGen',amt1)],
|
|
'10': [ ('98831F3A:E:1','0'),
|
|
('98831F3A:E:2','23.0218'),
|
|
('98831F3A:E:3','0.4321'),
|
|
('98831F3A:E:11',vbal1,'a1'),
|
|
('98831F3A:E:12',vbal2),
|
|
('98831F3A:E:21','2.345'),
|
|
(burn_addr + '\s+Non-MMGen',amt1)]
|
|
}
|
|
|
|
token_bals = {
|
|
'1': [ ('98831F3A:E:11','1000','1.234')],
|
|
'2': [ ('98831F3A:E:11','998.76544',vbal3,'a1'),
|
|
('98831F3A:E:12','1.23456','0')],
|
|
'3': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a1'),
|
|
('98831F3A:E:12','1.23456','0')],
|
|
'4': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a1'),
|
|
('98831F3A:E:12','1.23456','0'),
|
|
(burn_addr + '\s+Non-MMGen',amt2,amt1)],
|
|
'5': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a1'),
|
|
('98831F3A:E:12','1.23456','99.99895'),
|
|
(burn_addr + '\s+Non-MMGen',amt2,amt1)],
|
|
'6': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a1'),
|
|
('98831F3A:E:12','0',vbal2),
|
|
('98831F3A:E:13','1.23456','0'),
|
|
(burn_addr + '\s+Non-MMGen',amt2,amt1)],
|
|
'7': [ ('98831F3A:E:11','67.444317776666555545',vbal1a,'a2'),
|
|
('98831F3A:E:12','43.21',vbal2),
|
|
('98831F3A:E:13','1.23456','0'),
|
|
(burn_addr + '\s+Non-MMGen',amt2,amt1)]
|
|
}
|
|
token_bals_getbalance = {
|
|
'1': (vbal4,'999999.12345689012345678'),
|
|
'2': ('111.888877776666555545','888.111122223333444455')
|
|
}
|
|
|
|
from .ts_base import *
|
|
from .ts_shared import *
|
|
|
|
class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
|
|
'Ethereum transacting, token deployment and tracking wallet operations'
|
|
networks = ('eth','etc')
|
|
passthru_opts = ('coin',)
|
|
tmpdir_nums = [22]
|
|
solc_vers = ('0.5.1','0.5.3') # 0.5.1: Raspbian Stretch, 0.5.3: Ubuntu Bionic
|
|
cmd_group = (
|
|
('setup', 'Ethereum Parity dev mode tests for coin {} (start parity)'.format(g.coin)),
|
|
('wallet_upgrade1', 'upgrading the tracking wallet (v1 -> v2)'),
|
|
('wallet_upgrade2', 'upgrading the tracking wallet (v2 -> v3)'),
|
|
('addrgen', 'generating addresses'),
|
|
('addrimport', 'importing addresses'),
|
|
('addrimport_dev_addr', "importing Parity dev address 'Ox00a329c..'"),
|
|
|
|
('txcreate1', 'creating a transaction (spend from dev address to address :1)'),
|
|
('txsign1', 'signing the transaction'),
|
|
('tx_status0_bad', 'getting the transaction status'),
|
|
('txsign1_ni', 'signing the transaction (non-interactive)'),
|
|
('txsend1', 'sending the transaction'),
|
|
('bal1', 'the {} balance'.format(g.coin)),
|
|
|
|
('txcreate2', 'creating a transaction (spend from dev address to address :11)'),
|
|
('txsign2', 'signing the transaction'),
|
|
('txsend2', 'sending the transaction'),
|
|
('bal2', 'the {} balance'.format(g.coin)),
|
|
|
|
('txcreate3', 'creating a transaction (spend from dev address to address :21)'),
|
|
('txsign3', 'signing the transaction'),
|
|
('txsend3', 'sending the transaction'),
|
|
('bal3', 'the {} balance'.format(g.coin)),
|
|
|
|
('tx_status1', 'getting the transaction status'),
|
|
|
|
('txcreate4', 'creating a transaction (spend from MMGen address, low TX fee)'),
|
|
('txbump', 'bumping the transaction fee'),
|
|
|
|
('txsign4', 'signing the transaction'),
|
|
('txsend4', 'sending the transaction'),
|
|
('tx_status1a', 'getting the transaction status'),
|
|
('bal4', 'the {} balance'.format(g.coin)),
|
|
|
|
('txcreate5', 'creating a transaction (fund burn address)'),
|
|
('txsign5', 'signing the transaction'),
|
|
('txsend5', 'sending the transaction'),
|
|
|
|
('addrimport_burn_addr',"importing burn address"),
|
|
('bal5', 'the {} balance'.format(g.coin)),
|
|
|
|
('add_label1', 'adding a UTF-8 label (zh)'),
|
|
('chk_label1', 'the label'),
|
|
('add_label2', 'adding a UTF-8 label (lat+cyr+gr)'),
|
|
('chk_label2', 'the label'),
|
|
('remove_label', 'removing the label'),
|
|
|
|
('token_compile1', 'compiling ERC20 token #1'),
|
|
|
|
('token_deploy1a', 'deploying ERC20 token #1 (SafeMath)'),
|
|
('token_deploy1b', 'deploying ERC20 token #1 (Owned)'),
|
|
('token_deploy1c', 'deploying ERC20 token #1 (Token)'),
|
|
|
|
('tx_status2', 'getting the transaction status'),
|
|
('bal6', 'the {} balance'.format(g.coin)),
|
|
|
|
('token_compile2', 'compiling ERC20 token #2'),
|
|
|
|
('token_deploy2a', 'deploying ERC20 token #2 (SafeMath)'),
|
|
('token_deploy2b', 'deploying ERC20 token #2 (Owned)'),
|
|
('token_deploy2c', 'deploying ERC20 token #2 (Token)'),
|
|
|
|
('contract_deploy', 'deploying contract (create,sign,send)'),
|
|
|
|
('token_fund_users', 'transferring token funds from dev to user'),
|
|
('token_user_bals', 'show balances after transfer'),
|
|
('token_addrgen', 'generating token addresses'),
|
|
('token_addrimport_badaddr1','importing token addresses (no token address)'),
|
|
('token_addrimport_badaddr2','importing token addresses (bad token address)'),
|
|
('token_addrimport', 'importing token addresses'),
|
|
('token_addrimport_batch','importing token addresses (dummy batch mode)'),
|
|
|
|
('bal7', 'the {} balance'.format(g.coin)),
|
|
('token_bal1', 'the {} balance and token balance'.format(g.coin)),
|
|
|
|
('token_txcreate1', 'creating a token transaction'),
|
|
('token_txsign1', 'signing the transaction'),
|
|
('token_txsend1', 'sending the transaction'),
|
|
('tx_status3', 'getting the transaction status'),
|
|
('token_bal2', 'the {} balance and token balance'.format(g.coin)),
|
|
|
|
('token_txcreate2', 'creating a token transaction (to burn address)'),
|
|
('token_txbump', 'bumping the transaction fee'),
|
|
|
|
('token_txsign2', 'signing the transaction'),
|
|
('token_txsend2', 'sending the transaction'),
|
|
('token_bal3', 'the {} balance and token balance'.format(g.coin)),
|
|
|
|
('del_dev_addr', "deleting the dev address"),
|
|
|
|
('bal1_getbalance', 'the {} balance (getbalance)'.format(g.coin)),
|
|
|
|
('addrimport_token_burn_addr',"importing the token burn address"),
|
|
|
|
('token_bal4', 'the {} balance and token balance'.format(g.coin)),
|
|
('token_bal_getbalance','the token balance (getbalance)'),
|
|
|
|
('txcreate_noamt', 'creating a transaction (full amount send)'),
|
|
('txsign_noamt', 'signing the transaction'),
|
|
('txsend_noamt', 'sending the transaction'),
|
|
|
|
('bal8', 'the {} balance'.format(g.coin)),
|
|
('token_bal5', 'the token balance'),
|
|
|
|
('token_txcreate_noamt', 'creating a token transaction (full amount send)'),
|
|
('token_txsign_noamt', 'signing the transaction'),
|
|
('token_txsend_noamt', 'sending the transaction'),
|
|
|
|
('bal9', 'the {} balance'.format(g.coin)),
|
|
('token_bal6', 'the token balance'),
|
|
|
|
('listaddresses1', 'listaddresses'),
|
|
('listaddresses2', 'listaddresses minconf=999999999 (ignored)'),
|
|
('listaddresses3', 'listaddresses sort=age (ignored)'),
|
|
('listaddresses4', 'listaddresses showempty=1 sort=age (ignored)'),
|
|
|
|
('token_listaddresses1','listaddresses --token=mm1'),
|
|
('token_listaddresses2','listaddresses --token=mm1 showempty=1'),
|
|
|
|
('twview_cached_balances','twview (cached balances)'),
|
|
('token_twview_cached_balances','token twview (cached balances)'),
|
|
('txcreate_cached_balances','txcreate (cached balances)'),
|
|
('token_txcreate_cached_balances','token txcreate (cached balances)'),
|
|
|
|
('txdo_cached_balances', 'txdo (cached balances)'),
|
|
('txcreate_refresh_balances','refreshing balances'),
|
|
('bal10', 'the {} balance'.format(g.coin)),
|
|
|
|
('token_txdo_cached_balances', 'token txdo (cached balances)'),
|
|
('token_txcreate_refresh_balances','refreshing token balances'),
|
|
('token_bal7', 'the token balance'),
|
|
|
|
('twview1','twview'),
|
|
('twview2','twview wide=1'),
|
|
('twview3','twview wide=1 sort=age (ignored)'),
|
|
('twview4','twview wide=1 minconf=999999999 (ignored)'),
|
|
('twview5','twview wide=1 minconf=0 (ignored)'),
|
|
|
|
('token_twview1','twview --token=mm1'),
|
|
('token_twview2','twview --token=mm1 wide=1'),
|
|
('token_twview3','twview --token=mm1 wide=1 sort=age (ignored)'),
|
|
|
|
('edit_label1','adding label to addr #{} in {} tracking wallet (zh)'.format(del_addrs[0],g.coin)),
|
|
('edit_label2','adding label to addr #{} in {} tracking wallet (lat+cyr+gr)'.format(del_addrs[1],g.coin)),
|
|
('edit_label3','removing label from addr #{} in {} tracking wallet'.format(del_addrs[0],g.coin)),
|
|
|
|
('token_edit_label1','adding label to addr #{} in {} token tracking wallet'.format(del_addrs[0],g.coin)),
|
|
|
|
('remove_addr1','removing addr #{} from {} tracking wallet'.format(del_addrs[0],g.coin)),
|
|
('remove_addr2','removing addr #{} from {} tracking wallet'.format(del_addrs[1],g.coin)),
|
|
('token_remove_addr1','removing addr #{} from {} token tracking wallet'.format(del_addrs[0],g.coin)),
|
|
('token_remove_addr2','removing addr #{} from {} token tracking wallet'.format(del_addrs[1],g.coin)),
|
|
|
|
('stop', 'stopping parity'),
|
|
)
|
|
|
|
def __init__(self,trunner,cfgs,spawn):
|
|
from mmgen.daemon import CoinDaemon
|
|
self.rpc_port = CoinDaemon(g.coin,test_suite=True).rpc_port
|
|
os.environ['MMGEN_BOGUS_WALLET_DATA'] = ''
|
|
return TestSuiteBase.__init__(self,trunner,cfgs,spawn)
|
|
|
|
@property
|
|
def eth_args(self):
|
|
return ['--outdir={}'.format(self.tmpdir),'--coin='+g.coin,'--rpc-port={}'.format(self.rpc_port),'--quiet']
|
|
|
|
def setup(self):
|
|
self.spawn('',msg_only=True)
|
|
if solc_ver in self.solc_vers:
|
|
imsg('Found solc version {}'.format(solc_ver))
|
|
else:
|
|
imsg('Solc compiler {}. Using precompiled contract data'.format(
|
|
'version {} not supported by test suite'.format(solc_ver)
|
|
if solc_ver else 'not found' ))
|
|
srcdir = os.path.join(self.tr.repo_root,'test','ref','ethereum','bin')
|
|
from shutil import copytree
|
|
for d in ('mm1','mm2'):
|
|
copytree(os.path.join(srcdir,d),os.path.join(self.tmpdir,d))
|
|
restart_test_daemons(g.coin)
|
|
return 'ok'
|
|
|
|
def wallet_upgrade(self,src_file):
|
|
if g.coin == 'ETC':
|
|
msg('skipping test {!r} for ETC'.format(self.test_name))
|
|
return 'skip'
|
|
src_dir = joinpath(ref_dir,'ethereum')
|
|
dest_dir = joinpath(self.tr.data_dir,'altcoins',g.coin.lower())
|
|
w_from = joinpath(src_dir,src_file)
|
|
w_to = joinpath(dest_dir,'tracking-wallet.json')
|
|
os.makedirs(dest_dir,mode=0o750,exist_ok=True)
|
|
dest = shutil.copy2(w_from,w_to)
|
|
assert dest == w_to, dest
|
|
t = self.spawn('mmgen-tool', self.eth_args + ['twview'])
|
|
t.read()
|
|
os.unlink(w_to)
|
|
return t
|
|
|
|
def wallet_upgrade1(self): return self.wallet_upgrade('tracking-wallet-v1.json')
|
|
def wallet_upgrade2(self): return self.wallet_upgrade('tracking-wallet-v2.json')
|
|
|
|
def addrgen(self,addrs='1-3,11-13,21-23'):
|
|
from mmgen.addr import MMGenAddrType
|
|
t = self.spawn('mmgen-addrgen', self.eth_args + [dfl_words_file,addrs])
|
|
t.written_to_file('Addresses')
|
|
t.read()
|
|
return t
|
|
|
|
def addrimport(self,ext='21-23]{}.addrs',expect='9/9',add_args=[],bad_input=False):
|
|
ext = ext.format('-α' if g.debug_utf8 else '')
|
|
fn = self.get_file_with_ext(ext,no_dot=True,delete=False)
|
|
t = self.spawn('mmgen-addrimport', self.eth_args[1:-1] + add_args + [fn])
|
|
if bad_input:
|
|
t.read()
|
|
return t
|
|
t.expect('Importing')
|
|
t.expect(expect)
|
|
t.read()
|
|
return t
|
|
|
|
def addrimport_one_addr(self,addr=None,extra_args=[]):
|
|
t = self.spawn('mmgen-addrimport', self.eth_args[1:] + extra_args + ['--address='+addr])
|
|
t.expect('OK')
|
|
return t
|
|
|
|
def addrimport_dev_addr(self):
|
|
return self.addrimport_one_addr(addr=dfl_addr)
|
|
|
|
def addrimport_burn_addr(self):
|
|
return self.addrimport_one_addr(addr=burn_addr)
|
|
|
|
def txcreate(self,args=[],menu=[],acct='1',non_mmgen_inputs=0,caller='txcreate',
|
|
interactive_fee = '50G',
|
|
eth_fee_res = None,
|
|
fee_res_fs = '0.00105 {} (50 gas price in Gwei)',
|
|
fee_desc = 'gas price',
|
|
no_read = False):
|
|
fee_res = fee_res_fs.format(g.coin)
|
|
t = self.spawn('mmgen-'+caller, self.eth_args + ['-B'] + args)
|
|
t.expect(r'add \[l\]abel, .*?:.','p', regex=True)
|
|
t.written_to_file('Account balances listing')
|
|
t = self.txcreate_ui_common( t, menu=menu, caller=caller,
|
|
input_sels_prompt = 'to spend from',
|
|
inputs = acct,
|
|
file_desc = 'Ethereum transaction',
|
|
bad_input_sels = True,
|
|
non_mmgen_inputs = non_mmgen_inputs,
|
|
interactive_fee = interactive_fee,
|
|
fee_res = fee_res,
|
|
fee_desc = fee_desc,
|
|
eth_fee_res = eth_fee_res,
|
|
add_comment = tx_label_jp )
|
|
if not no_read:
|
|
t.read()
|
|
return t
|
|
|
|
def txsign(self,ni=False,ext='{}.rawtx',add_args=[]):
|
|
ext = ext.format('-α' if g.debug_utf8 else '')
|
|
keyfile = joinpath(self.tmpdir,parity_key_fn)
|
|
write_to_file(keyfile,dfl_privkey+'\n')
|
|
txfile = self.get_file_with_ext(ext,no_dot=True)
|
|
t = self.spawn( 'mmgen-txsign',
|
|
['--outdir={}'.format(self.tmpdir),'--coin='+g.coin,'--quiet']
|
|
+ ['--rpc-host=bad_host'] # ETH signing must work without RPC
|
|
+ add_args
|
|
+ ([],['--yes'])[ni]
|
|
+ ['-k', keyfile, txfile, dfl_words_file] )
|
|
return self.txsign_ui_common(t,ni=ni,has_label=True)
|
|
|
|
def txsend(self,ni=False,bogus_send=False,ext='{}.sigtx',add_args=[]):
|
|
ext = ext.format('-α' if g.debug_utf8 else '')
|
|
txfile = self.get_file_with_ext(ext,no_dot=True)
|
|
if not bogus_send: os.environ['MMGEN_BOGUS_SEND'] = ''
|
|
t = self.spawn('mmgen-txsend', self.eth_args + add_args + [txfile])
|
|
if not bogus_send: os.environ['MMGEN_BOGUS_SEND'] = '1'
|
|
txid = self.txsend_ui_common(t,quiet=not g.debug,bogus_send=bogus_send,has_label=True)
|
|
return t
|
|
|
|
def txcreate1(self):
|
|
# valid_keypresses = EthereumTwUnspentOutputs.key_mappings.keys()
|
|
menu = ['a','d','r','M','X','e','m','m'] # include one invalid keypress, 'X'
|
|
args = ['98831F3A:E:1,123.456']
|
|
return self.txcreate(args=args,menu=menu,acct='1',non_mmgen_inputs=1)
|
|
|
|
def txsign1(self): return self.txsign(add_args=['--use-internal-keccak-module'])
|
|
def tx_status0_bad(self):
|
|
return self.tx_status(ext='{}.sigtx',expect_str='neither in mempool nor blockchain',exit_val=1)
|
|
def txsign1_ni(self): return self.txsign(ni=True)
|
|
def txsend1(self): return self.txsend()
|
|
def bal1(self): return self.bal(n='1')
|
|
|
|
def txcreate2(self):
|
|
args = ['98831F3A:E:11,1.234']
|
|
return self.txcreate(args=args,acct='10',non_mmgen_inputs=1)
|
|
def txsign2(self): return self.txsign(ni=True,ext='1.234,50000]{}.rawtx')
|
|
def txsend2(self): return self.txsend(ext='1.234,50000]{}.sigtx')
|
|
def bal2(self): return self.bal(n='2')
|
|
|
|
def txcreate3(self):
|
|
args = ['98831F3A:E:21,2.345']
|
|
return self.txcreate(args=args,acct='10',non_mmgen_inputs=1)
|
|
def txsign3(self): return self.txsign(ni=True,ext='2.345,50000]{}.rawtx')
|
|
def txsend3(self): return self.txsend(ext='2.345,50000]{}.sigtx')
|
|
def bal3(self): return self.bal(n='3')
|
|
|
|
def tx_status(self,ext,expect_str,expect_str2='',add_args=[],exit_val=0):
|
|
ext = ext.format('-α' if g.debug_utf8 else '')
|
|
txfile = self.get_file_with_ext(ext,no_dot=True)
|
|
t = self.spawn('mmgen-txsend', self.eth_args + add_args + ['--status',txfile])
|
|
t.expect(expect_str)
|
|
if expect_str2:
|
|
t.expect(expect_str2)
|
|
t.read()
|
|
t.req_exit_val = exit_val
|
|
return t
|
|
|
|
def tx_status1(self):
|
|
return self.tx_status(ext='2.345,50000]{}.sigtx',expect_str='has 1 confirmation')
|
|
|
|
def tx_status1a(self):
|
|
return self.tx_status(ext='2.345,50000]{}.sigtx',expect_str='has 2 confirmations')
|
|
|
|
def txcreate4(self):
|
|
args = ['98831F3A:E:2,23.45495']
|
|
interactive_fee='40G'
|
|
fee_res_fs='0.00084 {} (40 gas price in Gwei)'
|
|
return self.txcreate( args = args,
|
|
acct = '1',
|
|
non_mmgen_inputs = 0,
|
|
interactive_fee = interactive_fee,
|
|
fee_res_fs = fee_res_fs,
|
|
eth_fee_res = True)
|
|
|
|
def txbump(self,ext=',40000]{}.rawtx',fee='50G',add_args=[]):
|
|
ext = ext.format('-α' if g.debug_utf8 else '')
|
|
txfile = self.get_file_with_ext(ext,no_dot=True)
|
|
t = self.spawn('mmgen-txbump', self.eth_args + add_args + ['--yes',txfile])
|
|
t.expect('or gas price: ',fee+'\n')
|
|
t.read()
|
|
return t
|
|
|
|
def txsign4(self): return self.txsign(ni=True,ext='.45495,50000]{}.rawtx')
|
|
def txsend4(self): return self.txsend(ext='.45495,50000]{}.sigtx')
|
|
def bal4(self): return self.bal(n='4')
|
|
|
|
def txcreate5(self):
|
|
args = [burn_addr + ','+amt1]
|
|
return self.txcreate(args=args,acct='10',non_mmgen_inputs=1)
|
|
def txsign5(self): return self.txsign(ni=True,ext=amt1+',50000]{}.rawtx')
|
|
def txsend5(self): return self.txsend(ext=amt1+',50000]{}.sigtx')
|
|
def bal5(self): return self.bal(n='5')
|
|
|
|
bal_corr = Decimal('0.0000032') # gas use for token sends varies between ETH and ETC!
|
|
def bal(self,n=None):
|
|
t = self.spawn('mmgen-tool', self.eth_args + ['twview','wide=1'])
|
|
for b in bals[n]:
|
|
addr,amt,adj = b if len(b) == 3 else b + (False,)
|
|
if adj and g.coin == 'ETC': amt = str(Decimal(amt) + Decimal(adj[1]) * self.bal_corr)
|
|
pat = r'{}\s+{}\s'.format(addr,amt.replace('.',r'\.'))
|
|
t.expect(pat,regex=True)
|
|
t.read()
|
|
return t
|
|
|
|
def token_bal(self,n=None):
|
|
t = self.spawn('mmgen-tool', self.eth_args + ['--token=mm1','twview','wide=1'])
|
|
for b in token_bals[n]:
|
|
addr,_amt1,_amt2,adj = b if len(b) == 4 else b + (False,)
|
|
if adj and g.coin == 'ETC':
|
|
_amt2 = str(Decimal(_amt2) + Decimal(adj[1]) * self.bal_corr)
|
|
pat = r'{}\s+{}\s+{}\s'.format(addr,_amt1.replace('.',r'\.'),_amt2.replace('.',r'\.'))
|
|
t.expect(pat,regex=True)
|
|
t.expect('Total MM1:')
|
|
t.read()
|
|
return t
|
|
|
|
def bal_getbalance(self,idx,etc_adj=False,extra_args=[]):
|
|
bal1 = token_bals_getbalance[idx][0]
|
|
bal2 = token_bals_getbalance[idx][1]
|
|
bal1 = Decimal(bal1)
|
|
if etc_adj and g.coin == 'ETC': bal1 += self.bal_corr
|
|
t = self.spawn('mmgen-tool', self.eth_args + extra_args + ['getbalance'])
|
|
t.expect(r'\n[0-9A-F]{8}: .* '+str(bal1),regex=True)
|
|
t.expect(r'\nNon-MMGen: .* '+bal2,regex=True)
|
|
total = t.expect_getend(r'\nTOTAL:\s+',regex=True).split()[0]
|
|
t.read()
|
|
assert Decimal(bal1) + Decimal(bal2) == Decimal(total)
|
|
return t
|
|
|
|
def add_label(self,lbl,addr='98831F3A:E:3'):
|
|
t = self.spawn('mmgen-tool', self.eth_args + ['add_label',addr,lbl])
|
|
t.expect('Added label.*in tracking wallet',regex=True)
|
|
return t
|
|
|
|
def chk_label(self,lbl_pat,addr='98831F3A:E:3'):
|
|
t = self.spawn('mmgen-tool', self.eth_args + ['listaddresses','all_labels=1'])
|
|
t.expect(r'{}\s+\S{{30}}\S+\s+{}\s+'.format(addr,lbl_pat),regex=True)
|
|
return t
|
|
|
|
def add_label1(self): return self.add_label(lbl=tw_label_zh)
|
|
def chk_label1(self): return self.chk_label(lbl_pat=tw_label_zh)
|
|
def add_label2(self): return self.add_label(lbl=tw_label_lat_cyr_gr)
|
|
def chk_label2(self): return self.chk_label(lbl_pat=tw_label_lat_cyr_gr)
|
|
|
|
def remove_label(self,addr='98831F3A:E:3'):
|
|
t = self.spawn('mmgen-tool', self.eth_args + ['remove_label',addr])
|
|
t.expect('Removed label.*in tracking wallet',regex=True)
|
|
return t
|
|
|
|
def token_compile(self,token_data={}):
|
|
odir = joinpath(self.tmpdir,token_data['symbol'].lower())
|
|
if not solc_ver:
|
|
imsg('Using precompiled contract data in {}'.format(odir))
|
|
return 'skip' if os.path.exists(odir) else False
|
|
self.spawn('',msg_only=True)
|
|
cmd_args = ['--{}={}'.format(k,v) for k,v in list(token_data.items())]
|
|
imsg("Compiling solidity token contract '{}' with 'solc'".format(token_data['symbol']))
|
|
try: os.mkdir(odir)
|
|
except: pass
|
|
cmd = [
|
|
'scripts/traceback_run.py',
|
|
'scripts/create-token.py',
|
|
'--coin=' + g.coin,
|
|
'--outdir=' + odir
|
|
] + cmd_args + [dfl_addr_chk]
|
|
imsg("Executing: {}".format(' '.join(cmd)))
|
|
cp = run(cmd,stdout=DEVNULL,stderr=PIPE)
|
|
if cp.returncode != 0:
|
|
rdie(2,'solc failed with the following output: {}'.format(cp.stderr))
|
|
imsg("ERC20 token '{}' compiled".format(token_data['symbol']))
|
|
return 'ok'
|
|
|
|
def token_compile1(self):
|
|
token_data = { 'name':'MMGen Token 1', 'symbol':'MM1', 'supply':10**26, 'decimals':18 }
|
|
return self.token_compile(token_data)
|
|
|
|
def token_compile2(self):
|
|
token_data = { 'name':'MMGen Token 2', 'symbol':'MM2', 'supply':10**18, 'decimals':10 }
|
|
return self.token_compile(token_data)
|
|
|
|
async def token_deploy(self,num,key,gas,mmgen_cmd='txdo',tx_fee='8G'):
|
|
keyfile = joinpath(self.tmpdir,parity_key_fn)
|
|
fn = joinpath(self.tmpdir,'mm'+str(num),key+'.bin')
|
|
os.environ['MMGEN_BOGUS_SEND'] = ''
|
|
args = ['-B',
|
|
'--tx-fee='+tx_fee,
|
|
'--tx-gas={}'.format(gas),
|
|
'--contract-data='+fn,
|
|
'--inputs='+dfl_addr,
|
|
'--yes' ]
|
|
if mmgen_cmd == 'txdo': args += ['-k',keyfile]
|
|
t = self.spawn( 'mmgen-'+mmgen_cmd, self.eth_args + args)
|
|
if mmgen_cmd == 'txcreate':
|
|
t.written_to_file('Ethereum transaction')
|
|
ext = '[0,8000]{}.rawtx'.format('-α' if g.debug_utf8 else '')
|
|
txfile = self.get_file_with_ext(ext,no_dot=True)
|
|
t = self.spawn('mmgen-txsign', self.eth_args + ['--yes','-k',keyfile,txfile],no_msg=True)
|
|
self.txsign_ui_common(t,ni=True)
|
|
txfile = txfile.replace('.rawtx','.sigtx')
|
|
t = self.spawn('mmgen-txsend', self.eth_args + [txfile],no_msg=True)
|
|
|
|
os.environ['MMGEN_BOGUS_SEND'] = '1'
|
|
txid = self.txsend_ui_common(t,caller=mmgen_cmd,
|
|
quiet = mmgen_cmd == 'txdo' or not g.debug,
|
|
bogus_send=False)
|
|
addr = t.expect_getend('Contract address: ')
|
|
from mmgen.altcoins.eth.tx import EthereumMMGenTX as etx
|
|
assert (await etx.get_exec_status(txid,True)) != 0,(
|
|
"Contract '{}:{}' failed to execute. Aborting".format(num,key))
|
|
if key == 'Token':
|
|
self.write_to_tmpfile('token_addr{}'.format(num),addr+'\n')
|
|
imsg('\nToken MM{} deployed!'.format(num))
|
|
return t
|
|
|
|
async def token_deploy1a(self): return await self.token_deploy(num=1,key='SafeMath',gas=200000)
|
|
async def token_deploy1b(self): return await self.token_deploy(num=1,key='Owned',gas=250000)
|
|
async def token_deploy1c(self): return await self.token_deploy(num=1,key='Token',gas=1100000,tx_fee='7G')
|
|
|
|
def tx_status2(self):
|
|
return self.tx_status(ext=g.coin+'[0,7000]{}.sigtx',expect_str='successfully executed')
|
|
|
|
def bal6(self): return self.bal5()
|
|
|
|
async def token_deploy2a(self): return await self.token_deploy(num=2,key='SafeMath',gas=200000)
|
|
async def token_deploy2b(self): return await self.token_deploy(num=2,key='Owned',gas=250000)
|
|
async def token_deploy2c(self): return await self.token_deploy(num=2,key='Token',gas=1100000)
|
|
|
|
async def contract_deploy(self): # test create,sign,send
|
|
return await self.token_deploy(num=2,key='SafeMath',gas=1100000,mmgen_cmd='txcreate')
|
|
|
|
async def token_transfer_ops(self,op,amt=1000):
|
|
self.spawn('',msg_only=True)
|
|
sid = dfl_sid
|
|
from mmgen.tool import MMGenToolCmdWallet
|
|
usr_mmaddrs = ['{}:E:{}'.format(sid,i) for i in (11,21)]
|
|
usr_addrs = [MMGenToolCmdWallet().gen_addr(addr,dfl_words_file) for addr in usr_mmaddrs]
|
|
|
|
from mmgen.altcoins.eth.contract import TokenResolve
|
|
from mmgen.altcoins.eth.tx import EthereumMMGenTX as etx
|
|
async def do_transfer():
|
|
for i in range(2):
|
|
tk = await TokenResolve(self.read_from_tmpfile('token_addr{}'.format(i+1)).strip())
|
|
imsg_r('\n' + await tk.info())
|
|
imsg('dev token balance (pre-send): {}'.format(await tk.get_balance(dfl_addr)))
|
|
imsg('Sending {} {} to address {} ({})'.format(amt,g.coin,usr_addrs[i],usr_mmaddrs[i]))
|
|
from mmgen.obj import ETHAmt
|
|
txid = await tk.transfer( dfl_addr, usr_addrs[i], amt, dfl_privkey,
|
|
start_gas = ETHAmt(60000,'wei'),
|
|
gasPrice = ETHAmt(8,'Gwei') )
|
|
assert (await etx.get_exec_status(txid,True)) != 0,'Transfer of token funds failed. Aborting'
|
|
|
|
async def show_bals():
|
|
for i in range(2):
|
|
tk = await TokenResolve(self.read_from_tmpfile(f'token_addr{i+1}').strip())
|
|
imsg('Token: {}'.format(await tk.get_symbol()))
|
|
imsg('dev token balance: {}'.format(await tk.get_balance(dfl_addr)))
|
|
imsg('usr token balance: {} ({} {})'.format(
|
|
await tk.get_balance(usr_addrs[i]),usr_mmaddrs[i],usr_addrs[i]))
|
|
|
|
silence()
|
|
if op == 'show_bals':
|
|
await show_bals()
|
|
elif op == 'do_transfer':
|
|
await do_transfer()
|
|
end_silence()
|
|
return 'ok'
|
|
|
|
def token_fund_users(self):
|
|
return self.token_transfer_ops(op='do_transfer')
|
|
|
|
def token_user_bals(self):
|
|
return self.token_transfer_ops(op='show_bals')
|
|
|
|
def token_addrgen(self):
|
|
self.addrgen(addrs='11-13')
|
|
ok_msg()
|
|
return self.addrgen(addrs='21-23')
|
|
|
|
def token_addrimport_badaddr1(self):
|
|
t = self.addrimport(ext='[11-13]{}.addrs',add_args=['--token=abc'],bad_input=True)
|
|
t.req_exit_val = 2
|
|
return t
|
|
|
|
def token_addrimport_badaddr2(self):
|
|
t = self.addrimport(ext='[11-13]{}.addrs',add_args=['--token='+'00deadbeef'*4],bad_input=True)
|
|
t.req_exit_val = 2
|
|
return t
|
|
|
|
def token_addrimport(self,extra_args=[],expect='3/3'):
|
|
for n,r in ('1','11-13'),('2','21-23'):
|
|
tk_addr = self.read_from_tmpfile('token_addr'+n).strip()
|
|
t = self.addrimport(ext='['+r+']{}.addrs',expect=expect,add_args=['--token='+tk_addr]+extra_args)
|
|
t.p.wait()
|
|
ok_msg()
|
|
t.skip_ok = True
|
|
return t
|
|
|
|
def token_addrimport_batch(self):
|
|
return self.token_addrimport(extra_args=['--batch'],expect='OK: 3')
|
|
|
|
def bal7(self): return self.bal5()
|
|
def token_bal1(self): return self.token_bal(n='1')
|
|
|
|
def token_txcreate(self,args=[],token='',inputs='1',fee='50G'):
|
|
t = self.spawn('mmgen-txcreate', self.eth_args + ['--token='+token,'-B','--tx-fee='+fee] + args)
|
|
t = self.txcreate_ui_common(
|
|
t,
|
|
menu = [],
|
|
inputs = inputs,
|
|
input_sels_prompt = 'to spend from',
|
|
file_desc = 'Ethereum token transaction',
|
|
add_comment = tx_label_lat_cyr_gr )
|
|
t.read()
|
|
return t
|
|
def token_txsign(self,ext='',token=''):
|
|
return self.txsign(ni=True,ext=ext,add_args=['--token='+token])
|
|
def token_txsend(self,ext='',token=''):
|
|
return self.txsend(ext=ext,add_args=['--token=mm1'])
|
|
|
|
def token_txcreate1(self):
|
|
return self.token_txcreate(args=['98831F3A:E:12,1.23456'],token='mm1')
|
|
def token_txsign1(self):
|
|
return self.token_txsign(ext='1.23456,50000]{}.rawtx',token='mm1')
|
|
def token_txsend1(self):
|
|
return self.token_txsend(ext='1.23456,50000]{}.sigtx',token='mm1')
|
|
|
|
def tx_status3(self):
|
|
return self.tx_status(
|
|
ext='1.23456,50000]{}.sigtx',
|
|
add_args=['--token=mm1'],
|
|
expect_str='successfully executed',
|
|
expect_str2='has 1 confirmation')
|
|
|
|
def token_bal2(self):
|
|
return self.token_bal(n='2')
|
|
|
|
def twview(self,args=[],expect_str='',tool_args=[],exit_val=0):
|
|
t = self.spawn('mmgen-tool', self.eth_args + args + ['twview'] + tool_args)
|
|
if expect_str:
|
|
t.expect(expect_str,regex=True)
|
|
t.read()
|
|
t.req_exit_val = exit_val
|
|
return t
|
|
|
|
def token_txcreate2(self):
|
|
return self.token_txcreate(args=[burn_addr+','+amt2],token='mm1')
|
|
def token_txbump(self):
|
|
return self.txbump(ext=amt2+',50000]{}.rawtx',fee='56G',add_args=['--token=mm1'])
|
|
def token_txsign2(self):
|
|
return self.token_txsign(ext=amt2+',50000]{}.rawtx',token='mm1')
|
|
def token_txsend2(self):
|
|
return self.token_txsend(ext=amt2+',50000]{}.sigtx',token='mm1')
|
|
|
|
def token_bal3(self):
|
|
return self.token_bal(n='3')
|
|
|
|
def del_dev_addr(self):
|
|
t = self.spawn('mmgen-tool', self.eth_args + ['remove_address',dfl_addr])
|
|
t.read() # TODO
|
|
return t
|
|
|
|
def bal1_getbalance(self):
|
|
return self.bal_getbalance('1',etc_adj=True)
|
|
|
|
def addrimport_token_burn_addr(self):
|
|
return self.addrimport_one_addr(addr=burn_addr,extra_args=['--token=mm1'])
|
|
|
|
def token_bal4(self):
|
|
return self.token_bal(n='4')
|
|
|
|
def token_bal_getbalance(self):
|
|
return self.bal_getbalance('2',extra_args=['--token=mm1'])
|
|
|
|
def txcreate_noamt(self):
|
|
return self.txcreate(args=['98831F3A:E:12'],eth_fee_res=True)
|
|
def txsign_noamt(self):
|
|
return self.txsign(ext='99.99895,50000]{}.rawtx')
|
|
def txsend_noamt(self):
|
|
return self.txsend(ext='99.99895,50000]{}.sigtx')
|
|
|
|
def bal8(self): return self.bal(n='8')
|
|
def token_bal5(self): return self.token_bal(n='5')
|
|
|
|
def token_txcreate_noamt(self):
|
|
return self.token_txcreate(args=['98831F3A:E:13'],token='mm1',inputs='2',fee='51G')
|
|
def token_txsign_noamt(self):
|
|
return self.token_txsign(ext='1.23456,51000]{}.rawtx',token='mm1')
|
|
def token_txsend_noamt(self):
|
|
return self.token_txsend(ext='1.23456,51000]{}.sigtx',token='mm1')
|
|
|
|
def bal9(self): return self.bal(n='9')
|
|
def token_bal6(self): return self.token_bal(n='6')
|
|
|
|
def listaddresses(self,args=[],tool_args=['all_labels=1'],exit_val=0):
|
|
t = self.spawn('mmgen-tool', self.eth_args + args + ['listaddresses'] + tool_args)
|
|
t.read()
|
|
t.req_exit_val = exit_val
|
|
return t
|
|
|
|
def listaddresses1(self):
|
|
return self.listaddresses()
|
|
def listaddresses2(self):
|
|
return self.listaddresses(tool_args=['minconf=999999999'])
|
|
def listaddresses3(self):
|
|
return self.listaddresses(tool_args=['sort=age'])
|
|
def listaddresses4(self):
|
|
return self.listaddresses(tool_args=['sort=age','showempty=1'])
|
|
|
|
def token_listaddresses1(self):
|
|
return self.listaddresses(args=['--token=mm1'])
|
|
def token_listaddresses2(self):
|
|
return self.listaddresses(args=['--token=mm1'],tool_args=['showempty=1'])
|
|
|
|
def twview_cached_balances(self):
|
|
return self.twview(args=['--cached-balances'])
|
|
def token_twview_cached_balances(self):
|
|
return self.twview(args=['--token=mm1','--cached-balances'])
|
|
|
|
def txcreate_cached_balances(self):
|
|
args = ['--tx-fee=20G','--cached-balances','98831F3A:E:3,0.1276']
|
|
return self.txcreate(args=args,acct='2')
|
|
def token_txcreate_cached_balances(self):
|
|
args=['--cached-balances','--tx-fee=12G','98831F3A:E:12,1.2789']
|
|
return self.token_txcreate(args=args,token='mm1')
|
|
|
|
def txdo_cached_balances(self,
|
|
acct = '2',
|
|
fee_res_fs = '0.00105 {} (50 gas price in Gwei)',
|
|
add_args = ['98831F3A:E:3,0.4321']):
|
|
args = ['--tx-fee=20G','--cached-balances'] + add_args + [dfl_words_file]
|
|
os.environ['MMGEN_BOGUS_SEND'] = ''
|
|
t = self.txcreate(args=args,acct=acct,caller='txdo',fee_res_fs=fee_res_fs,no_read=True)
|
|
os.environ['MMGEN_BOGUS_SEND'] = '1'
|
|
self._do_confirm_send(t,quiet=not g.debug,sure=False)
|
|
t.read()
|
|
return t
|
|
|
|
def txcreate_refresh_balances(self,
|
|
bals=['2','3'],
|
|
args=['-B','--cached-balances','-i'],
|
|
total= '1000126.14829832312345678',adj_total=True,total_coin=g.coin):
|
|
if g.coin == 'ETC' and adj_total:
|
|
total = str(Decimal(total) + self.bal_corr)
|
|
t = self.spawn('mmgen-txcreate', self.eth_args + args)
|
|
for n in bals:
|
|
t.expect('[R]efresh balance:\b','R')
|
|
t.expect(' main menu): ',n)
|
|
t.expect('Is this what you want? (y/N): ','y')
|
|
t.expect('[R]efresh balance:\b','q')
|
|
t.expect('Total unspent: {} {}'.format(total,total_coin))
|
|
t.read()
|
|
return t
|
|
|
|
def bal10(self): return self.bal(n='10')
|
|
|
|
def token_txdo_cached_balances(self):
|
|
return self.txdo_cached_balances(
|
|
acct='1',
|
|
fee_res_fs='0.0026 {} (50 gas price in Gwei)',
|
|
add_args=['--token=mm1','98831F3A:E:12,43.21'])
|
|
|
|
def token_txcreate_refresh_balances(self):
|
|
return self.txcreate_refresh_balances(
|
|
bals=['1','2'],
|
|
args=['--token=mm1','-B','--cached-balances','-i'],
|
|
total='1000',adj_total=False,total_coin='MM1')
|
|
|
|
def token_bal7(self): return self.token_bal(n='7')
|
|
|
|
def twview1(self):
|
|
return self.twview()
|
|
def twview2(self):
|
|
return self.twview(tool_args=['wide=1'])
|
|
def twview3(self):
|
|
return self.twview(tool_args=['wide=1','sort=age'])
|
|
def twview4(self):
|
|
return self.twview(tool_args=['wide=1','minconf=999999999'])
|
|
def twview5(self):
|
|
return self.twview(tool_args=['wide=1','minconf=0'])
|
|
|
|
def token_twview1(self):
|
|
return self.twview(args=['--token=mm1'])
|
|
def token_twview2(self):
|
|
return self.twview(args=['--token=mm1'],tool_args=['wide=1'])
|
|
def token_twview3(self):
|
|
return self.twview(args=['--token=mm1'],tool_args=['wide=1','sort=age'])
|
|
|
|
def edit_label(self,out_num,args=[],action='l',label_text=None):
|
|
t = self.spawn('mmgen-txcreate', self.eth_args + args + ['-B','-i'])
|
|
p1,p2 = ('efresh balance:\b','return to main menu): ')
|
|
p3,r3 = (p2,label_text+'\n') if label_text is not None else ('(y/N): ','y')
|
|
p4,r4 = (('(y/N): ',),('y',)) if label_text == '' else ((),())
|
|
for p,r in zip((p1,p1,p2,p3)+p4,('M',action,out_num+'\n',r3)+r4):
|
|
t.expect(p,r)
|
|
m = ( 'Account #{} removed' if action == 'D' else
|
|
'Label added to account #{}' if label_text else
|
|
'Label removed from account #{}' )
|
|
t.expect(m.format(out_num))
|
|
for p,r in zip((p1,p1),('M','q')):
|
|
t.expect(p,r)
|
|
t.expect('Total unspent:')
|
|
t.read()
|
|
return t
|
|
|
|
def edit_label1(self):
|
|
return self.edit_label(out_num=del_addrs[0],label_text=tw_label_zh)
|
|
def edit_label2(self):
|
|
return self.edit_label(out_num=del_addrs[1],label_text=tw_label_lat_cyr_gr)
|
|
def edit_label3(self):
|
|
return self.edit_label(out_num=del_addrs[0],label_text='')
|
|
|
|
def token_edit_label1(self):
|
|
return self.edit_label(out_num='1',label_text='Token label #1',args=['--token=mm1'])
|
|
|
|
def remove_addr1(self):
|
|
return self.edit_label(out_num=del_addrs[0],action='D')
|
|
def remove_addr2(self):
|
|
return self.edit_label(out_num=del_addrs[1],action='D')
|
|
def token_remove_addr1(self):
|
|
return self.edit_label(out_num=del_addrs[0],args=['--token=mm1'],action='D')
|
|
def token_remove_addr2(self):
|
|
return self.edit_label(out_num=del_addrs[1],args=['--token=mm1'],action='D')
|
|
|
|
def stop(self):
|
|
self.spawn('',msg_only=True)
|
|
stop_test_daemons(g.coin)
|
|
return 'ok'
|