I want to start implementing this RFC from mid-July 2018, and it is essential that @cgeek , @kimamila and @inso are read and approved beforehand, so please try to prioritize the review of this RFC.
The RFC is available on the gitlab :
Comme la langue de Shakespeare n’est pas ma tasse de thé, je vais expliciter ici dans la langue de Molière les principaux enjeux de cette RFC et les principaux choix que j’ai fait, en plus ça permettra aux curieux de suivre
Les 3 enjeux majeurs de WS2P v2 :
1. La synchronisation via WS2P
Historiquement la synchronisation était assurée par le crawler BMA (c’est toujours le cas dans Duniter 1.6), mais depuis WS2P v1 le crawler BMA n’est plus du tout utilisé et l’on ne garde son code que pour ça. En plus l’API BMA n’est désormais plus qu’une API Client, or la synchronisation ne concerne pas les clients, c’est plus logique qu’elle soit gérée par la couche réseau entre les nœuds duniter.
De plus une nouvelle API Client est prévue a moyen terme, donc BMA a vocation a etre totalement supprimée.
Enfin, je ne vais pas recoder BMA dans Duniter-Rust mais j’ai besoin de la synchronisation, c’est pourquoi avant de pouvoir aller plus loin dans le développement de Duniter-Rust je doit d’abord ajouter la synchronisation dans WS2P, tant que ce ne sera pas fait je suis bloqué.
2. La suppresion des fiches de pair partagées
Historiquement @cgeek avait créé les fiches de pair partagés pour 2 raisons : éviter les conflits d’identification entre 2 nœuds ayant la même clé ET permettre au crawler BMA d’attribuer un préfix différent a chacun des nœuds partageant la même clé.
Ces deux raisons n’ont plus lieu d’être car WS2P a introduit la notion de UUID que je renomme ici NodeId, c’est un nombre aléatoire de 4 octets qui permet de différencier des nœuds qui partageraient la même clé publique.
Désormais tout noeud Duniter du réseau peut être identitifié de manière unique par son couple (NodeId, PubKey) que je nomme NodeFullId.
Ensuite concernant les préfix, ils sont désormais attribués manuellement par l’utilisateur.
Enfin avec des fiches de peer partagées l’élagage des anciens endpoint est plus complexe car le noeud ne peut pas évaluer la pertinence de supprimer les endpoint venant d’un autre noeud qui partage la même fiche. Enfin dans l’absolue il pourrait mais l’élagage est infiniment plus simple a coder si tout les endpoints de la fiche de peer sont gérés par un seul noeud, il peut directement supprimer tout endpoint dont il ne se sert plus et qui n’ai pas dans la config manuelle.
Pour toute ces raisons, j’applique le principe du Rasoir d’Ockham et je supprime purement et simplement les fiches de peer partagés. Les fiches de peer v11 seront donc identifiés de manière unique par leur couple (NodeFullId, Blockstamp).
3. La binarisation partielle
En vue de préparer la future binarisation du protocole blockchain, je binarise déjà partiellement WS2Pv2. Ainsi les fiches de pair et les HEADs sont désormais générés et signés au format binaire.
La RFC définie également un format JSON, format dans lequel les fiches de peer et HEADs seront transformés avant d’être transmis a BMA, car de toute façon il faut bien garder un format “human-readable”.
Mais le gros avantage c’est que comme les signatures sont vérifiées sur le format binaire, et que le format JSON est auto-généré directement depuis le format binaire, on évite de facto le problème de l’ambiguïté du parsing des format texte.
Les messages de négociation de connexion (CONNECT, ACK, OK) sont également binarisés. D’ailleurs le protocole de négociation est modifié avec l’ajout d’une étape permettant de spécifier les features supportées (voir plus bas).
Enfin les requêtes et les documents blockchain(=tout les documents sauf la fiche de peer) ne sont pas binarisés coté Duniter-Typescript. Et ceux pour 2 raisons :
- Je ne propose dans cette RFC que des changements pour lesquels je me suis assuré d’avoir les compétences pour les implémenter dans Duniter-Typescript, et ceux en un temps court car je souhaite que WS2Pv2 soit intégré à Duniter-Typescript dés la version 1.7 !
- Pour des raisons de performance il serait plus intelligent de partager les documents binaires dans le format sous lequel ils seront signés, or ce format n’est pas encore défini puisque les spécifications de la binarisation du protocole blockchain ne sont pas encore écrites.
En revanche coté Duniter-Rust tout sera binarisé tout de suite, pour la simple et bonne raison que je manipule déjà tout au format binaire donc c’est plus simple pour moi de faire comme ça.
Duniter-Rust restera capable de lire les documents et requêtes au format JSON WS2Pv1 comme il le fait déjà actuellement, ainsi que de faire rebondir ces mêmes documents, en revanche je ne prévois pas de coder la génération de ces formats.
Les features
Les nœuds Duniter seront capables d’adapter leur façon de communiquer en fonction des features supportées par le noeud distant.
J’ai pensé WS2Pv2 pour qu’il soit évolutif et qu’il permette une compatibilité ascendante, ainsi les nœuds sous de futures versions (par exemple WS2Pv3) pourront communiquer automatiquement avec des nœuds WS2Pv2 sans qu’on est besoin de développer un code de gestion de compatibilité descendante, ce sera automatique.
Enfin il y a 2 features particulières que j’intègre déjà a WS2P v2 (sous Duniter-Rust uniquement mais @cgeek ou d’autres contributeurs peuvent très bien intégré ces features un jours dans Duniter-Ts s’il le souhaitent) : La compression et le bas débit.
Le protocole websocket intègre des spécifications permettant de négocier automatiquement la compression des donnés transmises : c’est l’extension de protocole permessage-deflate.
La crate que j’utilise pour Duniter-Rust permet de s’en servir alors je ne vais pas me priver, je trouve notamment la compression particulièrement intéressante pour la transmission des chunk lors d’une synchronisation par exemple.
En plus c’est hautement configurable, on peut fixer la taille en dessous de laquelle le message ne sera pas compressé (ben oui la compression et la décompression ayant un coût, ça ne vaut le coût que pour les messages suffisamment longs).
Un utilisateur derrière une connexion a faible débit pourra activer la feature bas débit dans sa config, ainsi le noeud préviendra qu’il faut faire attention a ne pas lui envoyer (ni lui demander) des trop gros volumes de données. Tout ce qui est rebondi automatiquement par exemple, ne sera pas systématiquement rebondis a ces nœuds la, concernant les piscines wot, seul les dossiers complets leur seront transmit, etc… j’ai plein de petites idées pour limiter le volume de données envoyés et reçus par ces nœuds.
Ce qui change pour les Clients
Le format des fiches de peer et des HEADs fournies par BMA : et c’est tout.
Vous devrez juste ajouter le code permettant de parser les fiches de peer v11 et les HEADs v3 puis les binariser pour vérifier leur signature.
En revanche il y a un changement spécial pour ceux qui ajoutent manuellement des endpoints tiers a leur fiche de peer.
La RFC définie un nouveau format de endpoint générique que devrons respecter toutes les API, y compris les API tierces, ce format générique est pensé pour permettre des usages assez étendus grâce au concept de features. Les features sont stockés sous la forme d’un entier non signé de 16 bits (extensibles), ce format supporte donc jusqu’à 16 features différentes (et même potentiellement une infinité via la feature EXTENDED).
Tout les endpoints qui avaient été saisis manuellement devront être recréer manuellement en respectant le nouveau format :
General utf8 endpoint format :
API_NAME VERSION NF1 .. NFn (AF1 .. AFn) IP4 [IP6] HOST PORT PATH
NF := NETWORK_FEATURE
AF := API_FEATURE
Lorsque le endpoint ne déclare aucune API_FEATURE les parenthèses sont facultatives.
Exemples concrets :
ES_CORE_API 1 g1.data.duniter.fr 443
WS2P 2 S (DEFLATE LOW RBF) g1.durs.ifee.fr 443 ws2p
Les features des API inconnus de Duniter pourront être déclarer de deux façons différentes, exemples avec une api tierce qui intégrerai les features 0 et 3 :
-
Soit en indiquant directement la valeur de l’entier non signé correspondant :
MON_API_TIERCE 1 (9) HOST PORT
-
Soit en indiquant explicitement la suite de bits :
MON_API_TIERCE 1 (0b0000_0000_0000_1001) HOST PORT
le premier format est plus compact mais le deuxième plus explicite, a chacun de choisir selon.
Tout est détaillé dans la RFC :