Duniter explorer

Bonjour,

J’annonçais en avril dans ma présentation vouloir contribuer sur un explorer de transactions. Avec Duniter-explorer, je vous présente aujourd’hui ma première contribution logicielle.

Explorer

La majorité des blockchains ont un explorer associé, c’est à dire un outil qui permet de parcourir les blocs et les transactions effectués sur la blockchain. Par exemple pour la blockchain polkadot : https://polkadot.subscan.io/ . Pour les utilisateurs novices de ce type d’outil permet de s’approprier les concepts de bases de la blockchain et pour les utilisateurs plus avancés d’analyser l’activité de la blockchain.

Ecosystème-duniter

L’écosystème Duniter est composé de différents outils d’analyse :

  • Cesium propose un annuaire, une page synthèse sur la G1, l’état du réseau et aussi la possibilité de parcourir des blocs et transactions via l’annuaire ou l’état du réseau.
  • G1 stat donne une vue statistique d’ensemble.
  • WotMap offre un aperçu de la toile de confiance sous forme de graph.

Bien que Cesium permette de parcourir blocs et transaction, je n’ai pas trouvé d’outils type explorer.

Duniter-explorer

J’ai donc conçu un petit outil qui donne une vue des blocs et transactions dans le style classique des explorers. Vous pouvez y accéder à l’adresse duniter-explorer.org, le code source est accessible à myrepo_ / duniter-explorer · GitLab.

L’outil a pour le moment seulement quelques fonctionnalités :

  • afficher les derniers blocs minés (?)
  • afficher les 10 dernières transactions
  • la recherche par numéro de bloc
  • L’outil est basé sur l’API bma et le noeud g1.duniter.org

Il reste quelques bugs et limitations identifiés :

  • Il manque une icône pour le chargement des transactions (amélioration de l’UX)
  • En cas de double requête avant le chargement des transactions, elles se mélangent entre les requêtes.
  • Il y a des transactions à un seul émetteur et 0 de montant : je ne comprend pas le sens des données sur ce point.
  • La recherche n’est pas gérée pour les 100 blocs / les premières transactions.

Globalement l’outil est à l’état de PoC et il y a peu de fonctionnalité à côté d’un explorer classique, par exemple il manque l’affichage des détails pour chaque élément (bloc, transaction, adresse).

La démarche de développement de l’outil c’est pour moi l’occasion de m’exercer à coder et j’en profite pour contribuer à l’écosystème Duniter. Je serai curieux d’un retour, si quelqu’un a la patience d’un code review. J’ai réalisé l’application sans framework, si je continue de dev l’outil je pense réécrire le code avec un framework Front-end.

La suite

Tant que j’ai du temps libre, je pense continuer à développer duniter-explorer. L’outil pourra prendre différente forme en fonction des retours : peut-être s’orienter vers de la vulgarisation blockchain pour les novices ou alors s’orienter plus vers un outils avec des détails (statistiques, recherche de pattern, etc.) pour les utilisateurs avancés.

Je me questionne pour la V2, il me semble qu’il y a un explorer fournit par substrate ?

N’hésitez pas à me faire des retours sur cette première contribution :slight_smile:

8 Likes

Ce sont peut-être les transactions de change, qui servent à créer un gros UTXO en en consommant plein de petits. Les clients font ça quand ils n’arrivent pas à faire une transaction sans dépasser la taille maximale autorisée (en nombre d’entrées/sorties).
L’utilisateur peut les ignorer mais l’intérêt d’un explorateur peut être justement d’afficher tous les détails.

1 Like

Bonjour à tous,

Je suis nouveau dans l’univers Duniter et Ğ1, et je me suis lancé dans un projet de développement d’explorateur de blockchain, principalement pour mieux comprendre le fonctionnement de cet environnement.

En explorant le sujet, je suis tombé sur ce thread, qui semble parfaitement aligné avec cet objectif.

Mon idée est de développer une application backend en Java Spring Boot, qui interagit avec mon nœud (sur ĞDev), analyse les blocs et les extrinsics, et expose une API. Par la suite, je prévois de créer une interface client avec React pour explorer visuellement les données.

Voici mon avancement actuel :

  • Mon application Spring Boot est initialisée, et je parviens à interagir avec mon nœud pour récupérer des informations sur les blocs et les extrinsics.

  • J’ai commencé à travailler sur le décodage des extrinsics. Pour l’instant, je me suis concentré sur ceux de type timestamp, car ce sont ceux qui apparaissent le plus souvent sur mon nœud (et probablement parmi les plus simples à traiter).

  • Mon objectif est maintenant d’élargir l’analyse pour couvrir d’autres types d’extrinsics, notamment les transactions monétaires, les certifications, et les adhésions.

Pour l’instant, le projet est hébergé sur l’un de mes dépôts GitHub. Si cela peut intéresser certains d’entre vous, je serais ravi de partager le lien pour recueillir vos retours ou suggestions, une fois que j’aurai un peu plus avancé.

Pensez-vous que cette évolution serait pertinente, ou que les outils existants couvrent déjà suffisamment ce besoin ?

Je précise que ce projet est avant tout un exercice personnel pour me familiariser avec l’écosystème Duniter et Ğ1, mais je serais heureux qu’il puisse également apporter une valeur ajoutée à la communauté.

Merci d’avance pour vos retours et conseils ! Je partagerai mes avancées ici régulièrement.

4 Likes

Bien que je sois pas actif ici, j’observe avec attention l’avancement de Duniter.

Dans mon usage d’autres blockchains, l’explorer était un outil indispensable. Le système de transaction étant transparent, il me semble important de pouvoir accéder facilement aux données. Avec des fonctionnalités élémentaires, ça permet de faire des analyses simples sur le volume de transactions et la répartition des tokens à travers les adresses.

Je m’étais essayé à l’exercice sur la v1 à partir de l’API BMA, c’était un simple front en vanilla JS. Depuis, je suis devenu développeur dans ma vie pro. J’ai pas vraiment de temps dispo pour ce projet, ceci dit, je suis très curieux de ton système. Pour le backend en Java, c’est pas dans ma stack, mais partage le repo quand t’as quelque chose !

Niveau front, si t’as besoin, je peux passer quelques soirées à faire de l’UI et éventuellement quelques composants en génération llm.

Ce sera un plaisir de lire tes avancées :wink:

2 Likes

Je pense que toute initiative est bonne à prendre, si ce projet t’anime et que tu es à l’aise dans ces technos, fonce, ça trouvera probablement une utilité le moment venu.

Sache que ce que tu décris est un indexeur blockchain, et que nous construisons le nôtre en TypeScript, basé sur un outil fait pour ça qui s’appelle Subsquid : Topics tagged subsquid

Ainsi que Hasura pour le moteur GraphQL (l’API).


La plupart des événements qu’on a jugés utiles au flux des clients actuels sont déjà traités, mais il reste probablement des événements à traiter, et des améliorations à apporter. Nous ne sommes que trop peu à maîtriser cette stack et à y avoir contribué (@HugoTrentesaux @bgallois et moi-même), monter en compétence dessus serait formidable en termes de résilience.

Ce qui est certain, c’est qu’il manque tout un écosystème client exploitant cet indexeur. Par exemple, un explorateur blockchain avancé :wink:

Si tu te lances dans ton propre indexeur, tu devras entre autres faire attention de bien gérer les cas de fork, leurs résolutions et rollbacks lorsque nécessaire. C’est un projet passionnant.

Dans tous les cas, merci pour tes contributions, quoi que tu fasses, continue de les partager, ça en motivera peut-être à t’aider ou trouver un usage à ce que tu fais.

6 Likes

On dirait que subscan est propriétaire, mais il est possible de leur demande d’indexer notre blockchain. On verra quand on aura un réseau live, ça me paraît overkill pour un réseau de dev.

Est-ce que tu utilises https://github.com/polkadot-java/api ?

1 Like

Bonjour à tous,

Wow ! Je tiens à remercier sincèrement chacun d’entre vous pour vos retours, vos conseils, et vos encouragements. Je ne m’attendais pas à autant d’interactions, et cela me motive énormément à poursuivre ce projet avec encore plus d’enthousiasme. C’est vraiment inspirant de voir une communauté aussi active et bienveillante. :grinning:


Plonger au plus bas niveau avec SCALE

Pour ce projet, mon objectif principal est de comprendre les bases, non seulement en surface, mais en explorant les fondations du fonctionnement des données dans une blockchain comme Duniter. J’ai voulu m’immerger au plus bas niveau possible, et c’est pourquoi j’ai choisi d’utiliser polkaj/polkaj-scale at master · splix/polkaj · GitHub, qui m’a permis de manipuler directement les encodages SCALE (Simple Concatenated Aggregate Little-Endian).

Cette exploration m’a ouvert les yeux sur des aspects fascinants, comme la gestion des MSB/LSB pour encoder des structures complexes ou des valeurs compactes dans les extrinsics. Cela m’a rappelé certains cours théoriques que j’avais suivis à l’époque, mais avec cette fois un contexte concret et pratique. Découvrir les rouages internes de l’encodage des données blockchain est une expérience passionnante, qui nourrit ma curiosité et ma compréhension technique.

Bien que cette démarche m’ait permis d’appréhender en profondeur la structure des extrinsics au niveau binaire, je me rends compte que cette approche brute, bien qu’incroyablement enrichissante sur le plan pédagogique, n’est pas optimale pour un projet à plus grande échelle. Pour aller plus loin et rendre le projet plus robuste, j’ai ressenti le besoin de repenser l’architecture.


Vers une architecture plus robuste avec Kafka

En avançant, j’ai réalisé que ma gestion actuelle des données, où je traite les blocs et extrinsics de manière séquentielle et sans buffer, montre rapidement ses limites en termes de performances et de résilience, notamment si le volume de données venait à augmenter avec l’évolution de la blockchain.

Pour relever ce défi, j’ai décidé de structurer mon projet autour de Kafka, afin d’expérimenter une architecture orientée événements, robuste et scalable. Voici les étapes clés que j’ai mises en place :

  1. Abonnement à chain_newHead via WebSocket :

    • Mon application reçoit en temps réel les notifications de nouveaux blocs.
    • Chaque bloc est encapsulé dans un DTO (Data Transfer Object) pour structurer les données.
  2. Publication des événements dans Kafka :

    • Les blocs reçus sont publiés dans Kafka, permettant de séparer leur réception et leur traitement.
    • Cela m’offre la possibilité de bufferiser les événements, garantissant une meilleure résilience en cas de traitement plus lent ou de panne temporaire.
  3. Traitement asynchrone des blocs :

    • Un consommateur Kafka traite chaque bloc de manière indépendante et suit les étapes suivantes :
      • Récupération des détails du bloc via une requête RPC (chain_getBlock).
      • Décodage et analyse des extrinsics pour en extraire les informations clés.
    • J’ai commencé par travailler sur les extrinsics de type timestamp, et je prévois d’élargir progressivement aux transactions monétaires, certifications, et adhésions.
  4. Stockage des données :

    • Les blocs et extrinsics sont persistés dans une base MySQL, ce qui me permet de les exposer via une API REST (et peut-être GraphQL à l’avenir).
    • J’ai opté pour MySQL pour sa simplicité et sa rapidité de mise en place, mais je reste conscient que PostgreSQL pourrait être un meilleur choix à long terme pour des fonctionnalités comme l’indexation JSON ou des analyses plus complexes.

En quête de compréhension et de progression

Je suis bien conscient que ma solution actuelle est encore rudimentaire par rapport à des outils comme Subsquid, qui semblent beaucoup plus avancés et adaptés pour indexer des blockchains Substrate. Cependant, ce projet est avant tout une démarche personnelle, une quête de découverte pour comprendre les rouages de base, avant de me tourner vers des outils plus abstraits.

Une fois que j’aurai consolidé mes acquis, je serais ravi d’explorer Subsquid, de comparer les approches, et pourquoi pas de contribuer à l’indexeur communautaire existant. Pour l’instant, cette démarche par étapes me permet de progresser à mon rythme et de vraiment saisir le fonctionnement fondamental de cet écosystème.


Prochaines étapes

  1. Étendre l’analyse des extrinsics pour couvrir les transactions monétaires, certifications, et adhésions.
  2. Finaliser l’intégration de Kafka, avec un focus sur la gestion des cas complexes comme les forks et rollbacks. (je n’avais pas du tout pensé à ça :cold_face:)
  3. Expérimenter avec Subsquid à plus long terme, pour mieux comprendre comment cette solution pourrait enrichir ou compléter mon projet actuel.

Encore merci à tous pour vos retours et vos propositions d’aide. Je partagerai régulièrement mes avancées ici, et je serais ravi de recueillir vos idées ou suggestions pour améliorer ce projet. C’est un vrai plaisir de faire partie de cette communauté ! :blush:

5 Likes

Et ça me semble être une très bonne approche. Peu importe que tu jettes éventuellement le code produit par la suite (même si tu pourrais certainement au moins en extraire une bibliothèque), ce qui compte c’est tout ce que tu apprends au passage. :slight_smile:

7 Likes

Bonjour et bonne année à toutes et tous ! :partying_face:

J’avais commencé à développer une base Websockets & Kafka, avec tests unitaires et javadoc, pour indexer des données. En explorant les pallets, j’ai créé un script JavaScript qui génère automatiquement un fichier SQL de migration pour Flyway à partir des métadonnées runtime.

Voici un extrait de mon script :

const { ApiPromise, WsProvider } = require('@polkadot/api');
const fs = require('fs');

// Generate SQL for modules
function generateModuleSql(pallets) {
  let sql = `-- Populate the module table with explicit IDs\nINSERT INTO module (id, name, description) VALUES\n`;
  pallets.forEach((pallet, index) => {
    const moduleName = pallet.name.toString();
    const moduleIndex = moduleName === 'System' ? 0 : pallet.index?.toNumber() || index;
    const moduleDescription = pallet.docs.length > 0
        ? pallet.docs.map(doc => doc.toString()).join(' ')
        : `No description available for ${moduleName} module.`;
    sql += `  (${moduleIndex}, '${moduleName}', '${moduleDescription.replace(/'/g, "''")}'),\n`;
  });
  return sql.trim().slice(0, -1) + ';\n';
}

// Generate SQL for extrinsics
function generateExtrinsicSql(api, pallets) {
  let sql = `-- Populate the function table with explicit IDs\nINSERT INTO function (id, module_id, call_index, name, description) VALUES\n`;
  let extrinsicIdCounter = 0;

  pallets.forEach((pallet) => {
    const moduleName = pallet.name.toString();
    const moduleIndex = moduleName === 'System' ? 0 : pallet.index?.toNumber();

    if (pallet.calls.isSome) {
      const lookupType = pallet.calls.unwrap().type;
      const callVariants = api.registry.lookup.getSiType(lookupType).def.asVariant.variants;

      callVariants.forEach((variant) => {
        const functionName = variant.name.toString();
        const functionDescription = variant.docs.length > 0
            ? variant.docs.map(doc => doc.toString()).join(' ')
            : `No description available for ${functionName} in ${moduleName}.`;
        const realIndex = variant.index; // Use the real index

        console.log(`Module: ${moduleName}`);
        console.log(`  - ${functionName} (ID: ${realIndex} / Hex: 0x${realIndex.toString(16)})`);
        console.log(`    Description: ${functionDescription}`);

        sql += `  (${extrinsicIdCounter}, ${moduleIndex}, ${realIndex}, '${functionName}', '${functionDescription.replace(/'/g, "''")}'),\n`;
        extrinsicIdCounter++;
      });
    }
  });

  return sql.trim().slice(0, -1) + ';\n';
}

// Write SQL to file
function writeSqlToFile(filepath, sql) {
  fs.writeFileSync(filepath, sql);
  console.log(`SQL file generated: ${filepath}`);
}

// Main function
async function main() {
  const wsProvider = new WsProvider('wss://gdev.coinduf.eu');
  const api = await ApiPromise.create({ provider: wsProvider });

  console.log('--- Generating SQL for modules and extrinsics ---');

  const pallets = api.runtimeMetadata.asLatest.pallets;
  const moduleSql = generateModuleSql(pallets);
  const extrinsicSql = generateExtrinsicSql(api, pallets);

  const sqlOutput = `${moduleSql}\n${extrinsicSql}`;
  writeSqlToFile('../src/main/resources/db/migration/V2__populate_modules_and_functions.sql', sqlOutput);

  process.exit(0);
}

main().catch(console.error);

Grâce à ce script, j’ai populé une base de données avec les modules, leurs descriptions, et les fonctions. Cela me permet d’exposer facilement les détails d’un bloc via un controller REST.


Problème rencontré

Si tout se passe bien pour parser des extrinsics non signés, j’ai des difficultés avec les extrinsics signés, notamment ce transferKeepAlive :

0x2d0284000ee7311392051d87658338ac593869b4dcdcee59e13bf787a722a71c0221fa0101a03314eb96260a5bd98a51b279712ebee3358b2ace7fff4e92c1476dedcf0e286903263be387683d997e0333c5d7d39e481f2bb95f3bd1124dea1e4bf6bd8a84d5037c0006030010dac8e6bb0f2b0c6495f4f8b08b8e8db7423ce66ad0cd2db952317f8d24b59c04

Voici un extrait des logs actuels :

is Signed: true
Extrinsic version: 4
Address prefix: 00
Signer address: 10DAC8E6BB0F2B0C6495F4F8B08B8E8DB7423CE66AD0CD2DB952317F8D24B59C
Nonce: 1
Signature: A4AC3B7210D563...
Tip: 12681238
Module ID: 0  <-- Devrait être 6
Function ID: 0 <-- Devrait être 3

En me basant sur la documentation Substrate:

je parviens à parser correctement jusqu’à l’adresse du signataire. Ensuite j’ai un doute sur le format et le contenu de ces “extra data” et comment parser tout cela.

  • Que contiennent exactement ces extra data ?
  • S’agit-il uniquement du nonce, du tip et de l’era ?
  • Pourquoi cela provoque-t-il un décalage dans le Module ID (6) et le Call ID (3) ?

Mes recherches

Je suis tombé sur des exemples intéressants dans Polkaj :

Cela m’a permis d’apprendre beaucoup, mais je réalise que je suis peut-être en train de réinventer la roue. :rofl:


Indexer : Extrinsics ou Events ?

En parcourant le forum, je suis tombé sur cette discussion :

Élois : “Sur les events uniquement, pas les extrinsics. Les events ont justement été créés pour les indexeurs, ce sont eux qui te disent ce qu’il se passe réellement. :blush:

Cela m’amène à me demander : suis-je en train de trop compliquer mon approche ? Peut-être devrais-je simplement m’abonner aux events.


Questions

  1. Extra data dans les extrinsics signés :
  • Que contiennent-elles exactement ?
  • Y a-t-il une méthodologie pour bien les décoder ?
  1. Indexation : extrinsics vs events :
  • Est-ce que se baser uniquement sur les events est une meilleure stratégie ?
  • Dans quels cas les extrinsics sont-ils essentiels ?

Voila un peu mon avancée, je vais continuer de creuser cette lib Polkaj, et éditerai ce message au fur et à mesure de mes découvertes, si cela peut servir à quelqu’un d’autre.

Merci d’avance pour vos éventuels retours ! :blush:

3 Likes