diff --git a/lbrynet/extras/daemon/Daemon.py b/lbrynet/extras/daemon/Daemon.py index 7b43880e2..e2d96e7d1 100644 --- a/lbrynet/extras/daemon/Daemon.py +++ b/lbrynet/extras/daemon/Daemon.py @@ -2,6 +2,7 @@ import os import asyncio import logging import json +import time import inspect import typing import aiohttp @@ -14,6 +15,7 @@ from traceback import format_exc from aiohttp import web from functools import wraps from torba.client.baseaccount import SingleKey, HierarchicalDeterministic +from torba.client.hash import aes_encrypt, sha256 from lbrynet import __version__, utils from lbrynet.conf import Config, Setting, SLACK_WEBHOOK @@ -1249,6 +1251,45 @@ class Daemon(metaclass=JSONRPCServerType): await self.analytics_manager.send_credits_sent() return result + @requires("wallet") + def jsonrpc_account_manifest(self, password, account_ids=None): + """ + Generate a manifest for all of the accounts or only for limited set of accounts. + + Usage: + account manifest [...] + + Options: + --password= : (str) password to use for encrypting values + --account-ids= : (list) list of accounts ids to limit manifest + + Returns: + (map) manifest + + """ + init_vector = b'!\tT\xef\x0c\x85j\x9elp\xf1\xa6\xe0\xe8\x188' + accounts = self.get_accounts_or_all(account_ids) + manifest = [] + status = [] + for account in accounts: + encrypted_data = aes_encrypt(password, json.dumps(account.to_dict(False)), init_vector).encode() + manifest.append({ + 'account_id': account.id, + 'timestamp': time.time(), + 'hash-data': hexlify(sha256(encrypted_data)), + 'hash-certificates': [ + aes_encrypt(password, cert, init_vector).encode() for cert in account.certificates.keys() + ] + }) + status.append(manifest[-1]['hash-data']) + status.extend(manifest[-1]['hash-certificates']) + return { + 'type': 'manifest', + 'generated': time.time(), + 'status': hexlify(sha256(b''.join(status))), + 'accounts': manifest + } + ADDRESS_DOC = """ Address management. """ diff --git a/lbrynet/extras/wallet/account.py b/lbrynet/extras/wallet/account.py index 05316a9f2..27b4aec29 100644 --- a/lbrynet/extras/wallet/account.py +++ b/lbrynet/extras/wallet/account.py @@ -176,9 +176,10 @@ class Account(BaseAccount): account.certificates = d.get('certificates', {}) return account - def to_dict(self): + def to_dict(self, with_certificates=True): d = super().to_dict() - d['certificates'] = self.certificates + if with_certificates: + d['certificates'] = self.certificates return d async def get_details(self, **kwargs): diff --git a/tests/integration/test_sync.py b/tests/integration/test_sync.py new file mode 100644 index 000000000..083b6e371 --- /dev/null +++ b/tests/integration/test_sync.py @@ -0,0 +1,79 @@ +from unittest import mock + +from lbrynet.schema.claim import ClaimDict + +from torba.orchstr8.node import WalletNode, SPVNode +from torba.testcase import AsyncioTestCase + +import lbrynet.schema +lbrynet.schema.BLOCKCHAIN_NAME = 'lbrycrd_regtest' + +from lbrynet.conf import Config +from lbrynet.extras.daemon.Daemon import Daemon +from lbrynet.extras.wallet import LbryWalletManager, RegTestLedger +from lbrynet.extras.daemon.Components import WalletComponent +from lbrynet.extras.daemon.Components import ( + DHT_COMPONENT, HASH_ANNOUNCER_COMPONENT, PEER_PROTOCOL_SERVER_COMPONENT, + UPNP_COMPONENT, EXCHANGE_RATE_MANAGER_COMPONENT +) +from lbrynet.extras.daemon.ComponentManager import ComponentManager + + +class AccountSynchronization(AsyncioTestCase): + + async def asyncSetUp(self): + self.wallet_node = WalletNode(LbryWalletManager, RegTestLedger) + await self.wallet_node.start( + SPVNode(None), + "carbon smart garage balance margin twelve chest sword toast envelope bottom stomach absent", + False + ) + self.account = self.wallet_node.account + + conf = Config() + conf.data_dir = self.wallet_node.data_path + conf.wallet_dir = self.wallet_node.data_path + conf.download_dir = self.wallet_node.data_path + conf.share_usage_data = False + conf.use_upnp = False + conf.reflect_streams = False + conf.blockchain_name = 'lbrycrd_regtest' + conf.lbryum_servers = [('localhost', 50001)] + conf.reflector_servers = [] + conf.known_dht_nodes = [] + + def wallet_maker(component_manager): + self.wallet_component = WalletComponent(component_manager) + self.wallet_component.wallet_manager = self.wallet_node.manager + self.wallet_component._running = True + return self.wallet_component + + conf.components_to_skip = [ + DHT_COMPONENT, UPNP_COMPONENT, HASH_ANNOUNCER_COMPONENT, + PEER_PROTOCOL_SERVER_COMPONENT, EXCHANGE_RATE_MANAGER_COMPONENT + ] + self.daemon = Daemon(conf, ComponentManager( + conf, skip_components=conf.components_to_skip, wallet=wallet_maker + )) + await self.daemon.initialize() + + async def asyncTearDown(self): + self.wallet_component._running = False + await self.daemon.stop() + + @mock.patch('time.time', mock.Mock(return_value=12345)) + def test_manifest(self): + self.account.certificates['abcdefg1234:0'] = '---PRIVATE KEY---' + self.assertEqual({ + 'type': 'manifest', + 'generated': 12345, + 'status': b'880fd3bef17c02d02710af94dcb69877407636432ad8cf17db2689be36fc52e4', + 'accounts': [{ + 'account_id': 'n4ZRwP4QjKwsmXCfqUPqnx133i83Ha7GbW', + 'timestamp': 12345, + 'hash-data': b'c9a0e30c9cccd995e0c241a5d6e34308a291581dd858ffe51b307094fa621f8a', + 'hash-certificates': [ + b'IQlU7wyFap5scPGm4OgYOBa5bXx9Fy0KJOfeX2QbTN4=' + ] + }] + }, self.daemon.jsonrpc_account_manifest('password'))