diff --git a/res/ui/certification.ui b/res/ui/certification.ui index 30d999832d91a8ad7c8b215a7ad7fdf52c213c99..59f527b542ae9c55862bc1c29a8ae8438b2aa1c7 100644 --- a/res/ui/certification.ui +++ b/res/ui/certification.ui @@ -30,10 +30,19 @@ <widget class="QComboBox" name="combo_community"/> </item> <item> - <widget class="QLabel" name="label_cert_stock"> - <property name="text"> - <string/> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Certifications stock</string> </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QLabel" name="label_cert_stock"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> </widget> </item> </layout> diff --git a/src/sakia/core/registry/identity.py b/src/sakia/core/registry/identity.py index e8335cb0a53ac15ee4dcc78c9de37fea02547f8a..427af5ead3eae4429888b7245090a4b5cc9f6bd1 100644 --- a/src/sakia/core/registry/identity.py +++ b/src/sakia/core/registry/identity.py @@ -284,7 +284,7 @@ class Identity(QObject): Get the list of this person certifiers :param sakia.core.registry.identities.IdentitiesRegistry identities_registry: The identities registry - :param sakia.core.community.Community community: The community target to request the join date + :param sakia.core.community.Community community: The community target :return: The list of the certifiers of this community :rtype: list """ @@ -301,10 +301,10 @@ class Identity(QObject): BlockchainState.VALIDATED, community) certifier['cert_time'] = certifier_data['cert_time']['medianTime'] - if 'written' in certifier_data and type(certifier_data['written']) is dict: + if certifier_data['written']: certifier['block_number'] = certifier_data['written']['number'] else: - certifier['block_number'] = certifier_data['cert_time']['block'] + certifier['block_number'] = None certifiers.append(certifier) except ValueError as e: @@ -348,7 +348,7 @@ class Identity(QObject): Get the certifications in the blockchain and in the pools Get only unique and last certification for each pubkey :param sakia.core.registry.identities.IdentitiesRegistry identities_registry: The identities registry - :param sakia.core.community.Community community: The community target to request the join date + :param sakia.core.community.Community community: The community target :return: The list of the certifiers of this community :rtype: list """ @@ -378,9 +378,7 @@ class Identity(QObject): """ Get the list of persons certified by this person :param sakia.core.registry.IdentitiesRegistry identities_registry: The registry - :param sakia.core.Community community: The community - - :param sakia.core.community.Community community: The community target to request the join date + :param sakia.core.community.Community community: The community target :return: The list of the certified persons of this community in BMA json format :rtype: list """ @@ -395,10 +393,10 @@ class Identity(QObject): BlockchainState.VALIDATED, community) certified['cert_time'] = certified_data['cert_time']['medianTime'] - if 'written' in certified_data and type(certified_data['written']) is dict: + if certified_data['written']: certified['block_number'] = certified_data['written']['number'] else: - certified['block_number'] = certified_data['cert_time']['block'] + certified['block_number'] = None certified_list.append(certified) except ValueError as e: if '404' in str(e): @@ -429,6 +427,14 @@ class Identity(QObject): return certified_list async def unique_valid_certified_by(self, identities_registry, community): + """ + Get the list of persons certified by this person, filtered to get only unique + and valid certifications. + :param sakia.core.registry.IdentitiesRegistry identities_registry: The registry + :param sakia.core.community.Community community: The community target + :return: The list of the certified persons of this community in BMA json format + :rtype: list + """ certified_list = await self.certified_by(identities_registry, community) unique_valid = [] # add certifiers of uid @@ -452,6 +458,12 @@ class Identity(QObject): return unique_valid async def membership_expiration_time(self, community): + """ + Get the remaining time before membership expiration + :param sakia.core.Community community: the community + :return: the remaining time + :rtype: int + """ membership = await self.membership(community) join_block = membership['blockNumber'] block = await community.get_block(join_block) @@ -461,6 +473,23 @@ class Identity(QObject): current_time = time.time() return expiration_date - current_time + async def cert_issuance_delay(self, identities_registry, community): + """ + Get the remaining time before being able to issue new certification. + :param sakia.core.Community community: the community + :return: the remaining time + :rtype: int + """ + certified = await self.certified_by(identities_registry, community) + if len(certified) > 0: + latest_time = max([c['cert_time'] for c in certified]) + parameters = await community.parameters() + if parameters: + current_time = time.time() + if current_time - latest_time < parameters['sigPeriod']: + return parameters['sigPeriod'] - (current_time - latest_time) + return 0 + def _refresh_uid(self, uids): """ Refresh UID from uids list, got from a successful lookup request diff --git a/src/sakia/gui/certification.py b/src/sakia/gui/certification.py index 4b847b300bf28b50e7a4356b38cfd87730d779a1..5711b5e07160b3a80fa068734731247b6178946c 100644 --- a/src/sakia/gui/certification.py +++ b/src/sakia/gui/certification.py @@ -8,7 +8,7 @@ import logging from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QApplication, QMessageBox -from PyQt5.QtCore import Qt, QObject +from PyQt5.QtCore import Qt, QObject, QLocale, QDateTime from ..gen_resources.certification_uic import Ui_CertificationDialog from .widgets import toast @@ -188,7 +188,23 @@ class CertificationDialog(QObject): logging.debug(str(e)) block_0 = None - if is_member or not block_0: + params = await self.community.parameters() + nb_certifications = len(await account_identity.certified_by(self.app.identities_registry, self.community)) + remaining_time = await account_identity.cert_issuance_delay(self.app.identities_registry, self.community) + cert_text = self.tr("Certifications sent : {nb_certifications}/{stock}").format( + nb_certifications=nb_certifications, + stock=params['sigStock']) + if remaining_time > 0: + cert_text += self.tr("Remaining time before next available certification : {0}").format( + QLocale.toString( + QLocale(), + QDateTime.fromTime_t(remaining_time), + QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat) + ), + ) + self.ui.label_cert_stock.setText(cert_text) + + if (is_member and remaining_time == 0 and nb_certifications < params['sigStock']) or not block_0: self.ui.button_box.button(QDialogButtonBox.Ok).setEnabled(True) self.ui.button_box.button(QDialogButtonBox.Ok).setText(self.tr("&Ok")) else: diff --git a/src/sakia/tests/mocks/bma/init_new_community.py b/src/sakia/tests/mocks/bma/init_new_community.py index 28deb6b8c676095df7ce09c5428c86a0335c15e1..566e89885f9e2128af27d2e40fc5da7fa2eef25a 100644 --- a/src/sakia/tests/mocks/bma/init_new_community.py +++ b/src/sakia/tests/mocks/bma/init_new_community.py @@ -66,10 +66,31 @@ bma_lookup_test_patrick = { ] } +bma_parameters = { + "currency": "test_currency", + "c": 0.1, + "dt": 86400, + "ud0": 100, + "sigPeriod": 600, + "sigValidity": 2629800, + "sigQty": 3, + "sigWoT": 3, + "sigStock": 10, + "sigWindow": 1000, + "msValidity": 2629800, + "stepMax": 3, + "medianTimeBlocks": 11, + "avgGenTime": 600, + "dtDiffEval": 20, + "blocksRot": 144, + "percentRot": 0.67 +} def get_mock(loop): mock = MockServer(loop) + mock.add_route('GET', '/blockchain/parameters', bma_parameters, 200) + mock.add_route('GET', '/blockchain/block/0', {"message": "Block not found"}, 404) mock.add_route('GET', '/blockchain/current', {'message': "Block not found"}, 404) diff --git a/src/sakia/tests/mocks/bma/new_blockchain.py b/src/sakia/tests/mocks/bma/new_blockchain.py index b2883b45e94a4cc4ead61d9ab5ae4440b9c40693..0b4bbd60110d3989477f0a0a9e137898fc54dc27 100644 --- a/src/sakia/tests/mocks/bma/new_blockchain.py +++ b/src/sakia/tests/mocks/bma/new_blockchain.py @@ -18,9 +18,31 @@ bma_wot_add = { ] } +bma_parameters = { + "currency": "test_currency", + "c": 0.1, + "dt": 86400, + "ud0": 100, + "sigPeriod": 600, + "sigValidity": 2629800, + "sigQty": 3, + "sigWoT": 3, + "sigStock": 10, + "sigWindow": 1000, + "msValidity": 2629800, + "stepMax": 3, + "medianTimeBlocks": 11, + "avgGenTime": 600, + "dtDiffEval": 20, + "blocksRot": 144, + "percentRot": 0.67 +} + def get_mock(loop): mock = MockServer(loop) + mock.add_route('GET', '/blockchain/parameters', bma_parameters, 200) + mock.add_route('GET', '/blockchain/block/0', {'message': "Block not found"}, 404) mock.add_route('GET', '/blockchain/current', {'message': "Block not found"}, 404) diff --git a/src/sakia/tests/mocks/bma/nice_blockchain.py b/src/sakia/tests/mocks/bma/nice_blockchain.py index 31190d48639149b435463bc7ec6f2bb0e8dc5c8b..47c0b9a4d54e3b0e740acc5a0c373b38d1023502 100644 --- a/src/sakia/tests/mocks/bma/nice_blockchain.py +++ b/src/sakia/tests/mocks/bma/nice_blockchain.py @@ -69,7 +69,20 @@ bma_lookup_doe = { "others": [] } ], - "signed": [] + "signed": [ + { + "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", + "meta": { + "block_number": 38580 + }, + "uids": [ + "john" + ], + "isMember": True, + "wasMember": True, + "signature": "4ulycI2MtBu/8bZipy+OsXDCNm9EyUIdZ1HA7hbJ66phKRNvv70Oo2YOF/+VDRJb97z9TqWKgfIQ0NbXU15xDg==" + }, + ] } ] } @@ -86,7 +99,7 @@ bma_certifiers_of_john = { "wasMember": True, "cert_time": { "block": 15, - "medianTime": 1447693329 + "medianTime": 1500000000 }, "sigDate": "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", "written": { @@ -107,12 +120,36 @@ bma_certified_by_john = { ] } +bma_certified_by_doe = { + "pubkey": "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", + "uid": "doe", + "isMember": True, + "certifications": [ + { + "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", + "uid": "john", + "isMember": True, + "wasMember": True, + "cert_time": { + "block": 15, + "medianTime": 1500000000 + }, + "sigDate": "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + "written": { + "number": 15, + "hash": "0000EC88BBBAA29D530D2B815DEE264DDC9F07F4" + }, + "signature": "oliiPDhniZAGHrIFL66oHR+cqD4aTgXX+20VFLMfNHwdYPeik76hy334zxhoDC4cPODMb9df2nF/EDfCefrNBg==" + }, + ] +} + bma_parameters = { "currency": "test_currency", "c": 0.1, "dt": 86400, "ud0": 100, - "sigDelay": 604800, + "sigPeriod": 600, "sigValidity": 2629800, "sigQty": 3, "sigWoT": 3, diff --git a/src/sakia/tests/unit/core/test_identity.py b/src/sakia/tests/unit/core/test_identity.py index 4aa90b3e8f242eef7eab4b28d8203e368c63a17e..dd7b9b7e6676b42eaee93468221d606aaff4651f 100644 --- a/src/sakia/tests/unit/core/test_identity.py +++ b/src/sakia/tests/unit/core/test_identity.py @@ -1,5 +1,5 @@ import unittest -from asynctest import Mock, CoroutineMock +from asynctest import Mock, CoroutineMock, patch from PyQt5.QtCore import QLocale from sakia.core.registry.identities import Identity, LocalState, BlockchainState @@ -49,6 +49,32 @@ class TestIdentity(unittest.TestCase, QuamashTest): self.lp.run_until_complete(exec_test()) + @patch('time.time', Mock(return_value=1500000400)) + def test_identity_cert_delay(self): + def bma_access(request, *args): + if request is bma.wot.CertifiedBy: + return nice_blockchain.bma_certified_by_doe + if request is bma.wot.Lookup: + return nice_blockchain.bma_lookup_doe + if request is bma.blockchain.Block: + return nice_blockchain.bma_blockchain_current + + identity = Identity("john", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", + BlockUID(20, "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67"), + LocalState.COMPLETED, BlockchainState.VALIDATED) + id_doe = Identity("doe", "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", + BlockUID(101, "BAD49448A1AD73C978CEDCB8F137D20A5715EBAA739DAEF76B1E28EE67B2C00C"), + LocalState.COMPLETED, BlockchainState.VALIDATED) + + self.community.bma_access.future_request = CoroutineMock(side_effect=bma_access) + self.community.parameters = CoroutineMock(side_effect=lambda: nice_blockchain.bma_parameters) + self.identities_registry.from_handled_data = Mock(return_value=id_doe) + async def exec_test(): + cert_delay = await identity.cert_issuance_delay(self.identities_registry, self.community) + self.assertEqual(cert_delay, 200) + + self.lp.run_until_complete(exec_test()) + def test_identity_membership(self): def bma_access(request, *args): if request is bma.blockchain.Membership: