diff --git a/modules/will-members.ts b/modules/will-members.ts new file mode 100644 index 0000000000000000000000000000000000000000..d6c29949a22d9863217659bac1b3195a6cd08abc --- /dev/null +++ b/modules/will-members.ts @@ -0,0 +1,516 @@ +import {MonitConstants} from "../lib/constants2"; +import {DBIdentity} from "duniter/app/lib/dal/sqliteDAL/IdentityDAL"; +import {DBMembership} from "duniter/app/lib/dal/sqliteDAL/MembershipDAL"; +import {showExecutionTimes} from "../lib/MonitorExecutionTime"; +import {DataFinder} from "../lib/DataFinder"; +import {Server} from "duniter/server"; + +// Préserver les résultats en cache +let lockWillMembers = false +let willMembersLastUptime = 0 +let identitiesList: WillMemberIdentity[] = [] +let idtysPendingCertifsList: PendingCert[][] = [] +let nbMaxCertifs = 0 +let countMembersWithSigQtyValidCert = 0 +let sentries = [] +let sentriesIndex = [] +let wotbIdIndex = [] +let meanSentriesReachedByIdtyPerCert: number[] = [] +let meanMembersReachedByIdtyPerCert: number[] = [] +let countIdtiesPerReceiveCert: number[] = [] +let membersQualityExt: { [k: string]: string } = {} + +export async function willMembers(duniterServer: Server, days = 65, order = 'desc', sort_by = 'registrationPackage', showIdtyWithZeroCert = 'no', sortSig = 'Availability') { + + const dataFinder = await DataFinder.getInstanceReindexedIfNecessary() + + // get blockchain timestamp + let resultQueryCurrentBlock: any = await dataFinder.getCurrentBlockOrNull(); + const currentBlockchainTimestamp = resultQueryCurrentBlock.medianTime; + const currentMembersCount = resultQueryCurrentBlock.membersCount; + const currentBlockNumber = resultQueryCurrentBlock.number; + + // Initaliser les constantes + const conf = duniterServer.conf; + const dSen = Math.ceil(Math.pow(currentMembersCount, 1 / conf.stepMax)); + + // Initaliser les variables + let errors = ""; + let idtysListOrdered: WillMemberIdentityWithPendingCerts[] = [] + + // Calculer le timestamp limite à prendre en compte + let limitTimestamp = currentBlockchainTimestamp + (days*86400); + + // Alimenter wotb avec la toile de confiance + const wotbInstance = duniterServer.dal.wotb; + + + // Vérifier si le cache doit être Réinitialiser + let reinitCache = (Math.floor(Date.now() / 1000) > (willMembersLastUptime + MonitConstants.MIN_WILLMEMBERS_UPDATE_FREQ)); + + // Si le cache willMembers est dévérouillé, le vérouiller, sinon ne pas réinitialiser le cache + if (reinitCache && !lockWillMembers) { + lockWillMembers = true; + } else if(lockWillMembers) { + reinitCache = false; + } + + if (reinitCache) + { + // Réinitialiser le cache + dataFinder.invalidateCache() + identitiesList = []; + idtysPendingCertifsList = []; + nbMaxCertifs = 0; + countMembersWithSigQtyValidCert = 0; + sentries = []; + sentriesIndex = []; + wotbIdIndex = []; + membersQualityExt = {}; + willMembersLastUptime = Math.floor(Date.now() / 1000); + + // Récupérer la liste des membres référents + sentries = wotbInstance.getSentries(dSen); + + // Récupérer la liste des identités en piscine + const resultQueryIdtys: DBIdentity[] = await dataFinder.findPendingMembers() + + // Récupérer pour chaque identité, l'ensemble des certifications qu'elle à reçue. + for (let i=0;i<resultQueryIdtys.length;i++) + { + // Extraire le numéro de bloc d'émission de l'identité + let idtyBlockStamp = resultQueryIdtys[i].buid.split("-"); + let idtyBlockNumber = idtyBlockStamp[0]; + + // récupérer le medianTime et le hash du bloc d'émission de l'identité + let idtyEmittedBlock = await dataFinder.getBlock(parseInt(idtyBlockNumber)); + + // Récupérer l'identifiant wotex de l'identité (en cas d'identité multiple) + let idties = await dataFinder.getWotexInfos(resultQueryIdtys[i].uid); + let wotexId = ''; + if (idties.length > 1) + { + let pos = 0; + for (const idty of idties) + { + if (idty.hash == resultQueryIdtys[i].hash) { wotexId = '['+pos+']'; } + pos++; + } + } + + // vérifier la validité du blockstamp de l'identité + let validIdtyBlockStamp = false; + if (typeof(idtyEmittedBlock) == 'undefined' || idtyEmittedBlock.hash == idtyBlockStamp[1]) + { validIdtyBlockStamp = true; } + + // vérifier si l'identité a été révoquée ou non + let idtyRevoked = false; + if (resultQueryIdtys[i].revocation_sig != null) + { + idtyRevoked = true; + } + + // Stocker les informations de l'identité + identitiesList.push({ + BlockNumber: parseInt(idtyBlockNumber), + creationTimestamp: (typeof(idtyEmittedBlock) == 'undefined' ) ? currentBlockchainTimestamp:idtyEmittedBlock.medianTime, + pubkey: resultQueryIdtys[i].pubkey, + uid: resultQueryIdtys[i].uid, + hash: resultQueryIdtys[i].hash, + wotexId: wotexId, + expires_on: resultQueryIdtys[i].expires_on || 0, + nbCert: 0, + nbValidPendingCert: 0, + registrationAvailability: 0, + validBlockStamp: validIdtyBlockStamp, + idtyRevoked: idtyRevoked + }); + idtysPendingCertifsList.push([]) + + // récupérer l'ensemble des certifications en attente destinées à l'identité courante + let tmpQueryPendingCertifsList = await dataFinder.findPendingCertsToTarget(resultQueryIdtys[i].pubkey, resultQueryIdtys[i].hash); + + // Récupérer les uid des émetteurs des certifications reçus par l'utilisateur + // Et stocker les uid et dates d'expiration dans un tableau + for (let j=0;j<tmpQueryPendingCertifsList.length;j++) + { + // Récupérer le medianTime et le hash du bloc d'émission de la certification + let emittedBlock = await dataFinder.getBlock(tmpQueryPendingCertifsList[j].block_number) + + // Vérifier que l'émetteur de la certification correspond à une identité inscrite en blockchain + let tmpQueryGetUidIssuerPendingCert = await dataFinder.getUidOfPub(tmpQueryPendingCertifsList[j].from) + if (emittedBlock && tmpQueryGetUidIssuerPendingCert.length > 0) + { + // Récupérer la pubkey de l'émetteur + let issuerPubkey = tmpQueryPendingCertifsList[j].from; + + // Récupérer le wotb_id + let wotb_id = 0; + if (typeof(wotbIdIndex[issuerPubkey]) == 'undefined') + { + wotb_id = await dataFinder.getWotbIdByIssuerPubkey(issuerPubkey) + wotbIdIndex[issuerPubkey] = wotb_id; + } + else { wotb_id = wotbIdIndex[issuerPubkey]; } + + // Vérifier si l'émetteur de la certification est référent + let issuerIsSentry = false; + if (typeof(sentriesIndex[issuerPubkey]) == 'undefined') + { + sentriesIndex[issuerPubkey] = false; + for (let s=0;s<sentries.length;s++) + { + if (sentries[s] == wotb_id) + { + issuerIsSentry=true; + sentriesIndex[issuerPubkey] = true; + sentries.splice(s, 1); + } + } + } + else { issuerIsSentry = sentriesIndex[issuerPubkey]; } + + // Vérifier si le blockstamp est correct + var validBlockStamp = false; + if (emittedBlock.hash == tmpQueryPendingCertifsList[j].block_hash) + { validBlockStamp = true; } + + // récupérer le timestamp d'enchainement de la dernière certification écrite par l'émetteur + let tmpQueryLastIssuerCert = await dataFinder.getChainableOnByIssuerPubkey(issuerPubkey) + let certTimestampWritable = 0; + if ( typeof(tmpQueryLastIssuerCert[0]) != 'undefined' && typeof(tmpQueryLastIssuerCert[0].chainable_on) != 'undefined' ) + { certTimestampWritable = tmpQueryLastIssuerCert[0].chainable_on; } + //identitiesList[i].registrationAvailability = (certTimestampWritable > identitiesList[i].registrationAvailability) ? certTimestampWritable : identitiesList[i].registrationAvailability; + + // Vérifier que l'identité courant n'a pas déjà reçu d'autre(s) certification(s) de la part du même membre ET dans le même état de validité du blockstamp + let doubloonPendingCertif = false; + for (const pendingCert of idtysPendingCertifsList[i]) + { + if (pendingCert.from == tmpQueryGetUidIssuerPendingCert[0].uid && pendingCert.validBlockStamp == validBlockStamp) + { + doubloonPendingCertif = true; + } + } + if (!doubloonPendingCertif) + { + // Stoker la liste des certifications en piscine qui n'ont pas encore expirées + if (tmpQueryPendingCertifsList[j].expires_on > currentBlockchainTimestamp) + { + idtysPendingCertifsList[i].push({ + from: tmpQueryGetUidIssuerPendingCert[0].uid, + pubkey: issuerPubkey, + wotb_id: wotb_id, + issuerIsSentry: issuerIsSentry, + blockNumber: tmpQueryPendingCertifsList[j].block_number, + creationTimestamp: emittedBlock.medianTime, + timestampExpire: tmpQueryPendingCertifsList[j].expires_on, + timestampWritable: certTimestampWritable, + validBlockStamp: validBlockStamp + }); + identitiesList[i].nbCert++; + if (validBlockStamp) { identitiesList[i].nbValidPendingCert++; } + } + } + } + } + + // Calculer le nombre maximal de certifications reçues par l'identité courante + if ( identitiesList[i].nbCert > nbMaxCertifs) { nbMaxCertifs = identitiesList[i].nbCert; } + + // calculate countMembersWithSigQtyValidCert + if ( identitiesList[i].nbValidPendingCert >= conf.sigQty) { countMembersWithSigQtyValidCert++; } + } // END IDENTITIES LOOP + + // Réinitialiser sumSentriesReachedByIdtyPerCert, sumMembersReachedByIdtyPerCert et countIdtiesPerReceiveCert + for (let i=0;i<=nbMaxCertifs;i++) + { + meanSentriesReachedByIdtyPerCert[i] = 0; + meanMembersReachedByIdtyPerCert[i] = 0; + countIdtiesPerReceiveCert[i] = 0; + } + } // END if (reinitCache) + + // Si demandé, retrier les, certifications par date de disponibilité + if (sortSig == "Availability") + { + const idtysPendingCertifsListSort: PendingCert[][] = [ [] ]; + for (var i=0;i<idtysPendingCertifsList.length;i++) + { + idtysPendingCertifsListSort[i] = Array(); + let min; + let idMin =0; + let tmpExcluded = Array(); + for (let j=0;j<idtysPendingCertifsList[i].length;j++) { tmpExcluded[j] = false; } + for (let j=0;j<idtysPendingCertifsList[i].length;j++) + { + min = currentBlockchainTimestamp+conf.sigValidity; // begin to min = max + + // search idMin (id of certif with min timestampWritable) + for (let k=0;k<idtysPendingCertifsList[i].length;k++) + { + if (idtysPendingCertifsList[i][k].timestampWritable < min && !tmpExcluded[k]) + { + min = idtysPendingCertifsList[i][k].timestampWritable; + idMin = k; + } + } + + // Push min value on sort table + idtysPendingCertifsListSort[i].push({ + from: idtysPendingCertifsList[i][idMin].from, + wotb_id: idtysPendingCertifsList[i][idMin].wotb_id, + issuerIsSentry: idtysPendingCertifsList[i][idMin].issuerIsSentry, + blockNumber: idtysPendingCertifsList[i][idMin].blockNumber, + creationTimestamp: idtysPendingCertifsList[i][idMin].creationTimestamp, + timestampExpire: idtysPendingCertifsList[i][idMin].timestampExpire, + timestampWritable: idtysPendingCertifsList[i][idMin].timestampWritable, + validBlockStamp: idtysPendingCertifsList[i][idMin].validBlockStamp + }); + + // Calculer la date de disponibilité du dossier d'inscription de l'identité correspondante + // := date de disponibilité maximale parmi les sigQty certifications aux dates de disponibilités les plus faibles + if (j<conf.sigQty) + { + let timestampWritable = idtysPendingCertifsList[i][idMin].timestampWritable; + identitiesList[i].registrationAvailability = (timestampWritable > identitiesList[i].registrationAvailability) ? timestampWritable : identitiesList[i].registrationAvailability; + } + + // Exclure la valeur min avant de poursuivre le tri + tmpExcluded[idMin] = true; + } + + } + idtysPendingCertifsList = idtysPendingCertifsListSort; + } + + // Récupérer la valeur du critère de tri pour chaque identité + var tabSort = []; + if (sort_by == "creationIdty") + { + for (const idty of identitiesList) + { + tabSort.push(idty.expires_on); + } + } + else if (sort_by == "sigCount" || sort_by == "registrationPackage") + { + for (const idty of identitiesList) + { + // Calculate registrationAvailabilityDelay + let registrationAvailabilityDelay = (idty.registrationAvailability > currentBlockchainTimestamp) ? (idty.registrationAvailability-currentBlockchainTimestamp):0; + + // Trier les identités par date de disponibilité de leur dossier d'inscription (le signe moins est nécessaire car plus un dossier est disponible tôt + // plus la valeur de registrationAvailabilityDelay sera petite, hors le nombre obtenu est classé de façon décroissante) + // Attribuer un malus de 2*sigValidity secondes par certification valide (plafonner à sigQty dans le cas de 'registrationPackage') + if (sort_by == "registrationPackage" && idty.nbValidPendingCert > conf.sigQty) + { + tabSort.push(-registrationAvailabilityDelay + (2*conf.sigValidity*conf.sigQty)); + } + else + { + tabSort.push(-registrationAvailabilityDelay + (2*conf.sigValidity*idty.nbValidPendingCert)); + } + } + } + else { errors += "<p>ERREUR : param <i>sort_by</i> invalid !</p>"; } + + // Trier les identités par ordre decroissant du critère sort_by + for (var i=0;i<identitiesList.length;i++) + { + let max = -1; + let idMax =0; + for (var j=0;j<identitiesList.length;j++) + { + if (tabSort[j] > max) + { + max = tabSort[j]; + idMax = j; + } + } + + // Push max value on sort table, only if respect days limit + if (limitTimestamp > identitiesList[idMax].expires_on) + { + // Vérifier que cette identité n'a pas déjà été prise en compte (empecher les doublons) + let doubloon = false; + for (const idty of idtysListOrdered) + { + if (identitiesList[idMax].uid == idty.uid && identitiesList[idMax].BlockNumber == idty.BlockNumber) + { + doubloon = true; + } + } + + // Push max value on sort table (and test distance rule) + if (!doubloon) + { + // Tester la présence de l'adhésion + let membership: DBMembership|null = null + const pendingMembershipsOfIdty: DBMembership[] = await duniterServer.dal.msDAL.getPendingINOfTarget(identitiesList[idMax].hash as string); + for (const ms of pendingMembershipsOfIdty) + { + if (!membership && ms.expires_on > currentBlockchainTimestamp) + { + membership = ms + } + } + + // Créer une wot temporaire + let tmpWot = wotbInstance.memCopy(); + + // Mesurer la qualité externe de chaque emetteur de chaque certification + for (const cert of idtysPendingCertifsList[idMax]) { + if (typeof (membersQualityExt[cert.from]) == 'undefined') { + const detailedDistanceQualityExt: DetailedDistance = tmpWot.detailedDistance(cert.wotb_id, dSen, conf.stepMax - 1, conf.xpercent); + membersQualityExt[cert.from] = ((detailedDistanceQualityExt.nbSuccess / detailedDistanceQualityExt.nbSentries) / conf.xpercent).toFixed(2); + } + } + + // Ajouter un noeud a la wot temporaire et lui donner toute les certifications valides reçues par l'indentité idMax + let pendingIdtyWID = tmpWot.addNode(); + for (const cert of idtysPendingCertifsList[idMax]) + { + if (cert.validBlockStamp) + { + tmpWot.addLink(cert.wotb_id, pendingIdtyWID); + } + } + // Récupérer les données de distance du dossier d'adhésion de l'indentité idMax + let detailedDistance = tmpWot.detailedDistance(pendingIdtyWID, dSen, conf.stepMax, conf.xpercent); + + // Nettoyer la wot temporaire + tmpWot.clear(); + + // Calculer percentSentriesReached et percentMembersReached + let percentSentriesReached = parseFloat(((detailedDistance.nbSuccess/detailedDistance.nbSentries)*100).toFixed(2)); + let percentMembersReached = parseFloat(((detailedDistance.nbReached/currentMembersCount)*100).toFixed(2)); + + // Pousser l'identité dans le tableau idtysListOrdered + idtysListOrdered.push({ + uid: identitiesList[idMax].uid, + wotexId: identitiesList[idMax].wotexId, + creationTimestamp: identitiesList[idMax].creationTimestamp, + pubkey: identitiesList[idMax].pubkey, + BlockNumber: identitiesList[idMax].BlockNumber, + expires_on: identitiesList[idMax].expires_on, + nbCert: identitiesList[idMax].nbCert, + registrationAvailability: identitiesList[idMax].registrationAvailability, + nbValidPendingCert: identitiesList[idMax].nbValidPendingCert, + detailedDistance, + percentSentriesReached, + percentMembersReached, + membership, + pendingCertifications: idtysPendingCertifsList[idMax], + validBlockStamp: identitiesList[idMax].validBlockStamp, + idtyRevoked: identitiesList[idMax].idtyRevoked + }); + + // Si le cache a été réinitialiser, recalculer les sommes meanSentriesReachedByIdtyPerCert et meanMembersReachedByIdtyPerCert + if (reinitCache && identitiesList[idMax].nbValidPendingCert > 0) + { + let nbReceiveCert = identitiesList[idMax].nbValidPendingCert; + meanSentriesReachedByIdtyPerCert[nbReceiveCert-1] += percentSentriesReached; + meanMembersReachedByIdtyPerCert[nbReceiveCert-1] += percentMembersReached; + countIdtiesPerReceiveCert[nbReceiveCert-1] += 1; + } + } // END if (!doubloon) + } // END days limit rule + + // Exclure la valeur max avant de poursuivre le tri + tabSort[idMax] = -1; + } // End sort identities loop + + // Si ordre croissant demandé, inverser le tableau + if (order == 'asc') + { + const idtysListOrdered2: WillMemberIdentityWithPendingCerts[] = [] + let tmpIdtysListOrderedLength = idtysListOrdered.length; + for (let i=0;i<tmpIdtysListOrderedLength;i++) + { + idtysListOrdered2[i] = idtysListOrdered[tmpIdtysListOrderedLength-i-1]; + } + idtysListOrdered = idtysListOrdered2; + } + + if (reinitCache) { + // Calculate meanSentriesReachedByIdtyPerCert and meanMembersReachedByIdtyPerCert + for (let i = 0; i <= nbMaxCertifs; i++) { + if (countIdtiesPerReceiveCert[i] > 0) { + meanSentriesReachedByIdtyPerCert[i] = parseFloat((meanSentriesReachedByIdtyPerCert[i] / countIdtiesPerReceiveCert[i]).toFixed(2)); + meanMembersReachedByIdtyPerCert[i] = parseFloat((meanMembersReachedByIdtyPerCert[i] / countIdtiesPerReceiveCert[i]).toFixed(2)); + } + else { + meanSentriesReachedByIdtyPerCert[i] = 0.0; + meanMembersReachedByIdtyPerCert[i] = 0.0; + } + } + + // Dévérouiller le cache willMembers + lockWillMembers = false; + } + + showExecutionTimes() + + return { + days, + order, + sort_by, + showIdtyWithZeroCert, + sortSig, + idtysListOrdered, + currentBlockNumber, + currentBlockchainTimestamp, + currentMembersCount, + limitTimestamp, + dSen, + conf, + nbMaxCertifs, + countMembersWithSigQtyValidCert, + meanSentriesReachedByIdtyPerCert, + meanMembersReachedByIdtyPerCert, + membersQualityExt, + } +} + +interface PendingCert { + from: string + pubkey?: string + wotb_id: number + issuerIsSentry: boolean + blockNumber: number + creationTimestamp: number + timestampExpire: number + timestampWritable: number + validBlockStamp: boolean +} + +interface DetailedDistance { + nbSuccess: number + nbSentries: number + nbReached: number + isOutdistanced: boolean +} + +interface WillMemberIdentity { + BlockNumber: number + creationTimestamp: number + pubkey: string + uid: string + hash?: string + wotexId: string + expires_on: number + nbCert: number + nbValidPendingCert: number + registrationAvailability: number + detailedDistance?: DetailedDistance + pendingCertifications?: PendingCert[] + validBlockStamp: boolean + idtyRevoked: boolean + percentSentriesReached?: number + percentMembersReached?: number + membership?: DBMembership|null +} + +interface WillMemberIdentityWithPendingCerts extends WillMemberIdentity { + pendingCertifications: PendingCert[] +} diff --git a/routes/willMembers2.ts b/routes/willMembers2.ts index 31f92ca59ea0771f7ecda34567052d607efbf915..513cb6cde5ea5c1bef087a858c5cae47717e8e61 100755 --- a/routes/willMembers2.ts +++ b/routes/willMembers2.ts @@ -1,469 +1,44 @@ import {Server} from 'duniter/server' -import {DBMembership} from 'duniter/app/lib/dal/sqliteDAL/MembershipDAL' -import {DBIdentity} from 'duniter/app/lib/dal/sqliteDAL/IdentityDAL' -import {showExecutionTimes} from '../lib/MonitorExecutionTime' -import {DataFinder} from '../lib/DataFinder' -import {MonitConstants} from "../lib/constants2"; +import {willMembers} from "../modules/will-members"; const timestampToDatetime = require(__dirname + '/../lib/timestampToDatetime') -// Préserver les résultats en cache -let lockWillMembers = false -let willMembersLastUptime = 0 -let identitiesList: WillMemberIdentity[] = [] -let idtysPendingCertifsList: PendingCert[][] = [] -let nbMaxCertifs = 0 -let countMembersWithSigQtyValidCert = 0 -let sentries = [] -let sentriesIndex = [] -let wotbIdIndex = [] -let meanSentriesReachedByIdtyPerCert: number[] = [] -let meanMembersReachedByIdtyPerCert: number[] = [] -let countIdtiesPerReceiveCert: number[] = [] -let membersQualityExt: { [k: string]: string } = {} - module.exports = async (req: any, res: any, next: any) => { const locals: { duniterServer: Server } = req.app.locals - const duniterServer = locals.duniterServer - const dataFinder = await DataFinder.getInstanceReindexedIfNecessary() - try { - // get blockchain timestamp - let resultQueryCurrentBlock: any = await dataFinder.getCurrentBlockOrNull(); - const currentBlockchainTimestamp = resultQueryCurrentBlock.medianTime; - const currentMembersCount = resultQueryCurrentBlock.membersCount; - const currentBlockNumber = resultQueryCurrentBlock.number; - - // Initaliser les constantes - const conf = duniterServer.conf; - const dSen = Math.ceil(Math.pow(currentMembersCount, 1 / conf.stepMax)); - - // Initaliser les variables - let errors = ""; - let idtysListOrdered: WillMemberIdentityWithPendingCerts[] = [] // Récupérer les paramètres - let days = req.query.d || 65 // Valeur par défaut - let order = req.query.d && req.query.order || 'desc' // Valeur par défaut - let sort_by = req.query.sort_by || "registrationPackage"; // Valeur par défaut - let showIdtyWithZeroCert = req.query.showIdtyWithZeroCert || "no"; // Valeur par défaut - let sortSig = req.query.sortSig || "Availability"; // Valeur par défaut - let format = req.query.format || 'HTML'; - - // Calculer le timestamp limite à prendre en compte - let limitTimestamp = currentBlockchainTimestamp + (days*86400); - - // Alimenter wotb avec la toile de confiance - const wotbInstance = duniterServer.dal.wotb; - - - // Vérifier si le cache doit être Réinitialiser - let reinitCache = (Math.floor(Date.now() / 1000) > (willMembersLastUptime + MonitConstants.MIN_WILLMEMBERS_UPDATE_FREQ)); - - // Si le cache willMembers est dévérouillé, le vérouiller, sinon ne pas réinitialiser le cache - if (reinitCache && !lockWillMembers) { - lockWillMembers = true; - } else if(lockWillMembers) { - reinitCache = false; - } - - if (reinitCache) - { - // Réinitialiser le cache - dataFinder.invalidateCache() - identitiesList = []; - idtysPendingCertifsList = []; - nbMaxCertifs = 0; - countMembersWithSigQtyValidCert = 0; - sentries = []; - sentriesIndex = []; - wotbIdIndex = []; - membersQualityExt = {}; - willMembersLastUptime = Math.floor(Date.now() / 1000); - - // Récupérer la liste des membres référents - sentries = wotbInstance.getSentries(dSen); - - // Récupérer la liste des identités en piscine - const resultQueryIdtys: DBIdentity[] = await dataFinder.findPendingMembers() - - // Récupérer pour chaque identité, l'ensemble des certifications qu'elle à reçue. - for (let i=0;i<resultQueryIdtys.length;i++) - { - // Extraire le numéro de bloc d'émission de l'identité - let idtyBlockStamp = resultQueryIdtys[i].buid.split("-"); - let idtyBlockNumber = idtyBlockStamp[0]; - - // récupérer le medianTime et le hash du bloc d'émission de l'identité - let idtyEmittedBlock = await dataFinder.getBlock(parseInt(idtyBlockNumber)); - - // Récupérer l'identifiant wotex de l'identité (en cas d'identité multiple) - let idties = await dataFinder.getWotexInfos(resultQueryIdtys[i].uid); - let wotexId = ''; - if (idties.length > 1) - { - let pos = 0; - for (const idty of idties) - { - if (idty.hash == resultQueryIdtys[i].hash) { wotexId = '['+pos+']'; } - pos++; - } - } - - // vérifier la validité du blockstamp de l'identité - let validIdtyBlockStamp = false; - if (typeof(idtyEmittedBlock) == 'undefined' || idtyEmittedBlock.hash == idtyBlockStamp[1]) - { validIdtyBlockStamp = true; } - - // vérifier si l'identité a été révoquée ou non - let idtyRevoked = false; - if (resultQueryIdtys[i].revocation_sig != null) - { - idtyRevoked = true; - } - - // Stocker les informations de l'identité - identitiesList.push({ - BlockNumber: parseInt(idtyBlockNumber), - creationTimestamp: (typeof(idtyEmittedBlock) == 'undefined' ) ? currentBlockchainTimestamp:idtyEmittedBlock.medianTime, - pubkey: resultQueryIdtys[i].pubkey, - uid: resultQueryIdtys[i].uid, - hash: resultQueryIdtys[i].hash, - wotexId: wotexId, - expires_on: resultQueryIdtys[i].expires_on || 0, - nbCert: 0, - nbValidPendingCert: 0, - registrationAvailability: 0, - validBlockStamp: validIdtyBlockStamp, - idtyRevoked: idtyRevoked - }); - idtysPendingCertifsList.push([]) - - // récupérer l'ensemble des certifications en attente destinées à l'identité courante - let tmpQueryPendingCertifsList = await dataFinder.findPendingCertsToTarget(resultQueryIdtys[i].pubkey, resultQueryIdtys[i].hash); - - // Récupérer les uid des émetteurs des certifications reçus par l'utilisateur - // Et stocker les uid et dates d'expiration dans un tableau - for (let j=0;j<tmpQueryPendingCertifsList.length;j++) - { - // Récupérer le medianTime et le hash du bloc d'émission de la certification - let emittedBlock = await dataFinder.getBlock(tmpQueryPendingCertifsList[j].block_number) - - // Vérifier que l'émetteur de la certification correspond à une identité inscrite en blockchain - let tmpQueryGetUidIssuerPendingCert = await dataFinder.getUidOfPub(tmpQueryPendingCertifsList[j].from) - if (emittedBlock && tmpQueryGetUidIssuerPendingCert.length > 0) - { - // Récupérer la pubkey de l'émetteur - let issuerPubkey = tmpQueryPendingCertifsList[j].from; - - // Récupérer le wotb_id - let wotb_id = 0; - if (typeof(wotbIdIndex[issuerPubkey]) == 'undefined') - { - wotb_id = await dataFinder.getWotbIdByIssuerPubkey(issuerPubkey) - wotbIdIndex[issuerPubkey] = wotb_id; - } - else { wotb_id = wotbIdIndex[issuerPubkey]; } - - // Vérifier si l'émetteur de la certification est référent - let issuerIsSentry = false; - if (typeof(sentriesIndex[issuerPubkey]) == 'undefined') - { - sentriesIndex[issuerPubkey] = false; - for (let s=0;s<sentries.length;s++) - { - if (sentries[s] == wotb_id) - { - issuerIsSentry=true; - sentriesIndex[issuerPubkey] = true; - sentries.splice(s, 1); - } - } - } - else { issuerIsSentry = sentriesIndex[issuerPubkey]; } - - // Vérifier si le blockstamp est correct - var validBlockStamp = false; - if (emittedBlock.hash == tmpQueryPendingCertifsList[j].block_hash) - { validBlockStamp = true; } - - // récupérer le timestamp d'enchainement de la dernière certification écrite par l'émetteur - let tmpQueryLastIssuerCert = await dataFinder.getChainableOnByIssuerPubkey(issuerPubkey) - let certTimestampWritable = 0; - if ( typeof(tmpQueryLastIssuerCert[0]) != 'undefined' && typeof(tmpQueryLastIssuerCert[0].chainable_on) != 'undefined' ) - { certTimestampWritable = tmpQueryLastIssuerCert[0].chainable_on; } - //identitiesList[i].registrationAvailability = (certTimestampWritable > identitiesList[i].registrationAvailability) ? certTimestampWritable : identitiesList[i].registrationAvailability; - - // Vérifier que l'identité courant n'a pas déjà reçu d'autre(s) certification(s) de la part du même membre ET dans le même état de validité du blockstamp - let doubloonPendingCertif = false; - for (const pendingCert of idtysPendingCertifsList[i]) - { - if (pendingCert.from == tmpQueryGetUidIssuerPendingCert[0].uid && pendingCert.validBlockStamp == validBlockStamp) - { - doubloonPendingCertif = true; - } - } - if (!doubloonPendingCertif) - { - // Stoker la liste des certifications en piscine qui n'ont pas encore expirées - if (tmpQueryPendingCertifsList[j].expires_on > currentBlockchainTimestamp) - { - idtysPendingCertifsList[i].push({ - from: tmpQueryGetUidIssuerPendingCert[0].uid, - pubkey: issuerPubkey, - wotb_id: wotb_id, - issuerIsSentry: issuerIsSentry, - blockNumber: tmpQueryPendingCertifsList[j].block_number, - creationTimestamp: emittedBlock.medianTime, - timestampExpire: tmpQueryPendingCertifsList[j].expires_on, - timestampWritable: certTimestampWritable, - validBlockStamp: validBlockStamp - }); - identitiesList[i].nbCert++; - if (validBlockStamp) { identitiesList[i].nbValidPendingCert++; } - } - } - } - } - - // Calculer le nombre maximal de certifications reçues par l'identité courante - if ( identitiesList[i].nbCert > nbMaxCertifs) { nbMaxCertifs = identitiesList[i].nbCert; } - - // calculate countMembersWithSigQtyValidCert - if ( identitiesList[i].nbValidPendingCert >= conf.sigQty) { countMembersWithSigQtyValidCert++; } - } // END IDENTITIES LOOP - - // Réinitialiser sumSentriesReachedByIdtyPerCert, sumMembersReachedByIdtyPerCert et countIdtiesPerReceiveCert - for (let i=0;i<=nbMaxCertifs;i++) - { - meanSentriesReachedByIdtyPerCert[i] = 0; - meanMembersReachedByIdtyPerCert[i] = 0; - countIdtiesPerReceiveCert[i] = 0; - } - } // END if (reinitCache) - - // Si demandé, retrier les, certifications par date de disponibilité - if (sortSig == "Availability") - { - const idtysPendingCertifsListSort: PendingCert[][] = [ [] ]; - for (var i=0;i<idtysPendingCertifsList.length;i++) - { - idtysPendingCertifsListSort[i] = Array(); - let min; - let idMin =0; - let tmpExcluded = Array(); - for (let j=0;j<idtysPendingCertifsList[i].length;j++) { tmpExcluded[j] = false; } - for (let j=0;j<idtysPendingCertifsList[i].length;j++) - { - min = currentBlockchainTimestamp+conf.sigValidity; // begin to min = max - - // search idMin (id of certif with min timestampWritable) - for (let k=0;k<idtysPendingCertifsList[i].length;k++) - { - if (idtysPendingCertifsList[i][k].timestampWritable < min && !tmpExcluded[k]) - { - min = idtysPendingCertifsList[i][k].timestampWritable; - idMin = k; - } - } - - // Push min value on sort table - idtysPendingCertifsListSort[i].push({ - from: idtysPendingCertifsList[i][idMin].from, - wotb_id: idtysPendingCertifsList[i][idMin].wotb_id, - issuerIsSentry: idtysPendingCertifsList[i][idMin].issuerIsSentry, - blockNumber: idtysPendingCertifsList[i][idMin].blockNumber, - creationTimestamp: idtysPendingCertifsList[i][idMin].creationTimestamp, - timestampExpire: idtysPendingCertifsList[i][idMin].timestampExpire, - timestampWritable: idtysPendingCertifsList[i][idMin].timestampWritable, - validBlockStamp: idtysPendingCertifsList[i][idMin].validBlockStamp - }); - - // Calculer la date de disponibilité du dossier d'inscription de l'identité correspondante - // := date de disponibilité maximale parmi les sigQty certifications aux dates de disponibilités les plus faibles - if (j<conf.sigQty) - { - let timestampWritable = idtysPendingCertifsList[i][idMin].timestampWritable; - identitiesList[i].registrationAvailability = (timestampWritable > identitiesList[i].registrationAvailability) ? timestampWritable : identitiesList[i].registrationAvailability; - } - - // Exclure la valeur min avant de poursuivre le tri - tmpExcluded[idMin] = true; - } - - } - idtysPendingCertifsList = idtysPendingCertifsListSort; - } - - // Récupérer la valeur du critère de tri pour chaque identité - var tabSort = []; - if (sort_by == "creationIdty") - { - for (const idty of identitiesList) - { - tabSort.push(idty.expires_on); - } - } - else if (sort_by == "sigCount" || sort_by == "registrationPackage") - { - for (const idty of identitiesList) - { - // Calculate registrationAvailabilityDelay - let registrationAvailabilityDelay = (idty.registrationAvailability > currentBlockchainTimestamp) ? (idty.registrationAvailability-currentBlockchainTimestamp):0; - - // Trier les identités par date de disponibilité de leur dossier d'inscription (le signe moins est nécessaire car plus un dossier est disponible tôt - // plus la valeur de registrationAvailabilityDelay sera petite, hors le nombre obtenu est classé de façon décroissante) - // Attribuer un malus de 2*sigValidity secondes par certification valide (plafonner à sigQty dans le cas de 'registrationPackage') - if (sort_by == "registrationPackage" && idty.nbValidPendingCert > conf.sigQty) - { - tabSort.push(-registrationAvailabilityDelay + (2*conf.sigValidity*conf.sigQty)); - } - else - { - tabSort.push(-registrationAvailabilityDelay + (2*conf.sigValidity*idty.nbValidPendingCert)); - } - } - } - else { errors += "<p>ERREUR : param <i>sort_by</i> invalid !</p>"; } - - // Trier les identités par ordre decroissant du critère sort_by - for (var i=0;i<identitiesList.length;i++) - { - let max = -1; - let idMax =0; - for (var j=0;j<identitiesList.length;j++) - { - if (tabSort[j] > max) - { - max = tabSort[j]; - idMax = j; - } - } - - // Push max value on sort table, only if respect days limit - if (limitTimestamp > identitiesList[idMax].expires_on) - { - // Vérifier que cette identité n'a pas déjà été prise en compte (empecher les doublons) - let doubloon = false; - for (const idty of idtysListOrdered) - { - if (identitiesList[idMax].uid == idty.uid && identitiesList[idMax].BlockNumber == idty.BlockNumber) - { - doubloon = true; - } - } - - // Push max value on sort table (and test distance rule) - if (!doubloon) - { - // Tester la présence de l'adhésion - let membership: DBMembership|null = null - const pendingMembershipsOfIdty: DBMembership[] = await duniterServer.dal.msDAL.getPendingINOfTarget(identitiesList[idMax].hash as string); - for (const ms of pendingMembershipsOfIdty) - { - if (!membership && ms.expires_on > currentBlockchainTimestamp) - { - membership = ms - } - } - - // Créer une wot temporaire - let tmpWot = wotbInstance.memCopy(); - - // Mesurer la qualité externe de chaque emetteur de chaque certification - for (const cert of idtysPendingCertifsList[idMax]) { - if (typeof (membersQualityExt[cert.from]) == 'undefined') { - const detailedDistanceQualityExt: DetailedDistance = tmpWot.detailedDistance(cert.wotb_id, dSen, conf.stepMax - 1, conf.xpercent); - membersQualityExt[cert.from] = ((detailedDistanceQualityExt.nbSuccess / detailedDistanceQualityExt.nbSentries) / conf.xpercent).toFixed(2); - } - } - - // Ajouter un noeud a la wot temporaire et lui donner toute les certifications valides reçues par l'indentité idMax - let pendingIdtyWID = tmpWot.addNode(); - for (const cert of idtysPendingCertifsList[idMax]) - { - if (cert.validBlockStamp) - { - tmpWot.addLink(cert.wotb_id, pendingIdtyWID); - } - } - // Récupérer les données de distance du dossier d'adhésion de l'indentité idMax - let detailedDistance = tmpWot.detailedDistance(pendingIdtyWID, dSen, conf.stepMax, conf.xpercent); - - // Nettoyer la wot temporaire - tmpWot.clear(); - - // Calculer percentSentriesReached et percentMembersReached - let percentSentriesReached = parseFloat(((detailedDistance.nbSuccess/detailedDistance.nbSentries)*100).toFixed(2)); - let percentMembersReached = parseFloat(((detailedDistance.nbReached/currentMembersCount)*100).toFixed(2)); - - // Pousser l'identité dans le tableau idtysListOrdered - idtysListOrdered.push({ - uid: identitiesList[idMax].uid, - wotexId: identitiesList[idMax].wotexId, - creationTimestamp: identitiesList[idMax].creationTimestamp, - pubkey: identitiesList[idMax].pubkey, - BlockNumber: identitiesList[idMax].BlockNumber, - expires_on: identitiesList[idMax].expires_on, - nbCert: identitiesList[idMax].nbCert, - registrationAvailability: identitiesList[idMax].registrationAvailability, - nbValidPendingCert: identitiesList[idMax].nbValidPendingCert, - detailedDistance, - percentSentriesReached, - percentMembersReached, - membership, - pendingCertifications: idtysPendingCertifsList[idMax], - validBlockStamp: identitiesList[idMax].validBlockStamp, - idtyRevoked: identitiesList[idMax].idtyRevoked - }); - - // Si le cache a été réinitialiser, recalculer les sommes meanSentriesReachedByIdtyPerCert et meanMembersReachedByIdtyPerCert - if (reinitCache && identitiesList[idMax].nbValidPendingCert > 0) - { - let nbReceiveCert = identitiesList[idMax].nbValidPendingCert; - meanSentriesReachedByIdtyPerCert[nbReceiveCert-1] += percentSentriesReached; - meanMembersReachedByIdtyPerCert[nbReceiveCert-1] += percentMembersReached; - countIdtiesPerReceiveCert[nbReceiveCert-1] += 1; - } - } // END if (!doubloon) - } // END days limit rule - - // Exclure la valeur max avant de poursuivre le tri - tabSort[idMax] = -1; - } // End sort identities loop - - // Si ordre croissant demandé, inverser le tableau - if (order == 'asc') - { - const idtysListOrdered2: WillMemberIdentityWithPendingCerts[] = [] - let tmpIdtysListOrderedLength = idtysListOrdered.length; - for (let i=0;i<tmpIdtysListOrderedLength;i++) - { - idtysListOrdered2[i] = idtysListOrdered[tmpIdtysListOrderedLength-i-1]; - } - idtysListOrdered = idtysListOrdered2; - } - - if (reinitCache) { - // Calculate meanSentriesReachedByIdtyPerCert and meanMembersReachedByIdtyPerCert - for (let i = 0; i <= nbMaxCertifs; i++) { - if (countIdtiesPerReceiveCert[i] > 0) { - meanSentriesReachedByIdtyPerCert[i] = parseFloat((meanSentriesReachedByIdtyPerCert[i] / countIdtiesPerReceiveCert[i]).toFixed(2)); - meanMembersReachedByIdtyPerCert[i] = parseFloat((meanMembersReachedByIdtyPerCert[i] / countIdtiesPerReceiveCert[i]).toFixed(2)); - } - else { - meanSentriesReachedByIdtyPerCert[i] = 0.0; - meanMembersReachedByIdtyPerCert[i] = 0.0; - } - } - - // Dévérouiller le cache willMembers - lockWillMembers = false; - } - - showExecutionTimes() + const format = req.query.format || 'HTML'; + + const { + // Paramètres + days, + order, + sort_by, + showIdtyWithZeroCert, + sortSig, + // Résultats + idtysListOrdered, + // Variables + currentBlockNumber, + currentBlockchainTimestamp, + currentMembersCount, + limitTimestamp, + dSen, + conf, + nbMaxCertifs, + countMembersWithSigQtyValidCert, + meanSentriesReachedByIdtyPerCert, + meanMembersReachedByIdtyPerCert, + membersQualityExt, + } = await willMembers(locals.duniterServer, req.query.d, + req.query.d && req.query.order, + req.query.sort_by, + req.query.showIdtyWithZeroCert, + req.query.sortSig + ) // Si le client demande la réponse au format JSON, le faire if (format == 'JSON') @@ -520,46 +95,3 @@ module.exports = async (req: any, res: any, next: any) => { } } - -interface PendingCert { - from: string - pubkey?: string - wotb_id: number - issuerIsSentry: boolean - blockNumber: number - creationTimestamp: number - timestampExpire: number - timestampWritable: number - validBlockStamp: boolean -} - -interface DetailedDistance { - nbSuccess: number - nbSentries: number - nbReached: number - isOutdistanced: boolean -} - -interface WillMemberIdentity { - BlockNumber: number - creationTimestamp: number - pubkey: string - uid: string - hash?: string - wotexId: string - expires_on: number - nbCert: number - nbValidPendingCert: number - registrationAvailability: number - detailedDistance?: DetailedDistance - pendingCertifications?: PendingCert[] - validBlockStamp: boolean - idtyRevoked: boolean - percentSentriesReached?: number - percentMembersReached?: number - membership?: DBMembership|null -} - -interface WillMemberIdentityWithPendingCerts extends WillMemberIdentity { - pendingCertifications: PendingCert[] -}