From 6bb72909ccb9d5bb545e5fba8ebbbaac90737130 Mon Sep 17 00:00:00 2001
From: inso <insomniak.fr@gmaiL.com>
Date: Thu, 29 Dec 2016 15:22:29 +0100
Subject: [PATCH] Migrate SelfCert broadcast in DocumentsService

---
 src/sakia/app.py                              | 93 ++++++++-----------
 src/sakia/data/processors/identities.py       | 32 +------
 .../gui/dialogs/certification/controller.py   |  2 +-
 src/sakia/gui/dialogs/certification/model.py  |  6 +-
 .../gui/dialogs/connection_cfg/controller.py  |  3 +-
 src/sakia/gui/dialogs/connection_cfg/model.py |  8 +-
 src/sakia/services/documents.py               | 73 +++++++++------
 src/sakia/tests/conftest.py                   | 10 +-
 .../functional/test_certification_dialog.py   |  1 +
 9 files changed, 101 insertions(+), 127 deletions(-)

diff --git a/src/sakia/app.py b/src/sakia/app.py
index e7308cbe..7af98fba 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 c20da37a..da208f02 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 0b537e63..c2b6bfc3 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 4d46e6d5..24ed41a4 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 09015388..ee980678 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 bb0e48ce..709897ee 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 a97a9726..73222ae4 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 eb5cf52b..bab6a63b 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 a95f107c..d2e5c49b 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])
-- 
GitLab