diff --git a/mmgen/base_proto/ethereum/tw/json.py b/mmgen/base_proto/ethereum/tw/json.py index 63d2faf4..ff2f5492 100755 --- a/mmgen/base_proto/ethereum/tw/json.py +++ b/mmgen/base_proto/ethereum/tw/json.py @@ -87,13 +87,15 @@ class EthereumTwJSON(TwJSON): async def do_import(self,batch): + from ....obj import TwComment + def gen_data(data): for d in data: if hasattr(d,'address'): if d.amount is None: # Python 3.9: {} | {} - yield (d.address, {'mmid':d.mmgen_id,'comment':d.comment}) + yield (d.address, {'mmid':d.mmgen_id,'comment':TwComment(d.comment)}) else: - yield (d.address, {'mmid':d.mmgen_id,'comment':d.comment,'balance':d.amount}) + yield (d.address, {'mmid':d.mmgen_id,'comment':TwComment(d.comment),'balance':d.amount}) else: yield ('params', {'symbol':d.symbol,'decimals':d.decimals}) diff --git a/mmgen/data/release_date b/mmgen/data/release_date index 021e426d..514dc607 100644 --- a/mmgen/data/release_date +++ b/mmgen/data/release_date @@ -1 +1 @@ -June 2022 +July 2022 diff --git a/mmgen/data/version b/mmgen/data/version index 34a9c281..391652bb 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -13.2.dev8 +13.2.dev9 diff --git a/mmgen/tool/rpc.py b/mmgen/tool/rpc.py index c0c8bfaa..f5b3361d 100755 --- a/mmgen/tool/rpc.py +++ b/mmgen/tool/rpc.py @@ -200,25 +200,31 @@ class tool_cmd(tool_cmd_base): ret = await (await TrackingWallet(self.proto,mode='w')).rescan_blockchain(start_block,stop_block) return True - async def twexport(self,include_amts=True): + async def twexport(self,include_amts=True,pretty=False): """ export a tracking wallet to JSON format - NOTE: + NOTES: If ‘include_amts’ is true (the default), Ethereum balances will be restored from the dump upon import. For Bitcoin and forks, amount fields in the dump are ignored. + + If ‘pretty’ is true, JSON will be dumped in human-readable format to allow + for editing of comment fields. """ from ..tw.json import TwJSON - await TwJSON.Export( self.proto, include_amts=include_amts ) + await TwJSON.Export( self.proto, include_amts=include_amts, pretty=pretty ) return True async def twimport(self,filename:str,ignore_checksum=False,batch=False): """ restore a tracking wallet from a JSON dump created by ‘twexport’ - NOTE: + NOTES: + + If comment fields in the JSON dump have been edited, ‘ignore_checksum’ must + be set to true. The restored tracking wallet will have correct balances but no record of historical transactions. These may be restored by running ‘mmgen-tool diff --git a/mmgen/tw/json.py b/mmgen/tw/json.py index 15eaedb4..de568547 100755 --- a/mmgen/tw/json.py +++ b/mmgen/tw/json.py @@ -50,8 +50,13 @@ class TwJSON: def dump_fn(self): return f'{self.fn_pfx}-{self.coin}-{self.network}.json' - def json_dump(self,data): - return json.dumps( data, cls=json_encoder, separators=(',', ':'), sort_keys=True ) + def json_dump(self,data,pretty=False): + return json.dumps( + data, + cls = json_encoder, + sort_keys = True, + separators = None if pretty else (',', ':'), + indent = 4 if pretty else None ) def make_chksum(self,data): return make_chksum_8( self.json_dump(data).encode() ).lower() @@ -128,7 +133,7 @@ class TwJSON: class Export(Base,metaclass=AsyncInit): - async def __init__(self,proto,include_amts=True): + async def __init__(self,proto,include_amts=True,pretty=False): super().__init__(proto) @@ -156,7 +161,10 @@ class TwJSON: from ..fileutil import write_data_to_file write_data_to_file( outfile = self.dump_fn, - data = self.json_dump({ - 'checksum': self.make_chksum(data), - 'data': data }), + data = self.json_dump( + { + 'checksum': self.make_chksum(data), + 'data': data + }, + pretty = pretty ), desc = f'tracking wallet JSON data' ) diff --git a/test/test_py_d/ts_ethdev.py b/test/test_py_d/ts_ethdev.py index 3a7c74f0..0c8be194 100755 --- a/test/test_py_d/ts_ethdev.py +++ b/test/test_py_d/ts_ethdev.py @@ -317,6 +317,12 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): ('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)'), ('stop', 'stopping daemon'), @@ -1143,6 +1149,10 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): 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=1']) + def token_listaddresses4(self): + return self.listaddresses(args=['--token=mm2'],tool_args=['showempty=1']) def twview_cached_balances(self): return self.twview(args=['--cached-balances']) @@ -1279,14 +1289,19 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): os.rename( tw.tw_fn, tw.tw_fn+'.bak.json' ) return 'ok' - def twimport(self,add_args=[]): + def twimport(self,add_args=[],expect_str=None): from mmgen.tw.json import TwJSON fn = joinpath( self.tmpdir, TwJSON.Base(self.proto).dump_fn ) - t = self.spawn('mmgen-tool',self.eth_args + ['twimport',fn] + add_args) + 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 @@ -1305,6 +1320,16 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): 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.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 opt.no_daemon_stop: diff --git a/test/test_py_d/ts_regtest.py b/test/test_py_d/ts_regtest.py index 0a172f4f..9a96c4e3 100755 --- a/test/test_py_d/ts_regtest.py +++ b/test/test_py_d/ts_regtest.py @@ -209,6 +209,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): ('bob_rescan_blockchain_gb', 'rescanning the blockchain (Genesis block)'), ('bob_rescan_blockchain_one','rescanning the blockchain (single block)'), ('bob_rescan_blockchain_ss', 'rescanning the blockchain (range of blocks)'), + ('bob_twexport', 'exporting a tracking wallet to JSON'), ('carol_twimport', 'importing a tracking wallet JSON dump'), ('carol_delete_wallet', 'unloading and deleting Carol’s tracking wallet'), @@ -216,6 +217,12 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): ('carol_twimport_nochksum', 'importing a tracking wallet JSON dump (ignore_checksum=1)'), ('carol_delete_wallet', 'unloading and deleting Carol’s tracking wallet'), ('carol_twimport_batch', 'importing a tracking wallet JSON dump (batch=1)'), + ('bob_twexport_pretty', 'exporting a tracking wallet to JSON (pretty=1)'), + ('bob_edit_json_twdump', 'editing a tracking wallet JSON dump'), + ('carol_delete_wallet', 'unloading and deleting Carol’s tracking wallet'), + ('carol_twimport_pretty', 'importing an edited tracking wallet JSON dump (ignore_checksum=1)'), + ('carol_listaddresses', 'viewing Carol’s tracking wallet'), + ('bob_split2', "splitting Bob's funds"), ('bob_0conf0_getbalance', "Bob's balance (unconfirmed, minconf=0)"), ('bob_0conf1_getbalance', "Bob's balance (unconfirmed, minconf=1)"), @@ -986,12 +993,26 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): def bob_twexport_noamt(self): return self.bob_twexport(add_args=['include_amts=0']) - def carol_twimport(self,add_args=[]): + def bob_twexport_pretty(self): + return self.bob_twexport(add_args=['pretty=1']) + + def bob_edit_json_twdump(self): + self.spawn('',msg_only=True) + from mmgen.tw.json import TwJSON + fn = TwJSON.Base(self.proto).dump_fn + text = json.loads(self.read_from_tmpfile(fn)) + text['data']['entries'][3][3] = f'edited comment [фубар] [{gr_uc}]' + self.write_to_tmpfile( fn, json.dumps(text,indent=4) ) + return 'ok' + + def carol_twimport(self,add_args=[],expect_str=None): from mmgen.tw.json import TwJSON fn = joinpath( self.tmpdir, TwJSON.Base(self.proto).dump_fn ) t = self.spawn('mmgen-tool',['--carol','twimport',fn] + add_args) t.expect('(y/N): ','y') - if 'batch=true' in add_args: + if expect_str: + t.expect(expect_str) + elif 'batch=true' in add_args: t.expect('{} addresses imported'.format(15 if self.proto.coin == 'BCH' else 25)) else: t.expect('import completed OK') @@ -1004,6 +1025,12 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): def carol_twimport_batch(self): return self.carol_twimport(add_args=['batch=true']) + def carol_twimport_pretty(self): + return self.carol_twimport(add_args=['ignore_checksum=true'],expect_str='ignoring incorrect checksum') + + def carol_listaddresses(self): + return self.spawn('mmgen-tool',['--carol','listaddresses','showempty=1']) + async def carol_delete_wallet(self): imsg(f'Unloading Carol’s tracking wallet') t = self.spawn('mmgen-regtest',['cli','unloadwallet','carol'])