diff --git a/lib/DataFinder.ts b/lib/DataFinder.ts index 488d50c56199cdda8b0a1c5b1ab9c2a452fedd81..e2070f66c0957054d4f0a86b3074af2cd7d6e768 100644 --- a/lib/DataFinder.ts +++ b/lib/DataFinder.ts @@ -133,14 +133,9 @@ export class DataFinder { } @MonitorExecutionTime() - getUidOfPub(pub: string): Promise<{ uid: string }[]> { + getUidOfPub(pub: string): Promise<FullIindexEntry> { return this.getFromCacheOrDB('getUidOfPub', pub, async () => { - const entry = await this.iindex.getFullFromPubkey(pub) - if (!entry.uid) { - // Not found - return [] - } - return [entry] + return this.iindex.getFromPubkey(pub) }) } diff --git a/modules/will-members.ts b/modules/will-members.ts index 1c2664c566595ea5ccec866980e79330a09d2f4b..b1c9abcbf0f5b47b4f77a0ca4d3459c3ec99c4ad 100644 --- a/modules/will-members.ts +++ b/modules/will-members.ts @@ -5,6 +5,19 @@ import {showExecutionTimes} from "../lib/MonitorExecutionTime"; import {DataFinder} from "../lib/DataFinder"; import {Server} from "duniter/server"; import {WotBuilder} from "duniter/neon/lib"; +import { + DetailedDistance, + PendingCert, + WillMemberIdentity, + WillMemberIdentityWithPendingCerts +} from "./willMembers/interfaces"; +import {triParDateDeDisponibilite} from "./willMembers/triParDateDeDisponibilite"; +import {WotbIdCache} from "./willMembers/wotbIdCache"; +import {getIdentityListOrdered} from "./willMembers/getIdentityListOrdered"; +import {computeMeansAndCounts} from "./willMembers/computeMeansAndCounts"; +import {getMembersQualityExt} from "./willMembers/getMembersQualityExt"; +import {getSorting} from "./willMembers/getSorting"; +import {SentryChecker} from "./willMembers/issuerIsSentry"; // Préserver les résultats en cache let lockWillMembers = false @@ -13,39 +26,23 @@ 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() - + const wotbIdCache = new WotbIdCache(dataFinder) // 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)); @@ -56,397 +53,171 @@ export async function willMembers(duniterServer: Server, days = 65, order = 'des reinitCache = false; } - if (reinitCache) - { + 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); + const sentryChecker = new SentryChecker(wotbInstance.getSentries(dSen)) // Récupérer la liste des identités en piscine - const resultQueryIdtys: DBIdentity[] = await dataFinder.findPendingMembers() + const pendingMembers: 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++) - { + for (const pendingIdty of pendingMembers) { // Extraire le numéro de bloc d'émission de l'identité - let idtyBlockStamp = resultQueryIdtys[i].buid.split("-"); + let idtyBlockStamp = pendingIdty.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 idties = await dataFinder.getWotexInfos(pendingIdty.uid); let wotexId = ''; - if (idties.length > 1) - { + if (idties.length > 1) { let pos = 0; - for (const idty of idties) - { - if (idty.hash == resultQueryIdtys[i].hash) { wotexId = '['+pos+']'; } + for (const idty of idties) { + if (idty.hash == pendingIdty.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; } + 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) - { + if (pendingIdty.revocation_sig != null) { idtyRevoked = true; } // Stocker les informations de l'identité - identitiesList.push({ + const identity = { BlockNumber: parseInt(idtyBlockNumber), creationTimestamp: (typeof(idtyEmittedBlock) == 'undefined' ) ? currentBlockchainTimestamp:idtyEmittedBlock.medianTime, - pubkey: resultQueryIdtys[i].pubkey, - uid: resultQueryIdtys[i].uid, - hash: resultQueryIdtys[i].hash, + pubkey: pendingIdty.pubkey, + uid: pendingIdty.uid, + hash: pendingIdty.hash, wotexId: wotexId, - expires_on: resultQueryIdtys[i].expires_on || 0, + expires_on: pendingIdty.expires_on || 0, nbCert: 0, nbValidPendingCert: 0, registrationAvailability: 0, validBlockStamp: validIdtyBlockStamp, idtyRevoked: idtyRevoked - }); - idtysPendingCertifsList.push([]) + } + identitiesList.push(identity); + const pendingCertifications: PendingCert[] = [] // 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); + let pendingCerts = await dataFinder.findPendingCertsToTarget(pendingIdty.pubkey, pendingIdty.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++) - { + for (const pendingCert of pendingCerts) { // Récupérer le medianTime et le hash du bloc d'émission de la certification - let emittedBlock = await dataFinder.getBlock(tmpQueryPendingCertifsList[j].block_number) + let emittedBlock = await dataFinder.getBlock(pendingCert.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) - { + let member = await dataFinder.getUidOfPub(pendingCert.from) + if (emittedBlock && member) { // Récupérer la pubkey de l'émetteur - let issuerPubkey = tmpQueryPendingCertifsList[j].from; + let issuerPubkey = pendingCert.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]; } + let wotb_id = await wotbIdCache.getWotbId(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]; } + let issuerIsSentry = sentryChecker.isIssuerSentry(issuerPubkey, wotb_id); // Vérifier si le blockstamp est correct - var validBlockStamp = false; - if (emittedBlock.hash == tmpQueryPendingCertifsList[j].block_hash) - { validBlockStamp = true; } - + const validBlockStamp = emittedBlock.hash == pendingCert.block_hash; // 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; + if ( typeof(tmpQueryLastIssuerCert[0]) != 'undefined' && typeof(tmpQueryLastIssuerCert[0].chainable_on) != 'undefined' ) { + certTimestampWritable = tmpQueryLastIssuerCert[0].chainable_on; + } + //identity.registrationAvailability = (certTimestampWritable > identity.registrationAvailability) ? certTimestampWritable : identity.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) - { + let doubloonPendingCertif = pendingCertifications.filter(c => c.from == member.uid && c.validBlockStamp == validBlockStamp).length > 0; + 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, + if (pendingCert.expires_on > currentBlockchainTimestamp) { + pendingCertifications.push({ + from: member.uid, pubkey: issuerPubkey, wotb_id: wotb_id, issuerIsSentry: issuerIsSentry, - blockNumber: tmpQueryPendingCertifsList[j].block_number, + blockNumber: pendingCert.block_number, creationTimestamp: emittedBlock.medianTime, - timestampExpire: tmpQueryPendingCertifsList[j].expires_on, + timestampExpire: pendingCert.expires_on, timestampWritable: certTimestampWritable, validBlockStamp: validBlockStamp }); - identitiesList[i].nbCert++; - if (validBlockStamp) { identitiesList[i].nbValidPendingCert++; } + identity.nbCert++; + if (validBlockStamp) { identity.nbValidPendingCert++; } } } } } + idtysPendingCertifsList.push(pendingCertifications) // Calculer le nombre maximal de certifications reçues par l'identité courante - if ( identitiesList[i].nbCert > nbMaxCertifs) { nbMaxCertifs = identitiesList[i].nbCert; } + if ( identity.nbCert > nbMaxCertifs) { + nbMaxCertifs = identity.nbCert; + } // calculate countMembersWithSigQtyValidCert - if ( identitiesList[i].nbValidPendingCert >= conf.sigQty) { countMembersWithSigQtyValidCert++; } + if ( identity.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; + if (sortSig == "Availability") { + idtysPendingCertifsList = triParDateDeDisponibilite(idtysPendingCertifsList, conf, currentBlockchainTimestamp, identitiesList) } // 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; - } - } + let tabSort = getSorting(identitiesList, sort_by, currentBlockchainTimestamp, conf) - // 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 = WotBuilder.fromWot(wotbInstance); - - // 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 - }); + // Trier les identités + let idtysListOrdered: WillMemberIdentityWithPendingCerts[] = await getIdentityListOrdered( + identitiesList, + idtysPendingCertifsList, + currentBlockchainTimestamp, + currentMembersCount, + limitTimestamp, + wotbInstance, + duniterServer, + conf, + dSen, + tabSort, + order) - // 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 + console.log('Calcul des qualités...') - // Exclure la valeur max avant de poursuivre le tri - tabSort[idMax] = -1; - } // End sort identities loop + let membersQualityExt = getMembersQualityExt(wotbInstance, idtysListOrdered, conf, dSen) - // 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; - } + console.log('Calcul des moyennes...') + const { + meanSentriesReachedByIdtyPerCert, + meanMembersReachedByIdtyPerCert, + } = computeMeansAndCounts(idtysListOrdered, nbMaxCertifs) 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; } @@ -472,48 +243,3 @@ export async function willMembers(duniterServer: Server, days = 65, order = 'des membersQualityExt, } } - -interface PendingCert { - from: string - pubkey?: string - wotb_id: number - issuerIsSentry: boolean - blockNumber: number - creationTimestamp: number - timestampExpire: number - timestampWritable: number - validBlockStamp: boolean -} - -interface DetailedDistance { - nbSentries: number; - nbSuccess: number; - nbSuccessAtBorder: number; - nbReached: number; - nbReachedAtBorder: number; - isOutdistanced: number; -} - -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/modules/willMembers/computeMeansAndCounts.ts b/modules/willMembers/computeMeansAndCounts.ts new file mode 100644 index 0000000000000000000000000000000000000000..62abe0b5ad31220f2bd769cd4aa2f818ad5059d0 --- /dev/null +++ b/modules/willMembers/computeMeansAndCounts.ts @@ -0,0 +1,40 @@ +import {WillMemberIdentityWithPendingCerts} from "./interfaces"; + +export function computeMeansAndCounts(idtysListOrdered: WillMemberIdentityWithPendingCerts[], nbMaxCertifs: number) { + const meanSentriesReachedByIdtyPerCert: number[] = [] + const meanMembersReachedByIdtyPerCert: number[] = [] + const countIdtiesPerReceiveCert: number[] = [] + // Réinitialiser sumSentriesReachedByIdtyPerCert, sumMembersReachedByIdtyPerCert et countIdtiesPerReceiveCert + for (let i=0; i<=nbMaxCertifs; i++) { + meanSentriesReachedByIdtyPerCert[i] = 0; + meanMembersReachedByIdtyPerCert[i] = 0; + countIdtiesPerReceiveCert[i] = 0; + } + + idtysListOrdered.forEach(pendingIdty => { + // Si le cache a été réinitialiser, recalculer les sommes meanSentriesReachedByIdtyPerCert et meanMembersReachedByIdtyPerCert + if (pendingIdty.nbValidPendingCert > 0) { + let nbReceiveCert = pendingIdty.nbValidPendingCert; + meanSentriesReachedByIdtyPerCert[nbReceiveCert-1] += pendingIdty.percentSentriesReached; + meanMembersReachedByIdtyPerCert[nbReceiveCert-1] += pendingIdty.percentMembersReached; + countIdtiesPerReceiveCert[nbReceiveCert-1] += 1; + } + }) + + // 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; + } + } + + return { + meanSentriesReachedByIdtyPerCert, + meanMembersReachedByIdtyPerCert + } +} \ No newline at end of file diff --git a/modules/willMembers/getIdentityListOrdered.ts b/modules/willMembers/getIdentityListOrdered.ts new file mode 100644 index 0000000000000000000000000000000000000000..8c38a08a104c2b9b1065533801be786fec50eaf5 --- /dev/null +++ b/modules/willMembers/getIdentityListOrdered.ts @@ -0,0 +1,121 @@ +import {DBMembership} from "duniter/app/lib/dal/sqliteDAL/MembershipDAL"; +import {Wot, WotBuilder} from "duniter/neon/lib"; +import {DetailedDistance, PendingCert, WillMemberIdentity, WillMemberIdentityWithPendingCerts} from "./interfaces"; +import {Server} from "duniter/server"; +import {ConfDTO} from "duniter/app/lib/dto/ConfDTO"; + +export async function getIdentityListOrdered(identitiesList: WillMemberIdentity[], + idtysPendingCertifsList: PendingCert[][], + currentBlockchainTimestamp: number, + currentMembersCount: number, + limitTimestamp: number, + wotbInstance: Wot, + duniterServer: Server, + conf: ConfDTO, + dSen: number, + tabSort: number[], + order: string +): Promise<WillMemberIdentityWithPendingCerts[]> { + + let idtysListOrdered: WillMemberIdentityWithPendingCerts[] = [] + for (let i=0; i<identitiesList.length; i++) { + let max = -1; + let idMax =0; + for (let j=0; j<identitiesList.length; j++) { + if (tabSort[j] > max) { + max = tabSort[j]; + idMax = j; + } + } + const pendingIdty = identitiesList[idMax] + const certsToPending = idtysPendingCertifsList[idMax] + + // Push max value on sort table, only if respect days limit + if (limitTimestamp <= pendingIdty.expires_on) { + // Exclure la valeur max avant de poursuivre le tri + tabSort[idMax] = -1; + console.log(`Traitement de l'identité ${i+1}/${identitiesList.length} (${pendingIdty.uid}) : expirée`) + continue; + } + + // Vérifier que cette identité n'a pas déjà été prise en compte (empecher les doublons) + let doubloon = idtysListOrdered.filter(idty => pendingIdty.uid == idty.uid && pendingIdty.BlockNumber == idty.BlockNumber).length; + if (doubloon) { + // Exclure la valeur max avant de poursuivre le tri + tabSort[idMax] = -1; + console.log(`Traitement de l'identité ${i+1}/${identitiesList.length} (${pendingIdty.uid}) : doublon`) + continue; + } + console.log(`Traitement de l'identité ${i+1}/${identitiesList.length} (${pendingIdty.uid})...`) + + // Push max value on sort table (and test distance rule) + + // Tester la présence de l'adhésion + const pendingMembershipsOfIdty: DBMembership[] = await duniterServer.dal.msDAL.getPendingINOfTarget(pendingIdty.hash as string); + let membership: DBMembership|null = pendingMembershipsOfIdty.filter(ms => ms.expires_on > currentBlockchainTimestamp)[0] + + // Créer une wot temporaire + let tmpWot = WotBuilder.fromWot(wotbInstance); + + // Ajouter un noeud a la wot temporaire et lui donner toute les certifications valides reçues par l'indentité en attente + let pendingIdtyWID = tmpWot.addNode(); + for (const cert of certsToPending) { + 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)); + // const percentSentriesReached = 0 + // const percentMembersReached = 0 + // const detailedDistance: DetailedDistance = { + // isOutdistanced: 0, + // nbReached: 0, + // nbReachedAtBorder: 0, + // nbSentries: 0, + // nbSuccessAtBorder: 0, + // nbSuccess: 0 + // } + + // Pousser l'identité dans le tableau idtysListOrdered + idtysListOrdered.push({ + uid: pendingIdty.uid, + wotexId: pendingIdty.wotexId, + creationTimestamp: pendingIdty.creationTimestamp, + pubkey: pendingIdty.pubkey, + BlockNumber: pendingIdty.BlockNumber, + expires_on: pendingIdty.expires_on, + nbCert: pendingIdty.nbCert, + registrationAvailability: pendingIdty.registrationAvailability, + nbValidPendingCert: pendingIdty.nbValidPendingCert, + detailedDistance, + percentSentriesReached, + percentMembersReached, + membership, + pendingCertifications: certsToPending, + validBlockStamp: pendingIdty.validBlockStamp, + idtyRevoked: pendingIdty.idtyRevoked + }); + // Exclure la valeur max avant de poursuivre le tri + tabSort[idMax] = -1; + } + + // 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; + } + + return idtysListOrdered +} \ No newline at end of file diff --git a/modules/willMembers/getMembersQualityExt.ts b/modules/willMembers/getMembersQualityExt.ts new file mode 100644 index 0000000000000000000000000000000000000000..4970d8e70b27076584dd05a381f714b1a901e279 --- /dev/null +++ b/modules/willMembers/getMembersQualityExt.ts @@ -0,0 +1,30 @@ +import {Wot, WotBuilder} from "duniter/neon/lib"; +import {DetailedDistance, WillMemberIdentityWithPendingCerts} from "./interfaces"; +import {ConfDTO} from "duniter/app/lib/dto/ConfDTO"; + +export function getMembersQualityExt(wotbInstance: Wot, + idtysListOrdered: WillMemberIdentityWithPendingCerts[], + conf: ConfDTO, + dSen: number +): { [k: string]: string } { + let membersQualityExt: { [k: string]: string } = {} + const nbIdentites = idtysListOrdered.length + let i = 0 + idtysListOrdered.forEach(pendingIdty => { + console.log(`Qualité de l'identité ${i+1}/${nbIdentites}`) + // Créer une wot temporaire + // let tmpWot = WotBuilder.fromWot(wotbInstance); + // Mesurer la qualité externe de chaque emetteur de chaque certification + for (const cert of pendingIdty.pendingCertifications) { + 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); + membersQualityExt[cert.from] = "9.99" + } + } + // Vider la mémoire + // tmpWot.clear() + i++ + }) + return membersQualityExt +} \ No newline at end of file diff --git a/modules/willMembers/getSorting.ts b/modules/willMembers/getSorting.ts new file mode 100644 index 0000000000000000000000000000000000000000..efe1d0604c85dd950d36cdb2223b5b12103b2f14 --- /dev/null +++ b/modules/willMembers/getSorting.ts @@ -0,0 +1,26 @@ +import {WillMemberIdentity} from "./interfaces"; +import {ConfDTO} from "duniter/app/lib/dto/ConfDTO"; + +export function getSorting(identitiesList: WillMemberIdentity[], sort_by: string, currentBlockchainTimestamp: any, conf: ConfDTO): number[] { + let tabSort: number[] = [] + if (sort_by == "creationIdty") { + tabSort = identitiesList.map(i => i.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)); + } + } + } + return tabSort +} \ No newline at end of file diff --git a/modules/willMembers/interfaces.ts b/modules/willMembers/interfaces.ts new file mode 100644 index 0000000000000000000000000000000000000000..c409ea0306575f16335b0fa86e385bee482ab2d9 --- /dev/null +++ b/modules/willMembers/interfaces.ts @@ -0,0 +1,48 @@ +import {DBMembership} from "duniter/app/lib/dal/sqliteDAL/MembershipDAL"; + +export interface PendingCert { + from: string + pubkey?: string + wotb_id: number + issuerIsSentry: boolean + blockNumber: number + creationTimestamp: number + timestampExpire: number + timestampWritable: number + validBlockStamp: boolean +} + +export interface DetailedDistance { + nbSentries: number; + nbSuccess: number; + nbSuccessAtBorder: number; + nbReached: number; + nbReachedAtBorder: number; + isOutdistanced: number; +} + +export 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 +} + +export interface WillMemberIdentityWithPendingCerts extends WillMemberIdentity { + pendingCertifications: PendingCert[] + percentSentriesReached: number + percentMembersReached: number +} \ No newline at end of file diff --git a/modules/willMembers/issuerIsSentry.ts b/modules/willMembers/issuerIsSentry.ts new file mode 100644 index 0000000000000000000000000000000000000000..556549d847a22876a63f26b342d44fe5d3dd6e6e --- /dev/null +++ b/modules/willMembers/issuerIsSentry.ts @@ -0,0 +1,25 @@ +export class SentryChecker { + + private sentriesIndex: { [k: string]: boolean } = {} + + + constructor(private sentries: number[]) { + } + + isIssuerSentry(issuerPubkey: string, wotb_id: number): boolean { + if (typeof(this.sentriesIndex[issuerPubkey]) == 'undefined') { + this.sentriesIndex[issuerPubkey] = false; + for (let s=0;s<this.sentries.length;s++) { + if (this.sentries[s] == wotb_id) { + this.sentriesIndex[issuerPubkey] = true; + this.sentries.splice(s, 1); + return true + } + } + return false + } + else { + return this.sentriesIndex[issuerPubkey]; + } + } +} \ No newline at end of file diff --git a/modules/willMembers/triParDateDeDisponibilite.ts b/modules/willMembers/triParDateDeDisponibilite.ts new file mode 100644 index 0000000000000000000000000000000000000000..0c5b0ac055e7ca528f1468f2aa08722a22307c5d --- /dev/null +++ b/modules/willMembers/triParDateDeDisponibilite.ts @@ -0,0 +1,57 @@ +import {PendingCert, WillMemberIdentity} from "./interfaces"; +import {ConfDTO} from "duniter/app/lib/dto/ConfDTO"; + +export function triParDateDeDisponibilite(idtysPendingCertifsList: PendingCert[][], + conf: ConfDTO, + currentBlockchainTimestamp: number, + identitiesList: WillMemberIdentity[]) { + + 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; + } + + } + return idtysPendingCertifsListSort; +} \ No newline at end of file diff --git a/modules/willMembers/wotbIdCache.ts b/modules/willMembers/wotbIdCache.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b53d15e80c62d2f3d70827eca12402bc500b8c6 --- /dev/null +++ b/modules/willMembers/wotbIdCache.ts @@ -0,0 +1,19 @@ +import {DataFinder} from "../../lib/DataFinder"; + +export class WotbIdCache { + + private wotbIdIndex: { [k: string]: number | undefined } = {} + private dataFinder: DataFinder + + + constructor(dataFinder: DataFinder) { + this.dataFinder = dataFinder; + } + + async getWotbId(pubkey: string): Promise<number> { + if (this.wotbIdIndex[pubkey] === undefined) { + this.wotbIdIndex[pubkey] = await this.dataFinder.getWotbIdByIssuerPubkey(pubkey); + } + return this.wotbIdIndex[pubkey] as number + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e9bac2bbbd082210154d173662e46a50920eb6d5..8d055e8029d247c5bf3442fc36628abc91d7c4e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "typescript": "^3.3.3" }, "peerDependencies": { - "duniter": "1.7.x" + "duniter": "1.8.6" } }, "node_modules/@types/mocha": { diff --git a/package.json b/package.json index 1e27b11118ddef1f7e1e1f657efaae4cb6c603b3..d12814d77df8706c1318a3906c3f88e06d336fe6 100755 --- a/package.json +++ b/package.json @@ -30,6 +30,6 @@ "ts-node": "^3.3.0" }, "peerDependencies": { - "duniter": "1.7.x" + "duniter": "1.8.6" } } diff --git a/routes/members2.ts b/routes/members2.ts index 2176f24ff034d24d45fb8eff2fbb8baddc6a9540..102a1a594e07345036322042c5c85783b5b50a92 100755 --- a/routes/members2.ts +++ b/routes/members2.ts @@ -353,7 +353,7 @@ module.exports = async (req: any, res: any, next: any) => { let tmpQueryGetUidProtagonistPendingCert = await dataFinder.getUidOfPub(tmpPub) // Vérifier que l'émetteur de la certification correspond à une identié connue - if ( tmpQueryGetUidProtagonistPendingCert.length > 0 ) + if ( tmpQueryGetUidProtagonistPendingCert ) { // Vérifier la validité du blockStamp de la certification en piscine let validBlockStamp = false; @@ -364,7 +364,7 @@ module.exports = async (req: any, res: any, next: any) => { let doubloonPendingCertif = false; for (const pendingCert of membersPendingCertifsList[m]) { - if (pendingCert.protagonist == tmpQueryGetUidProtagonistPendingCert[0].uid && pendingCert.validBlockStamp == validBlockStamp) + if (pendingCert.protagonist == tmpQueryGetUidProtagonistPendingCert.uid && pendingCert.validBlockStamp == validBlockStamp) { doubloonPendingCertif = true; } @@ -378,8 +378,8 @@ module.exports = async (req: any, res: any, next: any) => { if (tmpQueryPendingCertifsList[i].expires_on > currentBlockchainTimestamp) { membersPendingCertifsList[m].push({ - protagonist: tmpQueryGetUidProtagonistPendingCert[0].uid, - protagonistIsSentry: sentriesIndex[tmpQueryGetUidProtagonistPendingCert[0].uid], + protagonist: tmpQueryGetUidProtagonistPendingCert.uid, + protagonistIsSentry: sentriesIndex[tmpQueryGetUidProtagonistPendingCert.uid], blockNumber: tmpQueryPendingCertifsList[i].block_number, timestampExpire: tmpQueryPendingCertifsList[i].expires_on, timestampWritable: (typeof(tmpQueryLastIssuerCert[0]) == 'undefined') ? 0:tmpQueryLastIssuerCert[0].chainable_on, diff --git a/routes/willMembers2.ts b/routes/willMembers2.ts index 513cb6cde5ea5c1bef087a858c5cae47717e8e61..14ddb0761938e95c35636c96b96c52ae9a91047e 100755 --- a/routes/willMembers2.ts +++ b/routes/willMembers2.ts @@ -90,6 +90,7 @@ module.exports = async (req: any, res: any, next: any) => { } catch (e) { + console.error(e.stack); // En cas d'exception, afficher le message res.status(500).send(`<pre>${e.stack || e.message}</pre>`); }