restore.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 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. xmrwallet.ops.restore: Monero wallet ops for the MMGen Suite
  12. """
  13. from ...util import msg, gmsg, bmsg, ymsg, rmsg, die
  14. from ..file.outputs import MoneroWalletDumpFile
  15. from ..rpc import MoneroWalletRPC
  16. from .create import OpCreate
  17. class OpRestore(OpCreate):
  18. wallet_offline = True
  19. def check_uopts(self):
  20. if self.cfg.restore_height is not None:
  21. die(1, '--restore-height must be unset when running the ‘restore’ command')
  22. async def process_wallet(self, d, fn, last):
  23. def get_dump_data():
  24. def gen():
  25. for fn in [self.get_wallet_fn(d, watch_only=wo) for wo in (True, False)]:
  26. ret = fn.parent / (fn.name + '.dump')
  27. if ret.exists():
  28. yield ret
  29. match tuple(gen()):
  30. case [dump_fn, *rest]:
  31. if rest:
  32. ymsg(f'Warning: more than one dump file found for ‘{fn}’ - using the first!')
  33. case _:
  34. die(1, f'No suitable dump file found for ‘{fn}’')
  35. return MoneroWalletDumpFile.Completed(
  36. parent = self,
  37. fn = dump_fn).data._asdict()['wallet_metadata']
  38. def restore_accounts():
  39. bmsg(' Restoring accounts:')
  40. for acct_idx, acct_data in enumerate(data[1:], 1):
  41. msg(fs.format(acct_idx, 0, acct_data['address']))
  42. self.c.call('create_account')
  43. def restore_subaddresses():
  44. bmsg(' Restoring subaddresses:')
  45. for acct_idx, acct_data in enumerate(data):
  46. for addr_idx, addr_data in enumerate(acct_data['addresses'][1:], 1):
  47. msg(fs.format(acct_idx, addr_idx, addr_data['address']))
  48. self.c.call('create_address', account_index=acct_idx)
  49. def restore_labels():
  50. bmsg(' Restoring labels:')
  51. for acct_idx, acct_data in enumerate(data):
  52. for addr_idx, addr_data in enumerate(acct_data['addresses']):
  53. addr_data['used'] = False # do this so that restored data matches
  54. msg(fs.format(acct_idx, addr_idx, addr_data['label']))
  55. self.c.call(
  56. 'label_address',
  57. index = {'major': acct_idx, 'minor': addr_idx},
  58. label = addr_data['label'])
  59. def make_format_str():
  60. return ' acct {:O>%s}, addr {:O>%s} [{}]' % (
  61. len(str(len(data) - 1)),
  62. len(str(max(len(acct_data['addresses']) for acct_data in data) - 1)))
  63. def check_restored_data():
  64. restored_data = h.get_wallet_data(print=False).addrs_data
  65. if restored_data != data:
  66. rmsg('Restored data does not match original dump! Dumping bad data.')
  67. MoneroWalletDumpFile.New(
  68. parent = self,
  69. wallet_fn = fn,
  70. data = {'wallet_metadata': restored_data}
  71. ).write(add_suf='.bad')
  72. die(3, 'Fatal error')
  73. await super().process_wallet(d, fn, last)
  74. h = MoneroWalletRPC(self, d)
  75. h.open_wallet('newly created')
  76. msg('')
  77. data = get_dump_data()
  78. fs = make_format_str()
  79. gmsg('\nRestoring accounts, subaddresses and labels from dump file:\n')
  80. restore_accounts()
  81. restore_subaddresses()
  82. restore_labels()
  83. check_restored_data()
  84. return True