1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714 |
- #!/usr/bin/env python3
- #
- # MMGen Wallet, a terminal-based cryptocurrency wallet
- # Copyright (C)2013-2025 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/>.
- """
- test.cmdtest_d.ethdev: Ethdev tests for the cmdtest.py test suite
- """
- import sys, time, os, re, shutil, asyncio, json
- from decimal import Decimal
- from collections import namedtuple
- from subprocess import run, PIPE, DEVNULL
- from pathlib import Path
- from mmgen.color import red, yellow, blue, cyan, set_vt100
- from mmgen.util import msg, rmsg, die
- from mmgen.proto.eth.misc import compute_contract_addr
- from ..include.common import (
- cfg,
- check_solc_ver,
- omsg,
- imsg,
- imsg_r,
- joinpath,
- read_from_file,
- write_to_file,
- cmp_or_die,
- strip_ansi_escapes,
- silence,
- end_silence,
- gr_uc,
- stop_test_daemons)
- from .include.common import (
- ref_dir,
- dfl_words_file,
- tx_comment_jp,
- tx_comment_lat_cyr_gr,
- tw_comment_zh,
- tw_comment_lat_cyr_gr,
- get_file_with_ext,
- ok_msg,
- Ctrl_U,
- cleanup_env)
- from .base import CmdTestBase
- from .shared import CmdTestShared
- from .httpd.etherscan import EtherscanServer
- etherscan_server = EtherscanServer()
- del_addrs = ('4', '1')
- dfl_sid = '98831F3A'
- # The OpenEthereum dev address with lots of coins. Create with "ethkey -b info ''":
- dfl_devaddr = '00a329c0648769a73afac7f9381e08fb43dbea72'
- dfl_devkey = '4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7'
- def get_reth_dev_keypair(cfg):
- from mmgen.bip39 import bip39
- from mmgen.bip_hd import MasterNode
- mn = 'test test test test test test test test test test test junk' # See ‘reth node --help’
- seed = bip39().generate_seed(mn.split())
- m = MasterNode(cfg, seed)
- node = m.to_chain(idx=0, coin='eth').derive_private(0)
- return (node.key.hex(), node.address)
- burn_addr = 'deadbeef'*5
- burn_addr2 = 'beadcafe'*5
- amt1 = '999777.12345689012345678'
- amt2 = '888.111122223333444455'
- parity_devkey_fn = 'parity.devkey'
- def set_vbals(daemon_id):
- global vbal1, vbal2, vbal3, vbal4, vbal5, vbal6, vbal7, vbal9
- if daemon_id == 'geth':
- vbal1 = '1.2288334'
- vbal2 = '99.996560752'
- vbal3 = '1.2314176'
- vbal4 = '127.0287834'
- vbal5 = '999904.14775104212345678'
- vbal6 = '999904.14880104212345678'
- vbal7 = '999902.91891764212345678'
- vbal9 = '1.2262504'
- elif daemon_id == 'reth':
- vbal1 = '1.2288334'
- vbal2 = '99.996560752'
- vbal3 = '1.23142525'
- vbal3 = '1.2314176'
- vbal4 = '127.0287834'
- vbal5 = '999904.14775104212345678'
- vbal6 = '999904.14880104212345678'
- vbal7 = '999902.91891764212345678'
- vbal9 = '1.2262504'
- else:
- vbal1 = '1.2288396'
- vbal2 = '99.997088092'
- vbal3 = '1.23142525'
- vbal3 = '1.2314246'
- vbal4 = '127.0287896'
- vbal5 = '1000126.14828458212345678'
- vbal6 = '1000126.14933458212345678'
- vbal7 = '1000124.91944498212345678'
- vbal9 = '1.226261'
- coin = cfg.coin
- class CmdTestEthdevMethods: # mixin class
- def _addrgen(self, addrs='1-3,11-13,21-23', no_msg=False):
- t = self.spawn(
- 'mmgen-addrgen',
- [f'--coin={self.proto.coin}'] + self.eth_args + [dfl_words_file, addrs],
- no_msg = no_msg,
- no_passthru_opts = True)
- t.written_to_file('Addresses')
- return t
- def _create_tx(self, *, fee, args, add_opts=[]):
- return self.txcreate_ui_common(
- self.spawn('mmgen-txcreate', add_opts + ['-B'] + args),
- caller = 'txcreate',
- input_sels_prompt = 'to spend from',
- inputs = '1',
- file_desc = 'transaction',
- interactive_fee = fee,
- fee_desc = 'transaction fee or gas price')
- def _send_tx(self, *, desc='transaction', add_opts=[]):
- t = self.spawn('mmgen-txsend', add_opts, no_passthru_opts=['coin'])
- t.view_tx('t')
- t.expect('(y/N): ', 'n')
- self._do_confirm_send(t, quiet=True)
- t.written_to_file(f'Sent {desc}')
- return t
- def _txdo(self, *, args, acct, fee='50G'):
- t = self.txcreate(
- args,
- acct = acct,
- caller = 'txdo',
- no_read = True,
- bad_input_sels = False,
- interactive_fee = fee,
- print_listing = False)
- t.written_to_file('Signed transaction')
- t.expect('confirm: ', 'YES\n')
- return t
- def _txbump(self, *, fee, ext, add_opts=[], add_args=[]):
- ext = ext.format('-α' if self.cfg.debug_utf8 else '')
- txfile = self.get_file_with_ext(ext, no_dot=True)
- t = self.spawn('mmgen-txbump', self.eth_args + add_opts + ['--yes', txfile] + add_args)
- t.expect('or gas price: ', fee+'\n')
- return t
- def _tx_receipt(self, ext='{}.regtest.sigtx'):
- self.mining_delay()
- return self.txsend(
- ext = ext,
- add_args = ['--receipt'],
- return_early = True,
- env = cleanup_env(cfg=self.cfg))
- def _fund_mmgen_address(self, arg):
- return self._txdo(
- args = [f'--keys-from-file={joinpath(self.tmpdir, parity_devkey_fn)}', arg, dfl_words_file],
- acct = '10')
- def _token_addrgen(self, *, mm_idxs, naddrs):
- self.spawn(msg_only=True)
- for idx in mm_idxs:
- t = self._addrgen(addrs=f'{idx}-{idx+naddrs-1}', no_msg=True)
- return t
- def _token_addrimport(self, addr_file, addr_range, expect, extra_args=[]):
- token_addr = self.read_from_tmpfile(addr_file).strip()
- return self.addrimport(
- ext = f'[{addr_range}]{{}}.regtest.addrs',
- expect = expect,
- add_args = ['--token-addr='+token_addr]+extra_args)
- def _get_contract_address(self, deployer_addr):
- t = self.spawn(
- 'mmgen-cli',
- ['--regtest=1', 'eth_getTransactionCount', '0x'+deployer_addr, 'pending'],
- env = cleanup_env(cfg=self.cfg),
- silent = True,
- no_msg = True)
- nonce = t.read().strip()
- imsg(f'Nonce: {red(nonce)}')
- ret = compute_contract_addr(self.cfg, deployer_addr, int(nonce, 16))
- imsg(f'Computed contract address: {blue(ret)}')
- return ret
- async def _token_deploy(self, num, key, gas, mmgen_cmd='txdo', gas_price='8G', get_receipt=True):
- keyfile = joinpath(self.tmpdir, parity_devkey_fn)
- fn = joinpath(self.tmpdir, 'mm'+str(num), key+'.bin')
- args = [
- '-B',
- f'--fee={gas_price}',
- f'--gas={gas}',
- f'--contract-data={fn}',
- f'--inputs={dfl_devaddr}',
- '--yes',
- ]
- contract_addr = self._get_contract_address(dfl_devaddr)
- if key == 'Token':
- self.write_to_tmpfile(f'token_addr{num}', contract_addr+'\n')
- 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('transaction')
- ext = '[0,8000]{}.regtest.rawtx'.format('-α' if self.cfg.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, no_passthru_opts=['coin'])
- self.txsign_ui_common(t, ni=True)
- txfile = txfile.replace('.rawtx', '.sigtx')
- t = self.spawn('mmgen-txsend',
- self.eth_args + [txfile], no_msg=True, no_passthru_opts=['coin'])
- txid = self.txsend_ui_common(t,
- caller = mmgen_cmd,
- quiet = mmgen_cmd == 'txdo' or not self.cfg.debug,
- bogus_send = False)
- _ = strip_ansi_escapes(t.expect_getend('Contract address: '))
- assert _ == contract_addr, f'Contract address mismatch: {_} != {contract_addr}'
- if get_receipt:
- if (await self.get_tx_receipt(txid)).status == 0:
- die(2, f'Contract {num}:{key} failed to execute. Aborting')
- if key == 'Token':
- imsg(f'\nToken MM{num} deployed!')
- return t
- async def _token_deploy_math(self, *, num, get_receipt=True, mmgen_cmd='txdo'):
- return await self._token_deploy(
- num=num, key='SafeMath', gas=500_000, get_receipt=get_receipt, mmgen_cmd=mmgen_cmd)
- async def _token_deploy_owned(self, *, num, get_receipt=True):
- return await self._token_deploy(
- num=num, key='Owned', gas=1_000_000, get_receipt=get_receipt)
- async def _token_deploy_token(self, *, num, get_receipt=True):
- return await self._token_deploy(
- num=num, key='Token', gas=4_000_000, gas_price='7G', get_receipt=get_receipt)
- def _bal_check(self, *, pat):
- self.mining_delay()
- t = self.spawn('mmgen-tool', ['--regtest=1', '--token=mm1', 'twview', 'wide=1'])
- text = t.read(strip_color=True)
- assert re.search(pat, text, re.DOTALL), f'output failed to match regex {pat}'
- return t
- def _create_token_tx(self, *, cmd, fee, args, add_opts=[]):
- return self.txcreate_ui_common(
- self.spawn(
- f'mmgen-{cmd}',
- ['--token=MM1', '-B', f'--fee={fee}'] + add_opts + args),
- inputs = '1',
- input_sels_prompt = 'to spend from',
- caller = cmd,
- file_desc = 'Unsigned automount transaction')
- async def _token_transfer_ops(self, *, op, mm_idxs, amt=1000, get_receipt=True, sid=dfl_sid):
- self.spawn(msg_only=True)
- from mmgen.tool.wallet import tool_cmd
- usr_mmaddrs = [f'{sid}:E:{i}' for i in mm_idxs]
- from mmgen.proto.eth.contract import ResolvedToken
- async def fund_user(rpc):
- for i in range(len(usr_mmaddrs)):
- tk = await ResolvedToken(
- self.cfg,
- self.proto,
- rpc,
- self.read_from_tmpfile(f'token_addr{i+1}').strip())
- imsg_r('\n' + await tk.info())
- imsg('dev token balance (pre-send): {}'.format(await tk.get_balance(dfl_devaddr)))
- imsg(f'Sending {amt} {self.proto.dcoin} to address {usr_addrs[i]} ({usr_mmaddrs[i]})')
- txid = await tk.transfer(
- from_addr = dfl_devaddr,
- to_addr = usr_addrs[i],
- amt = amt,
- key = dfl_devkey,
- gas = self.proto.coin_amt(120000, from_unit='wei'),
- gasPrice = self.proto.coin_amt(8, from_unit='Gwei'))
- if get_receipt and (await self.get_tx_receipt(txid)).status == 0:
- die(2, 'Transfer of token funds failed. Aborting')
- async def show_bals(rpc):
- for i in range(len(usr_mmaddrs)):
- tk = await ResolvedToken(
- self.cfg,
- self.proto,
- rpc,
- self.read_from_tmpfile(f'token_addr{i+1}').strip())
- imsg('Token: {}'.format(await tk.get_symbol()))
- imsg(f'dev token balance: {await tk.get_balance(dfl_devaddr)}')
- imsg('usr token balance: {} ({} {})'.format(
- await tk.get_balance(usr_addrs[i]),
- usr_mmaddrs[i],
- usr_addrs[i]))
- def gen_addr(addr):
- return tool_cmd(
- self.cfg, cmdname='gen_addr', proto=self.proto).gen_addr(addr, wallet=dfl_words_file)
- silence()
- usr_addrs = list(map(gen_addr, usr_mmaddrs))
- if op == 'show_bals':
- await show_bals(await self.rpc)
- elif op == 'fund_user':
- await fund_user(await self.rpc)
- end_silence()
- return 'ok'
- class CmdTestEthdev(CmdTestBase, CmdTestShared, CmdTestEthdevMethods):
- 'Ethereum transacting, token deployment and tracking wallet operations'
- networks = ('eth', 'etc')
- passthru_opts = ('coin', 'daemon_id', 'eth_daemon_id', 'http_timeout', 'rpc_backend')
- tmpdir_nums = [22]
- color = True
- menu_prompt = 'efresh balance:\b'
- input_sels_prompt = 'to spend from: '
- bals = lambda self, k: {
- '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 + r'\s+non-MMGen', amt1)],
- '8': [ ('98831F3A:E:1', '0'),
- ('98831F3A:E:2', '23.45495'),
- ('98831F3A:E:11', vbal1),
- ('98831F3A:E:12', '99.99895'),
- ('98831F3A:E:21', '2.345'),
- (burn_addr + r'\s+non-MMGen', amt1)],
- '9': [ ('98831F3A:E:1', '0'),
- ('98831F3A:E:2', '23.45495'),
- ('98831F3A:E:11', vbal1),
- ('98831F3A:E:12', vbal2),
- ('98831F3A:E:21', '2.345'),
- (burn_addr + r'\s+non-MMGen', amt1)],
- '10': [ ('98831F3A:E:1', '0'),
- ('98831F3A:E:2', '23.0218'),
- ('98831F3A:E:3', '0.4321'),
- ('98831F3A:E:11', vbal1),
- ('98831F3A:E:12', vbal2),
- ('98831F3A:E:21', '2.345'),
- (burn_addr + r'\s+non-MMGen', amt1)]
- }[k]
- token_bals = lambda self, k: {
- '1': [ ('98831F3A:E:11', '1000', '1.234')],
- '2': [ ('98831F3A:E:11', '998.76544', vbal3),
- ('98831F3A:E:12', '1.23456', '0')],
- '3': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
- ('98831F3A:E:12', '1.23456', '0')],
- '4': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
- ('98831F3A:E:12', '1.23456', '0'),
- (burn_addr + r'\s+non-MMGen', amt2, amt1)],
- '5': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
- ('98831F3A:E:12', '1.23456', '99.99895'),
- (burn_addr + r'\s+non-MMGen', amt2, amt1)],
- '6': [ ('98831F3A:E:11', '110.654317776666555545', vbal1),
- ('98831F3A:E:12', '0', vbal2),
- ('98831F3A:E:13', '1.23456', '0'),
- (burn_addr + r'\s+non-MMGen', amt2, amt1)],
- '7': [ ('98831F3A:E:11', '67.444317776666555545', vbal9),
- ('98831F3A:E:12', '43.21', vbal2),
- ('98831F3A:E:13', '1.23456', '0'),
- (burn_addr + r'\s+non-MMGen', amt2, amt1)]
- }[k]
- token_bals_getbalance = lambda self, k: {
- '1': (vbal4, '999777.12345689012345678'),
- '2': ('111.888877776666555545', '888.111122223333444455')
- }[k]
- cmd_group_in = (
- ('setup', f'dev mode tests for coin {coin} (start daemon)'),
- ('subgroup.misc', []),
- ('subgroup.init', []),
- ('subgroup.msg', ['init']),
- ('subgroup.main', ['init']),
- ('subgroup.contract', ['main']),
- ('subgroup.token', ['contract']),
- ('subgroup.twexport', ['token']),
- ('subgroup.cached', ['token']),
- ('subgroup.view', ['cached']),
- ('subgroup.label', ['cached']),
- ('subgroup.remove', ['cached']),
- ('stop', 'stopping daemon'),
- )
- cmd_subgroups = {
- 'misc': (
- 'miscellaneous commands',
- ('daemon_version', 'mmgen-tool daemon_version'),
- ),
- 'init': (
- 'initializing wallets',
- ('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 dev faucet address 'Ox00a329c..'"),
- ('fund_dev_address', 'funding the default (Parity dev) address'),
- ('cli_dev_balance', 'mmgen-cli eth_getBalance'),
- ),
- 'msg': (
- 'message signing',
- ('msgsign_chk', "signing a message (low-level, check against 'eth_sign' RPC call)"),
- ('msgcreate', 'creating a message file'),
- ('msgsign', 'signing the message file'),
- ('msgverify', 'verifying the message file'),
- ('msgexport', 'exporting the message file data to JSON for third-party verifier'),
- ('msgverify_export', 'verifying the exported JSON data'),
- ('msgcreate_raw', 'creating a message file (--msghash-type=raw)'),
- ('msgsign_raw', 'signing the message file (msghash_type=raw)'),
- ('msgverify_raw', 'verifying the message file (msghash_type=raw)'),
- ('msgexport_raw', 'exporting the message file data to JSON (msghash_type=raw)'),
- ('msgverify_export_raw', 'verifying the exported JSON data (msghash_type=raw)'),
- ),
- 'main': (
- 'creating, signing, sending and bumping Ethereum transactions',
- ('txcreate1', 'creating a transaction (spend from dev address to address :1)'),
- ('txview1_raw', 'viewing the raw transaction'),
- ('txsign1', 'signing the transaction'),
- ('txview1_sig', 'viewing the signed transaction'),
- ('tx_status0_bad', 'getting the transaction status'),
- ('txsign1_ni', 'signing the transaction (non-interactive)'),
- ('etherscan_server_start','starting the Etherscan server'),
- ('txsend_etherscan_test','sending the transaction via Etherscan (simulation, with --test)'),
- ('txsend_etherscan', 'sending the transaction via Etherscan (simulation)'),
- ('etherscan_server_stop','stopping the Etherscan server'),
- ('txsend1', 'sending the transaction'),
- ('bal1', f'the {coin} balance'),
- ('txcreate2', 'creating a transaction (spend from dev address to address :11)'),
- ('txsign2', 'signing the transaction'),
- ('txsend2', 'sending the transaction'),
- ('bal2', f'the {coin} balance'),
- ('txcreate3', 'creating a transaction (spend from dev address to address :21)'),
- ('txsign3', 'signing the transaction'),
- ('txsend3', 'sending the transaction'),
- ('bal3', f'the {coin} balance'),
- ('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', f'the {coin} balance'),
- ('txcreate5', 'creating a transaction (fund burn address)'),
- ('txsign5', 'signing the transaction'),
- ('txsend5', 'sending the transaction'),
- ('addrimport_burn_addr', 'importing burn address'),
- ('bal5', f'the {coin} balance'),
- ('add_comment1', 'adding a UTF-8 label (zh)'),
- ('chk_comment1', 'checking the label'),
- ('add_comment2', 'adding a UTF-8 label (lat+cyr+gr)'),
- ('chk_comment2', 'checking the label'),
- ('remove_comment', 'removing the label'),
- ),
- 'contract': (
- 'creating and deploying ERC20 tokens',
- ('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', f'the {coin} balance'),
- ('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)'),
- ),
- 'token': (
- 'creating, signing, sending and bumping ERC20 token transactions',
- ('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_addr1', 'importing token addresses using token address (MM1)'),
- ('token_addrimport_addr2', 'importing token addresses using token address (MM2)'),
- ('token_addrimport_batch', 'importing token addresses (dummy batch mode) (MM1)'),
- ('token_addrimport_sym', 'importing token addresses using token symbol (MM2)'),
- ('bal7', f'the {coin} balance'),
- ('token_bal1', f'the {coin} balance and token balance'),
- ('token_txcreate1', 'creating a token transaction'),
- ('token_txview1_raw', 'viewing the raw transaction'),
- ('token_txsign1', 'signing the transaction'),
- ('token_txsend1', 'sending the transaction'),
- ('token_txview1_sig', 'viewing the signed transaction'),
- ('tx_status3', 'getting the transaction status'),
- ('token_bal2', f'the {coin} balance and token balance'),
- ('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', f'the {coin} balance and token balance'),
- ('del_dev_addr', 'deleting the dev address'),
- ('bal1_getbalance', f'the {coin} balance (getbalance)'),
- ('addrimport_token_burn_addr', 'importing the token burn address'),
- ('token_bal4', f'the {coin} balance and token balance'),
- ('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', f'the {coin} balance'),
- ('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', f'the {coin} balance'),
- ('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'),
- ),
- 'twexport': (
- 'exporting and importing tracking wallet to JSON',
- ('twexport_noamt', 'exporting the tracking wallet (include_amts=0)'),
- ('twmove', 'moving the tracking wallet'),
- ('twimport', 'importing the tracking wallet'),
- ('twview7', 'twview (cached_balances=1)'),
- ('twview8', 'twview'),
- ('twexport', 'exporting the tracking wallet'),
- ('tw_chktotal', 'checking total value in tracking wallet dump'),
- ('twmove', 'moving the tracking wallet'),
- ('twimport', 'importing the tracking wallet'),
- ('twcompare', 'comparing imported tracking wallet with original'),
- ('edit_json_twdump', 'editing the tracking wallet JSON dump'),
- ('twmove', 'moving the tracking wallet'),
- ('twimport_nochksum', 'importing the edited tracking wallet JSON dump (ignore_checksum=1)'),
- ('token_listaddresses3', 'listaddresses --token=mm1 showempty=1'),
- ('token_listaddresses4', 'listaddresses --token=mm2 showempty=1'),
- ('twview9', 'twview (check balance)'),
- ),
- 'cached': (
- 'creating and sending transactions using cached balances',
- ('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', f'the {coin} balance'),
- ('token_txdo_cached_balances', 'token txdo (cached balances)'),
- ('token_txcreate_refresh_balances', 'refreshing token balances'),
- ('token_bal7', 'the token balance'),
- ),
- 'view': (
- 'viewing addresses and unspent outputs',
- ('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)'),
- ),
- 'label': (
- 'creating, editing and removing labels',
- ('edit_comment1', f'adding label to addr #{del_addrs[0]} in {coin} tracking wallet (zh)'),
- ('edit_comment2', f'editing label for addr #{del_addrs[0]} in {coin} tracking wallet (zh)'),
- ('edit_comment3', f'adding label to addr #{del_addrs[1]} in {coin} tracking wallet (lat+cyr+gr)'),
- ('edit_comment4', f'removing label from addr #{del_addrs[0]} in {coin} tracking wallet'),
- ('token_edit_comment1', f'adding label to addr #{del_addrs[0]} in {coin} token tracking wallet'),
- ),
- 'remove': (
- 'removing addresses from tracking wallet',
- ('remove_addr1', f'removing addr #{del_addrs[0]} from {coin} tracking wallet'),
- ('twview6', 'twview (balance reduced after address removal)'),
- ('remove_addr2', f'removing addr #{del_addrs[1]} from {coin} tracking wallet'),
- ('token_remove_addr1', f'removing addr #{del_addrs[0]} from {coin} token tracking wallet'),
- ('token_remove_addr2', f'removing addr #{del_addrs[1]} from {coin} token tracking wallet'),
- ),
- }
- def __init__(self, cfg, trunner, cfgs, spawn):
- CmdTestBase.__init__(self, cfg, trunner, cfgs, spawn)
- if trunner is None:
- return
- global coin
- coin = cfg.coin
- self.eth_args = [f'--outdir={self.tmpdir}', '--regtest=1', '--quiet']
- self.eth_args_noquiet = [f'--outdir={self.tmpdir}', '--regtest=1']
- from mmgen.protocol import init_proto
- self.proto = init_proto(cfg, network_id=self.proto.coin+'_rt', need_amt=True)
- from mmgen.daemon import CoinDaemon
- self.daemon = CoinDaemon(cfg, network_id=self.proto.coin+'_rt', test_suite=True)
- if self.daemon.id == 'reth':
- global dfl_devkey, dfl_devaddr
- dfl_devkey, dfl_devaddr = get_reth_dev_keypair(cfg)
- set_vbals(self.daemon.id)
- self.using_solc = check_solc_ver()
- if not self.using_solc:
- omsg(yellow('Using precompiled contract data'))
- omsg(blue(f'Coin daemon {self.daemon.id!r} selected'))
- self.genesis_fn = joinpath(self.tmpdir, 'genesis.json')
- self.keystore_dir = os.path.relpath(joinpath(self.daemon.datadir, 'keystore'))
- write_to_file(
- joinpath(self.tmpdir, parity_devkey_fn),
- dfl_devkey+'\n')
- self.message = 'attack at dawn'
- self.spawn_env['MMGEN_BOGUS_SEND'] = ''
- @property
- async def rpc(self):
- from mmgen.rpc import rpc_init
- return await rpc_init(self.cfg, self.proto)
- def mining_delay(self): # workaround for mining race condition in dev mode
- if self.daemon.id == 'reth':
- time.sleep(1)
- async def setup(self):
- self.spawn(msg_only=True)
- d = self.daemon
- if not self.using_solc:
- subdir = 'reth' if d.id == 'reth' else 'geth'
- srcdir = os.path.join(self.tr.repo_root, 'test', 'ref', 'ethereum', 'bin', subdir)
- from shutil import copytree
- for _ in ('mm1', 'mm2'):
- copytree(os.path.join(srcdir, _), os.path.join(self.tmpdir, _))
- if d.id in ('geth', 'erigon'):
- self.genesis_setup(d)
- set_vt100()
- if d.id == 'erigon':
- self.write_to_tmpfile('signer_key', self.keystore_data['key']+'\n')
- d.usr_coind_args = [
- '--miner.sigfile={}'.format(os.path.join(self.tmpdir, 'signer_key')),
- '--miner.etherbase={}'.format(self.keystore_data['address'])]
- if d.id in ('geth', 'erigon'):
- imsg(' {:19} {}'.format('Cmdline:', ' '.join(e for e in d.start_cmd if not 'verbosity' in e)))
- if not self.cfg.no_daemon_autostart:
- if not d.id in ('geth', 'erigon'):
- d.stop(silent=True)
- d.remove_datadir()
- d.start(silent=self.tr.quiet)
- rpc = await self.rpc
- imsg(f'Daemon: {rpc.daemon.coind_name} v{rpc.daemon_version_str}')
- return 'ok'
- @property
- def keystore_data(self):
- if not hasattr(self, '_keystore_data'):
- wallet_fn = os.path.join( self.keystore_dir, os.listdir(self.keystore_dir)[0])
- from mmgen.proto.eth.misc import decrypt_geth_keystore
- key = decrypt_geth_keystore(
- cfg = self.cfg,
- wallet_fn = wallet_fn,
- passwd = b'')
- with open(wallet_fn) as fh:
- res = json.loads(fh.read())
- res.update( { 'key': key.hex()})
- self._keystore_data = res
- return self._keystore_data
- def genesis_setup(self, d):
- def make_key():
- pwfile = joinpath(self.tmpdir, 'account_passwd')
- write_to_file(pwfile, '')
- run(['rm', '-rf', self.keystore_dir])
- cmd = f'geth account new --password={pwfile} --lightkdf --keystore {self.keystore_dir}'
- cp = run(cmd.split(), stdout=PIPE, stderr=PIPE)
- if cp.returncode:
- die(1, cp.stderr.decode())
- def make_genesis(signer_addr, prealloc_addr):
- return {
- 'config': {
- 'chainId': 1337, # TODO: replace constant with var
- 'homesteadBlock': 0,
- 'eip150Block': 0,
- 'eip155Block': 0,
- 'eip158Block': 0,
- 'byzantiumBlock': 0,
- 'constantinopleBlock': 0,
- 'petersburgBlock': 0,
- 'istanbulBlock': 0,
- 'muirGlacierBlock': 0,
- 'berlinBlock': 0,
- 'londonBlock': 0,
- 'arrowGlacierBlock': 0,
- 'grayGlacierBlock': 0,
- 'shanghaiTime': 0,
- 'terminalTotalDifficulty': 0,
- 'terminalTotalDifficultyPassed': True,
- 'isDev': True
- },
- 'nonce': '0x0',
- 'timestamp': '0x0',
- 'extraData': '0x',
- 'gasLimit': '0xaf79e0',
- 'difficulty': '0x1',
- 'mixHash': '0x0000000000000000000000000000000000000000000000000000000000000000',
- 'coinbase': '0x0000000000000000000000000000000000000000',
- 'number': '0x0',
- 'gasUsed': '0x0',
- 'parentHash': '0x0000000000000000000000000000000000000000000000000000000000000000',
- 'baseFeePerGas': '0x3b9aca00',
- 'excessBlobGas': None,
- 'blobGasUsed': None,
- 'alloc': {
- prealloc_addr: { 'balance': hex(prealloc_amt.toWei())}
- }
- }
- def init_genesis(fn):
- cmd = f'{d.exec_fn} init --datadir {d.datadir} {fn}'
- cp = run( cmd.split(), stdout=PIPE, stderr=PIPE)
- if cp.returncode:
- die(1, cp.stderr.decode())
- d.stop(quiet=True)
- d.remove_datadir()
- imsg(cyan('Initializing Genesis Block:'))
- prealloc_amt = self.proto.coin_amt('1_000_000_000')
- make_key()
- signer_addr = self.keystore_data['address']
- self.write_to_tmpfile('signer_addr', signer_addr + '\n')
- imsg(f' Keystore: {self.keystore_dir}')
- imsg(f' Signer key: {self.keystore_data["key"]}')
- imsg(f' Signer address: {signer_addr}')
- imsg(f' Faucet: {dfl_devaddr} ({prealloc_amt} ETH)')
- imsg(f' Genesis block data: {self.genesis_fn}')
- genesis_data = make_genesis(signer_addr, dfl_devaddr)
- write_to_file( self.genesis_fn, json.dumps(genesis_data, indent=' ')+'\n')
- init_genesis(self.genesis_fn)
- def daemon_version(self):
- t = self.spawn('mmgen-tool', self.eth_args + ['daemon_version'])
- t.expect('version')
- return t
- def cli_dev_balance(self):
- t = self.spawn(
- 'mmgen-cli',
- [f'--coin={self.proto.coin}', '--regtest=1', 'eth_getBalance', '0x'+dfl_devaddr, 'latest'])
- if self.daemon.id == 'geth':
- t.expect('0x33b2e3c91ec0e9113986000')
- elif self.daemon.id == 'reth':
- t.expect('0xd3c21bcecceda1000000')
- return t
- async def _wallet_upgrade(self, src_fn, expect1, expect2=None):
- if self.proto.coin == 'ETC':
- msg(f'skipping test {self.test_name!r} for ETC')
- return 'skip'
- from mmgen.tw.ctl import TwCtl
- twctl = await TwCtl(self.cfg, self.proto, no_wallet_init=True)
- from_fn = Path(ref_dir) / 'ethereum' / src_fn
- bak_fn = twctl.tw_dir / f'upgraded-{src_fn}'
- twctl.tw_dir.mkdir(mode=0o750, parents=True, exist_ok=True)
- dest = shutil.copy2(from_fn, twctl.tw_path)
- assert dest == twctl.tw_path, f'{dest} != {twctl.tw_path}'
- t = self.spawn('mmgen-tool', self.eth_args + ['twview'])
- t.expect(expect1)
- if expect2:
- t.expect(expect2)
- t.read()
- twctl.tw_path.rename(bak_fn)
- return t
- async def wallet_upgrade1(self):
- return await self._wallet_upgrade('tracking-wallet-v1.json', 'accounts field', 'network field')
- async def wallet_upgrade2(self):
- return await self._wallet_upgrade('tracking-wallet-v2.json', 'token params field', 'network field')
- def addrgen(self):
- return self._addrgen()
- def addrimport(
- self,
- ext = '21-23]{}.regtest.addrs',
- expect = '9/9',
- add_args = [],
- bad_input = False,
- exit_val = None):
- ext = ext.format('-α' if self.cfg.debug_utf8 else '')
- fn = self.get_file_with_ext(ext, no_dot=True, delete=False)
- t = self.spawn('mmgen-addrimport', ['--regtest=1'] + add_args + [fn], exit_val=exit_val)
- if bad_input:
- return t
- t.expect('Importing')
- t.expect(expect)
- return t
- def addrimport_one_addr(self, addr=None, extra_args=[]):
- t = self.spawn('mmgen-addrimport', ['--regtest=1', '--quiet', f'--address={addr}'] + extra_args)
- t.expect('OK')
- return t
- def addrimport_dev_addr(self):
- return self.addrimport_one_addr(addr=dfl_devaddr)
- def addrimport_burn_addr(self):
- return self.addrimport_one_addr(addr=burn_addr)
- def txcreate(
- self,
- args = [],
- menu = [],
- acct = '1',
- caller = 'txcreate',
- interactive_fee = '50G',
- fee_info_data = ('0.00105', '50'),
- no_read = False,
- print_listing = True,
- bad_input_sels = True,
- tweaks = []):
- fee_info_pat = r'\D{}\D.*{c} .*\D{}\D.*gas price in Gwei'.format(*fee_info_data, c=self.proto.coin)
- t = self.spawn(f'mmgen-{caller}', self.eth_args + ['-B'] + args)
- if print_listing:
- 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 = 'transaction',
- bad_input_sels = bad_input_sels,
- interactive_fee = interactive_fee,
- fee_info_pat = fee_info_pat,
- fee_desc = 'transaction fee or gas price',
- add_comment = tx_comment_jp,
- tweaks = tweaks)
- if not no_read:
- t.read()
- return t
- def txsign(self, ni=False, ext='{}.regtest.rawtx', add_args=[], dev_send=False, has_label=True):
- ext = ext.format('-α' if self.cfg.debug_utf8 else '')
- keyfile = joinpath(self.tmpdir, parity_devkey_fn)
- txfile = self.get_file_with_ext(ext, no_dot=True)
- t = self.spawn(
- 'mmgen-txsign',
- self.eth_args
- + ['--rpc-host=bad_host'] # ETH signing must work without RPC
- + ([], ['--yes'])[ni]
- + ([f'--keys-from-file={keyfile}'] if dev_send else [])
- + add_args
- + [txfile, dfl_words_file],
- no_passthru_opts = ['coin'])
- return self.txsign_ui_common(t, ni=ni, has_label=has_label)
- def txsend(
- self,
- ext = '{}.regtest.sigtx',
- add_args = [],
- test = False,
- return_early = False,
- has_label = True,
- env = {}):
- ext = ext.format('-α' if self.cfg.debug_utf8 else '')
- txfile = self.get_file_with_ext(ext, no_dot=True)
- t = self.spawn(
- 'mmgen-txsend',
- self.eth_args + add_args + [txfile],
- no_passthru_opts = ['coin'],
- env = env)
- if return_early:
- return t
- self.txsend_ui_common(
- t,
- quiet = not self.cfg.debug,
- bogus_send = False,
- test = test,
- has_label = has_label)
- return t
- def txview(self, ext_fs):
- ext = ext_fs.format('-α' if self.cfg.debug_utf8 else '')
- txfile = self.get_file_with_ext(ext, no_dot=True)
- return self.spawn('mmgen-tool', ['--verbose', 'txview', txfile])
- def fund_dev_address(self):
- """
- For Erigon, fund the default (Parity) dev address from the Erigon dev address
- For the others, send a junk TX to keep block counts equal for all daemons
- """
- dt = namedtuple('data', ['devkey_fn', 'dest', 'amt'])
- d = dt(parity_devkey_fn, burn_addr2, '1')
- t = self.txcreate(
- args = self.eth_args_noquiet + [
- f'--keys-from-file={joinpath(self.tmpdir, d.devkey_fn)}',
- f'{d.dest},{d.amt}',
- ],
- menu = ['a', 'r'],
- caller = 'txdo',
- acct = '1',
- no_read = True)
- self._do_confirm_send(t, quiet=not self.cfg.debug, sure=False)
- t.read()
- self.get_file_with_ext('sigtx', delete_all=True)
- return t
- def txcreate1(self):
- # include one invalid keypress 'X' -- see EthereumTwUnspentOutputs.key_mappings
- menu = ['a', 'd', 'r', 'M', 'X', 'e', 'm', 'm']
- args = ['98831F3A:E:1,123.456']
- return self.txcreate(args=args, menu=menu, acct='1', tweaks=['confirm_non_mmgen'])
- def txview1_raw(self):
- return self.txview(ext_fs='{}.regtest.rawtx')
- def txsign1(self):
- return self.txsign(add_args=['--use-internal-keccak-module'], dev_send=True)
- def tx_status0_bad(self):
- return self.tx_status(ext='{}.regtest.sigtx', expect_str='neither in mempool nor blockchain', exit_val=1)
- def txsign1_ni(self):
- return self.txsign(ni=True, dev_send=True)
- def etherscan_server_start(self):
- self.spawn(msg_only=True)
- etherscan_server.start()
- return 'ok'
- def txsend_etherscan_test(self):
- return self.txsend(add_args=['--tx-proxy=ether', '--test'], test='tx_proxy')
- def txsend_etherscan(self):
- return self.txsend(add_args=['--tx-proxy=ethersc'])
- def etherscan_server_stop(self):
- self.spawn(msg_only=True)
- etherscan_server.stop()
- return 'ok'
- def txsend1(self):
- return self.txsend()
- def txview1_sig(self): # do after send so that TxID is displayed
- return self.txview(ext_fs='{}.regtest.sigtx')
- def bal1(self):
- return self.bal(n='1')
- def txcreate2(self):
- args = ['98831F3A:E:11,1.234']
- return self.txcreate(args=args, acct='10', tweaks=['confirm_non_mmgen'])
- def txsign2(self):
- return self.txsign(ni=True, ext='1.234,50000]{}.regtest.rawtx', dev_send=True)
- def txsend2(self):
- return self.txsend(ext='1.234,50000]{}.regtest.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', tweaks=['confirm_non_mmgen'])
- def txsign3(self):
- return self.txsign(ni=True, ext='2.345,50000]{}.regtest.rawtx', dev_send=True)
- def txsend3(self):
- return self.txsend(ext='2.345,50000]{}.regtest.sigtx')
- def bal3(self):
- return self.bal(n='3')
- def tx_status(self, ext, expect_str, expect_str2='', add_args=[], exit_val=0):
- self.mining_delay()
- ext = ext.format('-α' if self.cfg.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],
- no_passthru_opts = ['coin'],
- exit_val = exit_val)
- t.expect(expect_str)
- if expect_str2:
- t.expect(expect_str2)
- return t
- def tx_status1(self):
- return self.tx_status(ext='2.345,50000]{}.regtest.sigtx', expect_str='has 1 confirmation')
- def tx_status1a(self):
- return self.tx_status(ext='2.345,50000]{}.regtest.sigtx', expect_str='has 2 confirmations')
- async def msgsign_chk(self): # NB: Geth only!
- def create_signature_mmgen():
- key = self.keystore_data['key']
- imsg(f'Key: {key}')
- from mmgen.proto.eth.misc import ec_sign_message_with_privkey
- return ec_sign_message_with_privkey(self.cfg, self.message, bytes.fromhex(key), 'eth_sign')
- async def create_signature_rpc():
- addr = self.read_from_tmpfile('signer_addr').strip()
- imsg(f'Address: {addr}')
- rpc = await self.rpc
- return await rpc.call(
- 'eth_sign',
- '0x' + addr,
- '0x' + self.message.encode().hex())
- if not self.daemon.id == 'geth':
- return 'skip'
- self.spawn(msg_only=True)
- sig = '0x' + create_signature_mmgen()
- sig_chk = await create_signature_rpc()
- # Compare signatures
- imsg(f'Message: {self.message}')
- imsg(f'Signature: {sig}')
- cmp_or_die(sig, sig_chk, 'message signatures')
- imsg('Geth and MMGen signatures match')
- return 'ok'
- def msgcreate(self, add_args=[]):
- t = self.spawn('mmgen-msg', self.eth_args_noquiet + add_args + ['create', self.message, '98831F3A:E:1'])
- t.written_to_file('Unsigned message data')
- return t
- def msgsign(self):
- fn = get_file_with_ext(self.tmpdir, 'rawmsg.json')
- t = self.spawn('mmgen-msg', self.eth_args_noquiet + ['sign', fn, dfl_words_file])
- t.written_to_file('Signed message data')
- return t
- def msgverify(self, fn=None, msghash_type='eth_sign'):
- fn = fn or get_file_with_ext(self.tmpdir, 'sigmsg.json')
- t = self.spawn('mmgen-msg', self.eth_args_noquiet + ['verify', fn])
- t.expect(msghash_type)
- t.expect('1 signature verified')
- return t
- def msgexport(self):
- fn = get_file_with_ext(self.tmpdir, 'sigmsg.json')
- t = self.spawn('mmgen-msg', self.eth_args_noquiet + ['export', fn])
- t.written_to_file('Signature data')
- return t
- def msgverify_export(self):
- return self.msgverify(
- fn = os.path.join(self.tmpdir, 'signatures.json'))
- def msgcreate_raw(self):
- get_file_with_ext(self.tmpdir, 'rawmsg.json', delete_all=True)
- return self.msgcreate(add_args=['--msghash-type=raw'])
- def msgsign_raw(self):
- get_file_with_ext(self.tmpdir, 'sigmsg.json', delete_all=True)
- return self.msgsign()
- def msgverify_raw(self):
- return self.msgverify(msghash_type='raw')
- def msgexport_raw(self):
- get_file_with_ext(self.tmpdir, 'signatures.json', no_dot=True, delete_all=True)
- return self.msgexport()
- def msgverify_export_raw(self):
- return self.msgverify(
- fn = os.path.join(self.tmpdir, 'signatures.json'),
- msghash_type = 'raw')
- def txcreate4(self):
- return self.txcreate(
- args = ['98831F3A:E:2,23.45495'],
- acct = '1',
- interactive_fee = '40G',
- fee_info_data = ('0.00084', '40'))
- def txbump(self):
- return self._txbump(fee='50G', ext=',40000]{}.regtest.rawtx')
- def txsign4(self):
- return self.txsign(ext='.45495,50000]{}.regtest.rawtx', add_args=['--no-quiet', '--no-yes'])
- def txsend4(self):
- return self.txsend(ext='.45495,50000]{}.regtest.sigtx')
- def bal4(self):
- return self.bal(n='4')
- def txcreate5(self):
- args = [burn_addr + ','+amt1]
- return self.txcreate(args=args, acct='10', tweaks=['confirm_non_mmgen'])
- def txsign5(self):
- return self.txsign(ni=True, ext=amt1+',50000]{}.regtest.rawtx', dev_send=True)
- def txsend5(self):
- return self.txsend(ext=amt1+',50000]{}.regtest.sigtx')
- def bal5(self):
- return self.bal(n='5')
- def bal(self, n):
- self.mining_delay()
- t = self.spawn('mmgen-tool', self.eth_args + ['twview', 'wide=1'])
- text = t.read(strip_color=True)
- for addr, amt in self.bals(n):
- pat = r'\D{}\D.*\D{}\D'.format(addr, amt.replace('.', r'\.'))
- assert re.search(pat, text), pat
- ss = f'Total {self.proto.coin}:'
- assert re.search(ss, text), ss
- return t
- def token_bal(self, n=None):
- self.mining_delay()
- t = self.spawn('mmgen-tool', self.eth_args + ['--token=mm1', 'twview', 'wide=1'])
- text = t.read(strip_color=True)
- for addr, _amt1, _amt2 in self.token_bals(n):
- pat = fr'{addr}\b.*\D{_amt1}\D.*\b{_amt2}\D'
- assert re.search(pat, text), pat
- ss = 'Total MM1:'
- assert re.search(ss, text), ss
- return t
- def bal_getbalance(self, sid, idx, etc_adj=False, extra_args=[]):
- bal1 = self.token_bals_getbalance(idx)[0]
- bal2 = self.token_bals_getbalance(idx)[1]
- bal1 = Decimal(bal1)
- t = self.spawn('mmgen-tool', self.eth_args + extra_args + ['getbalance'])
- t.expect(rf'{sid}:.*'+str(bal1), regex=True)
- t.expect(r'Non-MMGen:.*'+bal2, regex=True)
- total = strip_ansi_escapes(t.expect_getend(rf'TOTAL {self.proto.coin}')).split()[0]
- assert Decimal(bal1) + Decimal(bal2) == Decimal(total)
- return t
- def add_comment(self, comment, addr='98831F3A:E:3'):
- t = self.spawn('mmgen-tool', self.eth_args + ['add_label', addr, comment])
- t.expect('Added label.*in tracking wallet', regex=True)
- return t
- def chk_comment(self, comment_pat, addr='98831F3A:E:3'):
- t = self.spawn('mmgen-tool', self.eth_args + ['listaddresses', 'all_labels=1'])
- t.expect(fr'{addr}\b.*{comment_pat}', regex=True)
- return t
- def add_comment1(self):
- return self.add_comment(comment=tw_comment_zh)
- def chk_comment1(self):
- return self.chk_comment(comment_pat=tw_comment_zh[:3])
- def add_comment2(self):
- return self.add_comment(comment=tw_comment_lat_cyr_gr)
- def chk_comment2(self):
- return self.chk_comment(comment_pat=tw_comment_lat_cyr_gr[:3])
- def remove_comment(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 self.using_solc:
- imsg(f'Using precompiled contract data in {odir}')
- return 'skip' if os.path.exists(odir) else False
- self.spawn(msg_only=True)
- cmd_args = [f'--{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 = [
- 'python3',
- 'scripts/create-token.py',
- '--coin=' + self.proto.coin,
- '--outdir=' + odir
- ] + cmd_args + [self.proto.checksummed_addr(dfl_devaddr)]
- imsg('Executing: {}'.format(' '.join(cmd)))
- cp = run(cmd, stdout=DEVNULL, stderr=PIPE)
- if cp.returncode != 0:
- rmsg('solc failed with the following output:')
- die(2, cp.stderr.decode())
- imsg('ERC20 token {!r} 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 get_tx_receipt(self, txid):
- if self.daemon.id in ('geth', 'reth'): # workaround for mining race condition in dev mode
- await asyncio.sleep(1 if self.daemon.id == 'reth' else 0.5)
- from mmgen.tx import NewTX
- tx = await NewTX(cfg=self.cfg, proto=self.proto, target='tx')
- tx.rpc = await self.rpc
- res = await tx.get_receipt(txid)
- if not res:
- die(1, f'Error getting receipt for transaction {txid}')
- imsg(f'Gas sent: {res.gas_sent.hl():<9} {(res.gas_sent*res.gas_price).hl2(encl="()")}')
- imsg(f'Gas used: {res.gas_used.hl():<9} {(res.gas_used*res.gas_price).hl2(encl="()")}')
- imsg(f'Gas price: {res.gas_price.hl()}')
- if res.gas_used == res.gas_sent:
- omsg(yellow('Warning: all gas was used!'))
- return res
- async def token_deploy1a(self):
- return await self._token_deploy_math(num=1)
- async def token_deploy1b(self):
- return await self._token_deploy_owned(num=1)
- async def token_deploy1c(self):
- return await self._token_deploy_token(num=1)
- async def token_deploy2a(self): # test create, sign, send:
- return await self._token_deploy_math(num=2, mmgen_cmd='txcreate')
- async def token_deploy2b(self):
- return await self._token_deploy_owned(num=2)
- async def token_deploy2c(self):
- return await self._token_deploy_token(num=2)
- def tx_status2(self):
- return self.tx_status(
- ext = self.proto.coin+'[0,7000]{}.regtest.sigtx',
- expect_str = 'successfully executed')
- def bal6(self):
- return self.bal5()
- def token_fund_users(self):
- return self._token_transfer_ops(op='fund_user', mm_idxs=[11, 21])
- def token_user_bals(self):
- return self._token_transfer_ops(op='show_bals', mm_idxs=[11, 21])
- def token_addrgen(self):
- return self._token_addrgen(mm_idxs=[11, 21], naddrs=3)
- def token_addrimport_badaddr1(self):
- t = self.addrimport(
- ext = '[11-13]{}.regtest.addrs',
- add_args = ['--token=abc'],
- bad_input = True,
- exit_val = 2)
- t.expect('could not be resolved')
- return t
- def token_addrimport_badaddr2(self):
- t = self.addrimport(
- ext = '[11-13]{}.regtest.addrs',
- add_args = ['--token='+'00deadbeef'*4],
- bad_input = True,
- exit_val = 2)
- t.expect('could not be resolved')
- return t
- def token_addrimport_addr1(self):
- return self._token_addrimport('token_addr1', '11-13', expect='3/3')
- def token_addrimport_addr2(self):
- return self._token_addrimport('token_addr2', '21-23', expect='3/3')
- def token_addrimport_batch(self):
- return self._token_addrimport('token_addr1', '11-13', expect='3 addresses', extra_args=['--batch'])
- def token_addrimport_sym(self):
- return self.addrimport(
- ext = '[21-23]{}.regtest.addrs',
- expect = '3/3',
- add_args = ['--token=MM2'])
- 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',
- file_desc = 'Unsigned transaction'):
- return self.txcreate_ui_common(
- self.spawn('mmgen-txcreate',
- self.eth_args + [f'--token={token}', '-B', f'--fee={fee}'] + args),
- menu = [],
- inputs = inputs,
- input_sels_prompt = 'to spend from',
- add_comment = tx_comment_lat_cyr_gr,
- file_desc = file_desc)
- def token_txsign(self, ext='', token='', add_args=[], ni=True):
- return self.txsign(ni=ni, ext=ext, add_args=add_args)
- def token_txsend(self, ext='', token=''):
- return self.txsend(ext=ext)
- def token_txcreate1(self):
- return self.token_txcreate(args=['98831F3A:E:12,1.23456'], token='mm1')
- def token_txview1_raw(self):
- return self.txview(ext_fs='1.23456,50000]{}.regtest.rawtx')
- def token_txsign1(self):
- return self.token_txsign(
- ext = '1.23456,50000]{}.regtest.rawtx',
- token = 'mm1',
- ni = False,
- add_args = ['--no-quiet', '--no-yes'])
- def token_txsend1(self):
- return self.token_txsend(ext='1.23456,50000]{}.regtest.sigtx', token='mm1')
- def token_txview1_sig(self):
- return self.txview(ext_fs='1.23456,50000]{}.regtest.sigtx')
- def tx_status3(self):
- return self.tx_status(
- ext = '1.23456,50000]{}.regtest.sigtx',
- 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=[]):
- t = self.spawn('mmgen-tool', self.eth_args + args + ['twview'] + tool_args)
- if expect_str:
- t.expect(expect_str, regex=True)
- 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]{}.regtest.rawtx', fee='56G', add_opts=['--token=MM1'])
- def token_txsign2(self):
- return self.token_txsign(ext=amt2+',50000]{}.regtest.rawtx', token='mm1')
- def token_txsend2(self):
- return self.token_txsend(ext=amt2+',50000]{}.regtest.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_devaddr])
- t.expect(f"'{dfl_devaddr}' deleted")
- return t
- def bal1_getbalance(self):
- return self.bal_getbalance(dfl_sid, '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(dfl_sid, '2', extra_args=['--token=mm1'])
- def txcreate_noamt(self):
- return self.txcreate(args=['98831F3A:E:12'])
- def txsign_noamt(self):
- return self.txsign(ext='99.99895,50000]{}.regtest.rawtx')
- def txsend_noamt(self):
- return self.txsend(ext='99.99895,50000]{}.regtest.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]{}.regtest.rawtx', token='mm1')
- def token_txsend_noamt(self):
- return self.token_txsend(ext='1.23456,51000]{}.regtest.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']):
- return self.spawn('mmgen-tool', self.eth_args + args + ['listaddresses'] + tool_args)
- 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=amt', 'reverse=1'])
- def listaddresses4(self):
- return self.listaddresses(tool_args=['sort=age', 'showempty=0'])
- 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 token_listaddresses3(self):
- return self.listaddresses(args=['--token=mm1'], tool_args=['showempty=0'])
- def token_listaddresses4(self):
- return self.listaddresses(args=['--token=mm2'], tool_args=['sort=age', 'reverse=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 = ['--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', '--fee=12G', '98831F3A:E:12, 1.2789']
- return self.token_txcreate(args=args, token='mm1')
- def txdo_cached_balances(
- self,
- acct = '2',
- fee_info_data = ('0.00105', '50'),
- add_args = ['98831F3A:E:3,0.4321']):
- t = self.txcreate(
- args = ['--fee=20G', '--cached-balances'] + add_args + [dfl_words_file],
- acct = acct,
- caller = 'txdo',
- fee_info_data = fee_info_data,
- no_read = True)
- self._do_confirm_send(t, quiet=not self.cfg.debug, sure=False)
- return t
- def txcreate_refresh_balances(self):
- return self._txcreate_refresh_balances(
- bals = ['2', '3'],
- args = ['-B', '--cached-balances', '-i'],
- total = vbal5,
- adj_total = True,
- total_coin = None)
- def _txcreate_refresh_balances(self, bals, args, total, adj_total, total_coin):
- self.mining_delay()
- if total_coin is None:
- total_coin = self.proto.coin
- 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+'\n')
- t.expect('Is this what you want? (y/N): ', 'y')
- t.expect('[R]efresh balance:\b', 'q')
- t.expect(rf'Total unspent:.*\D{total}\D.*{total_coin}', regex=True)
- return t
- def bal10(self):
- return self.bal(n='10')
- def token_txdo_cached_balances(self):
- return self.txdo_cached_balances(
- acct = '1',
- fee_info_data = ('0.0026', '50'),
- 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 twview6(self):
- return self.twview(expect_str=vbal7)
- def twview7(self):
- return self.twview(args=['--cached-balances'])
- def twview8(self):
- return self.twview()
- def twview9(self):
- return self.twview(args=['--cached-balances'], expect_str=vbal6)
- 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_comment(
- self,
- out_num,
- args = [],
- action = 'l',
- comment_text = None,
- changed = False,
- pexpect_spawn = None):
- t = self.spawn('mmgen-txcreate', self.eth_args + args + ['-B', '-i'], pexpect_spawn=pexpect_spawn)
- t.expect(self.menu_prompt, 'M')
- t.expect(self.menu_prompt, action)
- t.expect(r'return to main menu): ', out_num+'\n')
- for p, r in (
- ('Enter label text.*: ', comment_text+'\n') if comment_text is not None else (r'\(y/N\): ', 'y'),
- (r'\(y/N\): ', 'y') if comment_text == Ctrl_U else (None, None),
- ):
- if p:
- t.expect(p, r, regex=True)
- m = (
- 'Label for account #{} edited' if changed else
- 'Account #{} removed' if action == 'D' else
- 'Label added to account #{}' if comment_text and comment_text != Ctrl_U else
- 'Label removed from account #{}')
- t.expect(m.format(out_num))
- t.expect(self.menu_prompt, 'M')
- t.expect(self.menu_prompt, 'q')
- t.expect('Total unspent:')
- return t
- def edit_comment1(self):
- return self.edit_comment(out_num=del_addrs[0], comment_text=tw_comment_zh[:3])
- def edit_comment2(self):
- spawn = not sys.platform == 'win32'
- return self.edit_comment(
- out_num = del_addrs[0],
- comment_text = tw_comment_zh[3:],
- changed = True,
- pexpect_spawn = spawn)
- def edit_comment3(self):
- return self.edit_comment(out_num=del_addrs[1], comment_text=tw_comment_lat_cyr_gr)
- def edit_comment4(self):
- if self.skip_for_win('no pexpect_spawn'):
- return 'skip'
- return self.edit_comment(out_num=del_addrs[0], comment_text=Ctrl_U, pexpect_spawn=True)
- def token_edit_comment1(self):
- return self.edit_comment(out_num='1', comment_text='Token label #1', args=['--token=mm1'])
- def remove_addr1(self):
- return self.edit_comment(out_num=del_addrs[0], action='D')
- def remove_addr2(self):
- return self.edit_comment(out_num=del_addrs[1], action='D')
- def token_remove_addr1(self):
- return self.edit_comment(out_num=del_addrs[0], args=['--token=mm1'], action='D')
- def token_remove_addr2(self):
- return self.edit_comment(out_num=del_addrs[1], args=['--token=mm1'], action='D')
- def twexport_noamt(self):
- return self.twexport(add_args=['include_amts=0'])
- def twexport(self, add_args=[]):
- t = self.spawn('mmgen-tool', self.eth_args + ['twexport'] + add_args)
- t.written_to_file('JSON data')
- return t
- async def twmove(self):
- self.spawn(msg_only=True)
- from mmgen.tw.ctl import TwCtl
- twctl = await TwCtl(self.cfg, self.proto, no_wallet_init=True)
- imsg('Moving tracking wallet')
- fn_bak = twctl.tw_path.with_suffix('.bak.json')
- fn_bak.unlink(missing_ok=True)
- twctl.tw_path.rename(fn_bak)
- return 'ok'
- def twimport(self, add_args=[], expect_str=None):
- from mmgen.tw.json import TwJSON
- fn = joinpath(self.tmpdir, TwJSON.Base(self.cfg, self.proto).dump_fn)
- t = self.spawn('mmgen-tool', self.eth_args_noquiet + ['twimport', fn] + add_args)
- t.expect('(y/N): ', 'y')
- if expect_str:
- t.expect(expect_str)
- t.written_to_file('tracking wallet data')
- return t
- def twimport_nochksum(self):
- return self.twimport(add_args=['ignore_checksum=true'], expect_str='ignoring incorrect checksum')
- def tw_chktotal(self):
- self.spawn(msg_only=True)
- from mmgen.tw.json import TwJSON
- fn = joinpath(self.tmpdir, TwJSON.Base(self.cfg, self.proto).dump_fn)
- res = json.loads(read_from_file(fn))
- cmp_or_die(res['data']['value'], vbal6, 'value in tracking wallet JSON dump')
- return 'ok'
- async def twcompare(self):
- self.spawn(msg_only=True)
- from mmgen.tw.ctl import TwCtl
- twctl = await TwCtl(self.cfg, self.proto, no_wallet_init=True)
- fn = twctl.tw_path
- fn_bak = fn.with_suffix('.bak.json')
- imsg('Comparing imported tracking wallet with original')
- data = [json.dumps(json.loads(f.read_text()), sort_keys=True) for f in (fn, fn_bak)]
- cmp_or_die(*data, 'tracking wallets')
- return 'ok'
- def edit_json_twdump(self):
- self.spawn(msg_only=True)
- from mmgen.tw.json import TwJSON
- fn = TwJSON.Base(self.cfg, self.proto).dump_fn
- text = json.loads(self.read_from_tmpfile(fn))
- token_addr = self.read_from_tmpfile('token_addr2').strip()
- text['data']['entries']['tokens'][token_addr][2][3] = f'edited comment [фубар] [{gr_uc}]'
- self.write_to_tmpfile(fn, json.dumps(text, indent=4))
- return 'ok'
- def stop(self):
- self.spawn(msg_only=True)
- if not self.cfg.no_daemon_stop:
- if not stop_test_daemons(self.proto.coin+'_rt', remove_datadir=True):
- return False
- set_vt100()
- return 'ok'
|