Duniter-squid

Ben si, car cela dépend de l’usage. Rien n’empêche d’exploiter les données pour une graphique, etc. via un requeteur quelconque.
De plus, il peut y avoir jusqu’à 100 certificiations, plus l’historique (par exemple celles non renew).
Et dans une liste affichée sur un téléphone mobile, seule 10-15 certifications sont visibles dans l’écran. Rien ne sert d’en recupérer plus…

La question : faut il coder quelque chose pour que les requête xxxConnection fonctionne, dans subsquid ?

Ok, dans ce cas, il faut faire la bonne requête, parce que là, l’adresse ne peut pas être utilisée comme identifiant de l’émetteur d’une certification.

query MyQuery {
  certsConnection(orderBy: id_DESC, where: {issuer: {account: {id_eq: "5CQ8T4qpbYJq7uVsxGPQ5q2df7x3Wa4aRY6HUWMBYjfLZhnn"}}}) {
    totalCount
  }
}
{
  "data": {
    "certsConnection": {
      "totalCount": 19
    }
  }
}

Pour plus de détails, tu peux lire le schéma et ses commentaires, mais en gros l’idée c’est :

  • toute donnée a comme “id” l’event id responsable de sa création
    • genesis-xxx pour ce qui est créé au genesis
    • blocknumber-blockhash-eventnumber pour le reste
    • sauf pour les comptes qui ont comme id l’adresse ss58
  • une certification a comme issuer et receiver des identittés
  • les identités ont forcément un compte (qui peut changer)
  • les comptes peuvent avoir une identité associée, mais pas nécessairement
ta requête complète corrigée
query CertsConnectionByIssuer($address: String!, $limit: Int!, $orderBy: [CertOrderByInput!]!, $after: String) {
  certsConnection(
    first: $limit,
    after: $after,
    orderBy: $orderBy,
    where: {issuer: {account: {id_eq: $address}}}
  ) {
    totalCount
    pageInfo {
      endCursor
      hasNextPage
    }
    edges {
      node {
        id
        createdOn
        creation {
          blockNumber
        }
        issuer {
          id
          name
          membership {
            id
          }
        }
        receiver {
          id
          name
          membership {
            id
          }
        }
      }
    }
  }
}
la réponse
{
  "data": {
    "certsConnection": {
      "totalCount": 19,
      "pageInfo": {
        "endCursor": "10",
        "hasNextPage": true
      },
      "edges": [
        {
          "node": {
            "id": "genesis-61-970",
            "createdOn": 0,
            "creation": [],
            "issuer": {
              "id": "genesis-61",
              "name": "poka",
              "membership": {
                "id": "genesis-61"
              }
            },
            "receiver": {
              "id": "genesis-970",
              "name": "MamieCrypto",
              "membership": {
                "id": "genesis-970"
              }
            }
          }
        },
        {
          "node": {
            "id": "genesis-61-8352",
            "createdOn": 0,
            "creation": [],
            "issuer": {
              "id": "genesis-61",
              "name": "poka",
              "membership": {
                "id": "genesis-61"
              }
            },
            "receiver": {
              "id": "genesis-8352",
              "name": "Yvv",
              "membership": {
                "id": "genesis-8352"
              }
            }
          }
        },
        {
          "node": {
            "id": "genesis-61-6806",
            "createdOn": 0,
            "creation": [],
            "issuer": {
              "id": "genesis-61",
              "name": "poka",
              "membership": {
                "id": "genesis-61"
              }
            },
            "receiver": {
              "id": "genesis-6806",
              "name": "Paola",
              "membership": {
                "id": "genesis-6806"
              }
            }
          }
        },
        {
          "node": {
            "id": "genesis-61-6716",
            "createdOn": 0,
            "creation": [],
            "issuer": {
              "id": "genesis-61",
              "name": "poka",
              "membership": {
                "id": "genesis-61"
              }
            },
            "receiver": {
              "id": "genesis-6716",
              "name": "GUL40_L21",
              "membership": {
                "id": "genesis-6716"
              }
            }
          }
        },
        {
          "node": {
            "id": "genesis-61-6656",
            "createdOn": 0,
            "creation": [],
            "issuer": {
              "id": "genesis-61",
              "name": "poka",
              "membership": {
                "id": "genesis-61"
              }
            },
            "receiver": {
              "id": "genesis-6656",
              "name": "GUL40_K1r",
              "membership": {
                "id": "genesis-6656"
              }
            }
          }
        },
        {
          "node": {
            "id": "genesis-61-6622",
            "createdOn": 0,
            "creation": [],
            "issuer": {
              "id": "genesis-61",
              "name": "poka",
              "membership": {
                "id": "genesis-61"
              }
            },
            "receiver": {
              "id": "genesis-6622",
              "name": "GUL40_Fr1",
              "membership": {
                "id": "genesis-6622"
              }
            }
          }
        },
        {
          "node": {
            "id": "genesis-61-6322",
            "createdOn": 0,
            "creation": [],
            "issuer": {
              "id": "genesis-61",
              "name": "poka",
              "membership": {
                "id": "genesis-61"
              }
            },
            "receiver": {
              "id": "genesis-6322",
              "name": "kolsim",
              "membership": null
            }
          }
        },
        {
          "node": {
            "id": "genesis-61-24",
            "createdOn": 0,
            "creation": [],
            "issuer": {
              "id": "genesis-61",
              "name": "poka",
              "membership": {
                "id": "genesis-61"
              }
            },
            "receiver": {
              "id": "genesis-24",
              "name": "Paulart",
              "membership": {
                "id": "genesis-24"
              }
            }
          }
        },
        {
          "node": {
            "id": "genesis-61-2058",
            "createdOn": 0,
            "creation": [],
            "issuer": {
              "id": "genesis-61",
              "name": "poka",
              "membership": {
                "id": "genesis-61"
              }
            },
            "receiver": {
              "id": "genesis-2058",
              "name": "LeBrice",
              "membership": {
                "id": "genesis-2058"
              }
            }
          }
        },
        {
          "node": {
            "id": "genesis-61-1796",
            "createdOn": 0,
            "creation": [],
            "issuer": {
              "id": "genesis-61",
              "name": "poka",
              "membership": {
                "id": "genesis-61"
              }
            },
            "receiver": {
              "id": "genesis-1796",
              "name": "Damery",
              "membership": {
                "id": "genesis-1796"
              }
            }
          }
        }
      ]
    }
  }
}
1 Like

Par contre, pour l’historique de certification, la logique est différente. Une certification A → B est unique, mais elle peut être associée à un historique de créations, renouvellements, suppressions. Exemple :

query MyQuery {
  identities(limit: 1) {
    name
    certIssued(limit: 1) {
      receiver {
        name
      }
      active
      creation {
        id
      }
      removal {
        id
      }
      renewal {
        id
      }
    }
  }
}
{
  "data": {
    "identities": [
      {
        "name": "Maaltir",
        "certIssued": [
          {
            "receiver": {
              "name": "vincentux"
            },
            "active": false,
            "creation": [],
            "removal": [
              {
                "id": "0000494320-74c09-000000"
              }
            ],
            "renewal": []
          }
        ]
      }
    ]
  }
}

la certification Maaltirvincentux est un objet unique, mais elle peut être associée à une liste de créations, renouvellements, retraits. Ici la création n’est pas disponible, parce qu’elle a eu lieu avant le genesis (mais on pourrait l’ajouter), et elle a expiré au bloc 494320. Si @Maaltir re-certifie @vincentux, il y aura une nouvelle création, et si elle expire à nouveau, un nouveau retrait.

ok, je récupère bien les certifications, maintenant.

Ce qui me manque, c’est juste la date du dernier évenement (create/renew/remove) Je ne sais pas trop comment faire : faut il faire un max(max(<creation>), max(<renewal>)) ? Puis aller cherche les info du bloc ?

D’aiilleurs, je m’apercois que mon comptage du “nombre de certitications recues” n’est pas bon, car il faut plutot compter le nombre de certification recues ET active. Sinon je vais compter les anciennes (qui sont removal), non ?

1 Like

Su gecko je récupère tout ce que je peux depuis le storage Duniter directement. C’est le cas du nombre de certification émis/reçus:

Sinon oui il faut filtrer les certifications actives sur squid.

1 Like

Bonne idée, on pourrait ajouter un champ “lastTouched” sur l’identité et la certification pour faciliter ça. Pour l’instant l’indexeur est assez minimal, il ne faut pas hésiter à ajouter des champs si ça simplifie les traitements client. Ça te convient comme proposition ?

Si tu veux afficher le nombre de certifications reçues actives, oui, il faut filtrer par active. Comme dit @poka, ce genre d’infos qui sont indexées par Duniter peuvent être récupérées directement sur l’API RPC. Mais on pourrait également dupliquer ce champ côté indexeur, ce n’est pas gourmand ni trop compliqué. On peut ajouter certIssuedActiveCount et certReceivedActiveCount à l’index si c’est pratique.

La “date d’une certification” c’est la date de son dernier renouvellement ou de sa création, c’est bien ça ? Dans ce cas, ça peut rentrer dans le champ “lastTouched”. Pour le timestamp, on a deux stratégies :

  • soit on l’ajoute partout comme proposé dans #2
  • soit on ajoute une foreign key sur le bloc à la place ou en complément des “blockNumber” pour récupérer directement les infos dans le bloc en une seule requête

Les deux stratégies peuvent être combinées, bien sûr. Maintenant que tu me le dis, je pense que la foreign key serait plus puissante et pas forcément encombrante, je vais créer un ticket pour ça (edit : #4).

1 Like

Non non l’idée c’est d’ajouter tout ce qu’on à besoin à l’indexer sans avoir à reparser les blocs derrière, on va ajouter ce qu’il manque.

Oui on va déjà ajouter les dates en created_at en plus des numéros de blocs en created_on de la même façon que @ManUtopiK avait fait avec duniter-indexer.
Oui on peut ajouter ce champs lastTouched, à voir ce qu’on y met en plus de la date, probablement l’id de l’event pour pouvoir pointer dessus directement.
Je trouve le schema actuel un peu lourd, j’ai une sensation de doublonnage entre le status d’identité et les champs memberships, je vais peut être d’abords voir pour simplifier ça si possible, ou pas.
Je verrais ça dès que j’ai un peu de temps, peut être demain.

3 Likes

Mais du coup ça oblige à avoir une sacrée confiance dans l’indexer ? Ou je loupe un truc ?

1 Like

Finalement je suis plutôt d’avis de mettre une foreign key sur le bloc ce qui permet de récupérer le timestamp dans une seule requête sans dupliquer l’info dans autant de tables qu’il y a d’événements dans le bloc.

Pareil ici, une foreign key sur l’événement. L’id c’est juste une contrainte technique de squid, c’est pour ça que j’ai mis l’event-id, mais quand c’est un champ, il vaut mieux mettre une foreign key. En plus, à partir de l’event, on peut remonter au call, à l’extrinsic, au bloc :

image

Oui, on a la même sensation côté Duniter, et @cgeek avait proposé de fusionner tout ça, mais je n’étais pas encore prêt mentalement à franchir le pas (cf Résistance au changement, la nuit porte conseil). Mon idée pour l’instant est que le status est une forme d’index pour éviter d’avoir à filtrer sur un champ lié.

Oui, tout à fait, si on se fie uniquement à l’indexeur, on peut être manipulé par lui. Deux types de mensonges sont possibles : ajout/modification d’information et omission.
Pour l’ajout/modification, c’est pas trop compliqué, il suffit d’aller vérifier dans le bloc ciblé, et si ça ne correspond pas, c’est la blockchain qui a raison (donc c’est soit un bug soit une “attaque” de l’indexeur).
Pour le mensonge par omission, c’est plus compliqué. Si un indexeur ne donne pas une info, la seule manière de s’en apercevoir est de trouver l’info non indexée en blockchain. Cf ce message : Quel indexeur utiliser et scan réseau - #2 by HugoTrentesaux

1 Like

@Maaltir oui c’est sûr que les instances d’indexers doivent être digne de confiance.
C’est une excellente remarque, et subsquid semble fournir des réponses techniques aidant à ancrer cette confiance:

  • Trust-minimized queries: the data can be audited, and all clients can verify the query result

[…]

  • Node operators have to bond a security deposit, which can be slashed for byzantine behavior

Je n’ai pas vue ce genre de dépôt de garanti sur nos instances, est-ce une configuration particulière, ou parlent-ils de leur réseau de cloud ?

  • Any query can be verified by submitting a signed response to an on-chain smart contract

Et surtout ce paragraphe sur la validation des requêtes: https://docs.subsquid.io/subsquid-network/whitepaper/#query-validation

Je n’ai pas encore tout lu/compris à ce sujet, ça mérite d’être creuser je pense @HugoTrentesaux

Oui mais justement on ne va pas demander aux client d’aller vérifier chaque contenu de bloc pour chaque donnée, enfin je ne sais pas, ça me parait sous efficient.

Ah oui ok je te fait entièrement confiance sur ce genre de chose, on en discutera en MR j’aime bien notre manière de faire en ping pong ^^
Il faut que je prenne le temps d’étudier ce schéma, les différentes possibilités et de tester.

2 Likes

Peut-être, mais ça me semble compliqué. Alors que notre modèle de confiance est simple : on fait confiance aux gens de la toile. S’ils trahissent notre confiance, ça ne leur coûte pas des Ǧ1, mais ils y laissent leur honneur. C’est à mon avis autrement plus précieux. Donc mon approche serait plutôt : on fournit une liste signée d’indexeurs de confiance. Par exemple l’indexeur Axiom serait signé par les tech d’Axiom, et on se fait nos audits entre nous pour vérifier qu’on est bien sécurisé. Pareil pour les réseaux espagnols, ils mettent une infra en place et les tech qui la contrôlent signent un document qui atteste de leur confiance.
Et après on peut toujours penser que “la confiance n’exclut pas le contrôle” et réaliser des contrôles de routine, avec des rapports de sécurité, et éditer des blacklist si des membres mettent à dispo des indexeurs menteurs… mais pour moi c’est chercher à résoudre un problème qu’on n’a pas. Et je préfère commencer par résoudre les problèmes qu’on a.

3 Likes

Entièrement d’accord. Pour moi le rôle de l’indexeur est de fournir tous les besoins des clients. Il se doit d’être le plus complet possible dans les infos qu’il fournit. L’idéal serait que le client ne fasse appel que très rarement à l’API RPC.

Si Squid a des particularités techniques qui rendent des cas d’usage difficile à implémenter, alors le parsing d’un bloc doit être une solution temporaire en attendant de réaliser l’implémentation complète du cas d’usage. Amha.

2 Likes

Non Squid n’a aucune restriction particulière concernant son implémentation, on index vraiment ce qu’on veut à partir des événements :slight_smile:

Après à nous d’optimiser le schéma en l’organisant correctement.

La question était plus sur la confiance aveugle qu’on lui accorde, doublé de vérification onchain ou d’autre mécanismes

1 Like

Cependant, dans l’exemple concret ci-dessus, où il était proposé d’ajouter une foreign key sur le bloc, n’y a t’il pas des contraintes lié au rollback d’un bloc ?
Comment sont géré les rollbacks et les bloc non finalisées, dans subsquid ?
Es-ce par les delete cascade du bloc, où bien par ceux des évenements ?

Je ne sais pas comment sont gérés les rollbacks en interne, mais quand on regarde les logs subsquid, on voit de temps en temps des rollbacks (je n’en ai pas sous la main parce qu’apparemment on n’est plus que deux validateurs).

Donc à mon avis il utilise des transactions postgresql ou quelque chose similaire qu’il est capable de rollback si le bloc n’est plus dans la branche principale. Et la finalisation permet d’oublier les transactions.

Il y a aussi une histoire de checkpoints, je ne sais pas à quoi ça correspond.

duniter-gdev-subsquid-processor-1  | {"level":2,"time":1706260885014,"ns":"sqd:processor","msg":"819433 / 819433, rate: 0 blocks/sec, mapping: 6 blocks/sec, 17 items/sec, eta: 0s"}
duniter-gdev-subsquid-processor-1  | {"level":2,"time":1706260891005,"ns":"sqd:processor","msg":"819434 / 819434, rate: 0 blocks/sec, mapping: 6 blocks/sec, 18 items/sec, eta: 0s"}
duniter-gdev-subsquid-db-1         | 2024-01-26 09:21:31.569 UTC [27] LOG:  checkpoint starting: time
duniter-gdev-subsquid-processor-1  | {"level":2,"time":1706260897076,"ns":"sqd:processor","msg":"819435 / 819435, rate: 0 blocks/sec, mapping: 6 blocks/sec, 17 items/sec, eta: 0s"}
duniter-gdev-subsquid-processor-1  | {"level":2,"time":1706260903021,"ns":"sqd:processor","msg":"819436 / 819436, rate: 0 blocks/sec, mapping: 6 blocks/sec, 18 items/sec, eta: 0s"}
duniter-gdev-subsquid-db-1         | 2024-01-26 09:21:48.863 UTC [27] LOG:  checkpoint complete: wrote 175 buffers (1.1%); 0 WAL file(s) added, 0 removed, 0 recycled; write=17.245 s, sync=0.028 s, total=17.295 s; sync files=45, longest=0.010 s, average=0.001 s; distance=1117 kB, estimate=1117 kB
duniter-gdev-subsquid-processor-1  | {"level":2,"time":1706260909052,"ns":"sqd:processor","msg":"819437 / 819437, rate: 0 blocks/sec, mapping: 6 blocks/sec, 18 items/sec, eta: 0s"}
duniter-gdev-subsquid-processor-1  | {"level":2,"time":1706260915039,"ns":"sqd:processor","msg":"819438 / 819438, rate: 0 blocks/sec, mapping: 6 blocks/sec, 18 items/sec, eta: 0s"}

D’après la doc, c’est bien cela qui se passe en cas de rollback. Comme on pourrait le penser intuitivement, le processor squid va revert les changements en bdd en cas de détection de block orphelin, jusqu’à retrouver un bloc commun, puis ré-indexer à partir de ce bloc.

The processor will periodically (interval setting for EVM, Substrate) poll the RPC endpoint for changes in consensus. When the consensus changes, it will re-run the batch handler with the new consensus data and ask the Database to adjust its state. The Database then must roll back the changes made due to orphaned blocks and apply the new changes. With this, the state of the Database reflects the current blockchain consensus at all times.

Les blocs non finalisé sont flag comme étant hot par squid. Il semble être possible de le configurer pour indexer ou non les blocs “hots”.

Les checkpoints quand à eux, servent en cas d’erreur, de coupure ou de corruption de la base de donnée. Ca permet au processor de reprendre au dernier checkpoint valide en cas de problème, plutôt que de devoir tout réindexer.


PS: quand on tape “subsquid rollback sql” sur google, la première réponse c’est ce forum x)
Manque un peu de ressources côté subsquid…


Ce serait intéressant de tester ces rollback squid pour observer ça concrètement, mais je ne sais pas comment provoquer un fork volontairement sur une chaine de test.

4 Likes

En fait je me suis mal exprimé : je n’ai pas de doute sur les rollback des blocs, mais plutot sur les données indexées à partir des blocs. Transactions ou pas, il faut bien que l’algo parte des bloc, pour retrouver les données liées. Ces données filles doivent donc être liées par une lien physique (foreign key de la base de données) ou un logique (une colonne, sans lien physique : exemple : id d’évenement, id de block, etc.)

Mais effectivement, tester un rollback puis vérifier que tous les index ont bien été nettoyés devrait suffire.

Excellent ><
(il fallait immortaliser ça)

1 Like

Ah c’est marrant chez moi même en nav privée c’est vraiment le premier résultat

Si je tape juste “subsquid rollback” on arrive en 3ème position avec un message de moi …

bref ça m’est déjà arrivé de rechercher des choses sur substrate et tomber sur ce forum en premier aussi, on reste quand sur des sujets pas si répandu que ça ^^

1 Like

Hello,

Je viens de mettre en route mon instance d’indexer squid : https://indexer-gdev.pini.fr/graphql.

Ça a l’air de tourner.

1 Like