diff --git a/src/sakia/app.py b/src/sakia/app.py index e7308cbe2b6624f285bc955b98419c62f3bd2a21..7af98fba55f72419200870120601fc63ac39c3bc 100644 --- a/src/sakia/app.py +++ b/src/sakia/app.py @@ -1,9 +1,4 @@ -""" -Created on 1 févr. 2014 - -@author: inso -""" - +import attr import datetime import logging @@ -15,7 +10,8 @@ from duniterpy.api.bma import API from . import __version__ from .options import SakiaOptions from sakia.data.connectors import BmaConnector -from sakia.services import NetworkService, BlockchainService, IdentitiesService, SourcesServices, TransactionsService +from sakia.services import NetworkService, BlockchainService, IdentitiesService, \ + SourcesServices, TransactionsService, DocumentsService from sakia.data.repositories import SakiaDatabase from sakia.data.processors import BlockchainProcessor, NodesProcessor, IdentitiesProcessor, \ CertificationsProcessor, SourcesProcessor, TransactionsProcessor, ConnectionsProcessor @@ -24,58 +20,59 @@ from sakia.decorators import asyncify from sakia.money import Relative +@attr.s() class Application(QObject): """ Managing core application datas : Accounts list and general configuration Saving and loading the application state + + + :param QCoreApplication qapp: Qt Application + :param quamash.QEventLoop loop: quamash.QEventLoop instance + :param sakia.options.SakiaOptions options: the options + :param sakia.data.entities.AppData app_data: the application data + :param sakia.data.entities.UserParameters parameters: the application current user parameters + :param sakia.data.repositories.SakiaDatabase db: The database + :param dict network_services: All network services for current currency + :param dict blockchain_services: All blockchain services for current currency + :param dict identities_services: All identities services for current currency + :param dict sources_services: All sources services for current currency + :param dict transactions_services: All transactions services for current currency + :param sakia.services.DocumentsService documents_service: A service to broadcast documents """ - def __init__(self, qapp, loop, options, app_data, parameters, db, - network_services, blockchain_services, identities_services, - sources_services, transactions_services): - """ - Init a new "sakia" application - :param QCoreApplication qapp: Qt Application - :param quamash.QEventLoop loop: quamash.QEventLoop instance - :param sakia.options.SakiaOptions options: the options - :param sakia.data.entities.AppData app_data: the application data - :param sakia.data.entities.UserParameters parameters: the application current user parameters - :param sakia.data.repositories.SakiaDatabase db: The database - :param dict network_services: All network services for current currency - :param dict blockchain_services: All blockchain services for current currency - :param dict identities_services: All identities services for current currency - :param dict sources_services: All sources services for current currency - :param dict transactions_services: All transactions services for current currency - :return: - """ + qapp = attr.ib() + loop = attr.ib() + options = attr.ib() + app_data = attr.ib() + parameters = attr.ib() + db = attr.ib() + network_services = attr.ib(default=attr.Factory(dict)) + blockchain_services = attr.ib(default=attr.Factory(dict)) + identities_services = attr.ib(default=attr.Factory(dict)) + sources_services = attr.ib(default=attr.Factory(dict)) + transactions_services = attr.ib(default=attr.Factory(dict)) + documents_service = attr.ib(default=None) + available_version = attr.ib(init=False) + _translator = attr.ib(init=False) + + def __attrs_post_init__(self): super().__init__() - self.qapp = qapp - self.loop = loop - self.options = options - self.available_version = (True, - __version__, - "") self._translator = QTranslator(self.qapp) - self._app_data = app_data - self._parameters = parameters - self.db = db - self.network_services = network_services - self.blockchain_services = blockchain_services - self.identities_services = identities_services - self.sources_services = sources_services - self.transactions_services = transactions_services + self.available_version = True, __version__, "" @classmethod def startup(cls, argv, qapp, loop): options = SakiaOptions.from_arguments(argv) app_data = AppDataFile.in_config_path(options.config_path).load_or_init() - app = cls(qapp, loop, options, app_data, None, None, {}, {}, {}, {}, {}) + app = cls(qapp, loop, options, app_data, None, None) #app.set_proxy() #app.get_last_version() app.load_profile(app_data.default) app.start_coroutines() + app.documents_service = DocumentsService.instanciate(app) #app.switch_language() return app @@ -105,6 +102,7 @@ class Application(QObject): self.identities_services = {} self.sources_services = {} self.transactions_services = {} + self.documents_service = DocumentsService(bma_connector, blockchain_processor, identities_processor) for currency in self.db.connections_repo.get_currencies(): if currency not in self.identities_services: @@ -128,14 +126,6 @@ class Application(QObject): if currency not in self.sources_services: self.sources_services[currency] = SourcesServices(currency, sources_processor, bma_connector) - def set_proxy(self): - if self.preferences['enable_proxy'] is True: - API.aiohttp_connector = ProxyConnector("http://{0}:{1}".format( - self.preferences['proxy_address'], - self.preferences['proxy_port'])) - else: - API.aiohttp_connector = None - def switch_language(self): logging.debug("Loading translations") locale = self.preferences['lang'] @@ -150,13 +140,6 @@ class Application(QObject): else: logging.debug("Couldn't load translation") - @property - def parameters(self): - """ - :rtype: sakia.data.entities.UserParameters - """ - return self._parameters - def start_coroutines(self): for currency in self.db.connections_repo.get_currencies(): self.network_services[currency].start_coroutines() diff --git a/src/sakia/data/processors/identities.py b/src/sakia/data/processors/identities.py index c20da37af8fa9b8e32d03792ad4614c14f80c7d4..da208f028a1187f219604b3ba6a7682c89b22f7b 100644 --- a/src/sakia/data/processors/identities.py +++ b/src/sakia/data/processors/identities.py @@ -183,34 +183,4 @@ class IdentitiesProcessor: :param str password: The account SigningKey password :param str currency: The currency target of the self certification :param duniterpy.key.ScryptParams scrypt_params: The scrypt parameters of the key - """ - blockchain = self._blockchain_repo.get_one(currency=currency) - block_uid = blockchain.current_buid - timestamp = blockchain.median_time - selfcert = IdentityDoc(2, - currency, - identity.pubkey, - identity.uid, - block_uid, - None) - key = SigningKey(salt, password, scrypt_params) - selfcert.sign([key]) - self._logger.debug("Key publish : {0}".format(selfcert.signed_raw())) - - responses = await self._bma_connector.broadcast(currency, bma.wot.add, - req_args={'identity': selfcert.signed_raw()}) - result = (False, "") - for r in responses: - if r.status == 200: - result = (True, (await r.json())) - elif not result[0]: - result = (False, (await r.text())) - else: - await r.release() - - if result[0]: - identity.blockstamp = block_uid - identity.signature = selfcert.signatures[0] - identity.timestamp = timestamp - - return result, identity + """ \ No newline at end of file diff --git a/src/sakia/gui/dialogs/certification/controller.py b/src/sakia/gui/dialogs/certification/controller.py index 0b537e63db51744dbd15dcf246b22f88f9e788d5..c2b6bfc326d8fd1d1a6bfa59d7ab8b8f9325f2dd 100644 --- a/src/sakia/gui/dialogs/certification/controller.py +++ b/src/sakia/gui/dialogs/certification/controller.py @@ -115,7 +115,7 @@ class CertificationController(QObject): """ self.view.button_box.setDisabled(True) password = await PasswordAskerDialog(self.model.connection).async_exec() - if password: + if not password: self.view.button_box.setEnabled(True) return QApplication.setOverrideCursor(Qt.WaitCursor) diff --git a/src/sakia/gui/dialogs/certification/model.py b/src/sakia/gui/dialogs/certification/model.py index 4d46e6d51d6bfcc321e2ef8eafe2b23cfc19d66b..24ed41a4f93bbae808e98184d5cd6581bd542edd 100644 --- a/src/sakia/gui/dialogs/certification/model.py +++ b/src/sakia/gui/dialogs/certification/model.py @@ -16,6 +16,7 @@ class CertificationModel(QObject): _certifications_processor = attr.ib(default=None) _identities_processor = attr.ib(default=None) _blockchain_processor = attr.ib(default=None) + _documents_service = attr.ib(default=None) def __attrs_post_init__(self): super().__init__() @@ -90,5 +91,8 @@ class CertificationModel(QObject): connections = self._connections_processor.connections(currency=currency) self.connection = connections[index] - def certify(self, password, pubkey): + def certify_pubkey(self, password, pubkey): + self._documents_service.certify(self.connection, password, pubkey) + + def certify_identity(self, password, pubkey): self._certifications_processor.certify(self.connection, password, pubkey) \ No newline at end of file diff --git a/src/sakia/gui/dialogs/connection_cfg/controller.py b/src/sakia/gui/dialogs/connection_cfg/controller.py index 0901538858939c806e40a356c7b1a8caf3f7c846..ee9806780dbd1cd31c1a85f1503e8d4b31208aa8 100644 --- a/src/sakia/gui/dialogs/connection_cfg/controller.py +++ b/src/sakia/gui/dialogs/connection_cfg/controller.py @@ -156,8 +156,7 @@ class ConnectionConfigController(QObject): self.view.display_info(self.tr("Broadcasting identity...")) self.view.stream_log("Broadcasting identity...") password = await self.password_asker.async_exec() - result, connection_identity = await self.model.publish_selfcert(self.model.connection.salt, password, - self.view.scrypt_params) + result, connection_identity = await self.model.publish_selfcert(password) if result[0]: await self.view.show_success(self.model.notification()) else: diff --git a/src/sakia/gui/dialogs/connection_cfg/model.py b/src/sakia/gui/dialogs/connection_cfg/model.py index bb0e48ceefa8801068820a240a3d75333e6e56ed..709897eeb4a263ba01f060e0babef358b7bc1d5f 100644 --- a/src/sakia/gui/dialogs/connection_cfg/model.py +++ b/src/sakia/gui/dialogs/connection_cfg/model.py @@ -106,15 +106,11 @@ class ConnectionConfigModel(QObject): transactions_processor = TransactionsProcessor.instanciate(self.app) await transactions_processor.initialize_transactions(identity, log_stream) - async def publish_selfcert(self, salt, password, scrypt_params): + async def publish_selfcert(self, password): """" Publish the self certification of the connection identity """ - return await self.identities_processor.publish_selfcert(self.node_connector.node.currency, - Identity(self.connection.currency, - self.connection.pubkey, - self.connection.uid), - salt, password, scrypt_params) + return await self.app.documents_service.broadcast_identity(self.connection, password) async def check_registered(self): """ diff --git a/src/sakia/services/documents.py b/src/sakia/services/documents.py index a97a97268cf47205452a73c439825304dc37af07..73222ae4217321f7b110fee930051a7fc2e91a3a 100644 --- a/src/sakia/services/documents.py +++ b/src/sakia/services/documents.py @@ -6,9 +6,12 @@ from collections import Counter from duniterpy.key import SigningKey from duniterpy import PROTOCOL_VERSION -from duniterpy.documents import BlockUID, Block, Identity, Certification, Membership, Revocation +from duniterpy.documents import BlockUID, Block, Certification, Membership, Revocation +from duniterpy.documents import Identity as IdentityDoc from duniterpy.api import bma, errors -from sakia.data.entities import Node +from sakia.data.entities import Identity +from sakia.data.processors import BlockchainProcessor, IdentitiesProcessor, NodesProcessor +from sakia.data.connectors import BmaConnector from aiohttp.errors import ClientError, DisconnectedError @@ -21,37 +24,38 @@ class DocumentsService: _bma_connector = attr.ib() # :type: sakia.data.connectors.BmaConnector _blockchain_processor = attr.ib() # :type: sakia.data.processors.BlockchainProcessor _identities_processor = attr.ib() # :type: sakia.data.processors.IdentitiesProcessor - _logger = attr.ib(default=lambda: logging.getLogger('sakia')) + _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia'))) - async def send_selfcert(self, currency, salt, password): + @classmethod + def instanciate(cls, app): + """ + Instanciate a blockchain processor + :param sakia.app.Application app: the app + """ + return cls(BmaConnector(NodesProcessor(app.db.nodes_repo)), + BlockchainProcessor.instanciate(app), + IdentitiesProcessor.instanciate(app)) + + async def broadcast_identity(self, connection, password): """ Send our self certification to a target community - :param str currency: The currency of the identity - :param sakia.data.entities.Identity identity: The certified identity - :param str salt: The account SigningKey salt - :param str password: The account SigningKey password + :param sakia.data.entities.Connection connection: the connection published """ - try: - block_data = await self._bma_connector.get(currency, bma.blockchain.Current) - signed_raw = "{0}{1}\n".format(block_data['raw'], block_data['signature']) - block_uid = Block.from_signed_raw(signed_raw).blockUID - except errors.DuniterError as e: - if e.ucode == errors.NO_CURRENT_BLOCK: - block_uid = BlockUID.empty() - else: - raise - selfcert = Identity(PROTOCOL_VERSION, - currency, - self.pubkey, - self.name, - block_uid, - None) - key = SigningKey(salt, password) + block_uid = self._blockchain_processor.current_buid(connection.currency) + timestamp = self._blockchain_processor.time(connection.currency) + selfcert = IdentityDoc(2, + connection.currency, + connection.pubkey, + connection.uid, + block_uid, + None) + key = SigningKey(connection.salt, password, connection.scrypt_params) selfcert.sign([key]) self._logger.debug("Key publish : {0}".format(selfcert.signed_raw())) - responses = await self._bma_connector.broadcast(currency, bma.wot.Add, {}, {'identity': selfcert.signed_raw()}) + responses = await self._bma_connector.broadcast(connection.currency, bma.wot.add, + req_args={'identity': selfcert.signed_raw()}) result = (False, "") for r in responses: if r.status == 200: @@ -60,7 +64,16 @@ class DocumentsService: result = (False, (await r.text())) else: await r.release() - return result + + if result[0]: + identity = self._identities_processor.get_identity(connection.currency, connection.pubkey, connection.uid) + if not identity: + identity = Identity(connection.currency, connection.pubkey, connection.uid) + identity.blockstamp = block_uid + identity.signature = selfcert.signatures[0] + identity.timestamp = timestamp + + return result, identity async def send_membership(self, currency, identity, salt, password, mstype): """ @@ -68,7 +81,7 @@ class DocumentsService: Signal "document_broadcasted" is emitted at the end. :param str currency: the currency target - :param sakia.data.entities.Identity identity: the identitiy data + :param sakia.data.entities.IdentityDoc identity: the identitiy data :param str salt: The account SigningKey salt :param str password: The account SigningKey password :param str mstype: The type of membership demand. "IN" to join, "OUT" to leave @@ -99,7 +112,7 @@ class DocumentsService: Certify an other identity :param str currency: The currency of the identity - :param sakia.data.entities.Identity identity: The certified identity + :param sakia.data.entities.IdentityDoc identity: The certified identity :param str salt: The account SigningKey salt :param str password: The account SigningKey password """ @@ -133,7 +146,7 @@ class DocumentsService: Revoke self-identity on server, not in blockchain :param str currency: The currency of the identity - :param sakia.data.entities.Identity identity: The certified identity + :param sakia.data.entities.IdentityDoc identity: The certified identity :param str salt: The account SigningKey salt :param str password: The account SigningKey password """ @@ -168,7 +181,7 @@ class DocumentsService: Generate account revokation document for given community :param str currency: The currency of the identity - :param sakia.data.entities.Identity identity: The certified identity + :param sakia.data.entities.IdentityDoc identity: The certified identity :param str salt: The account SigningKey salt :param str password: The account SigningKey password """ diff --git a/src/sakia/tests/conftest.py b/src/sakia/tests/conftest.py index eb5cf52bdad90b0bfce99e713e66f2e8591d0ba7..bab6a63b706b0427ddbc40cbe879aa98f1d5e3ef 100644 --- a/src/sakia/tests/conftest.py +++ b/src/sakia/tests/conftest.py @@ -9,6 +9,7 @@ from sakia.options import SakiaOptions from sakia.data.files import AppDataFile from sakia.data.entities import * from sakia.data.repositories import * +from sakia.services import DocumentsService _application_ = [] @@ -61,7 +62,14 @@ def user_parameters(): @pytest.fixture def application(event_loop, meta_repo, sakia_options, app_data, user_parameters): - return Application(get_application(), event_loop, sakia_options, app_data, user_parameters, meta_repo, {}, {}, {}, {}, {}) + app = Application(qapp=get_application(), + loop=event_loop, + options=sakia_options, + app_data=app_data, + parameters=user_parameters, + db=meta_repo) + app.documents_service = DocumentsService.instanciate(app) + return app @pytest.fixture diff --git a/src/sakia/tests/functional/test_certification_dialog.py b/src/sakia/tests/functional/test_certification_dialog.py index a95f107c68f76181e7f9429b509c5050a4288d9a..d2e5c49bfe438ff95a70c67b8fb0d128ac6961b0 100644 --- a/src/sakia/tests/functional/test_certification_dialog.py +++ b/src/sakia/tests/functional/test_certification_dialog.py @@ -20,6 +20,7 @@ async def test_certification_init_community(application_with_one_connection, fak certification_dialog.view.radio_pubkey.setChecked(True) assert certification_dialog.view.edit_pubkey.isEnabled() is True QTest.keyClicks(certification_dialog.view.edit_pubkey, alice.key.pubkey) + await asyncio.sleep(0.1) QTest.mouseClick(certification_dialog.view.button_box.button(QDialogButtonBox.Ok), Qt.LeftButton) await asyncio.sleep(0.1) assert Certification is type(fake_server.forge.pool[0])