Duniter explorer

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