json.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
  4. # Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen-wallet
  9. # https://gitlab.com/mmgen/mmgen-wallet
  10. """
  11. tw.json: export and import tracking wallet to JSON format
  12. """
  13. import sys,os,json
  14. from collections import namedtuple
  15. from ..util import msg,ymsg,fmt,suf,die,make_timestamp,make_chksum_8
  16. from ..base_obj import AsyncInit
  17. from ..objmethods import MMGenObject
  18. from ..rpc import json_encoder
  19. from .ctl import TwCtl
  20. class TwJSON:
  21. class Base(MMGenObject):
  22. can_prune = False
  23. pruned = None
  24. fn_pfx = 'mmgen-tracking-wallet-dump'
  25. def __new__(cls,cfg,proto,*args,**kwargs):
  26. return MMGenObject.__new__(proto.base_proto_subclass(TwJSON,'tw.json',cls.__name__))
  27. def __init__(self,cfg,proto):
  28. self.cfg = cfg
  29. self.proto = proto
  30. self.coin = proto.coin_id.lower()
  31. self.network = proto.network
  32. self.keys = ['mmgen_id','address','amount','comment']
  33. self.entry_tuple = namedtuple('tw_entry',self.keys)
  34. @property
  35. def dump_fn(self):
  36. def get_fn(prune_id):
  37. return '{a}{b}-{c}-{d}.json'.format(
  38. a = self.fn_pfx,
  39. b = f'-pruned[{prune_id}]' if prune_id else '',
  40. c = self.coin,
  41. d = self.network )
  42. if self.pruned:
  43. from ..addrlist import AddrIdxList
  44. prune_id = AddrIdxList(idx_list=self.pruned).id_str
  45. fn = get_fn(prune_id)
  46. mf = 255 if sys.platform == 'win32' else os.statvfs(self.cfg.outdir or os.curdir).f_namemax
  47. if len(fn) > mf:
  48. fn = get_fn(f'idhash={make_chksum_8(prune_id.encode()).lower()}')
  49. else:
  50. fn = get_fn(None)
  51. return fn
  52. def json_dump(self,data,pretty=False):
  53. return json.dumps(
  54. data,
  55. cls = json_encoder,
  56. sort_keys = True,
  57. separators = None if pretty else (',', ':'),
  58. indent = 4 if pretty else None ) + ('\n' if pretty else '')
  59. def make_chksum(self,data):
  60. return make_chksum_8( self.json_dump(data).encode() ).lower()
  61. @property
  62. def mappings_chksum(self):
  63. return self.make_chksum(self.mappings_json)
  64. @property
  65. def entry_tuple_in(self):
  66. return namedtuple('entry_tuple_in',self.keys)
  67. class Import(Base,metaclass=AsyncInit):
  68. blockchain_rescan_warning = None
  69. async def __init__(
  70. self,
  71. cfg,
  72. proto,
  73. filename,
  74. ignore_checksum = False,
  75. batch = False ):
  76. super().__init__(cfg,proto)
  77. self.twctl = await TwCtl( cfg, proto, mode='i', rpc_ignore_wallet=True )
  78. def check_network(data):
  79. coin,network = data['network'].split('_')
  80. if coin != self.coin:
  81. die(2,f'Coin in wallet dump is {coin.upper()}, but configured coin is {self.coin.upper()}')
  82. if network != self.network:
  83. die(2,f'Network in wallet dump is {network}, but configured network is {self.network}')
  84. def check_chksum(d):
  85. chksum = self.make_chksum(d['data'])
  86. if chksum != d['checksum']:
  87. if ignore_checksum:
  88. ymsg(f'Warning: ignoring incorrect checksum {chksum}')
  89. else:
  90. die(3,f'File checksum incorrect! ({chksum} != {d["checksum"]})')
  91. def verify_data(d):
  92. check_network(d['data'])
  93. check_chksum(d)
  94. self.cfg._util.compare_or_die(
  95. val1 = self.mappings_chksum,
  96. val2 = d['data']['mappings_checksum'],
  97. desc1 = 'computed mappings checksum',
  98. desc2 = 'saved checksum' )
  99. if not await self.check_and_create_wallet():
  100. return
  101. from ..fileutil import get_data_from_file
  102. self.data = json.loads(get_data_from_file( self.cfg, filename, quiet=True ))
  103. self.keys = self.data['data']['entries_keys']
  104. self.entries = await self.get_entries()
  105. verify_data(self.data)
  106. addrs = await self.do_import(batch)
  107. await self.twctl.rescan_addresses(addrs)
  108. if self.blockchain_rescan_warning:
  109. ymsg('\n' + fmt(self.blockchain_rescan_warning.strip(),indent=' '))
  110. async def check_and_create_wallet(self):
  111. if await self.tracking_wallet_exists:
  112. die(3,
  113. f'Existing {self.twctl.rpc.daemon.desc} wallet detected!\n' +
  114. 'It must be moved, or backed up and securely deleted, before running this command' )
  115. msg('\n'+fmt(self.info_msg.strip(),indent=' '))
  116. from ..ui import keypress_confirm
  117. if not keypress_confirm( self.cfg, 'Continue?' ):
  118. msg('Exiting at user request')
  119. return False
  120. if not await self.create_tracking_wallet():
  121. die(3,'Wallet could not be created')
  122. return True
  123. class Export(Base,metaclass=AsyncInit):
  124. async def __init__(
  125. self,
  126. cfg,
  127. proto,
  128. include_amts = True,
  129. pretty = False,
  130. prune = False,
  131. warn_used = False,
  132. force_overwrite = False ):
  133. if prune and not self.can_prune:
  134. die(1,f'Pruning not supported for {proto.name} protocol')
  135. self.prune = prune
  136. self.warn_used = warn_used
  137. super().__init__(cfg,proto)
  138. if not include_amts:
  139. self.keys.remove('amount')
  140. self.twctl = await TwCtl( cfg, proto )
  141. self.entries = await self.get_entries()
  142. if self.prune:
  143. msg('Pruned {} address{}'.format( len(self.pruned), suf(self.pruned,'es') ))
  144. msg('Exporting {} address{}'.format( self.num_entries, suf(self.num_entries,'es') ))
  145. data = {
  146. 'id': 'mmgen_tracking_wallet',
  147. 'version': 1,
  148. 'network': f'{self.coin}_{self.network}',
  149. 'blockheight': self.twctl.rpc.blockcount,
  150. 'time': make_timestamp(),
  151. 'mappings_checksum': self.mappings_chksum,
  152. 'entries_keys': self.keys,
  153. 'entries': await self.entries_out,
  154. 'num_entries': self.num_entries,
  155. }
  156. if include_amts:
  157. data['value'] = await self.total
  158. from ..fileutil import write_data_to_file
  159. write_data_to_file(
  160. cfg = self.cfg,
  161. outfile = self.dump_fn,
  162. data = self.json_dump(
  163. {
  164. 'checksum': self.make_chksum(data),
  165. 'data': data
  166. },
  167. pretty = pretty ),
  168. desc = 'tracking wallet JSON data',
  169. ask_overwrite = not force_overwrite )