diff --git a/tikka/adapters/network/accounts.py b/tikka/adapters/network/accounts.py index ce451ef528f287e3ba3612c7f0c27905d261a665..5ea7e0fa23a63436a88f0ea021591600d18718db 100644 --- a/tikka/adapters/network/accounts.py +++ b/tikka/adapters/network/accounts.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import logging -from typing import Optional +from typing import List, Optional from tikka.interfaces.adapters.network.accounts import NetworkAccountsInterface @@ -59,3 +59,32 @@ class NetworkAccounts(NetworkAccountsInterface): get_balance.__doc__ = ( # pylint: disable=redefined-builtin, unused-variable NetworkAccountsInterface.get_balance.__doc__ ) + + def get_identity_indice(self, addresses: List[str]) -> List[Optional[int]]: + __doc__ = ( # pylint: disable=redefined-builtin, unused-variable + NetworkAccountsInterface.get_identity_indice.__doc__ + ) + if not self.connections.is_connected(): + return [] + if self.connections.rpc.client is None: + return [] + + storage_keys = [] + for address in addresses: + storage_keys.append( + self.connections.rpc.client.create_storage_key( + "Identity", "IdentityIndexOf", [address] + ) + ) + + try: + multi_result = self.connections.rpc.client.query_multi(storage_keys) + except Exception as exception: + logging.exception(exception) + return [] + + identity_indice = [] + for index, (storage_key, value_obj) in enumerate(multi_result): + identity_indice.append(value_obj.value) + + return identity_indice diff --git a/tikka/adapters/network/identities.py b/tikka/adapters/network/identities.py index 65c55880b5c3998287736bdd6a7720688d40bcc6..07ee07731363798f562f6ba3ad6fd53afa20757e 100644 --- a/tikka/adapters/network/identities.py +++ b/tikka/adapters/network/identities.py @@ -14,7 +14,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import logging import struct -from typing import Optional +from typing import List, Optional from substrateinterface import Keypair from substrateinterface.exceptions import SubstrateRequestException @@ -79,6 +79,45 @@ class NetworkIdentities(NetworkIdentitiesInterface): status=self.status_map[result["status"].value], ) + def get_identities(self, identity_indice: List[int]) -> List[Optional[Identity]]: + __doc__ = ( # pylint: disable=redefined-builtin, unused-variable + NetworkIdentitiesInterface.get_identities.__doc__ + ) + if not self.connections.is_connected(): + return [] + if self.connections.rpc.client is None: + return [] + + storage_keys = [] + for identity_index in identity_indice: + storage_keys.append( + self.connections.rpc.client.create_storage_key( + "Identity", "Identities", [identity_index] + ) + ) + + try: + multi_result = self.connections.rpc.client.query_multi(storage_keys) + except Exception as exception: + logging.exception(exception) + return [] + + identities: List[Optional[Identity]] = [] + for index, (storage_key, value_obj) in enumerate(multi_result): + if value_obj.value is not None: + identities.append( + Identity( + index=storage_keys[index].params[0], + next_creatable_on=value_obj.value["next_creatable_identity_on"], + removable_on=int(value_obj.value["next_scheduled"]), + status=self.status_map[value_obj.value["status"]], + ) + ) + else: + identities.append(None) + + return identities + def change_owner_key(self, old_keypair: Keypair, new_keypair: Keypair) -> bool: __doc__ = ( # pylint: disable=redefined-builtin, unused-variable NetworkIdentitiesInterface.change_owner_key.__doc__ diff --git a/tikka/adapters/network/smiths.py b/tikka/adapters/network/smiths.py index 2748e6413acd9fb1ca066862fb0ef5c2f872776d..51c9a3b15fe74ab7d481095957606bde6b31e0cc 100644 --- a/tikka/adapters/network/smiths.py +++ b/tikka/adapters/network/smiths.py @@ -34,141 +34,6 @@ class NetworkSmiths(NetworkSmithsInterface): "Excluded": SmithStatus.EXCLUDED, } - def request_membership(self, keypair: Keypair, session_keys: str) -> bool: - __doc__ = ( # pylint: disable=redefined-builtin, unused-variable - NetworkSmithsInterface.request_membership.__doc__ - ) - if not self.connections.is_connected() or self.connections.rpc.client is None: - return False - - try: - call = self.connections.rpc.client.compose_call( - call_module="SmithMembership", - call_function="request_membership", - call_params=None, - ) - except Exception as exception: - logging.exception(exception) - return False - - try: - extrinsic = self.connections.rpc.client.create_signed_extrinsic( - call=call, keypair=keypair - ) - except Exception as exception: - logging.exception(exception) - return False - - try: - # fixme: code stuck infinitely if no blocks are created on blockchain - # should have a timeout option - result = self.connections.rpc.client.submit_extrinsic( - extrinsic, wait_for_inclusion=True - ) - logging.debug( - "Extrinsic '%s' sent and included in block '%s'", - result.extrinsic_hash, - result.block_hash, - ) - except SubstrateRequestException as exception: - logging.exception(exception) - return False - - if result.is_success is False: - logging.error(result.error_message) - - return result.is_success - - def claim_membership(self, keypair: Keypair) -> bool: - __doc__ = ( # pylint: disable=redefined-builtin, unused-variable - NetworkSmithsInterface.claim_membership.__doc__ - ) - if not self.connections.is_connected() or self.connections.rpc.client is None: - return False - - try: - call = self.connections.rpc.client.compose_call( - call_module="SmithMembership", - call_function="claim_membership", - call_params=None, - ) - except Exception as exception: - logging.exception(exception) - return False - - try: - extrinsic = self.connections.rpc.client.create_signed_extrinsic( - call=call, keypair=keypair - ) - except Exception as exception: - logging.exception(exception) - return False - - try: - # fixme: code stuck infinitely if no blocks are created on blockchain - # should have a timeout option - result = self.connections.rpc.client.submit_extrinsic( - extrinsic, wait_for_inclusion=True - ) - logging.debug( - "Extrinsic '%s' sent and included in block '%s'", - result.extrinsic_hash, - result.block_hash, - ) - except SubstrateRequestException as exception: - logging.exception(exception) - return False - - if result.is_success is False: - logging.error(result.error_message) - - return result.is_success - - def revoke_membership(self, keypair: Keypair) -> bool: - __doc__ = ( # pylint: disable=redefined-builtin, unused-variable - NetworkSmithsInterface.revoke_membership.__doc__ - ) - if not self.connections.is_connected() or self.connections.rpc.client is None: - return False - - try: - call = self.connections.rpc.client.compose_call( - call_module="SmithMembership", - call_function="revoke_membership", - call_params=None, - ) - except Exception as exception: - logging.exception(exception) - return False - - try: - extrinsic = self.connections.rpc.client.create_signed_extrinsic( - call=call, keypair=keypair - ) - except Exception as exception: - logging.exception(exception) - return False - - try: - # fixme: code stuck infinitely if no blocks are created on blockchain - # should have a timeout option - result = self.connections.rpc.client.submit_extrinsic( - extrinsic, wait_for_inclusion=True - ) - logging.debug( - "Extrinsic '%s' sent and included in block '%s'", - result.extrinsic_hash, - result.block_hash, - ) - except SubstrateRequestException as exception: - logging.exception(exception) - return False - - if result.is_success is False: - logging.error(result.error_message) - - return result.is_success - def get_smith(self, identity_index: int) -> Optional[Smith]: __doc__ = ( # pylint: disable=redefined-builtin, unused-variable NetworkSmithsInterface.get_smith.__doc__ @@ -195,6 +60,44 @@ class NetworkSmiths(NetworkSmithsInterface): return smith + def get_smiths(self, identity_indice: List[int]) -> List[Optional[Smith]]: + __doc__ = ( # pylint: disable=redefined-builtin, unused-variable + NetworkSmithsInterface.get_smiths.__doc__ + ) + if not self.connections.is_connected(): + return [] + if self.connections.rpc.client is None: + return [] + + storage_keys = [] + for identity_index in identity_indice: + storage_keys.append( + self.connections.rpc.client.create_storage_key( + "SmithMembers", "Smiths", [identity_index] + ) + ) + + try: + multi_result = self.connections.rpc.client.query_multi(storage_keys) + except Exception as exception: + logging.exception(exception) + return [] + + smiths: List[Optional[Smith]] = [] + for index, (storage_key, value_obj) in enumerate(multi_result): + if value_obj.value is not None: + smiths.append( + Smith( + identity_index=storage_keys[index].params[0], + status=self.status_map[value_obj.value["status"]], + expire_on=value_obj.value["expires_on"], + ) + ) + else: + smiths.append(None) + + return smiths + def certs_by_receiver( self, receiver_address: str, receiver_identity_index: int ) -> Optional[List[SmithCertification]]: @@ -228,7 +131,7 @@ class NetworkSmiths(NetworkSmithsInterface): certifications.append( SmithCertification( issuer_identity_index=storage_keys[index].params[0], - issuer_address=value_obj["owner_key"], + issuer_address=value_obj.value["owner_key"], receiver_identity_index=receiver_identity_index, receiver_address=receiver_address, expire_on_block=result.value[index][1], diff --git a/tikka/domains/accounts.py b/tikka/domains/accounts.py index 041cf6844667e61260650b923006d373385ffd01..f75e12916087fe5c0cc32ec55ed4f67efe19f26c 100644 --- a/tikka/domains/accounts.py +++ b/tikka/domains/accounts.py @@ -249,6 +249,26 @@ class Accounts: """ return self.network.get_balance(address) + def fetch_identity_indice_from_network( + self, addresses: List[str] + ) -> List[Optional[int]]: + """ + Fetch account identity indice from current EntryPoint connection + + :param addresses: List of Account addresses + :return: + """ + identity_indice = self.network.get_identity_indice(addresses) + # todo: purge identities db from None index + # todo: purge smiths db from None index + for index, address in enumerate(addresses): + account = self.get_by_address(address) + if account is not None: + account.identity_index = identity_indice[index] + self.repository.update(account) + + return identity_indice + def list_by_category_id(self, category_id: Optional[UUID]) -> List[Account]: """ Return all accounts in category_id diff --git a/tikka/domains/identities.py b/tikka/domains/identities.py index 39f0db99d696c04dd13725769151871cf87caeec..3713bb31693abf497c50acd25e257fe995ab483c 100644 --- a/tikka/domains/identities.py +++ b/tikka/domains/identities.py @@ -12,7 +12,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -from typing import Optional +from typing import List, Optional from substrateinterface import Keypair @@ -148,6 +148,23 @@ class Identities: return identity + def fetch_multi_from_network(self, identity_indice: List[int]) -> None: + """ + Fetch all Identity instances from network + + :param identity_indice: Identity index list + :return: + """ + identities = self.network.get_identities(identity_indice) + for index, identity in enumerate(identities): + if identity is None: + self.repository.delete(identity_indice[index]) + continue + if self.exists(identity.index) is True: + self.update(identity) + else: + self.add(identity) + def change_owner_key(self, old_keypair: Keypair, new_keypair: Keypair) -> bool: """ Change identity owner from old_keypair to new_keypair diff --git a/tikka/domains/smiths.py b/tikka/domains/smiths.py index c008a17673125cc00e58b7d4721d0b8623d410e7..ee8f3fb76debed2e165e07a052cddf17f0fe9209 100644 --- a/tikka/domains/smiths.py +++ b/tikka/domains/smiths.py @@ -161,6 +161,23 @@ class Smiths: return smith + def fetch_multi_from_network(self, identity_indice: List[int]) -> None: + """ + Fetch Smith instances from network from identity_indice list + + :param identity_indice: Identity indice list + :return: + """ + smiths = self.network.get_smiths(identity_indice) + for index, smith in enumerate(smiths): + if smith is None: + self.repository.delete(identity_indice[index]) + continue + if self.exists(smith.identity_index) is True: + self.update(smith) + else: + self.add(smith) + def fetch_certs_by_receiver_from_network( self, receiver_address: str, receiver_identity_index: int ) -> Optional[List[SmithCertification]]: diff --git a/tikka/interfaces/adapters/network/accounts.py b/tikka/interfaces/adapters/network/accounts.py index 8a0d08e97a85f3eb9f07e5bc0a77b5846b323bf7..413104291aa3c097667dbd2b26464acc739f252b 100644 --- a/tikka/interfaces/adapters/network/accounts.py +++ b/tikka/interfaces/adapters/network/accounts.py @@ -14,7 +14,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import abc -from typing import Optional +from typing import List, Optional from tikka.interfaces.domains.connections import ConnectionsInterface @@ -42,3 +42,13 @@ class NetworkAccountsInterface(abc.ABC): :return: """ raise NotImplementedError + + @abc.abstractmethod + def get_identity_indice(self, addresses: List[str]) -> List[Optional[int]]: + """ + Return the account Identity index from addresses if exists + + :param addresses: Account address + :return: + """ + raise NotImplementedError diff --git a/tikka/interfaces/adapters/network/identities.py b/tikka/interfaces/adapters/network/identities.py index 4f9e062ec4127318e5ca8efdf7c8393a68c0287e..30e102bfdc7a705f457a3000299728ca9eb3df63 100644 --- a/tikka/interfaces/adapters/network/identities.py +++ b/tikka/interfaces/adapters/network/identities.py @@ -14,7 +14,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import abc -from typing import Optional +from typing import List, Optional from substrateinterface import Keypair @@ -56,6 +56,15 @@ class NetworkIdentitiesInterface(abc.ABC): """ raise NotImplementedError + def get_identities(self, identity_indice: List[int]) -> List[Optional[Identity]]: + """ + Return a dict with identity_index: Identity instance from network + + :param identity_indice: List of identity indice + :return: + """ + raise NotImplementedError + @abc.abstractmethod def change_owner_key(self, old_keypair: Keypair, new_keypair: Keypair) -> bool: """ diff --git a/tikka/interfaces/adapters/network/smiths.py b/tikka/interfaces/adapters/network/smiths.py index a6e2e8f0341ace3bb72ba28ce4264784b43c48ea..c77430cd16348ff3352cbe44cb164de55fb013bf 100644 --- a/tikka/interfaces/adapters/network/smiths.py +++ b/tikka/interfaces/adapters/network/smiths.py @@ -37,40 +37,18 @@ class NetworkSmithsInterface(abc.ABC): self.connections = connections @abc.abstractmethod - def request_membership(self, keypair: Keypair, session_keys: str) -> bool: - """ - Request a smith membership for the Keypair account with node session_keys - - :param keypair: Owner Keypair - :param session_keys: Session public keys (hex string "0x123XYZ") - :return: - """ - raise NotImplementedError - - @abc.abstractmethod - def claim_membership(self, keypair: Keypair) -> bool: - """ - Claim that last smith membership request for the Keypair account fulfill all requirements - - :param keypair: Owner Keypair - :return: - """ - raise NotImplementedError - - @abc.abstractmethod - def revoke_membership(self, keypair: Keypair) -> bool: + def get_smith(self, identity_index: int) -> Optional[Smith]: """ - Revoke smith membership for the Keypair account + Return Smith instance - :param keypair: Owner Keypair :return: """ raise NotImplementedError @abc.abstractmethod - def get_smith(self, identity_index: int) -> Optional[Smith]: + def get_smiths(self, identity_indice: List[int]) -> List[Optional[Smith]]: """ - Return Smith instance + Return list of Smith instance from list of identity indice :return: """ diff --git a/tikka/slots/pyqt/widgets/smith.py b/tikka/slots/pyqt/widgets/smith.py index a0cdac6191e31017ae81d67d6e86b0d8dbaec8c5..c7cfd7df58be05230330144c090e21330e93bddb 100644 --- a/tikka/slots/pyqt/widgets/smith.py +++ b/tikka/slots/pyqt/widgets/smith.py @@ -96,9 +96,9 @@ class SmithWidget(QWidget, Ui_SmithWidget): ############################## # ASYNC METHODS ############################## - self.fetch_smith_from_network_timer = QTimer() - self.fetch_smith_from_network_timer.timeout.connect( - self.fetch_smith_from_network_timer_function + self.fetch_all_from_network_timer = QTimer() + self.fetch_all_from_network_timer.timeout.connect( + self.fetch_all_from_network_timer_function ) self.fetch_authority_status_from_network_timer = QTimer() self.fetch_authority_status_from_network_timer.timeout.connect( @@ -166,6 +166,34 @@ class SmithWidget(QWidget, Ui_SmithWidget): self._update_ui() + def update_account(self, account: Optional[Account]) -> None: + """ + Update account and smith and authority status and certification list + + :param account: New account + :return: + """ + self.account = account + if self.account is None: + self.smith = None + self.certs_by_receiver = None + else: + self.smith = None + if self.account.identity_index is not None: + # get smith status + self.smith = self.application.smiths.get(self.account.identity_index) + + if self.smith is not None: + # get certification list + self.certs_by_receiver = ( + self.application.smiths.fetch_certs_by_receiver_from_network( + self.account.address, self.smith.identity_index + ) + ) + # todo: store authority status in DB + self.loaderIconLabel.show() + self.fetch_authority_status_from_network_timer.start() + def init_account_combo_box(self) -> None: """ Init combobox with validated identity accounts (with wallets) @@ -203,7 +231,7 @@ class SmithWidget(QWidget, Ui_SmithWidget): ) if index > -1: self.accountComboBox.setCurrentIndex(index) - self.account = preference_account_selected + self.update_account(preference_account_selected) else: self.application.preferences.set( SMITH_SELECTED_ACCOUNT_ADDRESS, None @@ -217,26 +245,17 @@ class SmithWidget(QWidget, Ui_SmithWidget): """ address = self.accountComboBox.currentData() if address is not None: - self.account = self.application.accounts.get_by_address(address) + self.update_account(self.application.accounts.get_by_address(address)) else: - self.account = None - self.smith = None - self.certs_by_receiver = None + self.update_account(None) self._update_ui() self.application.preferences_repository.set( SMITH_SELECTED_ACCOUNT_ADDRESS, - None if self.account is None else self.account.address, + address, ) - if self.account is not None: - self.loaderIconLabel.show() - # Disable button - self.refreshSmithButton.setEnabled(False) - self.fetch_smith_from_network_timer.start() - self.fetch_authority_status_from_network_timer.start() - def init_invite_account_combo_box(self) -> None: """ Init combobox with validated identity accounts (not smith) to invite to be smith @@ -458,7 +477,7 @@ class SmithWidget(QWidget, Ui_SmithWidget): self.accept_invitation_timer.stop() return - self.fetch_smith_from_network_timer.start( + self.fetch_all_from_network_timer.start( self.DELAY_BEFORE_UPDATE_MEMBERSHIP_STATUS_AFTER_REQUEST ) @@ -500,11 +519,9 @@ class SmithWidget(QWidget, Ui_SmithWidget): self.application.smiths.fetch_smith_from_network( self.certify_smith.identity_index ) - self.init_certify_smith_combo_box() self._update_ui() self.loaderIconLabel.hide() - self.certify_smith_timer.stop() def publish_keys_timer_function(self): @@ -560,7 +577,7 @@ class SmithWidget(QWidget, Ui_SmithWidget): self.errorLabel.setText(self._("Unable to og online")) self.goOnlineButton.setEnabled(True) - self.fetch_smith_from_network_timer.start( + self.fetch_authority_status_from_network_timer.start( self.DELAY_BEFORE_UPDATE_MEMBERSHIP_STATUS_AFTER_REQUEST ) @@ -591,7 +608,7 @@ class SmithWidget(QWidget, Ui_SmithWidget): self.errorLabel.setText(self._("Unable to go offline")) self.goOfflineButton.setEnabled(True) - self.fetch_smith_from_network_timer.start( + self.fetch_authority_status_from_network_timer.start( self.DELAY_BEFORE_UPDATE_MEMBERSHIP_STATUS_AFTER_REQUEST ) @@ -599,43 +616,49 @@ class SmithWidget(QWidget, Ui_SmithWidget): def _on_refresh_smith_button_clicked(self, _): """ """ - self.loader_movie.start() - self.loaderIconLabel.show() - self.fetch_smith_from_network_timer.start() - self.fetch_authority_status_from_network_timer.start() + self.fetch_all_from_network_timer.start() - def fetch_smith_from_network_timer_function(self): + def fetch_all_from_network_timer_function(self): """ - Update smith infos from current url connection + Update identities, smiths and authorities from current url connection :return: """ - if self.account is None or self.account.identity_index is None: + if not self.application.connections.is_connected(): self.refreshSmithButton.setEnabled(True) self.loaderIconLabel.hide() - self.fetch_smith_from_network_timer.stop() + self.fetch_all_from_network_timer.stop() return - # Start the thread - # get membership status - self.smith = self.application.smiths.fetch_smith_from_network( - self.account.identity_index - ) + self.loader_movie.start() + self.loaderIconLabel.show() - if self.smith is not None: - # get certification list - self.certs_by_receiver = ( - self.application.smiths.fetch_certs_by_receiver_from_network( - self.account.address, self.account.identity_index - ) + identity_indice = [ + identity_index + for identity_index in self.application.accounts.fetch_identity_indice_from_network( + [account.address for account in self.application.accounts.get_list()] ) + if identity_index is not None + ] + self.application.identities.fetch_multi_from_network(identity_indice) + self.application.smiths.fetch_multi_from_network(identity_indice) + + self.init_account_combo_box() + self.init_invite_account_combo_box() + self.init_certify_smith_combo_box() + + if self.account is None or self.account.identity_index is None: + self.refreshSmithButton.setEnabled(True) + self.loaderIconLabel.hide() + self.fetch_all_from_network_timer.stop() + return self.refreshSmithButton.setEnabled(True) self.loaderIconLabel.hide() self._update_ui() - self.fetch_smith_from_network_timer.stop() + self.fetch_all_from_network_timer.stop() def fetch_authority_status_from_network_timer_function(self): """ @@ -735,8 +758,7 @@ class SmithWidget(QWidget, Ui_SmithWidget): elif self.smith.status == SmithStatus.INVITED: self.acceptInvitationButton.setEnabled(True) elif self.smith.status == SmithStatus.SMITH: - if self.invite_account is not None: - self.inviteButton.setEnabled(True) + self.inviteButton.setEnabled(True) if self.inviteAccountComboBox.count() > 1: self.inviteAccountComboBox.setEnabled(True) if self.certify_smith is not None: @@ -818,9 +840,9 @@ class SmithWidget(QWidget, Ui_SmithWidget): self.init_invite_account_combo_box() self.init_certify_smith_combo_box() if self.account is not None: - self.fetch_smith_from_network_timer.start() - else: - self._update_ui() + self.fetch_all_from_network_timer.start() + + self._update_ui() def on_connections_event(self, _): """ @@ -855,10 +877,8 @@ class SmithWidget(QWidget, Ui_SmithWidget): self.init_account_combo_box() self.init_invite_account_combo_box() self.init_certify_smith_combo_box() - if self.account is not None: - self.fetch_smith_from_network_timer.start() - else: - self._update_ui() + + self._update_ui() def on_update_account_event(self, _): """ @@ -869,10 +889,8 @@ class SmithWidget(QWidget, Ui_SmithWidget): self.init_account_combo_box() self.init_invite_account_combo_box() self.init_certify_smith_combo_box() - if self.account is not None: - self.fetch_smith_from_network_timer.start() - else: - self._update_ui() + + self._update_ui() if __name__ == "__main__":