/!\ Significant : RFC WS2P V2 /!\

ws2p
duniter-rs
duniter-ts
ws

#1

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 :slight_smile:

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 :

  1. 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 !
  2. 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 :

  1. Soit en indiquant directement la valeur de l’entier non signé correspondant :

    MON_API_TIERCE 1 (9) HOST PORT

  2. 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 :


Synchro via WS2Pv1
#2

16 features ?
ça me semble beaucoup aujourd’hui et très peu demain.

prévois-tu une feature “extended” qui utilise un des 16 slot pour dire : il y a un autre entier non signé de 16 bits (ou plus) pour gérer les feature étendu qui ne logeait pas dans les 16 -> 15 premières ?


#3

Merci @1000i100, c’est une excellente idée je vais ajouter ça :slight_smile:
J’ai pensé que 16 features suffirait pour du très long terme mais ta solution est élégante et ne sacrifie qu’un seul bit, ça me conviens, si tu a d’autres idées comme ça pour améliorer la RFC surtout n’hésite pas, je ne commencerai a l’implémentée que mi-juillet :slight_smile:


#4

Pour vous motiver a review la RFC j’y est inséré volontairement une erreur, le premier qui la trouve gagnera 100 Ğ1 !

Évidemment si vous trouvez des erreurs involontaires ça ne compte pas, mais faites les remonter quand même :wink:


#5

Ah ? tu ne fait pas façon Donald Knuth (créateur de TeX) en proposant 1 Ǧ1 pour la première erreur trouvée, 2 pour la 2nd, 4 pour la 3ème… ?


#6

Je vais exploser mon budget si je fais ça :rofl:
A moins que l’on puisse prélever directement dans la caisse de Duniter pour remercier ceux qui trouvent des erreurs dans les RFC, ce serait une très bonne idée tiens :slight_smile:


#7

Première suggestion : pour le format utf-8, utiliser une normalisaiton NFKC :
http://dictionnaire.sensagent.leparisien.fr/Normalisation%20Unicode/fr-fr/
Çà me semble cohérent avec l’approche RFC : normaliser pour limiter les ambiguïté / divergence d’implémentation qui génèrent des problème.

Sinon, un peu au hasard pour l’erreur volontaire : les blockstamp sur 36 bit ça me fait bizarre, ce n’est pas 32 ? (mais sans avoir lu les précédente/participé à l’implémentation de blockstamp, ça me semble difficile de repérérer des erreurs efficacement)


#8

Oui bonne idée, je vais rajouter dans la RFC que touts les champs de type string utf-8 doivent êtres normalisés en NFKC avant d’être envoyés, et même plus généralement coté Duniter-Rust je normaliserai directement toute saisie utilisateur comme ça je serai tranquille :slight_smile:

Bien essayé mais non, le format du type Blockstamp est détaillé dans la section Conventions -> Binary format -> types notation :

Blockstamp : BlockNumber(u32) + BlockHash([u8; 32])

C’est bien du 36 octets : 4 octets pour le numéro du bloc plus 32 octets pour son hash.


#9

Ça m’arrangerait aussi que WS2P permette la synchro. Donc, le plus tôt sera le mieux.

J’essaierai de lire plus en détail cette RFC cette semaine.


#10

Bon ça ne fait que repousser la limite a 31 features au lieu de 16 ce qui ne change pas grand chose en fait, finalement j’ai opter pour un champ features de taille variable indiquée sur 1 octet, on peut donc monter jusqu’à 255 octets de features soit 2040 features là on n’est certain ne de pas en manquer pendant des siècles :stuck_out_tongue:
Et ceux sans aucun coût sur la taille des messages en v2 puisque je passe de 2 octets pour les features à… 2 octets pour les features -> ben oui quand le noeud n’utilise que les 8 premières features (ce qui est forcément le cas en v2 comme il y en a moins que 8), ben il déclare une taille de 1 octet, ce qui fait bien 2 octets au total : 1 pour la taille + 1 pour les features.


#11

Effectivement ça peux marcher comme ça, sinon il y a la possibilité de faire :
16slot dont un pour en ouvrir 32 dont un pour en ouvrir 64, dont un pour en ouvrir 128, dont un pour en ouvrir 256, etc. façon allocation mémoire exponentielle.


#12

J’ai posté de premiers commentaires principalement sur des typos ou problèmes de rendus, je me concentrerais sur les détails techniques plus tard. :slight_smile:


#13

Merci c’est corrigé :wink:


#14

Alors j’ai pas encore lu ta RFC, mais je me posais la question de savoir si I2P ne pourrait pas être intégré nativement au réseau WS2P :

https://geti2p.net/fr/comparison/tor


Qu’en penses tu ? I2P semble orienté anonymat en P2P. Le réseau serait nativement anonymisé, sans avoir à réaliser de configuration particulière. Je ne connais pas dans le détail I2P donc il y a surement des subtilités qui m’échappent…


#15

Tor est beaucoup plus développé, mature, maintenu par beaucoup de monde, avec plus de doc et a fait ses preuves. Perso je préfère rester sur Tor.

Pour I2P c’est beaucoup trop tôt, trop peu de dev, trop peu de doc, trop peu de bibliothèques, on en reparle dans 3/4 ans si ça à bien pris d’ici la :wink:


#16

Evidemment, par contre est-ce que l’implémentation de WS2Pv2 est compatible avec des évolutions drastiques du type “migration sur un réseau anonyme” ?


#17

Je pense que oui, j’ai volontairement rendu complètement abstrait le concept de surcouche réseau, ainsi les surcouches TLS et TOR ne sont que des features réseau. Sachant que je laisse beaucoup de places pour de futures Ğfeatures réseau, il sera normalement possible d’implémenter plus tard de nouvelles couches réseau sur WS2P sans avoir a modifier le code de WS2P lui-même :slight_smile:


#18

Parfait :slight_smile:

Bon j’essaie de trouver le temps de lire cette RFC avant la fin du mois…


#19

Oui il faut car je vais bientôt ne plus trouver quoi coder dans duniter-rust en attendant, j’ai vraiment besoin de commencer a l’implémentée mi-juillet mais ça veut dire qu’il faut qu’elle soit entièrement validée d’ici la !


#20

@cgeek @kimamila avez vous pu prendre le temps de regarder la rfc ? Je prévois de commencer a coder sont implémentation dés dimanche prochain donc je me permet de vous relancer :slight_smile: