From a64693f7fcdb488575eceacd58a42b15d48202fd Mon Sep 17 00:00:00 2001
From: Inso <insomniak.fr@gmail.com>
Date: Thu, 18 Jun 2015 12:55:46 +0200
Subject: [PATCH] New identities handling

---
 src/cutecoin/core/account.py                  |  34 +-
 src/cutecoin/core/app.py                      |  40 +-
 src/cutecoin/core/graph.py                    |   2 +-
 src/cutecoin/core/net/api/bma/__init__.py     |   1 +
 src/cutecoin/core/net/api/bma/access.py       |  18 +-
 .../core/net/api/bma/blockchain/__init__.py   | 159 +++---
 src/cutecoin/core/net/api/bma/wot/__init__.py |   6 +
 src/cutecoin/core/net/node.py                 |   4 +-
 src/cutecoin/core/person.py                   | 476 ------------------
 src/cutecoin/core/registry/__init__.py        |   2 +
 src/cutecoin/core/registry/identities.py      | 102 ++++
 src/cutecoin/core/registry/identity.py        | 282 +++++++++++
 src/cutecoin/core/wallet.py                   |  23 +-
 src/cutecoin/gui/community_tab.py             |  25 +-
 src/cutecoin/gui/contact.py                   |   2 +-
 src/cutecoin/gui/currency_tab.py              |  11 +-
 src/cutecoin/gui/informations_tab.py          |   1 +
 src/cutecoin/gui/mainwindow.py                |   2 +-
 src/cutecoin/gui/process_cfg_community.py     |   6 +-
 src/cutecoin/gui/transactions_tab.py          |   2 +-
 src/cutecoin/gui/wallets_tab.py               |   4 +-
 src/cutecoin/gui/wot_tab.py                   |  13 +-
 src/cutecoin/models/identities.py             |   3 +-
 src/cutecoin/tools/exceptions.py              |   2 +-
 24 files changed, 584 insertions(+), 636 deletions(-)
 delete mode 100644 src/cutecoin/core/person.py
 create mode 100644 src/cutecoin/core/registry/__init__.py
 create mode 100644 src/cutecoin/core/registry/identities.py
 create mode 100644 src/cutecoin/core/registry/identity.py

diff --git a/src/cutecoin/core/account.py b/src/cutecoin/core/account.py
index 0471dea1..bf88aeb9 100644
--- a/src/cutecoin/core/account.py
+++ b/src/cutecoin/core/account.py
@@ -18,7 +18,7 @@ from PyQt5.QtCore import QObject, pyqtSignal, QCoreApplication, QT_TRANSLATE_NOO
 
 from .wallet import Wallet
 from .community import Community
-from .person import Person
+from .registry import Identity, IdentitiesRegistry
 from ..tools.exceptions import ContactAlreadyExists
 
 
@@ -121,7 +121,7 @@ class Account(QObject):
     inner_data_changed = pyqtSignal()
     wallets_changed = pyqtSignal()
 
-    def __init__(self, salt, pubkey, name, communities, wallets, contacts):
+    def __init__(self, salt, pubkey, name, communities, wallets, contacts, identities_registry):
         '''
         Create an account
 
@@ -133,6 +133,7 @@ class Account(QObject):
         :param array communities: Community objects referenced by this account
         :param array wallets: Wallet objects owned by this account
         :param array contacts: Contacts of this account
+        :param cutecoin.core.registry.IdentitiesRegistry: The identities registry intance
 
         .. warnings:: The class methods create and load should be used to create an account
         '''
@@ -143,10 +144,11 @@ class Account(QObject):
         self.communities = communities
         self.wallets = wallets
         self.contacts = contacts
+        self._identities_registry = identities_registry
         self.referential = 0
 
     @classmethod
-    def create(cls, name):
+    def create(cls, name, identities_registry):
         '''
         Factory method to create an empty account object
         This new account doesn't have any key and it should be given
@@ -158,15 +160,17 @@ class Account(QObject):
         :param str name: The account name, same as network identity uid
         :return: A new empty account object
         '''
-        account = cls(None, None, name, [], [], [])
+        account = cls(None, None, name, [], [], [], identities_registry)
         return account
 
     @classmethod
-    def load(cls, network_manager, json_data):
+    def load(cls, json_data, network_manager, identities_registry):
         '''
         Factory method to create an Account object from its json view.
         :rtype : cutecoin.core.account.Account
         :param dict json_data: The account view as a json dict
+        :param PyQt5.QtNetwork import QNetworkManager: network_manager
+        :param cutecoin.core.registry.self._identities_registry: identities_registry
         :return: A new account object created from the json datas
         '''
         salt = json_data['salt']
@@ -180,7 +184,7 @@ class Account(QObject):
 
         wallets = []
         for data in json_data['wallets']:
-            wallets.append(Wallet.load(data))
+            wallets.append(Wallet.load(data, identities_registry))
 
         communities = []
         for data in json_data['communities']:
@@ -188,7 +192,7 @@ class Account(QObject):
             communities.append(community)
 
         account = cls(salt, pubkey, name, communities, wallets,
-                      contacts)
+                      contacts, identities_registry)
         return account
 
     def __eq__(self, other):
@@ -229,12 +233,12 @@ class Account(QObject):
         return community
 
     def refresh_cache(self):
-        '''
+        """
         Refresh the local account cache
         This needs n_wallets * n_communities cache refreshing to end
 
         .. note:: emit the Account pyqtSignal loading_progressed during refresh
-        '''
+        """
         loaded_wallets = 0
 
         def progressing(value, maximum):
@@ -288,7 +292,7 @@ class Account(QObject):
         if len(self.wallets) < size:
             for i in range(len(self.wallets), size):
                 wallet = Wallet.create(i, self.salt, password,
-                                       "Wallet {0}".format(i))
+                                       "Wallet {0}".format(i), self._identities_registry)
                 self.wallets.append(wallet)
         else:
             self.wallets = self.wallets[:size]
@@ -302,7 +306,7 @@ class Account(QObject):
         :param cutecoin.core.community.Community community: The community target of the certification
         :param str pubkey: The certified identity pubkey
         """
-        certified = Person.lookup(pubkey, community)
+        certified = self._identities_registry.lookup(pubkey, community)
         blockid = community.current_blockid()
 
         certification = Certification(PROTOCOL_VERSION, community.currency,
@@ -330,7 +334,7 @@ class Account(QObject):
         :param str password: The account SigningKey password
         :param cutecoin.core.community.Community community: The community target of the revocation
         """
-        revoked = Person.lookup(self.pubkey, community)
+        revoked = self._identities_registry.lookup(self.pubkey, community)
 
         revocation = Revocation(PROTOCOL_VERSION, community.currency, None)
 
@@ -383,7 +387,7 @@ class Account(QObject):
         :param community: The target community of this request
         :return: True if the account is a member of the target community
         '''
-        self_person = Person.lookup(self.pubkey, community)
+        self_person = self._identities_registry.lookup(self.pubkey, community)
         return self_person.published_uid(community)
 
     def member_of(self, community):
@@ -393,7 +397,7 @@ class Account(QObject):
         :param community: The target community of this request
         :return: True if the account is a member of the target community
         '''
-        self_person = Person.lookup(self.pubkey, community)
+        self_person = self._identities_registry.lookup(self.pubkey, community)
         logging.debug("Self person : {0}".format(self_person.uid))
         return self_person.is_member(community)
 
@@ -425,7 +429,7 @@ class Account(QObject):
         :param community: The community target of the membership document
         :param str mstype: The type of membership demand. "IN" to join, "OUT" to leave
         '''
-        self_ = Person.lookup(self.pubkey, community)
+        self_ = self._identities_registry.lookup(self.pubkey, community)
         selfcert = self_.selfcert(community)
 
         blockid = community.current_blockid()
diff --git a/src/cutecoin/core/app.py b/src/cutecoin/core/app.py
index 3a5e5ad6..ea72eb68 100644
--- a/src/cutecoin/core/app.py
+++ b/src/cutecoin/core/app.py
@@ -18,7 +18,7 @@ from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkReques
 
 from . import config
 from .account import Account
-from . import person
+from .registry.identities import IdentitiesRegistry
 from .. import __version__
 from ..tools.exceptions import NameAlreadyExists, BadAccountFile
 
@@ -46,8 +46,10 @@ class Application(QObject):
         self.available_version = (True,
                                   __version__,
                                   "")
+        self.identity_registry = None
         config.parse_arguments(argv)
         self._network_manager = QNetworkAccessManager()
+        self._identities_registry = IdentitiesRegistry()
         self.preferences = {'account': "",
                             'lang': 'en_GB',
                             'ref': 0
@@ -94,6 +96,10 @@ class Application(QObject):
 
         return account
 
+    @property
+    def identities_registry(self):
+        return self._identities_registry
+
     def add_account(self, account):
         self.accounts[account.name] = account
 
@@ -136,7 +142,7 @@ class Application(QObject):
         If the standard application state file can't be found,
         no error is raised.
         '''
-        self.load_persons()
+        self.load_registries()
         self.load_preferences()
         try:
             logging.debug("Loading data...")
@@ -147,17 +153,17 @@ class Application(QObject):
         except FileNotFoundError:
             pass
 
-    def load_persons(self):
+    def load_registries(self):
         '''
         Load the Person instances of the person module.
         Each instance is unique, and can be find by its public key.
         '''
         try:
-            persons_path = os.path.join(config.parameters['home'],
-                                        '__persons__')
-            with open(persons_path, 'r') as persons_path:
-                data = json.load(persons_path)
-                person.load_cache(data)
+            identities_path = os.path.join(config.parameters['home'],
+                                        '__identities__')
+            with open(identities_path, 'r') as identities_data:
+                data = json.load(identities_data)
+                self._identities_registry.load_json(data)
         except FileNotFoundError:
             pass
 
@@ -171,7 +177,7 @@ class Application(QObject):
                                     account_name, 'properties')
         with open(account_path, 'r') as json_data:
             data = json.load(json_data)
-            account = Account.load(self._network_manager, data)
+            account = Account.load(data, self._network_manager, self._identities_registry)
             self.load_cache(account)
             self.accounts[account_name] = account
 
@@ -269,14 +275,14 @@ class Application(QObject):
             account_path = os.path.join(config.parameters['home'], account.name)
             shutil.rmtree(account_path)
 
-    def save_persons(self):
-        '''
-        Save the person module cache
-        '''
-        persons_path = os.path.join(config.parameters['home'],
-                                    '__persons__')
-        with open(persons_path, 'w')as outfile:
-            data = person.jsonify_cache()
+    def save_registries(self):
+        """
+        Save the registries
+        """
+        identities_path = os.path.join(config.parameters['home'],
+                                    '__identities__')
+        with open(identities_path, 'w')as outfile:
+            data = self.identities_registry.jsonify()
             data['version'] = __version__
             json.dump(data, outfile, indent=4, sort_keys=True)
 
diff --git a/src/cutecoin/core/graph.py b/src/cutecoin/core/graph.py
index 01b8b4af..a4066087 100644
--- a/src/cutecoin/core/graph.py
+++ b/src/cutecoin/core/graph.py
@@ -2,7 +2,7 @@ import logging
 import time
 import datetime
 from PyQt5.QtCore import QLocale, QDateTime
-from cutecoin.core.person import Person
+from ..core.registry import Identity
 from cutecoin.gui.views.wot import NODE_STATUS_HIGHLIGHTED, NODE_STATUS_OUT, ARC_STATUS_STRONG, ARC_STATUS_WEAK
 
 
diff --git a/src/cutecoin/core/net/api/bma/__init__.py b/src/cutecoin/core/net/api/bma/__init__.py
index ce3f0b46..e00fc569 100644
--- a/src/cutecoin/core/net/api/bma/__init__.py
+++ b/src/cutecoin/core/net/api/bma/__init__.py
@@ -8,6 +8,7 @@ import logging
 
 logger = logging.getLogger("ucoin")
 
+PROTOCOL_VERSION = "1"
 
 class ConnectionHandler(object):
     """Helper class used by other API classes to ease passing server connection information."""
diff --git a/src/cutecoin/core/net/api/bma/access.py b/src/cutecoin/core/net/api/bma/access.py
index 4c7fc64a..b18a977e 100644
--- a/src/cutecoin/core/net/api/bma/access.py
+++ b/src/cutecoin/core/net/api/bma/access.py
@@ -65,7 +65,7 @@ class BmaAccess(QObject):
                             'value': data[d]})
         return entries
 
-    def get(self, caller, request, req_args={}, get_args={}):
+    def get(self, caller, request, req_args={}, get_args={}, tries=0):
         """
         Get Json data from the specified URL
         :rtype : dict
@@ -94,16 +94,16 @@ class BmaAccess(QObject):
             #after removing qthreads
             reply = self.request(request, req_args, get_args)
             reply.finished.connect(lambda:
-                                     self.handle_reply(caller, request, req_args, get_args))
+                                     self.handle_reply(caller, request, req_args, get_args, tries))
         return ret_data
 
     def request(self, request, req_args={}, get_args={}):
         '''
         Start a request to the network.
 
-        :param request: A bma request class calling for data
-        :param req_args: Arguments to pass to the request constructor
-        :param get_args: Arguments to pass to the request __get__ method
+        :param class request: A bma request class calling for data
+        :param dict req_args: Arguments to pass to the request constructor
+        :param dict get_args: Arguments to pass to the request __get__ method
         :return: The returned data if cached = True else return the QNetworkReply
         '''
         nodes = self._network.synced_nodes
@@ -119,7 +119,7 @@ class BmaAccess(QObject):
             raise NoPeerAvailable(self.currency, len(nodes))
 
     @pyqtSlot(int, dict, dict, QObject)
-    def handle_reply(self, caller, request, req_args, get_args):
+    def handle_reply(self, caller, request, req_args, get_args, tries):
         reply = self.sender()
         #logging.debug("Handling QtNetworkReply for {0}".format(str(request)))
         if reply.error() == QNetworkReply.NoError:
@@ -144,10 +144,10 @@ class BmaAccess(QObject):
                     change = True
             else:
                 change = True
-
-            if change == True:
+            if change:
                 self._data[cache_key]['value'] = json.loads(strdata)
                 caller.inner_data_changed.emit(request)
         else:
             logging.debug("Error in reply : {0}".format(reply.error()))
-            self.community.qtrequest(caller, request, req_args, get_args)
+            if tries < 3:
+                self.get(caller, request, req_args, get_args)
diff --git a/src/cutecoin/core/net/api/bma/blockchain/__init__.py b/src/cutecoin/core/net/api/bma/blockchain/__init__.py
index 6952dc6d..eecdfa3d 100644
--- a/src/cutecoin/core/net/api/bma/blockchain/__init__.py
+++ b/src/cutecoin/core/net/api/bma/blockchain/__init__.py
@@ -33,26 +33,35 @@ class Parameters(Blockchain):
         return self.requests_get('/parameters', **kwargs)
 
     null_value = {
-                'currency': "",
-                'c': 0,
-                'dt': 0,
-                'ud0': 0,
-                'sigDelay': 0,
-                'sigValidity': 0,
-                'sigQty': 0,
-                'sigWoT': 0,
-                'msValidity': 0,
-                'stepMax': 0,
-                'medianTimeBlocks': 0,
-                'avgGenTime': 0,
-                'dtDiffEval': 0,
-                'blocksRot': 0,
-                'percentRot': 0
-            }
+        'currency': "",
+        'c': 0,
+        'dt': 0,
+        'ud0': 0,
+        'sigDelay': 0,
+        'sigValidity': 0,
+        'sigQty': 0,
+        'sigWoT': 0,
+        'msValidity': 0,
+        'stepMax': 0,
+        'medianTimeBlocks': 0,
+        'avgGenTime': 0,
+        'dtDiffEval': 0,
+        'blocksRot': 0,
+        'percentRot': 0
+    }
 
 
 class Membership(Blockchain):
     """GET/POST a Membership document."""
+
+    null_value = \
+        {
+            "pubkey": "",
+            "uid": "",
+            "sigDate": 0,
+            "memberships": []
+        }
+
     def __init__(self, conn_handler, search=None):
         super().__init__(conn_handler)
         self.search = search
@@ -70,31 +79,31 @@ class Membership(Blockchain):
 class Block(Blockchain):
     """GET/POST a block from/to the blockchain."""
     null_value = {
-                "version": 1,
-                "nonce": 0,
-                "number": -1,
-                "powMin": 0,
-                "time": 0,
-                "medianTime": 0,
-                "membersCount": 0,
-                "monetaryMass": 0,
-                "currency": "",
-                "issuer": "",
-                "signature": "",
-                "hash": "",
-                "previousHash": "",
-                "previousIssuer": "",
-                "dividend": 0,
-                "membersChanges": [ ],
-                "identities": [],
-                "joiners": [],
-                "actives": [],
-                "leavers": [],
-                "excluded": [],
-                "certifications": [],
-                "transactions": [],
-                "raw": ""
-            }
+        "version": 1,
+        "nonce": 0,
+        "number": -1,
+        "powMin": 0,
+        "time": 0,
+        "medianTime": 0,
+        "membersCount": 0,
+        "monetaryMass": 0,
+        "currency": "",
+        "issuer": "",
+        "signature": "",
+        "hash": "",
+        "previousHash": "",
+        "previousIssuer": "",
+        "dividend": 0,
+        "membersChanges": [],
+        "identities": [],
+        "joiners": [],
+        "actives": [],
+        "leavers": [],
+        "excluded": [],
+        "certifications": [],
+        "transactions": [],
+        "raw": ""
+    }
 
     def __init__(self, conn_handler, number=None):
         """
@@ -122,31 +131,32 @@ class Block(Blockchain):
 class Current(Blockchain):
     """GET, same as block/[number], but return last accepted block."""
     null_value = {
-                "version": 1,
-                "nonce": 0,
-                "number": -1,
-                "powMin": 0,
-                "time": 0,
-                "medianTime": 0,
-                "membersCount": 0,
-                "monetaryMass": 0,
-                "currency": "",
-                "issuer": "",
-                "signature": "",
-                "hash": "",
-                "previousHash": None,
-                "previousIssuer": None,
-                "dividend": None,
-                "membersChanges": [ ],
-                "identities": [],
-                "joiners": [],
-                "actives": [],
-                "leavers": [],
-                "excluded": [],
-                "certifications": [],
-                "transactions": [],
-                "raw": ""
-            }
+        "version": 1,
+        "nonce": 0,
+        "number": -1,
+        "powMin": 0,
+        "time": 0,
+        "medianTime": 0,
+        "membersCount": 0,
+        "monetaryMass": 0,
+        "currency": "",
+        "issuer": "",
+        "signature": "",
+        "hash": "",
+        "previousHash": None,
+        "previousIssuer": None,
+        "dividend": None,
+        "membersChanges": [],
+        "identities": [],
+        "joiners": [],
+        "actives": [],
+        "leavers": [],
+        "excluded": [],
+        "certifications": [],
+        "transactions": [],
+        "raw": ""
+    }
+
     def __get__(self, **kwargs):
         return self.requests_get('/current', **kwargs)
 
@@ -215,11 +225,13 @@ class Excluded(Blockchain):
 
 class UD(Blockchain):
     """GET, return block numbers containing universal dividend."""
-    null_value = {
-                "result": {
-                "blocks": []
+    null_value = \
+        {
+            "result":
+                {
+                    "blocks": []
                 }
-            }
+        }
 
     def __get__(self, **kwargs):
         return self.requests_get('/with/ud', **kwargs)
@@ -227,6 +239,13 @@ class UD(Blockchain):
 
 class TX(Blockchain):
     """GET, return block numbers containing transactions."""
+    null_value = \
+        {
+            "result":
+                {
+                    "blocks": []
+                }
+        }
 
     def __get__(self, **kwargs):
         return self.requests_get('/with/tx', **kwargs)
diff --git a/src/cutecoin/core/net/api/bma/wot/__init__.py b/src/cutecoin/core/net/api/bma/wot/__init__.py
index 9eb01e13..745c5fef 100644
--- a/src/cutecoin/core/net/api/bma/wot/__init__.py
+++ b/src/cutecoin/core/net/api/bma/wot/__init__.py
@@ -39,6 +39,11 @@ class Add(WOT):
 
 class Lookup(WOT):
     """GET Public key data."""
+    null_value = \
+        {
+            "partial": False,
+            "results": []
+        }
 
     def __init__(self, conn_handler, search, module='wot'):
         super(WOT, self).__init__(conn_handler, module)
@@ -100,6 +105,7 @@ class Members(WOT):
         {
             "results": []
         }
+
     def __init__(self, conn_handler, module='wot'):
         super(WOT, self).__init__(conn_handler, module)
 
diff --git a/src/cutecoin/core/net/node.py b/src/cutecoin/core/net/node.py
index c2c162e4..f37776ae 100644
--- a/src/cutecoin/core/net/node.py
+++ b/src/cutecoin/core/net/node.py
@@ -6,8 +6,8 @@ Created on 21 févr. 2015
 
 from ucoinpy.documents.peer import Peer, BMAEndpoint, Endpoint
 from requests.exceptions import RequestException, ConnectionError
-from cutecoin.tools.exceptions import InvalidNodeCurrency, PersonNotFoundError
-from cutecoin.core.person import Person
+from cutecoin.tools.exceptions import InvalidNodeCurrency, LookupFailureError
+from ..registry import IdentitiesRegistry
 from cutecoin.core.net.api import bma as qtbma
 from cutecoin.core.net.api.bma import ConnectionHandler
 
diff --git a/src/cutecoin/core/person.py b/src/cutecoin/core/person.py
deleted file mode 100644
index ac44769f..00000000
--- a/src/cutecoin/core/person.py
+++ /dev/null
@@ -1,476 +0,0 @@
-'''
-Created on 11 févr. 2014
-
-@author: inso
-'''
-
-import logging
-import functools
-import time
-
-from ucoinpy.api import bma
-from ucoinpy import PROTOCOL_VERSION
-from ucoinpy.documents.certification import SelfCertification
-from ucoinpy.documents.membership import Membership
-from ..tools.exceptions import Error, PersonNotFoundError,\
-                                        MembershipNotFoundError, \
-                                        NoPeerAvailable
-from PyQt5.QtCore import QMutex
-
-
-def load_cache(json_data):
-    for person_data in json_data['persons']:
-        person = Person.from_json(person_data)
-        Person._instances[person.pubkey] = person
-
-
-def jsonify_cache():
-    data = []
-    for person in Person._instances.values():
-        data.append(person.jsonify())
-    return {'persons': data}
-
-
-class cached(object):
-    '''
-    Decorator. Caches a function's return value each time it is called.
-    If called later with the same arguments, the cached value is returned
-    (not reevaluated).
-    Delete it to clear it from the cache
-    '''
-    def __init__(self, func):
-        self.func = func
-
-    def __call__(self, inst, community):
-        inst._cache_mutex.lock()
-        try:
-            inst._cache[community.currency]
-        except KeyError:
-            inst._cache[community.currency] = {}
-
-        try:
-            value = inst._cache[community.currency][self.func.__name__]
-        except KeyError:
-            value = self.func(inst, community)
-            inst._cache[community.currency][self.func.__name__] = value
-
-        finally:
-            inst._cache_mutex.unlock()
-
-        return value
-
-    def __repr__(self):
-        '''Return the function's docstring.'''
-        return self.func.__repr__
-
-    def __get__(self, inst, objtype):
-        if inst is None:
-            return self.func
-        return functools.partial(self, inst)
-
-
-#TODO: Change Person to Identity ?
-class Person(object):
-    '''
-    A person with a uid and a pubkey
-    '''
-    _instances = {}
-
-    def __init__(self, uid, pubkey, cache):
-        '''
-        Initializing a person object.
-
-        :param str uid: The person uid, also known as its uid on the network
-        :param str pubkey: The person pubkey
-        :param cache: The last returned values of the person properties.
-        '''
-        super().__init__()
-        self.uid = uid
-        self.pubkey = pubkey
-        self._cache = cache
-        self._cache_mutex = QMutex()
-
-    @classmethod
-    def lookup(cls, pubkey, community, cached=True):
-        '''
-        Get a person from the pubkey found in a community
-
-        :param str pubkey: The person pubkey
-        :param community: The community in which to look for the pubkey
-        :param bool cached: True if the person should be searched in the
-        cache before requesting the community.
-
-        :return: A new person if the pubkey was unknown or\
-        the known instance if pubkey was already known.
-        '''
-        if cached and pubkey in Person._instances:
-            return Person._instances[pubkey]
-        else:
-            try:
-                data = community.request(bma.wot.Lookup, req_args={'search': pubkey},
-                                         cached=cached)
-            except ValueError as e:
-                if '404' in str(e):
-                    raise PersonNotFoundError(pubkey, community.name)
-
-            timestamp = 0
-
-            for result in data['results']:
-                if result["pubkey"] == pubkey:
-                    uids = result['uids']
-                    person_uid = ""
-                    for uid_data in uids:
-                        if uid_data["meta"]["timestamp"] > timestamp:
-                            timestamp = uid_data["meta"]["timestamp"]
-                            person_uid = uid_data["uid"]
-
-                        person = cls(person_uid, pubkey, {})
-                        Person._instances[pubkey] = person
-                        logging.debug("{0}".format(Person._instances.keys()))
-                        return person
-        raise PersonNotFoundError(pubkey, community.name)
-
-    @classmethod
-    def from_metadata(cls, metadata):
-        '''
-        Get a person from a metadata dict.
-        A metadata dict has a 'text' key corresponding to the person uid,
-        and a 'id' key corresponding to the person pubkey.
-
-        :param dict metadata: The person metadata
-        :return: A new person if pubkey wasn't knwon, else the existing instance.
-        '''
-        uid = metadata['text']
-        pubkey = metadata['id']
-        if pubkey in Person._instances:
-            return Person._instances[pubkey]
-        else:
-            person = cls(uid, pubkey, {})
-            Person._instances[pubkey] = person
-            return person
-
-    @classmethod
-    def from_json(cls, json_data):
-        '''
-        Create a person from json data
-
-        :param dict json_data: The person as a dict in json format
-        :return: A new person if pubkey wasn't known, else a new person instance.
-        '''
-        pubkey = json_data['pubkey']
-        if pubkey in Person._instances:
-            return Person._instances[pubkey]
-        else:
-            if 'name' in json_data:
-                uid = json_data['name']
-            else:
-                uid = json_data['uid']
-            if 'cache' in json_data:
-                cache = json_data['cache']
-            else:
-                cache = {}
-
-            person = cls(uid, pubkey, cache)
-            Person._instances[pubkey] = person
-            return person
-
-    def selfcert(self, community):
-        '''
-        Get the person self certification.
-        This request is not cached in the person object.
-
-        :param community: The community target to request the self certification
-        :return: A SelfCertification ucoinpy object
-        '''
-        data = community.request(bma.wot.Lookup, req_args={'search': self.pubkey})
-        logging.debug(data)
-        timestamp = 0
-
-        for result in data['results']:
-            if result["pubkey"] == self.pubkey:
-                uids = result['uids']
-                for uid_data in uids:
-                    if uid_data["meta"]["timestamp"] > timestamp:
-                        timestamp = uid_data["meta"]["timestamp"]
-                        uid = uid_data["uid"]
-                        signature = uid_data["self"]
-
-                return SelfCertification(PROTOCOL_VERSION,
-                                             community.currency,
-                                             self.pubkey,
-                                             timestamp,
-                                             uid,
-                                             signature)
-        raise PersonNotFoundError(self.pubkey, community.name)
-
-    @cached
-    def get_join_date(self, community):
-        '''
-        Get the person join date.
-        This request is not cached in the person object.
-
-        :param community: The community target to request the join date
-        :return: A datetime object
-        '''
-        try:
-            search = community.request(bma.blockchain.Membership, {'search': self.pubkey})
-            membership_data = None
-            if len(search['memberships']) > 0:
-                membership_data = search['memberships'][0]
-                return community.get_block(membership_data['blockNumber']).mediantime
-            else:
-                return None
-        except ValueError as e:
-            if '400' in str(e):
-                raise MembershipNotFoundError(self.pubkey, community.name)
-        except Exception as e:
-            logging.debug('bma.blockchain.Membership request error : ' + str(e))
-            raise MembershipNotFoundError(self.pubkey, community.name)
-
-#TODO: Manage 'OUT' memberships ? Maybe ?
-    @cached
-    def membership(self, community):
-        '''
-        Get the person last membership document.
-
-        :param community: The community target to request the join date
-        :return: The membership data in BMA json format
-        '''
-        try:
-            search = community.request(bma.blockchain.Membership,
-                                               {'search': self.pubkey})
-            block_number = -1
-            for ms in search['memberships']:
-                if ms['blockNumber'] > block_number:
-                    block_number = ms['blockNumber']
-                    if 'type' in ms:
-                        if ms['type'] is 'IN':
-                            membership_data = ms
-                    else:
-                        membership_data = ms
-
-            if membership_data is None:
-                raise MembershipNotFoundError(self.pubkey, community.name)
-        except ValueError as e:
-            if '400' in str(e):
-                raise MembershipNotFoundError(self.pubkey, community.name)
-        except Exception as e:
-            logging.debug('bma.blockchain.Membership request error : ' + str(e))
-            raise MembershipNotFoundError(self.pubkey, community.name)
-
-        return membership_data
-
-    @cached
-    def published_uid(self, community):
-        try:
-            data = community.request(bma.wot.Lookup,
-                                     req_args={'search': self.pubkey},
-                                     cached=cached)
-        except ValueError as e:
-            if '404' in str(e):
-                return False
-
-        timestamp = 0
-
-        for result in data['results']:
-            if result["pubkey"] == self.pubkey:
-                uids = result['uids']
-                person_uid = ""
-                for uid_data in uids:
-                    if uid_data["meta"]["timestamp"] > timestamp:
-                        timestamp = uid_data["meta"]["timestamp"]
-                        person_uid = uid_data["uid"]
-                    if person_uid == self.uid:
-                        return True
-        return False
-
-    @cached
-    def is_member(self, community):
-        '''
-        Check if the person is a member of a community
-
-        :param community: The community target to request the join date
-        :return: True if the person is a member of a community
-        '''
-        try:
-            certifiers = community.request(bma.wot.CertifiersOf, {'search': self.pubkey})
-            return certifiers['isMember']
-        except ValueError:
-            return False
-        except Exception as e:
-            logging.debug('bma.wot.CertifiersOf request error : ' + str(e))
-            return False
-
-    @cached
-    def certifiers_of(self, community):
-        '''
-        Get the list of this person certifiers
-
-        :param community: The community target to request the join date
-        :return: The list of the certifiers of this community in BMA json format
-        '''
-        try:
-            certifiers = community.request(bma.wot.CertifiersOf, {'search': self.pubkey})
-        except ValueError as e:
-            logging.debug('bma.wot.CertifiersOf request ValueError : ' + str(e))
-            try:
-                data = community.request(bma.wot.Lookup, {'search': self.pubkey})
-            except ValueError as e:
-                logging.debug('bma.wot.Lookup request ValueError : ' + str(e))
-                return list()
-
-            # convert api data to certifiers list
-            certifiers = list()
-            # add certifiers of uid
-
-            for result in data['results']:
-                if result["pubkey"] == self.pubkey:
-                    for uid_data in result['uids']:
-                        for certifier_data in uid_data['others']:
-                            for uid in certifier_data['uids']:
-                            # add a certifier
-                                certifier = {}
-                                certifier['uid'] = uid
-                                certifier['pubkey'] = certifier_data['pubkey']
-                                certifier['isMember'] = certifier_data['isMember']
-                                certifier['cert_time'] = dict()
-                                certifier['cert_time']['medianTime'] = community.get_block(certifier_data['meta']['block_number']).mediantime
-                                certifiers.append(certifier)
-
-            return certifiers
-
-        except Exception as e:
-            logging.debug('bma.wot.CertifiersOf request error : ' + str(e))
-            return list()
-
-        return certifiers['certifications']
-
-    def unique_valid_certifiers_of(self, community):
-        certifier_list = self.certifiers_of(community)
-        unique_valid = []
-        #  add certifiers of uid
-        for certifier in tuple(certifier_list):
-            # add only valid certification...
-            if community.certification_expired(certifier['cert_time']['medianTime']):
-                continue
-
-            # keep only the latest certification
-            already_found = [c['pubkey'] for c in unique_valid]
-            if certifier['pubkey'] in already_found:
-                index = already_found.index(certifier['pubkey'])
-                if certifier['cert_time']['medianTime'] > unique_valid[index]['cert_time']['medianTime']:
-                    unique_valid[index] = certifier
-            else:
-                unique_valid.append(certifier)
-        return unique_valid
-
-    @cached
-    def certified_by(self, community):
-        '''
-        Get the list of persons certified by this person
-
-        :param community: The community target to request the join date
-        :return: The list of the certified persons of this community in BMA json format
-        '''
-        try:
-            certified_list = community.request(bma.wot.CertifiedBy, {'search': self.pubkey})
-        except ValueError as e:
-            logging.debug('bma.wot.CertifiersOf request ValueError : ' + str(e))
-            try:
-                data = community.request(bma.wot.Lookup, {'search': self.pubkey})
-            except ValueError as e:
-                logging.debug('bma.wot.Lookup request ValueError : ' + str(e))
-                return list()
-
-            certified_list = list()
-            for result in data['results']:
-                if result["pubkey"] == self.pubkey:
-                    for certified in result['signed']:
-                        certified['cert_time'] = dict()
-                        certified['cert_time']['medianTime'] = certified['meta']['timestamp']
-                        certified_list.append(certified)
-
-            return certified_list
-
-        except Exception as e:
-            logging.debug('bma.wot.CertifiersOf request error : ' + str(e))
-            return list()
-
-        return certified_list['certifications']
-
-    def unique_valid_certified_by(self, community):
-        certified_list = self.certified_by(community)
-        unique_valid = []
-        #  add certifiers of uid
-        for certified in tuple(certified_list):
-            # add only valid certification...
-            if community.certification_expired(certified['cert_time']['medianTime']):
-                continue
-
-            # keep only the latest certification
-            already_found = [c['pubkey'] for c in unique_valid]
-            if certified['pubkey'] in already_found:
-                index = already_found.index(certified['pubkey'])
-                if certified['cert_time']['medianTime'] > unique_valid[index]['cert_time']['medianTime']:
-                    unique_valid[index] = certified
-            else:
-                unique_valid.append(certified)
-        return unique_valid
-
-    def membership_expiration_time(self, community):
-        join_block = self.membership(community)['blockNumber']
-        join_date = community.get_block(join_block)['medianTime']
-        parameters = community.parameters
-        expiration_date = join_date + parameters['sigValidity']
-        current_time = time.time()
-        return expiration_date - current_time
-
-    def reload(self, func, community):
-        '''
-        Reload a cached property of this person in a community.
-        This method is thread safe.
-        This method clears the cache entry for this community and get it back.
-
-        :param func: The cached property to reload
-        :param community: The community to request for data
-        :return: True if a changed was made by the reload.
-        '''
-        self._cache_mutex.lock()
-        change = False
-        try:
-            if community.currency not in self._cache:
-                self._cache[community.currency] = {}
-
-            try:
-                before = self._cache[community.currency][func.__name__]
-            except KeyError:
-                change = True
-
-            try:
-                value = func(self, community)
-
-                if not change:
-                    if type(value) is dict:
-                        hash_before = (str(tuple(frozenset(sorted(before.keys())))),
-                                     str(tuple(frozenset(sorted(before.items())))))
-                        hash_after = (str(tuple(frozenset(sorted(value.keys())))),
-                                     str(tuple(frozenset(sorted(value.items())))))
-                        change = hash_before != hash_after
-                    elif type(value) is bool:
-                        change = before != value
-                self._cache[community.currency][func.__name__] = value
-            except Error:
-                return False
-        finally:
-            self._cache_mutex.unlock()
-        return change
-
-    def jsonify(self):
-        '''
-        Get the community as dict in json format.
-        :return: The community as a dict in json format
-        '''
-        data = {'uid': self.uid,
-                'pubkey': self.pubkey,
-                'cache': self._cache}
-        return data
diff --git a/src/cutecoin/core/registry/__init__.py b/src/cutecoin/core/registry/__init__.py
new file mode 100644
index 00000000..4ab046a1
--- /dev/null
+++ b/src/cutecoin/core/registry/__init__.py
@@ -0,0 +1,2 @@
+from .identities import IdentitiesRegistry
+from .identity import Identity
\ No newline at end of file
diff --git a/src/cutecoin/core/registry/identities.py b/src/cutecoin/core/registry/identities.py
new file mode 100644
index 00000000..b23b8c41
--- /dev/null
+++ b/src/cutecoin/core/registry/identities.py
@@ -0,0 +1,102 @@
+from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal
+
+from cutecoin.core.net.api import bma as qtbma
+from .identity import Identity
+
+import json
+
+
+class IdentitiesRegistry:
+    """
+    Core class to handle identities lookup
+    """
+    def __init__(self, instances={}):
+        """
+        Initializer of the IdentitiesRegistry
+
+        :param list of Identity instances:
+        :return: An IdentitiesRegistry object
+        :rtype: IdentitiesRegistry
+        """
+        self._instances = instances
+
+    def load_json(self, json_data):
+        """
+        Load json data
+
+        :param dict json_data: The identities in json format
+        """
+        instances = {}
+
+        for person_data in json_data['registry']:
+            pubkey = person_data['pubkey']
+            if pubkey not in instances:
+                person = Identity.from_json(person_data)
+                instances[person.pubkey] = person
+        self._instances = instances
+
+    def jsonify(self):
+        identities_json = []
+        for identity in self._instances.values():
+            identities_json.append(identity.jsonify())
+        return {'registry': identities_json}
+
+    def lookup(self, pubkey, community):
+        """
+        Get a person from the pubkey found in a community
+
+        :param str pubkey: The person pubkey
+        :param cutecoin.core.community.Community community: The community in which to look for the pubkey
+        :param bool cached: True if the person should be searched in the
+        cache before requesting the community.
+
+        :return: A new person if the pubkey was unknown or\
+        the known instance if pubkey was already known.
+        """
+        if pubkey in self._instances:
+            identity = self._instances[pubkey]
+        else:
+            identity = Identity.empty(pubkey)
+            self._instances[pubkey] = identity
+        reply = community.bma_access.request(qtbma.wot.Lookup, req_args={'search': pubkey})
+        reply.finished.connect(lambda: self.handle_lookup(reply, identity))
+        return identity
+
+    def handle_lookup(self, reply, identity):
+        """
+        :param cutecoin.core.registry.identity.Identity identity: The looked up identity
+        :return:
+        """
+        strdata = bytes(reply.readAll()).decode('utf-8')
+        data = json.loads(strdata)
+
+        timestamp = 0
+        for result in data['results']:
+            if result["pubkey"] == identity.pubkey:
+                uids = result['uids']
+                identity_uid = ""
+                for uid_data in uids:
+                    if uid_data["meta"]["timestamp"] > timestamp:
+                        timestamp = uid_data["meta"]["timestamp"]
+                        identity_uid = uid_data["uid"]
+                    identity.uid = identity_uid
+                    identity.status = Identity.FOUND
+                    identity.inner_data_changed.emit(qtbma.wot.Lookup)
+                    return
+
+    def from_metadata(self, metadata):
+        """
+        Get a person from a metadata dict.
+        A metadata dict has a 'text' key corresponding to the person uid,
+        and a 'id' key corresponding to the person pubkey.
+
+        :param dict metadata: The person metadata
+        :return: A new person if pubkey wasn't knwon, else the existing instance.
+        """
+        pubkey = metadata['id']
+        if pubkey in self._instances:
+            return self._instances[pubkey]
+        else:
+            identity = Identity.from_metadata(metadata)
+            self._instances[pubkey] = identity
+            return identity
diff --git a/src/cutecoin/core/registry/identity.py b/src/cutecoin/core/registry/identity.py
new file mode 100644
index 00000000..3505cd6b
--- /dev/null
+++ b/src/cutecoin/core/registry/identity.py
@@ -0,0 +1,282 @@
+'''
+Created on 11 févr. 2014
+
+@author: inso
+'''
+
+import logging
+import time
+
+from ucoinpy.documents.certification import SelfCertification
+from cutecoin.tools.exceptions import Error, LookupFailureError,\
+                                        MembershipNotFoundError
+from cutecoin.core.net.api import bma as qtbma
+from cutecoin.core.net.api.bma import PROTOCOL_VERSION
+from PyQt5.QtCore import QObject, pyqtSignal
+
+
+class Identity(QObject):
+    """
+    A person with a uid and a pubkey
+    """
+    FOUND = 1
+    NOT_FOUND = 0
+
+    inner_data_changed = pyqtSignal(int)
+
+    def __init__(self, uid, pubkey, status):
+        """
+        Initializing a person object.
+
+        :param str uid: The person uid, also known as its uid on the network
+        :param str pubkey: The person pubkey
+        :param int status: The local status of the identity
+        """
+        super().__init__()
+        assert(status in (Identity.FOUND, Identity.NOT_FOUND))
+        self.uid = uid
+        self.pubkey = pubkey
+        self.status = status
+
+    @classmethod
+    def empty(cls, pubkey):
+        return cls("", pubkey, Identity.NOT_FOUND)
+
+    @classmethod
+    def from_metadata(cls, metadata):
+        return cls(metadata["text"], metadata["id"], Identity.NOT_FOUND)
+
+    @classmethod
+    def from_json(cls, json_data):
+        """
+        Create a person from json data
+
+        :param dict json_data: The person as a dict in json format
+        :return: A new person if pubkey wasn't known, else a new person instance.
+        """
+        pubkey = json_data['pubkey']
+        uid = json_data['uid']
+        status = json_data['status']
+
+        return cls(uid, pubkey, status)
+
+    def selfcert(self, community):
+        """
+        Get the person self certification.
+        This request is not cached in the person object.
+
+        :param cutecoin.core.community.Community community: The community target to request the self certification
+        :return: A SelfCertification ucoinpy object
+        :rtype: ucoinpy.documents.certification.SelfCertification
+        """
+        data = community.bma_access.get(self, qtbma.wot.Lookup, req_args={'search': self.pubkey})
+        if data != qtbma.wot.Lookup.null_value:
+            timestamp = 0
+
+            for result in data['results']:
+                if result["pubkey"] == self.pubkey:
+                    uids = result['uids']
+                    for uid_data in uids:
+                        if uid_data["meta"]["timestamp"] > timestamp:
+                            timestamp = uid_data["meta"]["timestamp"]
+                            uid = uid_data["uid"]
+                            signature = uid_data["self"]
+
+                    return SelfCertification(PROTOCOL_VERSION,
+                                             community.currency,
+                                             self.pubkey,
+                                             timestamp,
+                                             uid,
+                                             signature)
+
+    def get_join_date(self, community):
+        """
+        Get the person join date.
+        This request is not cached in the person object.
+
+        :param cutecoin.core.community.Community community: The community target to request the join date
+        :return: A datetime object
+        """
+        search = community.bma_access.get(self, qtbma.blockchain.Membership, {'search': self.pubkey})
+        if search != qtbma.blockchain.Membership.null_value:
+            if len(search['memberships']) > 0:
+                membership_data = search['memberships'][0]
+                return community.get_block(membership_data['blockNumber']).mediantime
+            else:
+                return None
+        else:
+            raise MembershipNotFoundError(self.pubkey, community.name)
+
+#TODO: Manage 'OUT' memberships ? Maybe ?
+    def membership(self, community):
+        """
+        Get the person last membership document.
+
+        :param cutecoin.core.community.Community community: The community target to request the join date
+        :return: The membership data in BMA json format
+        """
+        search = community.bma_access.get(self, qtbma.blockchain.Membership,
+                                           {'search': self.pubkey})
+        if search != qtbma.blockchain.Membership.null_value:
+            block_number = -1
+            for ms in search['memberships']:
+                if ms['blockNumber'] > block_number:
+                    block_number = ms['blockNumber']
+                    if 'type' in ms:
+                        if ms['type'] is 'IN':
+                            membership_data = ms
+                    else:
+                        membership_data = ms
+            return membership_data
+        else:
+            raise MembershipNotFoundError(self.pubkey, community.name)
+
+    def published_uid(self, community):
+        data = community.bma_access.get(self, qtbma.wot.Lookup,
+                                 req_args={'search': self.pubkey})
+        if data != qtbma.wot.Lookup.null_value:
+            timestamp = 0
+
+            for result in data['results']:
+                if result["pubkey"] == self.pubkey:
+                    uids = result['uids']
+                    person_uid = ""
+                    for uid_data in uids:
+                        if uid_data["meta"]["timestamp"] > timestamp:
+                            timestamp = uid_data["meta"]["timestamp"]
+                            person_uid = uid_data["uid"]
+                        if person_uid == self.uid:
+                            return True
+        return False
+
+    def is_member(self, community):
+        '''
+        Check if the person is a member of a community
+
+        :param cutecoin.core.community.Community community: The community target to request the join date
+        :return: True if the person is a member of a community
+        '''
+        certifiers = community.bma_access.get(self, qtbma.wot.CertifiersOf, {'search': self.pubkey})
+        if certifiers != qtbma.wot.CertifiersOf.null_value:
+            return certifiers['isMember']
+        return False
+
+    def certifiers_of(self, community):
+        """
+        Get the list of this person certifiers
+
+        :param cutecoin.core.community.Community community: The community target to request the join date
+        :return: The list of the certifiers of this community in BMA json format
+        """
+        certifiers = community.bma_access.get(self, qtbma.wot.CertifiersOf, {'search': self.pubkey})
+        if certifiers == qtbma.wot.CertifiersOf.null_value:
+            logging.debug('bma.wot.CertifiersOf request error')
+            data = community.bma_access.get(self, qtbma.wot.Lookup, {'search': self.pubkey})
+            if data == qtbma.wot.Lookup.null_value:
+                logging.debug('bma.wot.Lookup request error')
+                return list()
+
+            # convert api data to certifiers list
+            certifiers = list()
+            # add certifiers of uid
+
+            for result in data['results']:
+                if result["pubkey"] == self.pubkey:
+                    for uid_data in result['uids']:
+                        for certifier_data in uid_data['others']:
+                            for uid in certifier_data['uids']:
+                            # add a certifier
+                                certifier = {}
+                                certifier['uid'] = uid
+                                certifier['pubkey'] = certifier_data['pubkey']
+                                certifier['isMember'] = certifier_data['isMember']
+                                certifier['cert_time'] = dict()
+                                certifier['cert_time']['medianTime'] = community.get_block(
+                                    certifier_data['meta']['block_number']).mediantime
+                                certifiers.append(certifier)
+
+            return certifiers
+        return certifiers['certifications']
+
+    def unique_valid_certifiers_of(self, community):
+        certifier_list = self.certifiers_of(community)
+        unique_valid = []
+        #  add certifiers of uid
+        for certifier in tuple(certifier_list):
+            # add only valid certification...
+            if community.certification_expired(certifier['cert_time']['medianTime']):
+                continue
+
+            # keep only the latest certification
+            already_found = [c['pubkey'] for c in unique_valid]
+            if certifier['pubkey'] in already_found:
+                index = already_found.index(certifier['pubkey'])
+                if certifier['cert_time']['medianTime'] > unique_valid[index]['cert_time']['medianTime']:
+                    unique_valid[index] = certifier
+            else:
+                unique_valid.append(certifier)
+        return unique_valid
+
+    def certified_by(self, community):
+        '''
+        Get the list of persons certified by this person
+
+        :param cutecoin.core.community.Community community: The community target to request the join date
+        :return: The list of the certified persons of this community in BMA json format
+        '''
+        certified_list = community.bma_access.get(self, qtbma.wot.CertifiedBy, {'search': self.pubkey})
+        if certified_list == qtbma.wot.CertifiedBy.null_value:
+            logging.debug('bma.wot.CertifiersOf request error')
+            data = community.bma_access.get(self, qtbma.wot.Lookup, {'search': self.pubkey})
+            if data == qtbma.wot.Lookup.null_value:
+                logging.debug('bma.wot.Lookup request error')
+                return list()
+            else:
+                certified_list = list()
+                for result in data['results']:
+                    if result["pubkey"] == self.pubkey:
+                        for certified in result['signed']:
+                            certified['cert_time'] = dict()
+                            certified['cert_time']['medianTime'] = certified['meta']['timestamp']
+                            certified_list.append(certified)
+
+            return certified_list
+
+        return certified_list['certifications']
+
+    def unique_valid_certified_by(self, community):
+        certified_list = self.certified_by(community)
+        unique_valid = []
+        #  add certifiers of uid
+        for certified in tuple(certified_list):
+            # add only valid certification...
+            if community.certification_expired(certified['cert_time']['medianTime']):
+                continue
+
+            # keep only the latest certification
+            already_found = [c['pubkey'] for c in unique_valid]
+            if certified['pubkey'] in already_found:
+                index = already_found.index(certified['pubkey'])
+                if certified['cert_time']['medianTime'] > unique_valid[index]['cert_time']['medianTime']:
+                    unique_valid[index] = certified
+            else:
+                unique_valid.append(certified)
+        return unique_valid
+
+    def membership_expiration_time(self, community):
+        join_block = self.membership(community)['blockNumber']
+        join_date = community.get_block(join_block)['medianTime']
+        parameters = community.parameters
+        expiration_date = join_date + parameters['sigValidity']
+        current_time = time.time()
+        return expiration_date - current_time
+
+    def jsonify(self):
+        '''
+        Get the community as dict in json format.
+        :return: The community as a dict in json format
+        '''
+        data = {'uid': self.uid,
+                'pubkey': self.pubkey,
+                'status': self.status}
+        return data
diff --git a/src/cutecoin/core/wallet.py b/src/cutecoin/core/wallet.py
index ba53f61d..dedc572c 100644
--- a/src/cutecoin/core/wallet.py
+++ b/src/cutecoin/core/wallet.py
@@ -8,9 +8,9 @@ from ucoinpy.documents.transaction import InputSource, OutputSource, Transaction
 from ucoinpy.key import SigningKey
 
 from .net.api import bma as qtbma
-from ..tools.exceptions import NotEnoughMoneyError, NoPeerAvailable, PersonNotFoundError
+from ..tools.exceptions import NotEnoughMoneyError, NoPeerAvailable, LookupFailureError
 from .transfer import Transfer, Received
-from .person import Person
+from .registry import IdentitiesRegistry, Identity
 
 from PyQt5.QtCore import QObject, pyqtSignal
 
@@ -77,12 +77,12 @@ class Cache():
 
         try:
             issuer_uid = Person.lookup(tx.issuers[0], community).uid
-        except PersonNotFoundError:
+        except LookupFailureError:
             issuer_uid = ""
 
         try:
             receiver_uid = Person.lookup(receivers[0], community).uid
-        except PersonNotFoundError:
+        except LookupFailureError:
             receiver_uid = ""
 
         metadata = {'block': block_number,
@@ -199,7 +199,7 @@ class Wallet(QObject):
     inner_data_changed = pyqtSignal(int)
     refresh_progressed = pyqtSignal(int, int)
 
-    def __init__(self, walletid, pubkey, name):
+    def __init__(self, walletid, pubkey, name, identities_registry):
         '''
         Constructor of a wallet object
 
@@ -212,10 +212,11 @@ class Wallet(QObject):
         self.walletid = walletid
         self.pubkey = pubkey
         self.name = name
+        self._identities_registry = identities_registry
         self.caches = {}
 
     @classmethod
-    def create(cls, walletid, salt, password, name):
+    def create(cls, walletid, salt, password, name, identities_registry):
         '''
         Factory method to create a new wallet
 
@@ -228,10 +229,10 @@ class Wallet(QObject):
             key = SigningKey(salt, password)
         else:
             key = SigningKey("{0}{1}".format(salt, walletid), password)
-        return cls(walletid, key.pubkey, name)
+        return cls(walletid, key.pubkey, name, identities_registry)
 
     @classmethod
-    def load(cls, json_data):
+    def load(cls, json_data, identities_registry):
         '''
         Factory method to load a saved wallet.
 
@@ -240,7 +241,7 @@ class Wallet(QObject):
         walletid = json_data['walletid']
         pubkey = json_data['pubkey']
         name = json_data['name']
-        return cls(walletid, pubkey, name)
+        return cls(walletid, pubkey, name, identities_registry)
 
     def load_caches(self, json_data):
         '''
@@ -397,12 +398,12 @@ class Wallet(QObject):
 
         try:
             issuer_uid = Person.lookup(key.pubkey, community).uid
-        except PersonNotFoundError:
+        except LookupFailureError:
             issuer_uid = ""
 
         try:
             receiver_uid = Person.lookup(recipient, community).uid
-        except PersonNotFoundError:
+        except LookupFailureError:
             receiver_uid = ""
 
         metadata = {'block': block_number,
diff --git a/src/cutecoin/gui/community_tab.py b/src/cutecoin/gui/community_tab.py
index 09c7b99d..eb5a6945 100644
--- a/src/cutecoin/gui/community_tab.py
+++ b/src/cutecoin/gui/community_tab.py
@@ -17,8 +17,8 @@ from .wot_tab import WotTabWidget
 from .transfer import TransferMoneyDialog
 from .certification import CertificationDialog
 from . import toast
-from ..tools.exceptions import PersonNotFoundError, NoPeerAvailable
-from ..core.person import Person
+from ..tools.exceptions import LookupFailureError, NoPeerAvailable
+from ..core.registry import IdentitiesRegistry
 from ucoinpy.api import bma
 from ..core.net.api import bma as qtbma
 
@@ -41,6 +41,7 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget):
         super().__init__()
         self.setupUi(self)
         self.parent = parent
+        self.app = app
         self.community = community
         self.community.inner_data_changed.connect(self.handle_change)
         self.account = account
@@ -75,7 +76,7 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget):
             pubkey_index = model.sourceModel().index(source_index.row(),
                                                    pubkey_col)
             pubkey = model.sourceModel().data(pubkey_index, Qt.DisplayRole)
-            identity = Person.lookup(pubkey, self.community)
+            identity = self.app.identities_registry(pubkey, self.community)
             menu = QMenu(self)
 
             informations = QAction(self.tr("Informations"), self)
@@ -175,7 +176,7 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget):
         except ValueError as e:
             QMessageBox.critical(self, self.tr("Join demand error"),
                               str(e))
-        except PersonNotFoundError as e:
+        except LookupFailureError as e:
             QMessageBox.critical(self, self.tr("Key not sent to community"),
                               self.tr(""""Your key wasn't sent in the community.
 You can't request a membership."""))
@@ -282,7 +283,7 @@ Revoking your UID can only success if it is not already validated by the network
 
         persons = []
         for identity in response['results']:
-            persons.append(Person.lookup(identity['pubkey'], self.community))
+            persons.append(self.app.identities_registry(identity['pubkey'], self.community))
 
         self._last_search = 'text'
         self.edit_textsearch.clear()
@@ -298,14 +299,14 @@ Revoking your UID can only success if it is not already validated by the network
         Search members of community and display found members
         """
         pubkeys = self.community.members_pubkeys()
-        persons = []
+        identities = []
         for p in pubkeys:
-            persons.append(Person.lookup(p, self.community))
+            identities.append(self.app.identities_registry.lookup(p, self.community))
 
         self._last_search = 'members'
 
         self.edit_textsearch.clear()
-        self.refresh(persons)
+        self.refresh(identities)
 
     def search_direct_connections(self):
         """
@@ -320,16 +321,16 @@ Revoking your UID can only success if it is not already validated by the network
         If no identities is passed, use the account connections.
         '''
         if persons is None:
-            self_identity = Person.lookup(self.account.pubkey, self.community)
+            self_identity = self.app.identities_registry.lookup(self.account.pubkey, self.community)
             account_connections = []
             certifiers_of = []
             certified_by = []
             for p in self_identity.unique_valid_certifiers_of(self.community):
-                account_connections.append(Person.lookup(p['pubkey'], self.community))
+                account_connections.append(self.app.identities_registry.lookup(p['pubkey'], self.community))
             certifiers_of = [p for p in account_connections]
             logging.debug(persons)
             for p in self_identity.unique_valid_certified_by(self.community):
-                account_connections.append(Person.lookup(p['pubkey'], self.community))
+                account_connections.append(self.app.identities_registry.lookup(p['pubkey'], self.community))
             certified_by = [p for p in account_connections
                       if p.pubkey not in [i.pubkey for i in certifiers_of]]
             persons = certifiers_of + certified_by
@@ -360,7 +361,7 @@ Revoking your UID can only success if it is not already validated by the network
                 self.button_leaving.hide()
                 self.button_publish_uid.show()
                 self.button_revoke_uid.hide()
-        except PersonNotFoundError:
+        except LookupFailureError:
             self.button_membership.hide()
             self.button_leaving.hide()
             self.button_publish_uid.show()
diff --git a/src/cutecoin/gui/contact.py b/src/cutecoin/gui/contact.py
index 2f50c58e..59e2f004 100644
--- a/src/cutecoin/gui/contact.py
+++ b/src/cutecoin/gui/contact.py
@@ -7,7 +7,7 @@ import re
 import logging
 
 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox
-from ..core.person import Person
+from ..core.registry import IdentitiesRegistry
 from ..tools.exceptions import ContactAlreadyExists
 from ..gen_resources.contact_uic import Ui_ConfigureContactDialog
 
diff --git a/src/cutecoin/gui/currency_tab.py b/src/cutecoin/gui/currency_tab.py
index 3badb34f..8ce4776a 100644
--- a/src/cutecoin/gui/currency_tab.py
+++ b/src/cutecoin/gui/currency_tab.py
@@ -18,7 +18,7 @@ from .network_tab import NetworkTabWidget
 from .informations_tab import InformationsTabWidget
 from . import toast
 from ..tools.exceptions import MembershipNotFoundError
-from ..core.person import Person
+from ..core.registry import IdentitiesRegistry
 
 
 class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget):
@@ -127,7 +127,7 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget):
         logging.debug("Refresh block")
         self.status_info.clear()
         try:
-            person = Person.lookup(self.app.current_account.pubkey, self.community)
+            person = self.app.identities_registry.lookup(self.app.current_account.pubkey, self.community)
             expiration_time = person.membership_expiration_time(self.community)
             sig_validity = self.community.parameters['sigValidity']
             warning_expiration_time = int(sig_validity / 3)
@@ -144,15 +144,14 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget):
             if len(certifiers_of) < self.community.parameters['sigQty']:
                 self.status_info.append('warning_certifications')
                 toast.display(self.tr("Certifications number"),
-                              self.tr("<b>Warning : You are certified by only {0} persons, need {1}</b>").format(len(certifiers_of),
-                                                                                                                     self.community.parameters['sigQty']))
+                              self.tr("<b>Warning : You are certified by only {0} persons, need {1}</b>")
+                              .format(len(certifiers_of),
+                                     self.community.parameters['sigQty']))
 
         except MembershipNotFoundError as e:
             pass
 
         self.tab_history.start_progress()
-        self.app.monitor.blockchain_watcher(self.community).thread().start()
-        self.app.monitor.persons_watcher(self.community).thread().start()
         self.refresh_status()
 
     @pyqtSlot()
diff --git a/src/cutecoin/gui/informations_tab.py b/src/cutecoin/gui/informations_tab.py
index e4c3c854..eea1c0b0 100644
--- a/src/cutecoin/gui/informations_tab.py
+++ b/src/cutecoin/gui/informations_tab.py
@@ -23,6 +23,7 @@ class InformationsTabWidget(QWidget, Ui_InformationsTabWidget):
         super().__init__()
         self.setupUi(self)
         self.community = community
+        self.community.inner_data_changed.connect(self.refresh)
         self.account = account
 
         self.refresh()
diff --git a/src/cutecoin/gui/mainwindow.py b/src/cutecoin/gui/mainwindow.py
index 7cca2b20..583d753e 100644
--- a/src/cutecoin/gui/mainwindow.py
+++ b/src/cutecoin/gui/mainwindow.py
@@ -398,7 +398,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
     def closeEvent(self, event):
         if self.app.current_account:
             self.app.save_cache(self.app.current_account)
-        self.app.save_persons()
+        self.app.save_registries()
         super().closeEvent(event)
 
     def showEvent(self, event):
diff --git a/src/cutecoin/gui/process_cfg_community.py b/src/cutecoin/gui/process_cfg_community.py
index 037334f8..4b822a2e 100644
--- a/src/cutecoin/gui/process_cfg_community.py
+++ b/src/cutecoin/gui/process_cfg_community.py
@@ -17,9 +17,9 @@ from PyQt5.QtGui import QCursor
 from ..gen_resources.community_cfg_uic import Ui_CommunityConfigurationDialog
 from ..models.peering import PeeringTreeModel
 from ..core.community import Community
-from ..core.person import Person
+from ..core.registry import IdentitiesRegistry
 from ..core.net.node import Node
-from ..tools.exceptions import PersonNotFoundError, NoPeerAvailable
+from ..tools.exceptions import LookupFailureError, NoPeerAvailable
 
 
 class Step():
@@ -194,7 +194,7 @@ class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog):
     def accept(self):
         try:
             Person.lookup(self.account.pubkey, self.community, cached=False)
-        except PersonNotFoundError as e:
+        except LookupFailureError as e:
             reply = QMessageBox.question(self, self.tr("Pubkey not found"),
                                  self.tr("""The public key of your account wasn't found in the community. :\n
 {0}\n
diff --git a/src/cutecoin/gui/transactions_tab.py b/src/cutecoin/gui/transactions_tab.py
index f0c739e3..e281654e 100644
--- a/src/cutecoin/gui/transactions_tab.py
+++ b/src/cutecoin/gui/transactions_tab.py
@@ -6,7 +6,7 @@ from ..gen_resources.transactions_tab_uic import Ui_transactionsTabWidget
 from ..models.txhistory import HistoryTableModel, TxFilterProxyModel
 from ..core.transfer import Transfer
 from ..core.wallet import Wallet
-from ..core.person import Person
+from ..core.registry import IdentitiesRegistry
 from .transfer import TransferMoneyDialog
 
 import logging
diff --git a/src/cutecoin/gui/wallets_tab.py b/src/cutecoin/gui/wallets_tab.py
index 54654146..e07581ff 100644
--- a/src/cutecoin/gui/wallets_tab.py
+++ b/src/cutecoin/gui/wallets_tab.py
@@ -8,7 +8,7 @@ import logging
 from PyQt5.QtWidgets import QWidget, QMenu, QAction, QApplication, QDialog
 from PyQt5.QtCore import QDateTime, QModelIndex, Qt, QLocale
 from PyQt5.QtGui import QCursor
-from ..core.person import Person
+from ..core.registry import IdentitiesRegistry
 from ..core.wallet import Wallet
 from ..gui.password_asker import PasswordAskerDialog
 from ..models.wallets import WalletsTableModel, WalletsFilterProxyModel
@@ -44,7 +44,7 @@ class WalletsTabWidget(QWidget, Ui_WalletsTab):
         last_renewal = ""
         expiration = ""
         try:
-            person = Person.lookup(self.account.pubkey, self.community)
+            person = self.app.identities_registry.lookup(self.account.pubkey, self.community)
             membership = person.membership(self.community)
             renew_block = membership['blockNumber']
             last_renewal = self.community.get_block(renew_block)['medianTime']
diff --git a/src/cutecoin/gui/wot_tab.py b/src/cutecoin/gui/wot_tab.py
index b5be05f9..be83d962 100644
--- a/src/cutecoin/gui/wot_tab.py
+++ b/src/cutecoin/gui/wot_tab.py
@@ -7,7 +7,7 @@ from PyQt5.QtCore import pyqtSlot
 from ..gen_resources.wot_tab_uic import Ui_WotTabWidget
 from cutecoin.gui.views.wot import NODE_STATUS_HIGHLIGHTED, NODE_STATUS_SELECTED, NODE_STATUS_OUT, ARC_STATUS_STRONG, ARC_STATUS_WEAK
 from ucoinpy.api import bma
-from cutecoin.core.person import Person
+from ..core.registry import IdentitiesRegistry
 
 
 class WotTabWidget(QWidget, Ui_WotTabWidget):
@@ -41,6 +41,7 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
         self.account = account
         self.community = community
         self.password_asker = password_asker
+        self.app = app
 
         # nodes list for menu from search
         self.nodes = list()
@@ -59,8 +60,8 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
         self._current_metadata = metadata
 
         # create Person from node metadata
-        person = Person.from_metadata(metadata)
-        person_account = Person.from_metadata({'text': self.account.name,
+        person = self.app.identities_registry.from_metadata(metadata)
+        person_account = self.app.identities_registry.from_metadata({'text': self.account.name,
                                                'id': self.account.pubkey})
         certifier_list = person.certifiers_of(self.community)
         certified_list = person.certified_by(self.community)
@@ -152,15 +153,15 @@ class WotTabWidget(QWidget, Ui_WotTabWidget):
         )
 
     def identity_informations(self, metadata):
-        person = Person.from_metadata(metadata)
+        person = self.app.identities_registry.from_metadata(metadata)
         self.parent.identity_informations(person)
 
     def sign_node(self, metadata):
-        person = Person.from_metadata(metadata)
+        person = self.app.identities_registry.from_metadata(metadata)
         self.parent.certify_identity(person)
 
     def send_money_to_node(self, metadata):
-        person = Person.from_metadata(metadata)
+        person = self.app.identities_registry.from_metadata(metadata)
         self.parent.send_money_to_identity(person)
 
     def add_node_as_contact(self, metadata):
diff --git a/src/cutecoin/models/identities.py b/src/cutecoin/models/identities.py
index 2548f2c9..0f069f6f 100644
--- a/src/cutecoin/models/identities.py
+++ b/src/cutecoin/models/identities.py
@@ -4,8 +4,7 @@ Created on 5 févr. 2014
 @author: inso
 '''
 
-from ucoinpy.api import bma
-from ..core.person import Person
+from ..core.registry import IdentitiesRegistry
 from ..tools.exceptions import NoPeerAvailable, MembershipNotFoundError
 from PyQt5.QtCore import QAbstractTableModel, QSortFilterProxyModel, Qt, \
                         QDateTime, QModelIndex, QLocale
diff --git a/src/cutecoin/tools/exceptions.py b/src/cutecoin/tools/exceptions.py
index f394b397..9f2da1cc 100644
--- a/src/cutecoin/tools/exceptions.py
+++ b/src/cutecoin/tools/exceptions.py
@@ -31,7 +31,7 @@ class NotMemberOfCommunityError(Error):
             .__init__(account + " is not a member of " + community)
 
 
-class PersonNotFoundError(Error):
+class LookupFailureError(Error):
 
     '''
     Exception raised when looking for a person in a community
-- 
GitLab