From 02152046974855e47135f41ec5c6b1c8b62b0883 Mon Sep 17 00:00:00 2001
From: blavenie <benoit.lavenier@e-is.pro>
Date: Tue, 30 Oct 2018 19:34:16 +0100
Subject: [PATCH] [fix] Fix default wallet "self " selection,, when many
 identities with some with a bad block timestamp.

---
 www/i18n/locale-en-GB.json                    |   1 +
 www/i18n/locale-en.json                       |   1 +
 www/i18n/locale-es-ES.json                    |   1 +
 www/i18n/locale-fr-FR.json                    |   1 +
 www/js/controllers/wallet-controllers.js      |   2 +-
 www/js/controllers/wot-controllers.js         |  24 +-
 www/js/services/wallet-services.js            |  36 +--
 www/js/services/wot-services.js               | 278 +++++++++++-------
 .../wot/modal_select_pubkey_identity.html     |   1 +
 www/templates/wot/view_identity.html          |  12 +-
 10 files changed, 218 insertions(+), 139 deletions(-)

diff --git a/www/i18n/locale-en-GB.json b/www/i18n/locale-en-GB.json
index aa5530256..f44dd03cd 100644
--- a/www/i18n/locale-en-GB.json
+++ b/www/i18n/locale-en-GB.json
@@ -764,6 +764,7 @@
     "NOT_NEED_MEMBERSHIP": "Already a member.",
     "IDENTITY_WILL_MISSING_CERTIFICATIONS": "This identity will soon lack certification (at least {{willNeedCertificationCount}}).",
     "IDENTITY_NEED_MEMBERSHIP": "This identity did not send a membership request. She will have to if she wishes to become a member.",
+    "HAS_ALTERNATIVE_IDENTITIES": "There are <b>multiple identities</b> attached to this public key. <b>Before any certification</b>, please <a ng-click=\"doQuickFix('showSelectIdentities')\">check other identities</a> to choose the correct one, or contact the wallet owner.",
     "REVOCATION_SENT": "Revocation sent successfully",
     "REVOCATION_SENT_WAITING_PROCESS": "Revocation <b>has been sent successfully</b>. It is awaiting processing.",
     "FEATURES_NOT_IMPLEMENTED": "This features is not implemented yet.<br/><br/>Why not to contribute to get it faster? ;)",
diff --git a/www/i18n/locale-en.json b/www/i18n/locale-en.json
index 63f298443..fa6e5bdc5 100644
--- a/www/i18n/locale-en.json
+++ b/www/i18n/locale-en.json
@@ -764,6 +764,7 @@
     "NOT_NEED_MEMBERSHIP": "Already a member.",
     "IDENTITY_WILL_MISSING_CERTIFICATIONS": "This identity will soon lack certification (at least {{willNeedCertificationCount}}).",
     "IDENTITY_NEED_MEMBERSHIP": "This identity did not send a membership request. She will have to if she wishes to become a member.",
+    "HAS_ALTERNATIVE_IDENTITIES": "There are <b>multiple identities</b> attached to this public key. <b>Before any certification</b>, please <a ng-click=\"doQuickFix('showSelectIdentities')\">check other identities</a> to choose the correct one, or contact the wallet owner.",
     "REVOCATION_SENT": "Revocation sent successfully",
     "REVOCATION_SENT_WAITING_PROCESS": "Revocation <b>has been sent successfully</b>. It is awaiting processing.",
     "FEATURES_NOT_IMPLEMENTED": "This features is not implemented yet.<br/><br/>Why not to contribute to get it faster? ;)",
diff --git a/www/i18n/locale-es-ES.json b/www/i18n/locale-es-ES.json
index 694cace46..791537625 100644
--- a/www/i18n/locale-es-ES.json
+++ b/www/i18n/locale-es-ES.json
@@ -713,6 +713,7 @@
     "NOT_NEED_MEMBERSHIP": "Ya es miembro.",
     "IDENTITY_WILL_MISSING_CERTIFICATIONS": "Esta identidad pronto va a necesitar certificaciones (al menos {{willNeedCertificationCount}}).",
     "IDENTITY_NEED_MEMBERSHIP": "Esta identidad no envió una solicitud de membresía. Ella tendrá que hacerlo si desea hacerse miembro.",
+    "HAS_ALTERNATIVE_IDENTITIES": "Hay <b>identidades múltiples</b> adjuntas a esta clave pública. <b>Antes de cualquier certificación</b>, <a ng-click=\"doQuickFix('showSelectIdentities')\">verifique otras identidades</a> para elegir la correcta o comuníquese con el propietario de la cuenta.",
     "REVOCATION_SENT": "Revocación enviada",
     "REVOCATION_SENT_WAITING_PROCESS": "La <b>revocación de esta identidad</b> fue solicitada y está en espera de ser procesada.",
     "FEATURES_NOT_IMPLEMENTED": "Esta funcionalidad todavía está en proceso de desarrollo.<br/><br/>¿Por qué no <b>contribuir a Cesium</b>, para obtenerla más rápido? ;)",
diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json
index e5a8917ad..3fae40747 100644
--- a/www/i18n/locale-fr-FR.json
+++ b/www/i18n/locale-fr-FR.json
@@ -764,6 +764,7 @@
     "NOT_NEED_MEMBERSHIP": "Vous êtes déjà membre.",
     "IDENTITY_WILL_MISSING_CERTIFICATIONS": "Cette identité va bientôt manquer de certification (au moins {{willNeedCertificationCount}}).",
     "IDENTITY_NEED_MEMBERSHIP": "Cette identité n'a pas envoyée de demande d'adhésion. Elle devra si elle souhaite devenir membre.",
+    "HAS_ALTERNATIVE_IDENTITIES": "Il existe <b>plusieurs identités</b> rattachées à cette clé publique. <b>Avant toute certification</b>, pensez à <a ng-click=\"doQuickFix('showSelectIdentities')\">consulter les autres identités</a> pour choisir la bonne, ou bien contacter le propriétaire du compte.",
     "REVOCATION_SENT": "Revocation envoyée",
     "REVOCATION_SENT_WAITING_PROCESS": "La <b>révocation de cette identité</b> a été demandée et est en attente de traitement.",
     "FEATURES_NOT_IMPLEMENTED": "Cette fonctionnalité est encore en cours de développement.<br/>Pourquoi ne pas <b>contribuer à Cesium</b>, pour l'obtenir plus rapidement ? ;)",
diff --git a/www/js/controllers/wallet-controllers.js b/www/js/controllers/wallet-controllers.js
index db339a24a..88dc25108 100644
--- a/www/js/controllers/wallet-controllers.js
+++ b/www/js/controllers/wallet-controllers.js
@@ -628,7 +628,7 @@ function WalletController($scope, $rootScope, $q, $ionicPopup, $timeout, $state,
 
     var title = $scope.formData.name || $scope.formData.uid || $scope.formData.pubkey;
     // Use shareBasePath (fix #530) or rootPath (fix #390)
-    var url = (csConfig.shareBaseUrl || $rootScope.rootPath) + $state.href('app.wot_identity', {pubkey: $scope.formData.pubkey, uid: $scope.formData.name || $scope.formData.uid});
+    var url = (csConfig.shareBaseUrl || $rootScope.rootPath) + $state.href('app.wot_identity', {pubkey: $scope.formData.pubkey, uid: $scope.formData.uid});
 
     // Override default position, is small screen - fix #545
     if (UIUtils.screen.isSmall()) {
diff --git a/www/js/controllers/wot-controllers.js b/www/js/controllers/wot-controllers.js
index 19ab01e1c..ce71c927f 100644
--- a/www/js/controllers/wot-controllers.js
+++ b/www/js/controllers/wot-controllers.js
@@ -979,7 +979,7 @@ function WotIdentityAbstractController($scope, $rootScope, $state, $translate, $
 /**
  * Identity view controller - should extend WotIdentityAbstractCtrl
  */
-function WotIdentityViewController($scope, $rootScope, $controller, $timeout, UIUtils, csWallet) {
+function WotIdentityViewController($scope, $rootScope, $controller, $timeout, $state, UIUtils, Modals, csWallet) {
   'ngInject';
   // Initialize the super class and extend it.
   angular.extend(this, $controller('WotIdentityAbstractCtrl', {$scope: $scope}));
@@ -1044,6 +1044,28 @@ function WotIdentityViewController($scope, $rootScope, $controller, $timeout, UI
       $scope.showFab('fab-certify-' + $scope.formData.uid);
     }
   };
+
+  $scope.doQuickFix = function(event) {
+    if (event == "showSelectIdentities") {
+      return $scope.showSelectIdentities();
+    }
+  };
+
+  $scope.showSelectIdentities = function() {
+    if (!$scope.formData.requirements || !$scope.formData.requirements.alternatives) return;
+
+    return Modals.showSelectPubkeyIdentity({
+      identities: [$scope.formData.requirements].concat($scope.formData.requirements.alternatives)
+    })
+    .then(function(res) {
+      if (!res || !res.pubkey) return; // Skip if cancelled
+      // open the identity
+      return $state.go('app.wot_identity', {
+        pubkey: res.pubkey,
+        uid: res.uid
+      });
+    });
+  };
 }
 
 /**
diff --git a/www/js/services/wallet-services.js b/www/js/services/wallet-services.js
index 2cb7611ad..8d11e7141 100644
--- a/www/js/services/wallet-services.js
+++ b/www/js/services/wallet-services.js
@@ -687,35 +687,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
       cleanEventsByContext('requirements');
 
       // Get requirements
-      return csWot.loadRequirements(data)
-        .then(function(){
-
-          if (!data.requirements.uid) return;
-
-          // Get sigDate
-          var blockParts = data.requirements.blockUid.split('-', 2);
-          var blockNumber = parseInt(blockParts[0]);
-          var blockHash = blockParts[1];
-          // Retrieve registration date
-          return BMA.blockchain.block({block: blockNumber})
-            .then(function(block) {
-              data.sigDate = block.medianTime;
-
-              // Check if self has been done on a valid block
-              if (!data.isMember && blockNumber !== 0 && blockHash !== block.hash) {
-                data.requirements.hasBadSelfBlock = true;
-              }
-            })
-            .catch(function(err){
-              // Special case for currency init (root block not exists): use now
-              if (err && err.ucode == BMA.errorCodes.BLOCK_NOT_FOUND && blockNumber === 0) {
-                data.sigDate = moment().utc().unix();
-              }
-              else {
-                throw err;
-              }
-            });
-        });
+      return csWot.loadRequirements(data);
     },
 
     loadTxAndSources = function(fromTime) {
@@ -746,15 +718,15 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
     addEvents = function() {
       // Add user events
       if (data.requirements.revoked) {
-        delete data.requirements.hasBadSelfBlock;
+        delete data.requirements.meta.invalid;
         addEvent({type:'info', message: 'ERROR.WALLET_REVOKED', context: 'requirements'});
       }
       else if (data.requirements.pendingRevocation) {
-        delete data.requirements.hasBadSelfBlock;
+        delete data.requirements.meta.invalid;
         addEvent({type:'pending', message: 'INFO.REVOCATION_SENT_WAITING_PROCESS', context: 'requirements'});
       }
       else {
-        if (!data.isMember && data.requirements.hasBadSelfBlock) {
+        if (!data.isMember && data.requirements.meta.invalid) {
           addEvent({type: 'error', message: 'ERROR.WALLET_INVALID_BLOCK_HASH', context: 'requirements'});
           console.debug("Invalid membership for uid={0}: block hash changed".format(data.uid));
         }
diff --git a/www/js/services/wot-services.js b/www/js/services/wot-services.js
index a3b740b3c..473cc6a73 100644
--- a/www/js/services/wot-services.js
+++ b/www/js/services/wot-services.js
@@ -58,6 +58,7 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
 
       _resetRequirements = function(data) {
         data.requirements = {
+          meta: {},
           needSelf: true,
           needMembership: true,
           canMembershipOut: false,
@@ -77,8 +78,8 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
 
       _fillRequirements = function(requirements, currencyParameters) {
         // Add useful custom fields
-        requirements.hasSelf = true;
-        requirements.needSelf = false;
+        requirements.hasSelf = requirements.meta && requirements.meta.timestamp;
+        requirements.needSelf = !requirements.hasSelf || requirements.meta.invalid;
         requirements.wasMember = angular.isDefined(requirements.wasMember) ? requirements.wasMember : false; // Compat with Duniter 0.9
         requirements.needMembership = (!requirements.revoked && requirements.membershipExpiresIn <= 0 && requirements.membershipPendingExpiresIn <= 0 && !requirements.wasMember);
         requirements.needRenew = (!requirements.needMembership && !requirements.revoked &&
@@ -121,24 +122,84 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
         return requirements;
       },
 
+      _fillIdentitiesTime = function(identities) {
+        if (!identities) return $q.when(identities);
+
+        var blocks = [];
+        _.forEach(identities, function(identity) {
+          var blockUid = identity.meta.timestamp.split('-', 2);
+          identity.meta.number = parseInt(blockUid[0]);
+          identity.meta.hash = blockUid[1];
+          blocks.push(identity.meta.number);
+          if (identity.revocationNumber) {
+            blocks.push(identity.revocationNumber);
+          }
+        });
+
+        // Get identities blocks, to fill self and revocation time
+        return BMA.blockchain.blocks(_.uniq(blocks))
+          .then(function(blocks) {
+            _.forEach(identities, function(identity) {
+              var block = _.findWhere(blocks, {number: identity.meta.number});
+              identity.meta.time = block && block.medianTime;
+
+              // Check if self has been done on a valid block
+              if (block && identity.meta.number !== 0 && identity.meta.hash !== block.hash) {
+                identity.meta.invalid = true;
+              }
+
+              // Set revocation time
+              if (identity.revocationNumber) {
+                block = _.findWhere(blocks, {number: identity.revocationNumber});
+                identity.revocationTime = block && block.medianTime;
+              }
+            });
+
+            return identities;
+          })
+          .catch(function(err){
+            // Special case for currency init (root block not exists): use now
+            if (err && err.ucode == BMA.errorCodes.BLOCK_NOT_FOUND) {
+              _.forEach(identities, function(identity) {
+                if (identity.number === 0) {
+                  identity.meta.time = moment().utc().unix();
+                }
+              });
+              return identities;
+            }
+            else {
+              // FIXME workaround for issue #1304 ?
+              /*
+               if (identity.revocationNumber) {
+               identity.revocationTime = identity.revocationNumber;
+               return identity;
+               }*/
+              throw err;
+            }
+          });
+      },
+
       loadRequirements = function(data) {
         if (!data || (!data.pubkey && !data.uid)) return $q.when(data);
 
         return $q.all([
           // Get currency
           csCurrency.get(),
+
           // Get requirements
           BMA.wot.requirements({pubkey: data.pubkey||data.uid})
+            .then(function(res) {
+              return _fillIdentitiesTime(res && res.identities);
+            })
         ])
           .then(function(res){
             var currency = res[0];
+            var identities = res[1];
 
-            res = res[1];
-
-            if (!res.identities || !res.identities.length)  return;
+            if (!identities || !identities.length) return;
 
             // Sort to select the best identity
-            if (res.identities.length > 1) {
+            if (identities.length > 1) {
               // Select the best identity, by sorting using this order
               //  - same wallet uid
               //  - is member
@@ -149,42 +210,43 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
               //      max(count(certification)
               //    else
               //      max(membershipPendingExpiresIn) = must recent membership
-              res.identities = _.sortBy(res.identities, function(idty) {
+              identities = _.sortBy(identities, function(idty) {
                 var score = 0;
-                score += (10000000000 * ((data.uid && idty.uid === data.uid) ? 1 : 0));
-                score += (10000000000 * ((data.blockUid && idty.meta && idty.meta.timestamp === data.blockUid) ? 1 : 0));
-                score += (1000000000  * (idty.membershipExpiresIn > 0 ? 1 : 0));
-                score += (100000000   * (idty.membershipPendingExpiresIn > 0 ? 1 : 0));
-                score += (10000000    * (!idty.expired ? 1 : 0));
-                score += (1000000     * (!idty.outdistanced ? 1 : 0));
-                score += (100000      * (idty.wasMember ? 1 : 0));
+                score += (1000000000000* ((data.uid && idty.uid === data.uid) ? 1 : 0));
+                score += (100000000000 * (!idty.meta.invalid ? 1 : 0));
+                score += (10000000000  * ((data.blockUid && idty.meta.timestamp && idty.meta.timestamp === data.blockUid) ? 1 : 0));
+                score += (1000000000   * (idty.membershipExpiresIn > 0 ? 1 : 0));
+                score += (100000000    * (idty.membershipPendingExpiresIn > 0 ? 1 : 0));
+                score += (10000000     * (!idty.expired ? 1 : 0));
+                score += (1000000      * (!idty.outdistanced ? 1 : 0));
+                score += (100000       * (idty.wasMember ? 1 : 0));
                 var certCount = !idty.expired && idty.certifications ? idty.certifications.length : 0;
-                score += (1         * (certCount ? certCount : 0));
-                score += (1         * (!certCount && idty.membershipPendingExpiresIn > 0 ? idty.membershipPendingExpiresIn/1000 : 0));
+                score += (1            * (certCount ? certCount : 0));
+                score += (1            * (!certCount && idty.membershipPendingExpiresIn > 0 ? idty.membershipPendingExpiresIn/1000 : 0));
                 return -score;
               });
-              console.debug('[wot] Found {0} identities. Will selected the best one'.format(res.identities.length));
+              console.debug('[wot] Found {0} identities (in requirements). Will selected the best one'.format(identities.length));
             }
 
             // Select the first identity
-            var requirements = _fillRequirements(res.identities[0], currency.parameters);
+            data.requirements = _fillRequirements(identities[0], currency.parameters);
 
-            data.requirements = requirements;
-            data.pubkey = requirements.pubkey;
-            data.uid = requirements.uid;
-            data.isMember =  requirements.isMember;
-            data.blockUid =  requirements.blockUid;
+            // Copy some useful properties into data
+            data.pubkey = data.requirements.pubkey;
+            data.uid = data.requirements.uid;
+            data.isMember =  data.requirements.isMember;
+            data.blockUid =  data.requirements.meta &&  data.requirements.meta.timestamp;
+            data.hasSelf =  data.requirements.hasSelf;
+            data.sigDate =  data.requirements.meta && data.requirements.meta.time;
 
             // Prepare alternatives identities if any
-            if (!requirements.isMember && !requirements.wasMember && res.identities.length > 1) {
-              requirements.alternatives = res.identities.splice(1);
-              _.forEach(requirements.alternatives, function(requirements) {
+            if (!data.requirements.isMember && !data.requirements.wasMember && identities.length > 1) {
+              data.requirements.alternatives = identities.splice(1);
+              _.forEach(data.requirements.alternatives, function(requirements) {
                 _fillRequirements(requirements, currency.parameters);
               });
             }
 
-            // TODO : get sigDate from blockUid ??
-
             return data;
           })
           .catch(function(err) {
@@ -196,51 +258,99 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
               return data;
             }
             throw err;
-          })
-          ;
+          });
       },
 
+
+
       loadIdentityByLookup = function(pubkey, uid) {
+        var data = {
+          pubkey: pubkey,
+          uid: uid,
+          hasSelf: false
+        };
         return BMA.wot.lookup({ search: pubkey||uid })
           .then(function(res){
+            var blocksToRetrieve = [];
             var identities = res.results.reduce(function(idties, res) {
               return idties.concat(res.uids.reduce(function(uids, idty) {
                 var blockUid = idty.meta.timestamp.split('-', 2);
+                var blockNumber = parseInt(blockUid[0]);
+                blocksToRetrieve.push(blockNumber);
+                if (idty.revoked_on) {
+                  blocksToRetrieve.push(idty.revoked_on);
+                }
                 return uids.concat({
                   uid: idty.uid,
                   pubkey: res.pubkey,
-                  timestamp: idty.meta.timestamp,
-                  number: parseInt(blockUid[0]),
-                  hash: blockUid[1],
+                  meta: {
+                    timestamp: idty.meta.timestamp,
+                    number: blockNumber,
+                    hash: blockUid[1],
+                    sig: idty.self
+                  },
                   revoked: idty.revoked,
-                  revocationNumber: idty.revoked_on,
-                  sig: idty.self
+                  revocationNumber: idty.revoked_on
                 });
               }, []));
             }, []);
 
+            // Fill identities time
+            return _fillIdentitiesTime(identities)
+              .then(function(identities) {
+                return {
+                  identities: identities,
+                  results: res.results
+                };
+              });
+          })
+          .then(function(res){
+            var identities = res.identities;
+
             // Sort identities if need
-            if (identities.length) {
+            if (identities.length > 1) {
               // Select the best identity, by sorting using this order
+              //  - valid block
               //  - same given uid
               //  - not revoked
               //  - max(block_number)
-              identities = _.sortBy(identities, function(idty) {
+              res.identities = _.sortBy(identities, function(idty) {
                 var score = 0;
-                score += (10000000000 * ((uid && idty.uid === uid) ? 1 : 0));
-                score += (1000000000  * (!idty.revoked ? 1 : 0));
-                score += (1           * (idty.number ? idty.number : 0));
+                score += (100000000000 * ((data.uid && idty.uid === data.uid) ? 1 : 0));
+                score += (10000000000  * (!idty.meta.invalid ? 1 : 0));
+                score += (1000000000  * ((data.blockUid && idty.meta.timestamp && idty.meta.timestamp === data.blockUid) ? 1 : 0));
+                score += (100000000   * (!idty.revoked ? 1 : 0));
+                score += (1            * (idty.meta.number ? idty.meta.number : 0) / 1000);
                 return -score;
               });
+              console.debug('[wot] Found {0} identities (in lookup). Will selected the best one'.format(identities.length));
+            }
+
+            // Prepare alternatives identities
+            _.forEach(identities, function(idty) {
+              idty.hasSelf = !!(idty.uid && idty.meta.timestamp && idty.meta.sig);
+            });
+
+            // Select the first identity
+            data.requirements = identities[0];
+
+            // Copy some useful properties into data
+            data.pubkey = data.requirements.pubkey;
+            data.uid = data.requirements.uid;
+            data.blockUid = data.requirements.meta && data.requirements.meta.timestamp;
+            data.hasSelf = data.requirements.hasSelf;
+            data.sigDate =  data.requirements.meta && data.requirements.meta.time;
+
+            if (identities.length > 1) {
+              data.requirements.alternatives = identities.splice(1);
             }
-            var identity = identities[0];
 
-            identity.hasSelf = !!(identity.uid && identity.timestamp && identity.sig);
-            identity.lookup = {};
+            // Store additional data (e.g. certs)
+            data.lookup = {};
 
-            // Store received certifications
+            // Store received certifications (can be usefull later)
             var certPubkeys = [];
-            identity.lookup.certifications = !res.results ? {} : res.results.reduce(function(certsMap, res) {
+            data.lookup.certifications = !res.results ? {} : res.results.reduce(function(certsMap, res) {
               return res.uids.reduce(function(certsMap, idty) {
                 var idtyFullKey = idty.uid + '-' + (idty.meta ? idty.meta.timestamp : '');
                 certsMap[idtyFullKey] = idty.others.reduce(function(certs, cert) {
@@ -276,7 +386,7 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
 
             // Store given certifications
             certPubkeys = [];
-            identity.lookup.givenCertifications = !res.results ? [] : res.results.reduce(function(certs, res) {
+            data.lookup.givenCertifications = !res.results ? [] : res.results.reduce(function(certs, res) {
               return res.signed.reduce(function(certs, cert) {
                 var result = {
                   pubkey: cert.pubkey,
@@ -305,52 +415,12 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
               }, certs);
             }, []);
 
-            // Retrieve time (self and revocation)
-            var blocks = [identity.number];
-            if (identity.revocationNumber) {
-              blocks.push(identity.revocationNumber);
-            }
-            return BMA.blockchain.blocks(blocks)
-              .then(function(blocks){
-                identity.sigDate = blocks[0].medianTime;
-
-                // Check if self has been done on a valid block
-                if (identity.number !== 0 && identity.hash !== blocks[0].hash) {
-                  identity.hasBadSelfBlock = true;
-                }
-
-                // Set revocation time
-                if (identity.revocationNumber) {
-                  identity.revocationTime = blocks[1].medianTime;
-                }
-
-                return identity;
-              })
-              .catch(function(err){
-                // Special case for currency init (root block not exists): use now
-                if (err && err.ucode == BMA.errorCodes.BLOCK_NOT_FOUND && identity.number === 0) {
-                  identity.sigDate = moment().utc().unix();
-                  return identity;
-                }
-                else {
-                  // FIXME workaround for issue #1304 ?
-                  /*
-                  if (identity.revocationNumber) {
-                    identity.revocationTime = identity.revocationNumber;
-                    return identity;
-                  }*/
-                  throw err;
-                }
-              });
+            return data;
           })
           .catch(function(err) {
             if (!!err && err.ucode == BMA.errorCodes.NO_MATCHING_IDENTITY) { // Identity not found (if no self)
-              var identity = {
-                uid: null,
-                pubkey: pubkey,
-                hasSelf: false
-              };
-              return identity;
+              _resetRequirements(data);
+              return data;
             }
             else {
               throw err;
@@ -535,16 +605,16 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
       addEvents = function(data) {
 
         if (data.requirements.revoked) {
-          delete data.hasBadSelfBlock;
+          delete data.requirements.meta.invalid;
           addEvent(data, {type: 'error', message: 'ERROR.IDENTITY_REVOKED', messageParams: {revocationTime: data.revocationTime}});
           console.debug("[wot] Identity [{0}] has been revoked".format(data.uid));
         }
         else if (data.requirements.pendingRevocation) {
+          delete data.requirements.meta.invalid;
           addEvent(data, {type:'error', message: 'ERROR.IDENTITY_PENDING_REVOCATION'});
           console.debug("[wot] Identity [{0}] has pending revocation".format(data.uid));
         }
-        else if (data.hasBadSelfBlock) {
-          delete data.hasBadSelfBlock;
+        else if (data.requirements.meta && data.requirements.meta.invalid) {
           if (!data.isMember) {
             addEvent(data, {type: 'error', message: 'ERROR.IDENTITY_INVALID_BLOCK_HASH'});
             console.debug("[wot] Invalid membership for uid {0}: block hash changed".format(data.uid));
@@ -562,6 +632,9 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
           addEvent(data, {type: 'error', message: 'INFO.IDENTITY_NEED_MEMBERSHIP'});
           console.debug("[wot] Identity {0} has a self but no membership".format(data.uid));
         }
+        if (!data.isMember && data.requirements.alternatives) {
+          addEvent(data, {type: 'info', message: 'INFO.HAS_ALTERNATIVE_IDENTITIES'});
+        }
       },
 
       loadData = function(pubkey, withCache, uid, force) {
@@ -629,18 +702,24 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
 
             // Get identity using lookup
             loadIdentityByLookup(pubkey, uid)
-              .then(function (identity) {
-                  angular.merge(data, identity);
-              })
+
           ])
-          .then(function() {
-            if (!data.requirements.uid) return;
+          .then(function(res) {
+            var dataByLookup = res[3];
+
+            // If no requirements found: copy from lookup data
+            if (!data.requirements.uid) {
+              console.debug("[wot] No requirements found: using data from lookup");
+              angular.merge(data, dataByLookup);
+              delete data.lookup; // not need
+              return;
+            }
 
             var idtyFullKey = data.requirements.uid + '-' + data.requirements.meta.timestamp;
 
             return $q.all([
               // Get received certifications
-              loadCertifications(BMA.wot.certifiersOf, data.pubkey, data.lookup ? data.lookup.certifications[idtyFullKey] : null, parameters, medianTime, true /*certifiersOf*/)
+              loadCertifications(BMA.wot.certifiersOf, data.pubkey, dataByLookup.lookup ? dataByLookup.lookup.certifications[idtyFullKey] : null, parameters, medianTime, true /*certifiersOf*/)
                 .then(function (res) {
                   data.received_cert = res.valid;
                   data.received_cert_pending = res.pending;
@@ -648,7 +727,7 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
                 }),
 
               // Get given certifications
-              loadCertifications(BMA.wot.certifiedBy, data.pubkey, data.lookup ? data.lookup.givenCertifications : null, parameters, medianTime, false/*certifiersOf*/)
+              loadCertifications(BMA.wot.certifiedBy, data.pubkey, dataByLookup.lookup ? dataByLookup.lookup.givenCertifications : null, parameters, medianTime, false/*certifiersOf*/)
                 .then(function (res) {
                   data.given_cert = res.valid;
                   data.given_cert_pending = res.pending;
@@ -675,7 +754,6 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
           })
           .then(function() {
             if (!data.pubkey) return undefined; // not found
-            delete data.lookup; // not need anymore
             identityCache.put(data.pubkey, data); // add to cache
             console.debug('[wot] Identity '+ data.pubkey.substring(0, 8) +' loaded in '+ (Date.now()-now) +'ms');
             return data;
diff --git a/www/templates/wot/modal_select_pubkey_identity.html b/www/templates/wot/modal_select_pubkey_identity.html
index 9c7cbfb83..cd47546eb 100644
--- a/www/templates/wot/modal_select_pubkey_identity.html
+++ b/www/templates/wot/modal_select_pubkey_identity.html
@@ -25,6 +25,7 @@
           {{::item.pubkey | formatPubkey}}
           <span ng-if="::!item.revoked && !item.pendingRevocation && !item.isMember" class="assertive" translate>WOT.NOT_MEMBER_PARENTHESIS</span>
           <span ng-if="::item.revoked || item.pendingRevocation" class="assertive bold" translate>WOT.IDENTITY_REVOKED_PARENTHESIS</span>
+          <span ng-if="::item.meta.invalid" class="assertive" translate>ERROR.WOT_PENDING_INVALID_BLOCK_HASH</span>
         </h4>
 
         <ng-if ng-if="::!item.revoked && !item.pendingRevocation && (item.certificationCount || item.pendingCertificationCount)">
diff --git a/www/templates/wot/view_identity.html b/www/templates/wot/view_identity.html
index ae4cda7c9..18515178c 100644
--- a/www/templates/wot/view_identity.html
+++ b/www/templates/wot/view_identity.html
@@ -97,9 +97,9 @@
         <ion-item class="item-icon-left" ng-if=":rebind:formData.sigDate||formData.uid">
           <i class="icon ion-calendar"></i>
           <span translate>COMMON.UID</span>
-          <h5 class="dark" ng-if=":rebind:formData.sigDate">
+          <h5 class="dark" ng-if=":rebind:formData.sigDate ">
             <span translate>WOT.REGISTERED_SINCE</span>
-            {{:rebind:formData.sigDate | formatDate}}
+            {{:rebind:formData.sigDate| formatDate}}
           </h5>
           <span class="badge badge-stable">{{:rebind:formData.uid}}</span>
         </ion-item>
@@ -142,9 +142,11 @@
         </a>
 
         <div
-          class="item item-text-wrap item-icon-left item-wallet-event assertive"
-          ng-repeat="event in :rebind:formData.events | filter: {type: 'error'}">
-          <i class="icon ion-alert-circled"></i>
+          class="item item-text-wrap item-icon-left item-wallet-event"
+          ng-class="{'assertive': event.type=='error'}"
+          ng-repeat="event in :rebind:formData.events">
+          <i class="icon"
+             ng-class="{'ion-information-circled royal': event.type=='info','ion-alert-circled': event.type=='warn'||event.type=='error','assertive': event.type=='error','ion-clock': event.type=='pending'}"></i>
           <span trust-as-html="event.message | translate:event.messageParams"></span>
         </div>
 
-- 
GitLab