Nouvelle API : WS2P

Comme indiqué dans mon 1er post sur les développements nouveaux de Duniter, qui dit nouveau développement dit « explication préalable ». Voici.

Je compte développer pour Duniter 1.6 une nouvelle API de communication pour les nœuds. Je compte nommer celle-ci « WS2P » pour WebSocket 2 Peer.

Il s’agit d’une communication de type P2P directe et bidirectionnelle, qui ne nécessite pas de s’exposer soi-même publiquement sur le réseau. Typiquement, cette API permet de se connecter au réseau Duniter depuis à peu près n’importe quel réseau privé (derrière une box sans ouvrir de port, ou derrière un routeur 3G/4G comme son smartphone) et sans configuration particulière. Cela devrait aussi fonctionner dans un réseau d’université, d’entreprise, etc.

Cette API repose sur une technique permettant de traverser la barrière NAT des box et même les pare-feu. Tout cela est déjà très standard et nous utiliserons ici la variante HTTP de cette technique : les WebSocket.

Motivations

Les motivations principales pour le développement de cette API tiennent en quelques points :

  1. Zéro configuration
  2. Communication P2P bidirectionnelle
  3. Sans exposition systématique à l’extérieur (l’ouverture de port n’est pas nécessaire)
  4. Avoir une API spécialisée pour la communication inter-nœuds, plus rapide et allant à l’essentiel
  5. Avec authentification : tous les messages sont signés, ce qui autorise de nombreux contrôles supplémentaires ! (niveaux de trafic, tentatives d’attaques, etc.)

Aucun de ces points n’est satisfait avec l’API actuelle « BMA ».

Description fonctionnelle

Nous partons ici du principe que l’on a un nœud qui s’est déjà synchronisé sur une blockchain et possède un ensemble de fiches de pair, comme c’est le cas aujourd’hui quand on fait la synchronisation initiale.

Connexion réseau

Niveau 1 : Active

Tout nœud qui démarre scrute les fiches de pair à la recherche de nœuds acceptant la connexion WS2P. Le nœud tente alors de s’y connecter (dans la limite d’une dizaine de pairs par exemple), afin d’établir un lien de connexion.

Niveau 2 : Passive (facultatif)

Peu importe ce qui s’est passé au Niveau 1, le nœud peut aussi exposer sa propre interface réseau afin d’être sollicité par d’autres nœuds en vue qu’ils établissent une connexion de Niveau 1.

Ce niveau requiert une part de configuration identique à BMA aujourd’hui. Cela ouvre un port et expose une API WebSocket vouée uniquement à la connexion WS2P.

Ce niveau est facultatif, d’où la prétention du « Zéro configuration ». Mais bien sûr si tous les nœuds sont en Niveau 1, aucune communication WS2P n’est possible. Il doit nécessairement exister des Niveau 2 sur le réseau.

Plus il existe de Niveau 2 sur le réseau, plus celui-ci est décentralisé d’un point de vue communication et peut se mailler fortement.

Niveau 3 : Invitative

Si le Niveau 2 est activé, le nœud peut inviter d’autres nœuds à se connecter à lui. La liste des nœuds invités est configurable, et est vide par défaut.

Connexion logicielle

Une fois la connexion WS2P établie au niveau réseau, celle-ci doit passer l’étape d’acceptation de la connexion au niveau logiciel.

Négociation : connexion entrante

Celle-ci requiert une authentification : le nœud sollicitant doit être membre pour que la connexion soit autorisée¹. De plus, si le nœud sollicité a déjà trop de connexions, il peut refuser.

¹ : il existe toutefois des exceptions, configurables : on peut autoriser spécifiquement des clés, membres ou non-membres, de notre choix : ce sont les nœuds invités (voir : « Niveau 3 »).

Négociation : invitation de connexion

Toutefois, dans le cas d’une invitation on ne souhaite pas établir la connexion entrante : on souhaite inviter le nœud sollicité à devenir le nœud sollicitant dans le cadre d’une nouvelle connexion.

Dialogue

Quand une connexion WS2P est établie, toute communication se fait sur la base d’un dialogue Requête/Réponse. Plusieurs requêtes peuvent se faire en parallèle pour une même connexion, et toutes les connexions sont parallèles également.

Toute Requête et toute Réponse est signée. En cas de signature incorrecte, ou d’auteur différent de la connexion, la communication est considérée invalide. Toute communication invalide entraîne la fermeture de la connexion.

Les requêtes et réponses ne sont pas signées, on considère que l’authentification initiale suffit à établir la légitimité des communications.

Description technique

Niveaux de connexion

Le Niveau 1 est une simple requête de connexion à un WebSocket.

Le Niveau 2 implique la déclaration d’un point d’entrée dans la fiche de pair, par exemple :

WS2P duniter.org 443

Note : si le port de connexion est 443, le protocole de communication réseau est wss://. Sinon, le protocole est ws://.

Le Niveau 3 est équivalent au Niveau 1, qui va basculer sur une nouvelle connexion de Niveau 2.

Négociation

L’étape de connexion logicielle a deux cas possibles :

  • Connexion entrante : dans ce cas le nœud connecté physiquement envoie un message NEGO: IN (grammaire encore à l’étude). Cela aboutit sur une connexion établie ou une connexion fermée immédiatement.
  • Invitation : dans ce cas le nœud connecté physiquement envoie un message NEGO: OUT. Cela aboutit systématiquement à une connexion qui se ferme immédiatement. Le nœud sollicité peut alors décider de devenir sollicitant, ou pas.

Dialogue

Reste à établir une API interne de dialogue, en JSON. Par exemple :

Requête :

{
  "uuid": "c045a28e-aa5a-46cd-80b7-19729740c4d3",
  "message": "block",
  "body": {
    ...
  }
}

Réponse :

{
  "answer": "c045a28e-aa5a-46cd-80b7-19729740c4d3",
  "code": 200,
  "body": {
    ...
  }
}

Dans tous les cas cette API sera strictement limitée aux besoins du réseau de nœuds, et ne sera pas à destination des clients (Sakia, Cesium, Silkaj, …).

Transition

Étant donné un réseau qui communique exclusivement à travers BMA, les nouveaux nœuds utilisant WS2P sont initialement isolés : les nœuds BMA causent entre eux, les nœuds WS2P de même.

Pour qu’un pont s’établisse entre les deux réseaux, il faut avoir des nœuds qui utilisent à la fois BMA et WS2P simultanément.

Ce peut tout à fait être le rôle de nœuds miroir, qu’ont aurait invité à se connecter à notre nœud membre afin qu’ils soient bien au courant de ce qui se passe sur le réseau WS2P. Typiquement, je vais personnellement inviter le nœud g1.duniter.org à se connecter avec le mien.

Impact sur les clients

Les clients Sakia, Cesium ou Silkaj ne peuvent plus interroger directement les nœuds membres.

C’est un impact très fort, mais voulu afin que les nœuds Duniter soient libérés des sollicitations externes et puissent se concentrer sur leur travail de calculateur.

Comment communiquer, alors ?

Ces clients pourront toujours utiliser l’API BMA sur les nœuds où celle-ci est activée. Il s’agira principalement de nœuds miroirs a priori.

Donc les algorithmes qui prenaient en compte le caractère membre d’un nœud devront être revus à terme (par exemple Sakia, en tient compte pour connaître le consensus réseau le plus pertinent).

De même la vision réseau devra changer, puisqu’un nœud connecté via WS2P ne sera pas joignable par un client (sauf à ce que le client sache parler WS2P, ce qui est un autre sujet).

Comment connaître le consensus actuel ?

Des nouveautés seront introduites, à la fois dans WS2P et BMA, afin de connaître l’état du réseau et son consensus actuel. Ce point est en lien étroit avec l’algorithme de résolution de fork. L’arrivée de WS2P et de ces nouveautés sera d’ailleurs une étape clé permettant de nouveaux algorithme de résolution.

Je pense avoir à peu près tout dit. J’écouterai bien entendu vos retours, et j’espère bien entendu ne pas me planter :slight_smile:

J’accueillerai bien entendu toute âme de testeur quand le temps sera venu !

9 Likes

Tu pourras bien-sur compter sur moi pour tester cette API :wink:

Connexions de Niveau 1 : quels choix ?

Dans le cadre du Niveau 1, il peut exister de nombreux pairs membres auxquels ont pourrait se connecter. Lesquels choisir, sachant qu’on souhaite avoir une liste restreinte de connexions WS2P ?

Je pense partir sur un algorithme que favorise la connexion auprès de nœuds membres dont le propriétaire se trouve dans la fenêtre courante¹ des calculateurs.

C’est-à-dire qu’on tente de se connecter en priorité aux nœuds qui calculent effectivement des blocs. Ce sont bien eux les plus importants.


¹ : la fenêtre courante des calculateurs est une notion technique, mais grosso-modo elle correspond aux derniers blocs dont on estime que le calculateur est « potentiellement en train de calculer ». Exemple dans Remuniter : la liste « fenêtre courante » affiche les calculateurs de la fenêtre courante.

+1 pour l’usage du protocol websocket
.https://tools.ietf.org/html/rfc6455

Registration of “ws” Scheme
Registration of “wss” Scheme
.https://tools.ietf.org/html/rfc6455#page-54
"A |ws| URI identifies a WebSocket server and resource name."

“A |wss| URI identifies a WebSocket server and resource name and
indicates that traffic over that connection is to be protected via
TLS (including standard benefits of TLS such as data confidentiality
and integrity and endpoint authentication).”

dixit: “Note : si le port de connexion est 443, le protocole de communication réseau est wss://. Sinon, le protocole est ws://.”

On est bien d’accord que la considération du port n’est pas prise en compte par le schema de l’URI (ws:// wss://)
que en l’occurence tu fais toi même ce choix arbitraire ?


Rien a voir avec ton API mais sur le protocole:
J’ai travaillé avec des modules NodeJs / websocket sur une petite appli il y a un mois ou deux pour tester ce protocole.
J’ai utilisé un navigateur (chrome / firefox / safari ) pour faire une app de “chat” vidéo.

Pour l’implémentation de “wss” c’était comment dire; un peu la guerre pour générer les certificats dont chrome/safari sont très regardant sur leur authenticité … même pour travailler en local…

Exact. Dans le même temps pourquoi utiliser le port 443 pour faire du ws:// ?

Les nœuds Duniter vont majoritairement travailler en ws://, de toute façon il y a une authentification logicielle derrière (que l’on reconnaît déjà plus que la pyramide X-509) donc le wss:// n’a pas beaucoup de sens ici.

Simplement, je peux comprendre qu’il existe ce cas, alors voilà j’en tiens compte.

Génial c’est un gros chantier mais cela va à terme fortement stabiliser le réseau et donc renforcer la confiance en la monnaie :slight_smile:

Tu pourra compter sur le nœud mirroir g1.monnaielibreoccitanie.org que mes nœud membres inviteront pour faire le pont entre les réseaux BMA et WS2P :wink:

tu peut définir une liste de priorité pour établir les X connections WS2P visés :

  1. Demander aux nœuds invités personnellement par l’utilisateur s’il y en a
  2. Demander aux nœuds membres dans la fenêtre courante
  3. Piocher aléatoirement dans la liste des autres fiches de peer de nœuds membres

Et concernant le nombre X de connections WS2P ne pense tu pas que ce nombre devrai dépendre de la taille totale du réseau ? Ou qu’il puisse être réglable en fonction de la bande passante disponible ? (si la détect automatique de la bande passante disponible est trop complexe ça peut être un paramétré de config renseigné par l’utilisateur avec par exemple 3 niveaux : slow, medium, fast)

3 Likes

Bien !

Alors je ne l’ai pas précisé, mais les X connexions sont divisées en 2 parties :

  • les connexions Niveau 1, mettons limitées à 10
  • les connexions Niveau 2, mettons limitées à 10 aussi hors invités

Donc pour ton point 1), les nœuds invités personnellement se trouveraient plutôt dans la case « Niveau 2 invité ». Donc c’est hors de la recherche de pairs pour le Niveau 1. Mais à la limite, pourquoi pas avoir la possibilité de configurer soi-même quelques nœuds à prioriser pour son Niveau 1, mais ce serait juste une liste “des nœuds préférés” de Niveau 1 en quelque sorte.

Oui ça c’est certain.

Oui, encore que j’aurais tendance à regarder les nœuds qui suivent la fenêtre courante, avec un peu d’aléatoire quand même oui.

Je ne sais pas trop, comme tu dis la bande passante est aussi un critère. Je partais plutôt pour une valeur fixe (10, 10), et éventuellement laisser la possibilité à l’utilisateur de changer ces valeurs.

Pourquoi pas avec des niveaux prédéfinis oui. Car le calcul de la bande passante, bah j’aimerais bien, mais là tout de suite je ne sais pas faire.

Alors ca va pas resoudre le probleme de la bande passante dans l’absolue,

cependant je te recommande de jeter un coup d’oeil:
.Reliability and Congestion Avoidance over UDP | Gaffer On Games
…les titres que sont :

Congestion Avoidance
Measuring Round Trip Time
Simple Binary Congestion Avoidance

vers le bas de la page.
sachant que dans cet article , la connection se fait sur la couche UDP et toi TCP avec les Websockets.

Il serait intéressant en terme de vocabulaire de nommer les noeuds “accessibles depuis l’extérieur” et “non-accessibles depuis l’extérieur” avec 2 termes biens précis. Ca permettrait notamment de les nommer dans des graphes et de surveiller la qualité du réseau (qu’il y ait assez de noeuds accessibles)

1 Like

Vision du réseau

Du coup, comme il ne serait plus possible d’interroger chaque nœud publiquement pour savoir s’il est connecté et quel est son bloc courant, j’imagine ceci :

Chaque nœud, ayant connaissance des nœuds auxquels il est physiquement connecté, peut donner sa liste des nœuds « connectés » à lui. On peut imaginer que chaque nœud accepte de partager cette information et celle du niveau suivant (les nœuds de ses nœuds) sur demande d’un pair connecté, on a de cette manière l’accès à 3 niveaux de connexion à partir d’un seul nœud.

Si l’on imagine un maximum de 20 nœuds connectés par nœud, on a donc un potentiel de vue réseau de 20³ = 8.000 nœuds. Ce qui semble largement suffisant vu que Bitcoin est aux environs de 5.000 nœuds je crois, et encore ça se tasse.

Retourner 400 lignes d’information où chaque ligne est :

  • une clé publique (44 caractères)
  • un blockstamp, le HEAD du pair en question (64 caractères de hash + 8 caractères de n° de bloc = 72 caractères)
  • une signature authentifiant cette donnée (88 caractères)

= 400*(44 + 72 + 88) * 2 octets (UTF8) = 160ko.

Et pour le nœud qui récolte ces informations, ça fait 20 nœuds à interroger, soit 20*160 = 3,2Mo de données. Sans être léger, pour un réseau de 8.000 nœuds ça me semble tout à fait acceptable.

À notre échelle, aujourd’hui, on a environ 50 nœuds (je gonfle le chiffre), donc sur 3 niveaux ça fait en moyenne 3,6 nœuds connectés par nœud, arrondissons à 4 pour gonfler.

Donc remonter l’état de l’ensemble du réseau coûterait aujourd’hui à 4*(44 + 72 + 88) * 2 * 4 = 6,375 ko.

Autant dire peanuts.

Partage de l’état du HEAD pour chaque pair

Si ce que je disais concerne uniquement la remontée de la vue complète, il n’empêche qu’il faut préalablement la construire et que celle-ci bouge constamment (les pairs se connectent / se déconnectent, mais aussi leur HEAD change toutes les 5 minutes en moyenne, et pas tous exactement en même temps).

Là je réfléchis encore, mais il y a un truc à faire c’est sûr.

3 Likes

De quel problème parles-tu ?

Proposes ! :slight_smile:

Dans un cas tu as un nœud avec accès asymétrique (Niveau 1 d’actif seulement), dans l’autre tu as un symétrique. J’ai du mal à croire qu’il n’existe pas déjà une nomenclature à ce sujet !

de ton allusion a la bande passante…(c’est surement HS ma réponse par consequent…)
Edit: soit dit en passant me vient à l’esprit de , pourquoi la couche TCP ?:grin:

Ah oui pour sa mesure ! Le principal problème est d’ordre technique, je ne sais pas si j’ai accès à la mesure du flux de données avec les API Node.js que j’utilise pour les WebSocket.

Mais sinon dans l’absolu oui, je pense savoir le faire !

Pour éviter de se compliquer la vie, je n’ai pas un temps infini alors je fais ce que je sais faire et dans un temps assez court.

Ceci dit rien n’empêche d’améliorer ça plus tard, pour ceux qui sauront le faire.

Je trouve très intéressant et je comprends l’utilité de cette fonctionnalité.
Malgré tout j’ai une petite gène que vous arriverez sûrement vite à dissiper. En l’état, j’ai l’impression que ça crée plus de centralisation car les nœuds membres exposés me semblent avoir plus de poids étant donné qu’ils sont nécessaires à la bonne santé de la blockchain.

Est-ce qu’une incitation à être un nœud exposé est suffisante pour garder la sécurité une blockchain décentralisée ?

  • Si oui, alors on peut considérer qu’on entre dans le domaine non technique avec la question :
    Comment encourager les gens à être des nœuds exposés ?

  • Mais si non?

1 Like

La je pense qu’il ya qqch a faire avec les arbres de merkel, d’ailleurs on pourrait en imbriquer deux :
chaque nœud pourrait avoir un arbre de merkel du HEAD des nœuds auxquels il est directement connecté

  • chaque nœud pourrait avoir un 2ème arbre de merkel des racines des arbres de merkel de chaque nœud auquel il est connecté.

Il serait ainsi très rapide de savoir quel head a été modifié :slight_smile:

2 Likes

C’est déjà le cas avec les nœuds membres calculant.

“zéro config” donc a terme quand cet API sera stable elle sera activée par défaut sur tout nœud duniter avec une clé membre, tout comme le calcul de blocks s’active par défaut, c’est transparent pour l’utilisateur :wink:

2 Likes

edit : grilled par Eloïs.

En quoi la situation aujourd’hui serait moins centralisée justement ?

En fait, il me semble que l’exposition doit être le comportement par défaut s’il est possible. Sinon, on peut vite tomber dans une situation délicate où « plus rien ne se partage », notamment les blocs. Blockchain arrêtée, fin de l’histoire. Aujourd’hui cette partie fonctionne bien car les utilisateurs mettent un point d’honneur à ce que leur nœud soit bien visible en vert dans Sakia / Cesium.

Donc pourquoi pas avoir l’exposition activée par défaut ? Notamment si UPnP est présent et fonctionne, ça me semble important.

Mais après il y a une problématique encore plus profonde … c’est justement d’être « exposé » publiquement. S’il y a une liste publiquement consultables des nœuds existants, alors une personne avec suffisamment de moyens peut assaillir ces nœuds de connexions zombies. Je ne sais pas dans quelle mesure c’est possible, mais j’y pense et ça fait froid dans le dos.

L’idéal serait donc que ce point de connexion soit a minima privé, et même mieux, dynamique.

Mais je n’en suis pas là :confused:

2 Likes

Aujourd’hui, les gens n’ont pas le choix d’exposer s’ils veulent calculer. Il y aura donc bien une différence avec des nœuds écrivains de la blockchain exposés qui ont un poids plus important que des nœuds écrivains de la blockchain non exposés.

Demain les gens en revanche auront le choix sauf si leur box est mal configurée et autorise UPnP et dans ce cas on leur force la main. C’est une solution…

On peut imaginer que Remuniter rémunère au double les noeuds qu’il arrive à contacter depuis l’extérieur par exemple…

A noter que pour les clients, ces noeuds n’apparaitront toujours pas en vert. Mais au moins, ils répartiront bien le calcul !

2 Likes