working sync

This commit is contained in:
Lex Berezhny 2019-03-11 09:52:35 -04:00
parent 2dee0ff0bf
commit a914de155a
3 changed files with 83 additions and 44 deletions

View file

@ -14,8 +14,9 @@ from binascii import hexlify, unhexlify
from traceback import format_exc
from aiohttp import web
from functools import wraps
from torba.client.wallet import Wallet
from torba.client.baseaccount import SingleKey, HierarchicalDeterministic
from torba.client.hash import aes_encrypt, sha256
from torba.client.hash import sha256
from lbrynet import __version__, utils
from lbrynet.conf import Config, Setting, SLACK_WEBHOOK
@ -1252,42 +1253,61 @@ class Daemon(metaclass=JSONRPCServerType):
return result
@requires("wallet")
def jsonrpc_account_manifest(self, password, account_ids=None):
def jsonrpc_sync_hash(self):
"""
Generate a manifest for all of the accounts or only for limited set of accounts.
Deterministic hash of the wallet.
Usage:
account manifest <password> [<account_ids>...]
sync hash
Options:
--password=<password> : (str) password to use for encrypting values
--account-ids=<account_ids> : (list) list of accounts ids to limit manifest
Returns:
(map) manifest
(str) sha256 hash of wallet
"""
return hexlify(self.default_wallet.hash).decode()
@requires("wallet")
def jsonrpc_sync_apply(self, password, data=None, encrypt_password=None):
"""
Apply incoming synchronization data, if provided, and then produce a sync hash and
an encrypted wallet.
Usage:
sync apply <password> [--data=<data>] [--encrypt-password=<encrypt_password>]
Options:
--password=<password> : (str) password to decrypt incoming and encrypt outgoing data
--data=<data> : (str) incoming sync data, if any
--encrypt-password=<encrypt_password> : (str) password to encrypt outgoing data if different
from the decrypt password, used during password changes
Returns:
(map) sync hash and data
"""
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'])
if data is not None:
decrypted_data = Wallet.unpack(password, data)
for account_data in decrypted_data['accounts']:
_, _, pubkey = LBCAccount.keys_from_dict(self.ledger, account_data)
account_id = pubkey.address
local_match = None
for local_account in self.default_wallet.accounts:
if account_id == local_account.id:
local_match = local_account
break
if local_match is not None:
local_match.name = account_data.get('name', local_match.name)
local_match.certificates.update(account_data.get('certificates', {}))
else:
new_account = LBCAccount.from_dict(self.ledger, self.default_wallet, account_data)
if self.ledger.network.is_connected:
asyncio.create_task(self.ledger.subscribe_account(new_account))
encrypted = self.default_wallet.pack(encrypt_password or password)
return {
'type': 'manifest',
'generated': time.time(),
'status': hexlify(sha256(b''.join(status))),
'accounts': manifest
'hash': self.jsonrpc_sync_hash(),
'data': encrypted.decode()
}
ADDRESS_DOC = """

View file

@ -1,6 +1,7 @@
import json
import logging
import binascii
from hashlib import sha256
from lbrynet.schema.validator import validate_claim_id
from torba.client.baseaccount import BaseAccount
@ -24,6 +25,13 @@ class Account(BaseAccount):
super().__init__(*args, **kwargs)
self.certificates = {}
@property
def hash(self) -> bytes:
h = sha256(json.dumps(self.to_dict(False)).encode())
for cert in sorted(self.certificates.keys()):
h.update(cert.encode())
return h.digest()
def add_certificate_private_key(self, ref: TXORef, private_key):
assert ref.id not in self.certificates, 'Trying to add a duplicate certificate.'
self.certificates[ref.id] = private_key
@ -176,9 +184,9 @@ class Account(BaseAccount):
account.certificates = d.get('certificates', {})
return account
def to_dict(self, with_certificates=True):
def to_dict(self, include_certificates=True):
d = super().to_dict()
if with_certificates:
if include_certificates:
d['certificates'] = self.certificates
return d

View file

@ -62,18 +62,29 @@ class AccountSynchronization(AsyncioTestCase):
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'))
def test_sync(self):
starting_hash = 'fcafbb9bd3943d8d425a4c00d3982a4c6aaff763d5289f7852296f8ea882214f'
self.assertEqual(self.daemon.jsonrpc_sync_hash(), starting_hash)
self.assertEqual(self.daemon.jsonrpc_sync_apply('password')['hash'], starting_hash)
self.assertFalse(self.account.certificates)
hash_w_cert = 'ef748d7777bb01ce9be6f87d3e46aeb31abbbe1648b1f2ddfa5aa9bcf0736a2d'
add_cert = (
'5sx5Q4ruPFDSftJ3+5l0rKDEacDE7npsee2Pz+jsYTiNSBtDXt/fbvpKELpn6BWYDM1rqDCHDgZoy6609KbTCu'
'TqlYnrtMVpSz8QXc/Gzry2zXgtuuG6CAAvhntfELfwiJW4r1wvKDq30+IDrX8HIM5TiErLsLqfvfhc4t9Qfn5Y'
'IgJk9pYxu+xC7rJh+kYra+zu6JtEI9hdq+peXX6uAnqEKlRQCTLDPA6Z9Pk9Hdbhl9QJ3TVTNeTkMQyCZZ49SJ'
'PtOghGXIA9Gtkp86nKvuzV7rKpVEJEe/mcUsBkQ/W9W/7bok3tOXBs7SCis0MMyYFbCQ1LVDy6RUD28UHp/P5O'
'4kbxptuRzGKrkrQX00QEqzPuQwbbxuOMarGWUBP4USX6GmtK0e3AL1bUJzdJEuy937DdcvbhrzfxT0Jphjal5s'
'BSDufxZaQcHLHOhjQ8DDnFscjbAChcjxCLgcYMtdxYGM0WmCU7vdKyWK7sULi+LSqPTf/75lYoW1FxXt3v/blX'
'I3nJF5owVEZPx/5dNy95WDVCpQyDNd/Zw9ke2P+4d6hyMXbsz9Oei0q4BlKDM3MNGHd+MNSiX23xZq+FtTQdbw'
'ZOBhRTcQRB8VoR9M27acQApcdd2AXj0ZKrj/T+p8O0tuM0kWYOOAt6P/WxbU16im+WoR+4OTPggxu8r8SFFsXZ'
'EXYXT3tUSNzpU32OH2jXzo7P4Wa69s8u+X8RgA=='
)
self.assertEqual(self.daemon.jsonrpc_sync_apply('password', data=add_cert)['hash'], hash_w_cert)
self.assertEqual(self.daemon.jsonrpc_sync_hash(), hash_w_cert)
self.assertEqual(self.account.certificates, {'abcdefg1234:0': '---PRIVATE KEY---'})
# applying the same diff is idempotent
self.assertEqual(self.daemon.jsonrpc_sync_apply('password', data=add_cert)['hash'], hash_w_cert)
self.assertEqual(self.daemon.jsonrpc_sync_hash(), hash_w_cert)
self.assertEqual(self.account.certificates, {'abcdefg1234:0': '---PRIVATE KEY---'})