Liste des endpoints

Lors d’une discussion hier avec @kimamila, nous avons parlé de la liste des endpoints. Cela concerne à la fois les endpoint RPC de Duniter, les endpoint GraphQL de duniter-squid, des datapods, des passerelles ipfs… Et cela rejoint la discussion "Fiches de pair" de datapods.

Mon point de vue initial était que l’on pouvait hardcoder dans les clients une liste de endpoints de bootstrap “fiables” au sens où il y avait peu de chance qu’ils tombent tous en panne dans l’année à venir. L’utilisateur pouvant toujours ajouter un endpoint manuellement. Cela laisserait assez de temps pour construire une autre solution post-migration.

Mais Benoit a insisté sur la nécessité de pouvoir modifier la liste des endpoints sans intervention de l’utilisateur et sans déployer une mise-à-jour de l’application. J’ai donc re-priorisé cette tâche s’insère dans la question Oplog onchain vs offchain.


En v1, chaque nœud pouvait déclarer un endpoint dans son fichier de configuration :

$ head .config/duniter/duniter_default/conf.json 
{
 "currency": "g1",
 "endpoints": [
  "BMAS g1.trentesaux.fr 443",
  "WS2P 23456abc g1.trentesaux.fr 443 /ws2p"
 ],

Ces informations étaient partagées dans les fiches de pair et utilisées par Cesium et Kazou pour avoir une liste d’API à explorer pour sélectionner des nœuds à jour et avec un bon temps de réponse.

En v2, l’équivalent de ws2p est DUNITER_PUBLIC_ADDR, et pour rpc (équivalent
bma) il faut qu’on ajoute PUBLIC_ADDR (je m’en aperçois maintenant @Pini, → #246).

--public-addr <PUBLIC_ADDR>...
    Public address that other nodes will use to connect to this node.
    This can be used if there's a proxy in front of this node.

Reste à publier une liste de endpoint duniter-squid et datapod. Le plus efficace serait de les déclarer onchain avec un extrinsic dédié du style remark, mais qualifié, et de les stocker dans la base de données hors chaîne de Duniter via OffchainIndex. En gros, c’est là qu’on met les informations utiles mais optionnelles car non nécessaires au consensus. Il faut s’occuper de #97 et à l’occasion voir ce qu’on peut faire pour les autres endpoints.

Et ensuite se pose la question de la mise-à-jour de ces listes : comment mettre en avant les endpoints les plus fiables et retirer ceux qui ne répondent plus ou ne sont plus à jour ? Peut-être qu’un système de upvote / downvote par les membres pourrait être intéressant à implémenter dans un deuxième temps.

3 Likes

Je ne suis pas sûr de comprendre. N’est-ce pas justement déjà pris en charge par la variable d’environnement DUNITER_PUBLIC_ADDR ?

3 Likes

Oups, oui tu as raison c’est effectivement uniquement pour la connexion p2p, et il nous faut une autre manière d’indiquer le endpoint rpc. C’est ça que je cherchais et j’ai lu trop vite :person_facepalming:. “address that other nodes will use to connect to this node” ça veut dire “p2p” pas “rpc”. je trouve qu’il manque une option --public-rpc-addr ou quelque chose comme ça, mais c’est à modifier dans substrate directement, pas dans Duniter. Merci pour ta réponse :pray: :smile_cat:

2 Likes

Par contre, ils sont vraiment bouchés sur le stackexchange substrate : networking - How to declare a RPC endpoint in the node config? - Substrate and Polkadot Stack Exchange, ils comprennent pas du tout la question.

Idée avec @1000i100 : avoir un oracle blockchain qui épure la liste automatiquement avec des checks http sur la dispo + synchro. (non prioritaire tant que la liste reste humainement gérable)

cf

3 Likes

Je viens alimenter ce fil de deux discussions.

La première avec @1000i100 pendant la Ğ1ntada 2024. Au lieu d’avoir un oracle qui produit des inherents, ce serait un simple petit programme offchain qui fait le ménage dans la liste de nœuds. Parce que une telle liste ne peut résulter d’un consensus. Chaque membre pourrait déclarer un endpoint quand il devient disponible (éventuellement avec une limite antispam) et retirer le endpoint s’il est décommissionné. Les nœuds testeraient régulièrement la liste des endpoints sur certains critères (joignabilité, genesis, état de synchro…) et ne fourniraient au clients que des endpoints valides, ce qui “prémâche” le travail pour les clients qui auraient alors moins de endpoints à tester et un plus gros taux de succès.

La deuxième avec @poka hier au téléphone. Plutôt que de s’embêter à développer des fonctionnalités complexes qui ne changent rien pour l’utilisateur final, on hardcode une liste dans les clients, et on complète cette liste par une liste modifiable sans mise-à-jour disponible via gitlab (du genre https://git.duniter.org/clients/cesium-grp/cesium2s/-/raw/master/endpoints.json). C’est certes centralisé, mais tout le monde peut faire une MR pour compléter cette liste et c’est soumis au processus de review gitlab.

La deuxième option est largement suffisante à mon avis dans un premier temps, ça pourra être remplacé post migration, et ça nous donne une chance de finir cette migration en un temps raisonnable. Donc j’annonce : je ne compte pas développer de système de endpoint distribué. Il en sera de même pour duniter squid et les datapods. Comme d’habitude, ça reste du logiciel libre donc si quelqu’un veut l’implémenter, c’est avec plaisir !!

2 Likes

À force de réfléchir, je viens de penser à une troisième option qui a l’avantage d’être un intermédiaire ne demandant quasi aucun travail : abuser des commentaires de transaction. Je viens de dériver des comptes dans ce but :

//Alice//gdev-endpoint/duniter 5DcC9gL3VbcLTYrEWqxCETvfibxaDaQTeSpg7YkViLGVqEt8
//Alice//gdev-endpoint/squid 5FhRNHjVU69rSA6GdzNNpprwhdsADx65M8k9WEQ12XsK3Vv9
//Alice//gdev-endpoint/datapod 5EXJ5ck7GQWY2LQerK2D9HujgtMscXoTwot5qUznU6p3dr3r
//Alice//gdev-endpoint/ipfs 5CAKxtRX1daFbdWPAfk8bz8JwLztNE6FQCwxqaBNhWB81oEy

Il suffit de leur envoyer une transaction avec en commentaire onchain le endpoint à ajouter :

gcli account transfer 1 5DcC9gL3VbcLTYrEWqxCETvfibxaDaQTeSpg7YkViLGVqEt8 --comment "wss://gdev.coinduf.eu/"

Cela permet ensuite de récupérer la liste des suggestions avec une simple requête graphql (enfin si on a un indexeur à jour mais il n’y en n’a plus aucun à cause de deux bugs) :

query GetEndpoints {
  accountByPk(id: "5DcC9gL3VbcLTYrEWqxCETvfibxaDaQTeSpg7YkViLGVqEt8") {
    transfersReceived {
      comment {
        id
      }
    }
  }
}

Bien entendu, cela ne fournit aucun filtre et est sensible au spam, mais il faut de toute façons faire un scan réseau côté client pour vérifier que les endpoints sont sur le bon réseau et correctement synchronisés. On peut également limiter cette requête aux compte membres pour connaître la personne qui a suggéré un endpoint et décider si on lui fait confiance ou non.

Voilà les solutions temporaires qu’on trouve quand on est en mode “désespoir” devant la quantité de choses à faire ><

6 Likes

Maintenant que j’ai à nouveau un indexeur synchronisé

Je peux récupérer les commentaires qui indiquent les endpoints :

query GetEndpoints {
  accountByPk(id: "5DcC9gL3VbcLTYrEWqxCETvfibxaDaQTeSpg7YkViLGVqEt8") {
    transfersReceived {
      comment {
        remark
      }
    }
  }
}
{
  "data": {
    "accountByPk": {
      "transfersReceived": [
        {
          "comment": {
            "remark": "wss://gdev.coinduf.eu/"
          }
        }
      ]
    }
  }
}
1 Like

Exemple de comment faire ce filtre :

query GetEndpointsProvidedByMembers {
  accountByPk(id: "5DcC9gL3VbcLTYrEWqxCETvfibxaDaQTeSpg7YkViLGVqEt8") {
    transfersReceived(where: {from: {identity: {isMember: {_eq: true}}}}) {
      comment {
        remark
      }
    }
  }
}

La requête ci-dessus ne récupère que les commentaires de transactions venant de membres.

2 Likes

@kimamila m’a fait remarquer que c’était crado de mettre ça dans des commentaires de transaction et que ça faisait pas bon effet. On peut faire aussi une simple remark avec un préfix prédéfini, par exemple duniter endpoint gdev add squid https://squid.gdev.coinduf.eu/v1/graphql, puis filtrer les remark par ce préfixe :

query SquidEndpoints {
  txComment(where: {remark: {_like: "duniter endpoint gdev add squid %"}}) {
    remark
    blockNumber
    authorId
  }
}

Après il faut juste extraire l’url du résultat :

{
  "data": {
    "txComment": [
      {
        "remark": "duniter endpoint gdev add squid https://squid.gdev.coinduf.eu/v1/graphql",
        "blockNumber": 3181935,
        "authorId": "5Dq8xjvkmbz7q4g2LbZgyExD26VSCutfEc6n4W4AfQeVHZqz"
      }
    ]
  }
}

On pourrait éventuellement traiter ça plus spécifiquement côté indexeur.

1 Like

Je continue à réfléchir sur ce point dans l’espoir de trouver une solution simple et élégante. Le sujet Partager facilement un fichier de configuration pour changer les endpoints par défaut m’a donné des idées. J’aimerais bien récapituler les objectifs que l’on cherche à atteindre par ordre d’importance.

  1. connecter automatiquement l’utilisateur lambda à un nœud bien synchronisé, avec un bon uptime, et une faible latence, et cela en un temps et une consommation de ressources raisonnable
  2. ne pas avoir à recompiler l’app et distribuer une mise à jour pour changer la liste des endpoints
  3. ne pas fournir une liste de endpoints de manière centralisée
  4. récolter automatiquement des endpoints une fois en place sans intervention nécessaire de la part de l’hébergeur du service, à la manière de duniter v1

Maintenant, je reprends ces points en sens inverse et cherche à en tirer des conclusions.

Récole automatique des endpoints

Dans Duniter v1, cette récole automatique passait directement par la couche réseau p2p une fois un endpoint BMA / WS2P défini dans un fichier de configuration :

 "endpoints": [
  "BMAS g1.trentesaux.fr 443",
  "WS2P 23456abc g1.trentesaux.fr 443 /ws2p"
 ],
 "nobma": false,
 "ipv4": "127.0.0.1",
 "port": 10900,
 "remoteipv4": "78.199.27.8",
 "remotehost": "g1.trentesaux.fr",
 "remoteport": 443,
 "ws2p": {
  "uuid": "23456abc",
  "privateAccess": true,
  "publicAccess": true,
  "preferedOnly": false,
  "privilegedOnly": false,
  "upnp": false,
  "host": "127.0.0.1",
  "port": 20900,
  "remotehost": "g1.trentesaux.fr",
  "remoteport": 443,
  "remotepath": "/ws2p",
  "maxPrivate": 10,
  "maxPublic": 10
 },

Ici, rien ne garantit que ma configuration est bonne. Je pourrais mettre n’importe quoi, quand même être connecté au réseau, mais que ce qui est déclaré dans ma fiche de pair pointe vers un autre nœud. Donc ça fait une base donnant beaucoup de endpoints mais sur lesquels il faut faire un gros filtre si les utilisateurs ne gèrent pas bien leur config (par ex reverse proxy).

Pour l’instant nous n’avons pas de telle méthode de récolte de endpoints de manière décentralisée dans Duniter v2. Nous pourrions en implémenter une, mais ça me semble un peu compliqué parce qu’il faut toucher à la couche réseau qui n’est pas pensée pour être customisée et qui est relativement bas niveau.

Une autre option serait d’utiliser un ou plusieurs endpoints centralisés comme ceux de la télémétrie. Ils pourraient être indiqués dans les chainspecs, à côté de la télémétrie, et si le nœud reçoit un paramètre “public rpc url”, il fournit ce paramètre au serveur extérieur. Comme ça, pas besoin d’intervention de l’utilisateur autre qu’un paramètre, ce qui peut être configuré automatiquement dans Yunohost par exemple.

On note que toute action de publication en blockchain nécessite les clés de l’utilisateur et donc une action de sa part, donc plus contraignant.

Constituer une liste filtrée de manière non centralisée

Pour répondre au critère de la connexion en un temps raisonnable, il convient de fournir à l’utilisateur une liste filtrée, c’est-à-dire à laquelle on a déjà retiré :

  • les endpoints injoignables
  • les endpoints sur le mauvais réseau ou désynchronisés

Ce travail doit pouvoir être réalisé par plusieurs instances de “tri” car on ne veut pas que le contrôle de cette liste soit centralisé. Il faut pouvoir récupérer le résultat de ce tri auprès de plusieurs instances, en s’attendant à avoir des résultats légèrement différents de l’une à l’autre.

Ne pas avoir à recompiler l’application

Pour ne pas avoir besoin de recompiler l’application, il faut que les serveurs de bootstrap hard-codés dans l’application soient en mesure de fournir d’autres endpoints pour récupérer la liste de pairs, ce qui permet de faire une découverte du réseau de proche en proche sans se limiter à une liste prédéfinie.

Connecter automatiquement l’utilisateur

Une fois les étapes précédentes établies, voici comment un client peut procéder :

  • (uniquement à la première connexion) remplir son local storage avec les nœuds de découverte du réseau hardcodés
  • récupérer des listes préfiltrées de serveurs de liste et de endpoints auprès des noeuds présents dans son storage
  • effectuer un scan réseau sur les endpoints connus
  • se connecter à un endpoint (rpc, graphql, serveur de liste…) ayant bien répondu au scan réseau

À chaque redémarrage ou reconnexion de l’application, refaire un scan réseau pour mettre à jour les listes, et re-récupérer des listes préfiltrées. Le premier scan réseau peut se faire avec la liste des endpoints déjà triés dans le localstorage pour un démarrage rapide. Un deuxième scan réseau peut être lancé en arrière plan pour mettre à jour les listes.


Voilà un ensemble de souhaits de fonctionnalités assez large et complet. Pour l’instant, rien n’est implémenté :

  • récolte automatique des endpoints
  • constitution de listes pré-filtrées côté serveur
  • exposition de liste pré-filtrées et récupération par le client
  • scan réseau côté client et sélection automatique des endpoints utilisés pour la session

Seul le check de synchro entre le endpoint rpc et l’indexeur existe (gcli, duniter panel). La prochaine étape est donc de fournir une solution généraliste pour éviter de réinventer la roue pour chaque type de endpoint (rpc, graphql, serveur de liste, datapod…).

2 Likes

Les stations “Astroport.ONE” ont un système de création de carte de l’essaim qui permet d’identifier le noeud, sa localisation et son capitaine… Est-ce que ça peut servir ?

https://forum.duniter.org/t/duniter-squid-doubts/12563/9?u=frederic_renault

1 Like

Oui, on pourrait utiliser une convention de ce genre pour servir les endpoints : même nom de domaine mais un autre port connu à l’avance. Mais c’est quand même mieux si on part sur une base configurable déclarative.

Les bootstrap initiaux sont inscrit dans A_boostrap_nodes.txt

Chaque noeud publie sur son adresse IPNS (et son l’état vu depuis son “point de vue”. Provenant de la clef PGP de l’utilisateur qui exécute le processus, les clefs publiques sont vérifiables, ce qui permet de s’assurer de la correspondance d’identité crypto entre le réseau applicatif et le réseau “ssh” (remote login).

Régulièrement, les noeuds se regardent le port 12345 pour déclencher leur synchronisation, le code de ce “micro service” est géré par _12345.sh

############ UPSYNC CALL
echo "STATION MAP UPSYNC : curl -s http://${nodeip}:12345/?${NODEG1PUB}=${IPFSNODEID}"
curl -s -m 10 http://${nodeip}:12345/?${NODEG1PUB}=${IPFSNODEID} \
     -o ~/.zen/tmp/swarm/${ipfsnodeid}/12345.${nodeip}.json


Chaque jour (à 20h12 où se trouve le noeud), le processus DRAGON_p2p_ssh.sh peut adapter le bootstrap de chaque noeud en fonction la vérification de concordance des “révélations de concordance crypto” de chacun… Pour le moment, elles vérifie le respect de la procédure “keygen” (et optionnellement les ***** sur Gchange) , mais on peut étendre ça à d’autre vérification d’interaction crypto (squid, duniterv2s)

############################################
## PUBLISH SSH PUBKEY OVER IPFS
## ssh-keygen style
[[ -s ~/.ssh/id_ed25519.pub ]] && cp ~/.ssh/id_ed25519.pub ~/.zen/tmp/${IPFSNODEID}/y_ssh.pub
## DRAGONz PGP/SSH style (https://pad.p2p.legal/keygen)
gpg --export-ssh-key $(cat ~/.zen/game/players/.current/.player) 2>/dev/null > ~/.zen/tmp/${IPFSNODEID}/z_ssh.pub
[[ ! -s ~/.zen/tmp/${IPFSNODEID}/z_ssh.pub ]] && rm ~/.zen/tmp/${IPFSNODEID}/z_ssh.pub # remove empty file

Astroport.ONE fonctionne de façon indépendante du domaine, c’est le partage du secret (UPLANETNAME : ~/.ipfs/swarm.key) qui réuni les noeuds en les reliant en essaim privé IPFS qui fabrique une “UPlanet privée”.

Pour l’évolutivité des essaims, j’ai commencé à coder ce processus (pas finalisé…) pour qu’il ai lieu automatiquement lorsqu’un capitaine de station inscrivent dans leur DB un json portant le [tag[ForkUPlanetZERO]]

C’est ce genre de micro-service qu’il faut écrire pour les différents endpoints à collectionner. Chaque nœud doit :

  • déclarer son endpoint rpc public s’il en a un
  • déclarer son endpoint squid public s’il en a un
  • déclarer la liste de endpoints qu’il connaît et dont il a vérifié la cohérence

Mais pour interroger les autres nœuds on ne peut pas se contenter d’un port par défaut à mon avis parce que rpc est souvent servi sur 443 derrière un proxy dépendant du nom de domaine.

Les concepts sont introduits dans cette documentation :

En gros, les architectures existantes sont :

  • mDNS uniquement sur réseau local, similaire à ce que fait Fred
  • rendez-vous qui introduit une certaine centralisation
  • une table de routage décentralisée type kademlia DHT

Ce serait pas trop compliqué de faire un service centralisé de plus type Ğ1specte contacté automatiquement par les nœuds mais je vais regarder s’il n’est pas possible de passer par le gossip p2p de substrate directement.

1 Like

Je vais trancher ce sujet, parce qu’il ne mérite pas d’y passer plus de temps, la solution suivante utilisant le dépôt nodes / networks · GitLab ou en tout cas un simple json disponible à une URL est déjà largement satisfaisante à mon avis :

  1. l’application (Cesium ou autre) embarque une liste hardcodée de endpoints correspondant plus ou moins à celle présente sur le dépôt network au moment de la compilation
  2. le stockage utilisateur est rempli à partir de cette liste
  3. au démarrage de l’app, un scan réseau est fait sur les endpoints présents dans le stockage local pour éventuellement les re-trier en fonction de critères comme
    • le réseau (hash du genesis)
    • la synchro (dernier bloc)
    • le temps de réponse
  4. au démarrage de l’app, le fichier network est vérifié pour éventuellement compléter la liste locale si changement, ça peut arriver qu’il soit hors ligne, c’est pas gênant on s’en fiche
  5. l’utilisateur peut configurer le endpoint pour récupérer le fichier network s’il veut éviter que des endpoints soient ajoutés automatiquement ou s’il veut mettre à jour une liste autrement

L’avantage de cette technique est que ça ouvre la possibilité de produire un fichier json autrement que via des MR gitlab, par exemple avec un système externe type Ğ1specte qui collecte des endpoints. L’inconvénient est que l’action utilisateur pour signaler un nouveau endpoint est un peu plus lourde, puisqu’il faut soit faire une MR directement sur le dépôt, soit contacter quelqu’un pour qu’il le fasse. L’épuration de la liste des endpoints n’a pas besoin d’être automatique, on peut simplement faire un scan réseau de temps en temps sur cette liste et demander aux auteurs s’ils veulent réparer leur nœud ou le retirer.

2 Likes

Est-il possible d’accéder à un playground GraphQL pour les endpoints Squid ?

La console hasura pour les admin, mais sinon le plus simple est d’utiliser un outil comme Altaïr.

Sinon sur mon instance sauid gdev, j’ai volontairement laissé le MDP admin par défaut : .env.example · main · nodes / duniter-squid · GitLab

https://gdev-squid.axiom-team.fr

1 Like

J’ai creusé un peu le sujet pour la partie RPC. Voici un 1er résultat :

Cette liste ne prétend pas lister tous les endpoints RPC disponibles, mais une sous-partie substantielle. Il s’agit de la liste des endpoints RPC rendus accessibles à un client par mécanisme de découverte, le tout en pur JS front, donc accessible à Vue/Anglular et par extension à Cesium².

Je sais qu’il en manque (par ex. wss://mirror-rpc.gdev.brussels.ovh/ws) mais pas sûr que ce nombre soit si élevé.

Qu’est-ce qu’il y a derrière ? js-libp2p pour la découverte du réseau P2P Substrate conjugué à une hypothèse disant que les nœud P2P sont généralement exposés à la même adresse mais avec une API RPC.

Peut-être que ça pourrait satisfaire la demande de @kimamila sans pour autant investir dans un système de publication élaboré qui retarderait le déploiement de Duniter V2.

En détail

js-libp2p

Permet de démarrer une couche réseau qui va faire de la découverte des autres nœuds Substrate pour un réseau donné (ici la ĞDev) à partir de bootnodes.

Là j’ai pris mon nœud gdev.cgeek.fr comme bootnode. Ensuite js-libp2p fait tout seul le random walk Kademlia et trouve les autres nœuds Substrate.

Hypothèse de la même adresse (DNS/host)

Pour chaque nœud découvert via la couche P2P comme celui-ci :

/dns/gdev.cgeek.fr/tcp/30333/ws/p2p/12D3KooWN7QhcPbTZgNMnS7AUZh3ZfnM43VdVKqy4JbAEp5AJh4f

Je tente de me connecter à ses hypothétiques API RPC, je tente :

  • wss://gdev.cgeek.fr
  • ws://gdev.cgeek.fr:9944
  • ws://gdev.cgeek.fr
  • ws://gdev.cgeek.fr/ws
  • wss://gdev.cgeek.fr/ws

Et je finis par tomber sur un bon paquet d’APIs, toutes synchronisées de surcroit.

Mon avis

Il ne s’agit là que de la liste des endpoints RPC, pas des éventuels indexeurs.

Mais si je devais résumer :

Voilà. Il ne faut pas oublier d’où on vient : Duniter v1 est fragile bien en amont de sa couche réseau. De plus on est quoi, 20 forgerons sur la Ğ1 ? Combien d’API BMA ? Pas vraiment plus.

Une liste centralisée de quelques serveurs suffirait largement pour démarrer. Pour moi la situation actuelle est bien plus précaire :face_with_thermometer:

En fait je ne sais pas ce qui pousse kimamila à mettre un prérequis important sur cet aspect.

4 Likes

Cette hypothèse peut être transformée en convention ! :slight_smile:

« dans DUNITER_PUBLIC_ADDR, indiquez le nom de domaine de votre endpoint rpc qui devra écouter sur :443/ ou :443/ws »

C’est une manière élégante de “hacker” l’annonce réseau p2p et s’en servir pour rpc, très joli :open_mouth:

On peut prolonger la convention et demander de mettre squid sur un sous-domaine “squid.*” sur :443/ également, ou le même domaine sur :443/squid :smiley:

Suite à la dernière visio dev, @kimamila s’est montré satisfait par la solution https://git.duniter.org/nodes/networks, et je pense qu’il le sera encore plus par celle-ci ! La dépendance js-libp2p est légère, bien conçue et maintenue. À voir si elle n’est pas trop gourmande sur réseau mobile. Pour ma part, j’ai choisi de limiter les connexions aux pairs connus (https://git.duniter.org/HugoTrentesaux/duniter-vue/-/blob/5da1bb474268adf834b0682d5b302d2c1e9ad58e/src/ipfs.ts#L83-84) pour éviter de me retrouver avec des centaines de connexions avec des nœuds IPFS.

3 Likes