Geek++ · Dossier 07 / 25

ED2K Hashset, ICH, AICH, corruption et récupération

Étude technique publique · 4,004 mots · ≈ 18 min de lecture attentive

Étude intégraleSans réflexion CODEXSources & constantes conservées
Intégrité
AICH
H1H2
✓✓×✓

0. Résumé exécutif

Le pipeline d’intégrité de l’eMule Community 0.72a étudié est plus solide et plus stratifié que ne le laisse penser la vieille formule « eMule vérifie les chunks en MD4 ».

Il faut distinguer au moins quatre couches :

1. ED2K / MD4
   identité historique du fichier et vérification des parties

2. ICH
   tentative de sauver une partie corrompue par retéléchargements progressifs

3. AICH
   arbre SHA-1 jusqu’à la granularité EMBLOCKSIZE = 180 KiB,
   récupération ciblée et authentifiée par le Master Hash

4. CorruptionBlackBox
   provenance locale des plages reçues et sanction éventuelle des sources

La règle d’intégrité centrale de la branche étudiée est très conservatrice :

si MD4 et AICH sont tous les deux disponibles pour une partie, les deux doivent l’accepter.

Le mécanisme AICH ne fait pas confiance à un Recovery Packet isolé : il nécessite un Master Hash fiable, vérifie l’arbre reçu, compare les hashes locaux bloc par bloc puis repasse la partie reconstituée par HashSinglePart().

Le BlackBox historique attribue les plages aux IP et, dans son calcul de culpabilité, compte chaque enregistrement corrompu comme au moins :

EMBLOCKSIZE = 180 KiB

même si la source n’a fourni qu’une petite sous-plage.

Speculative Block Racing
Partial Final-Tail Rescue
réaffectations ciblées

qui peuvent créer une provenance plus fine et potentiellement plusieurs contributeurs autour du même bloc AICH.

Ce n’est pas un bug démontré : les doublons déjà entièrement acquis sont rejetés avant enregistrement et l’Endgame nettoie ses races.

Mais l’interaction doit être auditée.

Le corpus historique renforce fortement cette priorité : NetF WARP avait introduit un Dynamic Corruption Threshold « to avoid mass banning when using Dynamic Block Requests » et un CorruptionBlackBoxFix.

instrumenter l’attribution exacte sous E6bis avant de modifier le seuil de 32 %.


1. Racines protocolaires : Hashset eDonkey

Le document historique edonkey-protocol.txt décrit déjà côté client-à-client :

0x51 Hashset request
0x52 Hashset answer

et définit :

Part hash list ::= WORD HASH*
File hash      ::= HASH
HASH           ::= 16 bytes MD4 digest

Donc la vérification par hashes de parties ne vient pas d’une extension tardive d’eMule.

Elle appartient au cœur historique eDonkey/eD2K.


2. Taille fondamentale : PARTSIZE

Dans la branche étudiée :

PARTSIZE = 9728000ui64;

Soit :

9 728 000 octets
≈ 9,27734375 MiB

Cette valeur participe directement :

  • au découpage ED2K ;
  • au hashset ;
  • au FileHash ;
  • aux cartes de parties ;
  • à ICH ;
  • à l’organisation supérieure AICH.

3. Taille fondamentale : EMBLOCKSIZE

Dans opcodes.h :

EMBLOCKSIZE = 184320;

Soit :

180 KiB

Cette valeur sert notamment :

  • à la granularité classique des demandes ;
  • au niveau inférieur de l’arbre AICH ;
  • à la récupération AICH ;
  • au calcul conservateur de CorruptionBlackBox.

Contrairement à PARTSIZE, il faut distinguer :

taille logique historique utilisée par les mécanismes

et :

obligation absolue que toute requête fasse exactement 180 KiB

Une demande peut couvrir moins.

AICH reste cependant organisé autour de feuilles de 180 KiB, sauf dernière feuille plus courte.


4. Hash ED2K — comportement exact

4.1 Petit fichier

Si le fichier tient dans une seule partie et ne tombe pas sur le cas frontière historique :

FileHash = MD4(file)

4.2 Plusieurs parties

Chaque partie reçoit un MD4 de 16 octets.

Le hash final est calculé sur la concaténation des hashes de parties.

4.3 Cas historique des multiples exacts de PARTSIZE

KnownFile.cpp et FileIdentifier.cpp confirment le comportement ED2K particulier.

Pour :

taille = 1 × PARTSIZE

le hashset comporte :

hash(partie réelle)
+
MD4(partie vide)

Le hash MD4 du vide est le classique :

31D6CFE0D16AE931B73C59D7E0C089C0

GetTheoreticalMD4PartHashCount() reflète cette convention :

uResult = fileSize / PARTSIZE;
if (uResult > 0)
    uResult++;

Conclusion

Ce n’est pas un bug moderne à corriger.

C’est un cas de compatibilité ED2K figé par l’histoire du réseau.


5. Le Hashset n’est pas accepté aveuglément

La branche 0.72a possède une classe CFileIdentifier qui vérifie la cohérence du hashset reçu.

Pour MD4 :

FileHash attendu
nombre théorique de hashes
hashes de parties reçus
→ recalcul du FileHash
→ comparaison

Si le hashset ne reconstruit pas le FileHash attendu, il est rejeté.

Donc :

un pair envoie une liste de hashes
≠
eMule l’accepte comme vérité

C’est déjà une barrière de confiance importante.


6. Évolution du protocole Hashset

opcodes.h de la branche étudiée contient encore :

OP_HASHSETREQUEST  0x51 // *DEPRECATED*
OP_HASHSETANSWER   0x52 // *DEPRECATED*

mais également :

OP_HASHSETREQUEST2 0xB1
OP_HASHSETANSWER2  0xB2

La nouvelle forme utilise :

FileIdentifier
+
Options
+
hashsets demandés

CFileIdentifier::WriteHashSetsToPacket() réserve notamment :

bit 0 → MD4 HashSet
bit 1 → AICH HashSet

Intérêt documentaire

Le site doit expliquer deux époques :

eDonkey / premier eMule :
    hashset MD4 dédié

eMule moderne :
    FileIdentifier
    + MD4
    + AICH

sans donner l’impression que les vieux opcodes ont simplement disparu.


7. HashSinglePart() : le juge local

CPartFile::HashSinglePart() est une fonction essentielle.

Le commentaire source indique explicitement la politique :

si nous disposons de MD4 et AICH,
les deux doivent être d’accord

Objectif du code :

ne jamais livrer comme correcte
une partie que l’un des deux systèmes considère corrompue

MD4

Si le hashset MD4 est disponible :

hash réel de la partie
vs
hash MD4 attendu

AICH

Si le Master Hash et le part-hashset sont disponibles :

arbre SHA local de la partie
vs
AICH Part Hash attendu

Retour

return !bMD4Error && !bAICHError;

8. Que se passe-t-il lorsqu’une partie complète échoue ?

Dans FlushBuffer() :

partie devenue complète
→ HashSinglePart()

Si la fonction échoue :

  1. log « part corrupt » ;
  2. AddGap() rouvre la plage de la partie ;
  3. la partie est ajoutée à corrupted_list ;
  4. si AICH peut être utile, RequestAICHRecovery() est lancé ;
  5. les statistiques de corruption sont augmentées.

Point important :

les octets restent physiquement dans le .part

mais la gap list dit désormais :

ces positions ne sont plus considérées acquises

Cela permet justement à ICH/AICH de réhabiliter ensuite les zones prouvées bonnes.


9. ICH — comportement réel dans 0.72a

ICH ne possède pas une carte cryptographique fine indiquant immédiatement le bloc faux.

Le mécanisme est opportuniste.

Après corruption :

partie ouverte en gaps

Au fur et à mesure que des données sont retéléchargées, la branche vérifie de nouveau la partie.

Dans FlushBuffer() :

else if (IsCorruptedPart(part)
         && (ICH enabled || forced))
{
    if (HashSinglePart(part))
    {
        ...
    }
}

Si la partie complète redevient valide avant d’avoir remplacé tous les octets :

les gaps restants sont refermés
les anciennes données correspondantes sont sauvées

Caractérisation

ICH = récupération par revalidation globale

Ce n’est pas :

ICH = localisation précise du mauvais bloc

10. AICH — architecture du hash tree

SHAHashSet.h documente directement l’organisation.

Le niveau supérieur représente le fichier.

Puis :

parties de 9,28 MiB

puis, à l’intérieur des parties :

blocs de 180 KiB

Le hash AICH fait :

HASHSIZE = 20;

et l’implémentation CSHA utilisée est SHA-1.

Schéma conceptuel :

MasterHash
   ├── branche(s)
   │    ├── PartHash
   │    │    ├── 180K hash
   │    │    ├── 180K hash
   │    │    └── ...
   │    └── PartHash
   └── ...

Ce n’est pas un hashset plat.

C’est un arbre permettant de fournir seulement les hashes nécessaires pour authentifier une partie jusqu’au Master Hash.


11. AICH V2 et grands fichiers

SHAHashSet.h précise que la version 2 du mécanisme prend en charge des identifiants de hashes 32 bits pour les grands fichiers.

CreatePartRecoveryData() choisit :

identifiants 16 bits
ou
identifiants 32 bits

selon le type de fichier.

Ce détail montre que l’AICH actuel a lui-même évolué pour suivre l’extension des tailles de fichiers eMule.


12. États de confiance AICH

La branche définit :

AICH_ERROR
AICH_EMPTY
AICH_UNTRUSTED
AICH_TRUSTED
AICH_VERIFIED
AICH_HASHSETCOMPLETE

Il faut absolument conserver cette distinction dans la documentation.

UNTRUSTED

Le client a vu une valeur, mais n’a pas encore de raison suffisante de la considérer comme référence.

TRUSTED

Un consensus réseau suffisant a été atteint.

VERIFIED

La valeur vient d’un chemin où le client peut la considérer comme vérifiée.

HASHSETCOMPLETE

Pour un fichier local complet, l’arbre entier a été calculé.


13. Consensus AICH : découverte de code

CAICHRecoveryHashSet::UntrustedHashReceived() collecte plusieurs Master Hashes reçus et les origines qui les annoncent.

La branche utilise actuellement :

MINUNIQUEIPS_TOTRUST = 10
MINPERCENTAGE_TOTRUST = 92

et accepte le hash dominant lorsque :

au moins 10 groupes/origines
ET
au moins 92 % des déclarations

Une préférence :

AICHTrustEveryHash

permet de court-circuiter ce mécanisme.

Dans Preferences.cpp, sa valeur par défaut est :

false

14. Anomalie documentaire mineure

Près du code de confiance AICH, un commentaire historique parle encore d’un seuil de :

95 %

alors que la constante exécutée vaut :

92

Comme pour le commentaire 2^25 rencontré dans le dossier HighID :

comportement du code = clair
commentaire = légèrement obsolète

15. RequestAICHRecovery()

Une récupération n’est demandée que si :

Master Hash valide
ET
status == TRUSTED ou VERIFIED

Le code refuse également :

  • les tailles trop petites pour lesquelles la récupération n’a pas de sens ;
  • une seconde demande déjà pending pour la même partie.

Il vérifie d’abord si les données AICH nécessaires sont déjà en mémoire.


16. Sélection du pair AICH

RequestAICHRecovery() parcourt les sources.

Une source n’est candidate que si :

support AICH
+
possède un AICH hash annoncé
+
aucune requête AICH déjà pending
+
Master Hash == notre Master Hash fiable

Les HighID sont préférés.

Le choix final parmi les candidats utilise historiquement :

rand()

Analyse

Ce rand() n’a pas la même gravité que le challenge SecureIdent du Dossier 03.

Ici il ne produit pas un secret ni une preuve cryptographique.

Il choisit simplement un pair parmi plusieurs candidats compatibles.

Ce n’est donc pas une priorité de sécurité.


17. Échec d’une source AICH

CAICHRecoveryHashSet::ClientAICHRequestFailed() :

efface l’état pending
récupère le contexte de la requête
si le PartFile existe toujours
→ RequestAICHRecovery(part) de nouveau

Le système sait donc essayer une autre source.

18. Structure du Recovery Packet

CreatePartRecoveryData() détermine le nombre exact de hashes nécessaires :

hashes du chemin dans l’arbre
+
hashes des blocs EMBLOCKSIZE de la partie

ReadRecoveryData() :

  • vérifie la quantité attendue ;
  • refuse l’identifiant du Master Hash (identifier == 1) afin qu’un packet de recovery ne puisse pas remplacer la racine ;
  • vérifie les limites des identifiants ;
  • insère les hashes dans l’arbre ;
  • appelle VerifyHashTree(true) ;
  • vérifie ensuite que chaque hash de niveau bloc demandé est réellement présent.

Conclusion

Le Recovery Packet ne peut pas simplement décréter :

« tel bloc est bon »

sans rester cohérent avec le Master Hash déjà accepté.


19. AICHRecoveryDataAvailable()

Une fois l’arbre de la partie disponible :

  1. FlushBuffer() force les données locales sur disque ;
  2. eMule calcule son propre arbre SHA de la partie corrompue ;
  3. boucle sur la partie par tranches EMBLOCKSIZE ;
  4. compare hash local et hash vérifié ;
  5. pour chaque bloc valide :
    • FillGap()
    • RemoveBlockFromList()
    • CorruptionBlackBox.VerifiedData()
  6. pour chaque bloc faux :
    • CorruptionBlackBox.CorruptedData()
  7. CorruptionBlackBox.EvaluateData(part).

C’est une excellente séparation :

AICH localise
GapList décide ce qu’il manque
BlackBox conserve la provenance

20. Sanity check après récupération

Après réhabilitation des bons blocs, si la partie semble désormais complète :

HashSinglePart(part)

est relancé.

Si MD4/AICH n’acceptent toujours pas la partie :

AddGap(part entière)

et, dans certains cas, l’état AICH peut passer en erreur.

Donc :

la récupération ne contourne jamais le juge final.


21. Rehash final du fichier

PartFileHashFinished() effectue encore une vérification des parties complètes.

Il calcule pour chaque partie :

MD4
AICH si disponible

Si l’un échoue :

la partie est rouverte en gap

Si MD4 et AICH sont tous deux présents mais ne donnent pas le même verdict :

DebugLogError

Le fichier ne passe en finalisation normale que s’il n’y a pas d’erreur.


22. known2_64.met

Dans la branche Community inspectée :

KNOWN2_MET_FILENAME = "known2_64.met"
OLD_KNOWN2_MET_FILENAME = "known2.met"

Le fichier stocke les AICH Recovery Hashsets calculés localement.

SaveHashSet() enregistre notamment :

Master Hash
nombre de hashes de niveau inférieur
hashes des blocs

LoadHashSet() :

  • retrouve le Master Hash ;
  • vérifie le nombre attendu ;
  • recharge les feuilles ;
  • recalcule l’arbre ;
  • compare le Master Hash reconstruit à celui attendu.

23. CorruptionBlackBox : structure

Constante :

CBB_BANTHRESHOLD = 32

TransferredData(start,end,client) conserve :

partie
plage relative
IP source
état BBR_NONE

Les enregistrements adjacents compatibles peuvent être fusionnés.

Lorsque des octets sont réécrits par une autre source, le code ajuste/sépare les anciens enregistrements afin que la provenance reflète la dernière donnée conservée sur la plage.


24. Provenance enregistrée avant écriture

Dans CPartFile::ProcessBlockPacket() :

IsComplete(start,end) ?
→ si déjà écrit, retour avant BlackBox

Puis :

m_CorruptionBlackBox.TransferredData(start, end, client);

avant ajout au buffer.

Implication pour Speculative Racing

Une copie tardive qui arrive après que toute sa plage a déjà été acquise est normalement rejetée avant de remplacer la provenance.

C’est un garde-fou important.

Il réduit fortement le risque que le « perdant » d’une course soit arbitrairement considéré comme l’auteur des données gagnantes.


25. VerifiedData() / CorruptedData()

Après vérification :

BBR_NONE
→ BBR_VERIFIED

ou :

BBR_NONE / BBR_VERIFIED
→ BBR_CORRUPTED

Le code sait scinder les enregistrements lorsqu’une sous-plage seulement change de statut.

L’architecture peut donc déjà conserver une provenance plus fine qu’une partie entière.


26. EvaluateData() : calcul de culpabilité

Lorsqu’un bloc AICH est déclaré corrompu :

  1. liste les IP impliquées dans les records corrompus ;
  2. ignore celles déjà bannies ;
  3. parcourt les records du fichier ;
  4. cumule par IP :
    • données VERIFIED
    • données CORRUPTED
  5. calcule :
corrupt %
=
corrupt
/
(corrupt + verified)

Si :

corrupt % > 32

l’adresse est bannie.

Sinon elle est signalée/suivie.


27. Détail crucial : plancher de 180 KiB

Dans EvaluateData() :

aDataCorrupt[k] +=
    max(record_length, EMBLOCKSIZE);

Donc un record corrompu de :

20 KiB

peut compter comme :

180 KiB

pour l’évaluation de cette source.

Pourquoi ?

AICH sait essentiellement dire :

ce bloc de contrôle de 180 KiB est faux

Il ne sait pas nécessairement prouver quel octet précis à l’intérieur a cassé le hash.

Le code applique donc une pénalité conservatrice à l’échelle du bloc.


28. Problème potentiel avec contributions mixtes

Exemple conceptuel :

Bloc AICH de 180 KiB

Source A : 150 KiB
Source B :  30 KiB

Le bloc final échoue.

La seule preuve cryptographique disponible est :

les 180 KiB assemblés ne correspondent pas au hash attendu

Elle ne permet pas de dire automatiquement :

A est fautive

ou :

B est fautive

Si les deux possèdent des records recouvrant ce bloc, une politique conservatrice peut charger lourdement les deux.

E6bis

Partial Final-Tail Rescue et les réallocations rendent les contributions mixtes sur les zones finales plus plausibles qu’avec le scheduler historique.

D’où la nécessité d’un audit.


29. Speculative Racing : pourquoi le risque est limité mais réel à étudier

Le comportement actuel possède plusieurs protections :

plage déjà complète → paquet ignoré
Racing Cleanup
Partial Final-Tail Rescue ne demande que la gap réelle

Donc :

deux requêtes envoyées
≠
deux écritures conservées

Cependant les situations suivantes méritent mesure :

  • arrivée alternée de sous-plages ;
  • challenger qui remplit seulement une queue de bloc ;
  • source primaire ayant déjà fourni un préfixe ;
  • corruption d’un bloc AICH constitué de plusieurs provenances.

Statut

RISQUE ARCHITECTURAL À MESURER
PAS BUG E6bis DÉMONTRÉ

30. Confirmation historique : NetF WARP

Le changelog NetF WARP 0.2a contient exactement :

CorruptionBlackBoxFix
- ensure all data been processed before banning

Dynamic Corruption Threshold
- to avoid mass banning when using Dynamic Block Requests

Only Recover Banned Data
- only redownload data that is "known" to be corrupt

La présence conjointe :

DBR
+
CorruptionBlackBoxFix
+
Dynamic Corruption Threshold

est extrêmement instructive.

Une modification de la granularité des requêtes avait déjà obligé un développeur de Mod à repenser l’évaluation de corruption.


31. Mais NetF fournit aussi un avertissement

Le même document annonce dans ses « Known bugs » :

'Only Recover Banned Data'
might cause looping downloads
or complete stalls
if corruption occurs and recovery fails

Et une version ultérieure mentionne avoir corrigé :

some corrupt parts downloading forever

Conclusion

Ne pas porter Only Recover Banned Data tel quel.

Le statut banned est une conclusion heuristique.

Il ne doit pas devenir la source cryptographique de vérité permettant de décider quelles données sont bonnes.


32. Pourquoi la logique « banni = mauvais octets » est circulaire

Chaîne dangereuse :

CBB soupçonne A
→ A est banni
→ on décide que seules les données de A sont mauvaises
→ on conserve les autres
→ si l’hypothèse CBB était fausse ou incomplète :
   la partie ne peut jamais converger correctement

C’est précisément le genre de boucle capable d’expliquer :

redownload loop
complete stall

34. Shadow audit CBB × E6bis

Pour chaque bloc AICH finalement corrompu :

part
aich_block_index
contributors
accepted_bytes_by_contributor
raced?
partial-tail-rescue?
primary/challenger
verified_history_by_contributor
historical_CBB_charge
historical_CBB_ratio
historical_ban_decision

Puis produire un événement :

[CBB_SHADOW]

Part              42
AICHBlock          17
BlockSize          184320

Source A
  bytes accepted   151552
  role             ENDGAME_PRIMARY
  CBB charge       184320

Source B
  bytes accepted    32768
  role             ENDGAME_CHALLENGER
  CBB charge       184320

Result
  block            CORRUPT
  CBB would ban A  no
  CBB would ban B  yes
  mixed provenance yes

C’est exactement le type de cas que nous devons observer avant tout changement.


35. Première amélioration possible : retarder une sanction ambiguë

Si les mesures montrent réellement des faux positifs autour des blocs à provenance multiple, la première évolution ne devrait pas être :

augmenter 32 % à 50 %

pour tout le monde.

Approche plus fine :

bloc corrompu
+
plusieurs contributeurs
+
aucune preuve permettant de distinguer le fautif
→ evidence = AMBIGUOUS

Puis :

ne pas déclencher seul un ban fort
mais conserver l’anomalie
et attendre une répétition indépendante

Cette stratégie colle mieux au Dossier 05 :

preuves graduées
réactions graduées

36. Deuxième amélioration possible : identité sécurisée

Le BlackBox historique indexe la provenance sur :

IP

EvaluateData() finit par :

FindClientByIP()
ou
AddBannedClient(IP)

Limite 2026

Une IP peut être :

dynamique
partagée par NAT
partagée par CGNAT
sortie VPN

Prudence

Ne pas remplacer le ban IP par un ban UserHash non authentifié.

Un UserHash seul est falsifiable.


37. Troisième amélioration possible : séparer attribution et sanction

Architecture historique :

CorruptionBlackBox
→ mesure
→ décide ban
RangeProvenance
       ↓
IntegrityEvidence
       ↓
Trust Policy
       ↓
action graduée

Avantage :

la couche d’intégrité dit :

ce que nous savons

et la Policy dit :

ce que nous faisons

sans mélanger preuve et sanction.


38. Pas de score global « leecher »

Connexion directe avec Dossier 05.

Une source qui a envoyé :

1 bloc corrompu
+
10 GiB valides

n’est pas équivalente à une source qui envoie :

90 % de données corrompues

Et l’intégrité ne doit pas être fusionnée avec :

ModString
QR distant
nickname

Dimension dédiée :

DataIntegrity

avec preuve issue des hashes.


39. AICH root trust : amélioration de diagnostic

AICH Master Hash
state=UNTRUSTED
source_count=...

AICH Master Hash
state=TRUSTED
support=...
consensus=...

AICH Master Hash
state=VERIFIED
origin=...

sans exposer cette complexité dans l’UI normale.

Intérêt

Lorsqu’une récupération AICH ne démarre pas, l’utilisateur saurait si la raison est :

pas de Master Hash
Master Hash non encore trusted
aucun pair compatible avec le même hash
recovery request en cours
recovery data invalide

Aujourd’hui ces états sont surtout visibles dans les logs/debug.


40. AICHTrustEveryHash

Préférence actuelle :

AICHTrustEveryHash=false

Recommandation

Conserver le défaut.

libellé explicite
avertissement
pas de recommandation d’activation

Car cette option désactive précisément le consensus destiné à empêcher qu’un seul pair impose une racine AICH.


41. known2_64.met et résilience locale

La récupération AICH dépend également de métadonnées locales.

Avant d’ajouter un nouveau système :

pour vérifier s’il protège déjà correctement :

.part.met
.part.met.bak
known.met
known2_64.met
preferences.dat
cryptkey.dat
preferencesKad.dat

42. Métadonnées PartFile : idée historique SF/IOM/X-Mod

Le corpus des anciens Mods contient des approches de sauvegarde/reconstruction des métadonnées .part.met.

Conceptuellement elles peuvent être utiles après :

part.met endommagé
crash
migration
.part.met.bak
+
ses propres fonctions de backup

Statut

AUDIT AVANT PORTAGE

Ne pas dupliquer un mécanisme existant.


43. MD4 et SHA-1 en 2026

Du point de vue cryptographique moderne :

MD4 = cassé
SHA-1 = collision-resistant insuffisant pour de nouveaux designs
remplacer MD4 et SHA-1 dans le protocole

ED2K

Le MD4 fait partie de l’identité historique du fichier.

Le changer donne un autre identifiant et casse les liens existants.

AICH

Le SHA-1 et le format du tree font partie de l’interopérabilité AICH.

Un client seul ne peut pas faire comprendre un nouvel arbre aux anciens pairs.


46. Réinitialisation de l’état Endgame après corruption

Lorsqu’une partie complète devient corrompue :

AddGap()

réintroduit brutalement de nouveaux octets manquants.

Cela change :

remaining bytes
remaining blocks
remaining parts
part dispersion
source usefulness
ETA

E6bis calcule justement son risque à partir de ces dimensions.

47. Delayed NNP retrouve ici tout son sens

Dossier 06 :

Delayed NNP à auditer

Dossier 07 explique pourquoi la fonction avait été créée.

Après corruption :

partie auparavant complète
→ devient gap
→ une source qui semblait NNP
   peut soudain redevenir utile

Donc la fenêtre de grâce historique de NetF n’est pas seulement une optimisation générale.

Elle est directement liée au cycle d’intégrité.

48. Corruption telemetry minimale à conserver

parts_corrupt
ich_parts_saved
ich_bytes_saved

aich_recovery_requests
aich_recovery_success
aich_recovery_failover
aich_bytes_recovered

cbb_suspects
cbb_bans
cbb_mixed_provenance_blocks

endgame_corrupt_parts
raced_blocks_later_corrupt
partial_tail_blocks_later_corrupt

Sans stocker durablement les IP ou UserHash complets.


49. Indicateur public possible

INTÉGRITÉ

Parties vérifiées        1 248
Corruptions détectées        3
ICH récupérées                1
AICH récupérées               2
Données sauvées AICH     17,6 Mio

Éviter :

3 leechers détectés

car :

corruption détectée
≠
intention malveillante prouvée

50. Integrity Health diagnostic

Vue avancée possible :

FICHIER
ED2K Hash          OK
MD4 Hashset        complet
AICH Master        TRUSTED
AICH Part Hashset  complet
Recovery Data      disponible

PARTIE 42
MD4                CORRUPT
AICH               CORRUPT
Blocs AICH         53
Vérifiés           52
À retélécharger     1
Recovery source     nouvelle tentative

Cette vue serait extrêmement utile pour comprendre les cas « bloqué à 99,9 % ».


59. Ordre de travail recommandé

A — Audit CBB × E6bis

Priorité la plus forte.

Aucune modification comportementale initiale.

B — Audit Endgame state après corruption

Très lié à E6bis et facile à instrumenter.

C — Audit Delayed NNP autour d’une réouverture de gap

L’étude intégrité donne enfin le contexte exact.

D — Integrity diagnostics

Statistiques et raisons d’échec AICH.

E — Backup metadata

F — seulement si preuves

Modifier politique de sanction CBB.


60. Ce que nous ne devons pas faire

remplacer MD4 dans ED2K
remplacer SHA-1 AICH unilatéralement
accepter un AICH root unique non fiable
faire confiance à un recovery packet sans arbre valide
considérer toute corruption comme du leeching
porter Only Recover Banned Data tel quel
augmenter arbitrairement le seuil CBB
désactiver le final rehash pour accélérer

63. Relation avec le Dossier 05 Trust

Le Dossier 05 proposait :

REMOTE_CLAIM
LOCAL_MEASURED
HASH_VERIFIED
CRYPTO_VERIFIED

Le présent dossier donne un cas parfait :

pair annonce sa PartStatus
→ REMOTE_CLAIM

octets reçus
→ LOCAL_MEASURED

bloc AICH comparé au tree
→ HASH_VERIFIED / HASH_FAILED

partie MD4 + AICH valide
→ HASH_VERIFIED fort

Le futur Trust Engine ne doit jamais remplacer cette hiérarchie.

Il doit la consommer.


64. Relation avec le Dossier 06 Endgame

Baseline :

Endgame by Akshaan E6bis

Le présent dossier n’a pas trouvé de raison de modifier son comportement immédiatement.

Il révèle cependant deux audits obligatoires avant une évolution E8 :

CBB × provenance fine/racing
corruption → recalcul état Endgame / NNP

Cela devient une dépendance explicite de la roadmap.


65. Conclusion

Le système d’intégrité eMule est l’un des sous-systèmes les mieux construits de l’ancien client :

Hashset
→ validation de partie

ICH
→ récupération opportuniste

AICH
→ récupération fine authentifiée par arbre

CorruptionBlackBox
→ provenance et anti-abus

Elle se trouve à la jonction entre deux époques :

CorruptionBlackBox
pensé pour des blocs historiques de 180 KiB

vs

Endgame E6bis
capable de racing,
de rescue partiel
et de provenance plus fine

NetF WARP nous donne un signal historique très fort : son propre passage aux Dynamic Block Requests l’avait conduit à ajouter un CorruptionBlackBoxFix et un seuil de corruption dynamique.

Mais son expérience montre aussi ce qu’il ne faut pas faire : Only Recover Banned Data pouvait conduire à des boucles et des blocages.

mesurer précisément qui a réellement fourni quels octets, conserver les hashes comme arbitres, puis rendre les sanctions plus explicables si les mesures démontrent que le modèle IP/180K historique est devenu trop grossier.



Dossier 07 terminé

ED2K Hashset, ICH, AICH, corruption et récupération

← Dossier 06Dossier 08 →