Depuis longtemps, je voulais refaire l’indexeur on-chain pour plusieurs raisons expliquées plus bas, mais j’étais concentré sur le passage à la v2. Puis, j’ai eu besoin de prendre une pause de quelques mois après la migration vers la v2.
À mon retour, j’ai commencé à travailler sur une refonte de l’indexeur on-chain, que j’ai fusionnée avec un autre projet : une couche de stockage pour les données off-chain (voir ce message lié).
Je suis donc parti sur un ensemble de microservices permettant d’indexer les données on-chain et off-chain, et de propager les données off-chain sur un réseau décentralisé de pods.
Le projet se nomme « Dunipod » et est découpé en six microservices indépendants :
- onchain-indexer : remplace Squid
- distance-worker : évalue périodiquement la distance de toute la toile
- offchain-indexer : indexe les documents JSON off-chain soumis via HTTP ou reçus via P2P
- graphql : expose, via une API GraphQL unifiée, les données on-chain, l’évaluation de la distance et les données off-chain
- p2p : couche générique de propagation des documents off-chain et des blobs
- blob-gateway : API HTTP permettant de récupérer les blobs (les documents JSON sont exposés via GraphQL)
Le code source du projet est disponible sur le GitLab de Duniter : nodes / rust / Dunipod · GitLab
L’API GraphQL est exposée sur mon pod : g1pod.elo.tf/v1/graphql
Le tableau de bord GraphiQL : Dunipod GraphiQL
Pourquoi refaire un indexeur on-chain ?
Pour plusieurs raisons :
- Squid nécessite un nœud Duniter d’archive, ce qui prend beaucoup de place et rend donc difficile l’hébergement d’un indexeur on-chain. Je veux un indexeur on-chain qui puisse fonctionner avec un nœud Duniter miroir minimal supprimant les anciens blocs.
- Squid conserve forcément toutes les données indexées depuis la Genèse. Je veux un indexeur permettant de paramétrer un espace disque maximal et d’élaguer automatiquement les données les plus anciennes en fonction de l’espace disque restant.
- Squid ne se base que sur les événements Substrate. Il ne peut donc pas indexer correctement l’historique du solde d’un compte, car certains changements de solde n’ont pas d’événement associé. Plus largement, les événements Substrate sont simplement des tags qui donnent une sémantique à certaines transactions : ils n’ont pas vocation à tracer tous les changements de stockage que le runtime Substrate peut engendrer. Je veux un indexeur on-chain capable de tracer les changements du stockage brut afin d’indexer proprement l’historique du solde d’un compte.
- Squid ne fournit pas aux applications un moyen déterministe de calculer à l’avance le coût d’une requête ni de connaître précisément le quota restant. Je veux un indexeur qui utilise un mécanisme de limitation de débit fondé sur un Token Bucket, associé à un modèle de coût GraphQL public et déterministe. Afin qu’une application ou un script puisse calculer le coût d’une requête avant de l’envoyer, connaître la capacité encore disponible sur chaque pod et répartir automatiquement sa charge entre plusieurs pods lorsqu’elle doit récupérer un volume important de données.
Comment fonctionne l’indexeur off-chain ?
Les données off-chain sont des documents JSON et des blobs binaires. Chaque document appartient à un domaine : profiles, user_private_data, tx_comment, ad, etc.
Chaque pod choisit les domaines qu’il accepte. Il partage alors via P2P uniquement les documents relatifs à ces domaines.
Chaque pod définit ses propres règles d’indexation off-chain par domaine. Ces règles sont définies dans un dossier qui doit contenir un manifeste JSON, des scripts Rhai et un sous-schéma GraphQL. Elles définissent notamment :
- Sous quelles conditions un document JSON est admissible
- Comment un document JSON admis doit être projeté dans la base de données
- Sous quelles conditions un blob référencé par un document JSON est promu
- Quelles primitives génériques du binaire offchain-indexer sont utilisées et comment. Il existe notamment des primitives pour la génération de miniatures d’avatars et pour la classification des noms similaires. J’ai ouvers un sujet dédié à la classification des noms de profils : Recherche par nom de Profil sécurisée (y compris pour les non-membre).
Comment fonctionne la couche P2P ?
Un document JSON doit être admis par un pod pour pouvoir être partagé via P2P. Il n’est pas possible de le soumettre directement au réseau P2P, afin de protéger efficacement le réseau contre le spam. Un hébergeur de pod malveillant peut toujours soumettre du spam au réseau P2P, mais il est possible de bannir un pod. Il existe également un système de certificats pour les pods hébergés par des membres de la toile de confiance, qui sont prioritaires par rapport aux pods anonymes.
Le microservice P2P communique avec l’indexeur off-chain via une outbox PostgreSQL. Il partage tous les documents des domaines suivis par le pod, quelles que soient les règles off-chain propres à celui-ci. Il partage également les blobs promus localement.
La couche réseau bas niveau est gérée par Iroh, tandis que le protocole de synchronisation des documents JSON s’appuie sur RIBLT, une structure de réconciliation qui permet à deux nœuds d’identifier précisément leurs différences sans échanger leurs inventaires complets. Son coût s’adapte au nombre de documents divergents, ce qui convient particulièrement à un réseau où des nœuds décentralisés possèdent généralement des ensembles très proches, mais différents.
Roadmap
Dans les prochains jours
Stabiliser le prototype. Publier une version un peu plus stable et aider d’autres personnes à héberger Dunipod.
Dans les prochaines semaines
Stabiliser la gestion des profils et contribuer aux applications (Cesiumv2s, Gecko, Ginkgo, Tikka, etc.) afin de les aider à gérer les profils via Dunipod et, éventuellement, à effectuer une migration si cette solution est retenue.
Dans les prochains mois
Ajouter les domaines off-chain pour les commentaires de transaction et les annonces qui serviront à créer un Gchange v2 décentralisé.
Dunipod n’apporterait que le backend : il pourrait y avoir plusieurs applications différentes servant les annonces du réseau Dunipod, et chaque application pourrait éventuellement décider de servir également les annonces d’autres réseaux.
Ce prototype est une proposition. Je suis resté très succinct dans ce post, mais j’ai longuement réfléchi aux choix de conception, que je détaillerai au fil des questions. Si vous parvenez à me convaincre de modifier certains choix d’architecture, je le ferai volontiers. Il faut toutefois que je comprenne ce qui motive vos propositions et qu’elles soient justifiées au regard des besoins et des contraintes de l’écosystème G1.