Prototype de GVA

Du nouveau sur GVA, le fruit des dev de la semaine :

  1. Ajout query genComplexTx pour générer des transactions plus complexes
  2. Correction d’un bug sur l’indexation gva de la g1-test.
  3. Ajout de la commande duniter wizard gva pour configurer gva. L’option --gva n’existe plus, il vous suffit d’activer gva dans la conf une bonne fois pour toutes et c’est bon :grinning:
  4. Possibilité d’écouter sur une ipv6
  5. Ajout de la requête balance, pour avoir le solde d’un compte :

EDIT: pour profiter de la requête balance il faut ré-indexer GVA, pour cela supprimer les bases sled puis faites un dex migrate :

rm -rf .config/duniter/duniter_default/data/*_sled
dex migrate
4 Likes

En faisant:

git fetch && ./bin/duniter stop && git reset --hard origin/gva-proto-2 && cargo xtask build --production && ./bin/duniter webstart

Pas de soucis puis:

rm -rf .config/duniter/duniter_default/data/*_sled
ok
Mais:
./target/release/dex migrate
Donne pour résultat:

[...]
Apply chunk #69750-#69999 ..
Apply chunk #70000-#70249 ..
Apply chunk #70250-#70499 ..
Apply chunk #70500-#70749 ..
Apply chunk #70750-#70999 ..
Apply chunk #71000-#71249 ..
Apply chunk #71250-#71499 ..
Apply chunk #71500-#71749 ..
Apply chunk #71750-#71999 ..
Error: DB corrupted:Not found origin tx of uxto B28939C91BBB386AD00B53566FFE73B250CE0982287CB1068FD119473D28B6F0:0
$ ./target/release/dex --version
duniter-dbex 0.1.0

Je suis sur la ḠTest.

Normal tu n’a pas recompilé dex, un binaire n’à pas le pouvoir magique de se maj tout seul :wink:

1 Like

Mieux …

Apply chunk #664500-#664749 ..
Apply chunk #664750-#664999 ..
Apply chunk #665000-#665155 ..
Flush DBs caches on disk...
Migration successfully completed on 458 seconds.

Je savais pas que tu avais fait des modifs à dex c’est pour ça

1 Like

En fait je n’ai pas fait de modif à dex mais dex utilise en très grande partie le même code Rust que Duniter, notamment le code d’indexation de GVA est le même donc quand ce code change il faut recompiler dex aussi.

2 Likes

Tu as changé le champs genTxs en genTx aussi ?

Ha oui je trouvais ça plus logique. C’est aussi pour être consistant avec genComplexTx :slight_smile:

2 Likes

Rapide GVA !

4 Likes

Du nouveau sur GVA, le fruit des dev de la semaine :

1. Pagination

La pagination était en place sur utxosOfScript seulement mais elle était factice, le serveur récupérait toutes les UTXO en base puis renvoyait la page demandée.

J’ai réalisé un gros travail de fond pour mettre en place un vrai système de pagination qui s’applique au plus près de la base de donnée. C’est à dire que désormais seule la page demandée sera récupérée en base, ce qui devrait permettre de réduire les traitements à chaque requête et donc de les accélérer.

Cela à également nécessité un changement dans la manière de stocker les données, une réindexation est donc indispensable !

J’ai aussi modifié les inputs gérant la pagination, c’est désormais un «objet» Pagination qu’il faut renseigner avec les 3 champs suivant :

  • cursor: position de départ (ou de fin si ordre décroissant)
  • ord: ordre croissant (ASC) ou décroissant (DESC). Par défaut l’ordre est ASC. On parle aussi d’ordre normal pour ASC et d’ordre inverse pour DESC.
  • pageSize: ai-je vraiment besoin d’expliquer ce champ ?

2. Historique des DU créé

Suite à une demande de @vit j’ai implémenté une requête fournissant l’historique de tous les DU créé (consommés ou non).

Dans le schéma c’est la requête existante udsOfPubkey qui fourni désormais au choix tous les DU ou les DU non-consommés via le paramètre d’entrée filter de type UdsFilter :

enum UdsFilter {
  ALL
  UNSPENT
}

Si filter n’est pas renseigné il vaut ALL par défaut, c’est indiqué dans le schéma :

image

Exemple : récupérer les 2 derniers DU créé

Évidemment j’ai choisi 2 pour l’exemple mais il suffit d’augmenter pageSize pour avoir des pages plus grande.

Comment passer à la page suivante ?

Si vous parcourez les données dans l’ordre décroissant, il faut récupérer la valeur du champ endCursor et la placée dans le champ cursor en input :slight_smile:
Si en revanche vous parcourez les données dans l’ordre croissant, c’est le champ startCursor qu’il faut utiliser.

Enfin le champ hasPreviousPage vous indique s’il y a une page précédente (donc une page suivante pour vous si vous parcourez les données dans l’ordre DESC). Et si vous parcourez les données dans l’ordre ASC il faut utiliser hasNextPage à la place).

3. Récupérer des sources jusqu’à un certain montant

Les requêtes udsOfPubkey et utxosOfScript acceptent désormais un paramètre d’entrée amount, si ce paramètre est renseigné la requête va cesser son parcours de la DB dès que le montant demandé est atteint ou dépassé.

Cela permet à ceux qui le souhaitent de pouvoir continuer à sélectionner les sources eux-mêmes.

4. Champ sum sur les requetes udsOfPubkey et utxosOfScript

Les requetes udsOfPubkey et utxosOfScript exposent désormais un champ sum indiquant la somme des montants des sources de la page (avec gestion des bases).

Cela permet par exemple de calculer la somme de tout les DU d’un compte (attention requête lourde si le compte à beaucoup de DU). Je sais par exemple que tous les DU que j’ai créé représentent 13741,69 Ğ1 :

Et que mes DU non-consommés représentent 705,87 Ğ1 :


Est-ce que c’est clair au niveau du système de pagination ? Vous avez des questions ?

4 Likes

Excellent pour l’historique des DUs ! Merci beaucoup ! :+1:

Par contre la gestion de la pagination me paraît bien compliquée par le fait que les noms des paramètres à gérer changent selon le sens du tri, et qu’il faut gérer différemment le curseur si on est en descendant.

Si je me base sur mes habitudes en SQL, depuis des années, la pagination est assez triviale, car on joue simplement sur les paramètres OFFSET (ce qui ici se nomme cursor) et LIMIT (ce qui ici se nomme pageSize). Et ceci que je sois en tri ascendant ou descendant peu importe.

Ainsi, pour parcourir les pages en ascendant comme en descendant, j’incrémente simplement mon curseur OFFSET de numéro_itération * LIMIT (ici cursor sera incrémenté de numéro_itération * pageSize).

Je ne comprends pas pourquoi j’aurais besoin de valeurs en retour pour parcourir des pages. Et gérer la pagination différemment dans un sens et pas dans l’autre. Si tu pouvais m’éclairer sur ce point.

Parce que c’est les spec de graphql pour la pagination qui sont faites ainsi. Et que la lib que j’utilise implémente ces spec :

https://relay.dev/graphql/connections.htm

Le seul endroit où je ne respecte pas ces spec c’est pour les paramètres en input, car les paramètres attendus en input par ces spec (first, last, before, after) impliquent des contrôles supplémentaires, je peux toutefois proposer ces paramètres si quelqu’un utilise une lib client implémentant ces spec.

Il y a aussi une autre raison, pour que la pagination s’applique dès la base de donnée (et donc que ce soi une vraie pagination), il faut nécessairement fournir un cursor indiquant la clé de départ (sauf si on souhaite partir du tout début ou de la toute fin), cursor qui doit être une valeur correspondant à la clé stockée en base. Cela est dû au fait que la DB est un stockage clé-valeur, ce n’est pas du SQL, donc le cursor ne peut pas être un nombre entier qui s’incrémente comme en SQL.

3 Likes

@vit @poka @Moul @tuxmain et tous les potentiels utilisateurs de GVA, voici un article officiel de graphql sur la pagination, je vous invite à le lire pour comprendre pourquoi ils ont fait ça ainsi :

3 Likes

Ça ne vient pas de nulle part : soit on fait de la pagination par offset, soit par curseur. En demandant la page https://example.org/?page=2, cette page va changer dans le temps car de nouveaux items vont être créés. Avec la page https://example.org/?cursor=xxxx, on est sûr d’avoir toujours le même résultat !
Je prend l’exemple d’un lien, mais le principe est le même avec une bdd.

La pagination de graphql.org est un standard, et beaucoup de librairies le suivent comme relay.dev qui est méga utilisé par React. Vraiment super d’utiliser cette spec ! Ça simplifie aussi les choses en front pour les composants de pagination… mais à l’usage, il manque un truc.

En ayant tous les champs du pageInfo, on peut afficher une pagination du type :
« first < prev next > last ». Et griser next quand c’est hasNextPage est null par exemple.
Mais quand on ne connaît pas le nombre de page, on ne peut pas faire de pagination avec les numéros de page, ni afficher une info du type node 1 à 10 sur 97. Exemple ici.
Pour ça on a besoin d’avoir le nombre de edges.

Je vois que tu as sum { amount }. J’ai vu des API graphql qui utilisent aggregate et qui permet d’obtenir plein d’infos sur l’ensemble des edges, ex :

aggregate {
  avg
  count
  max
  min
  stddev
  stddev_pop
  stddev_samp
  sum
  var_pop
  var_samp
  variance
}

Je ne sais pas à quoi sert tout les champs, mais je pense que ce type d’interface est plus facile à maintenir et faire évoluer…

Gva est en panne là, mais j’ai retrouver une requête que j’ai fait il y a quelques jours… Avec aggregate ça donnerait :

query fetchRelayUtxos {
  utxosOfScript(
    script: "Hey elois !? Tu loggues les erreurs ? C'est quoi ce champ script ??"
  ) {
    pageInfo {
      startCursor
      endCursor
      hasNextPage
      hasPreviousPage
    }
    aggregate {
      count
      sum
    }
    edges {
      cursor
      node {
        amount
        base
        txHash
        outputIndex
        writtenTime
      }
    }
  }
}

Après, je ne sais pas si c’est pertinent pour toutes les queries et les types d’object utilisés pour duniter…

4 Likes

@ManUtopiK non non mon serveur GVA tourne bien, c’est parce que tu as essayé pile pendant que je le recompilai :stuck_out_tongue:

Merci pour tes retours d’expérience @ManUtopiK. Je vais utiliser ce champ aggregate ça me parle bien, c’est vrai que c’est plus facile à maintenir et faire évoluer ainsi :slight_smile:

Tu voudrais que le champ aggregate.count indique le nombre total d’éléments c’est ça ?

Le problème c’est que je ne peux pas déterminer ce nombre total d’éléments sans parcourir toutes les données, car ce nombre total ne correspond pas à la taille de la collection mais au nombre d’entrées qui vérifient les filtres demandés par le client.

Par exemple pour udsOfPubkey un filtre implicite mais bien présent c’est le fait que le client veut les UD d’une clé publique précise, pas tous les UD.
En fait pour quasiment toutes les requêtes paginées il y aura des filtres implicites ou explicites, dès que la requete est spécifique à un compte typiquement.
Je ne pourrais fournir un champ count que pour les requêtes générales et sans filtres, comme udsReval et blocks par exemple.

Suite aux déboires avec la messagerie Cesium+ je me lancé dans le développement d’un système anti-spam pour GVA !

C’est déployé sur g1.librelois.fr, si vous faites trop de requêtes vous vous prendrez un beau « too many requests » :stuck_out_tongue:

J’ai dans le même temps codé un système de whitelist, si vous avez besoin d’utiliser GVA en intensif vous devez utiliser votre propre serveur GVA et whitelister vos ip :slight_smile:

Les ip locales 127.0.0.1 et ::1 sont déjà whitelistés par défaut.

Si besoin, je peux aussi whitelister les IP des dev des clients sur mon nœud, envoyez-moi votre IP par MP. Mais une fois que Duniter 1.9 sera stabilisé je préférerai que vous utilisiez votre propre serveur GVA autant que possible.

L’anti-spam limite à 40 requêtes par tranche de 20 secondes, ça me semble suffisant pour un usage d’un utilisateur final, on pourra toujours augmenter un peu si l’on constate par l’expérience que c’est trop restrictif.

Attention si votre serveur GVA est derrière un reverse proxy, votre reverse proxy doit ajouter le header X-Real-IP, sinon l’anti-spam croira que toutes les requêtes sont locales et ne bloquera donc rien !

Par exemple sur nginx il faut ajouter la ligne suivante dans le bloc location dédié à GVA :

proxy_set_header        X-Real-IP       $remote_addr;
3 Likes

Ah oui, gva remarche :slight_smile:

Un intérêt aussi avec aggregate, c’est que tu peux faire passer toutes les datas concernant l’ensemble des nœuds d’un coup, et faire des composants du style :

<Stats :data="aggregate" />
<List :data="nodes" />
<Pagination :data="pageInfo" />

On n’est pas obligé de déclarer chaque data:

<Stats :sum="sum" :count="count" :avg="avg" ... />

Et puis c’est plus clair : pageInfo contient les informations de la page en cours, nodes les éléments, et aggregate les informations sur l’ensemble des éléments.

Oui, c’est ça. Tu ne peux pas obtenir le total d’éléments en fonction des filtres ? Car en ayant le total, et connaissant la limite de pagination (argument first, limit ou pageSize) on peut connaître le nombre de pages et afficher des paginations plus complexe avec le numéro des pages du style :
<prev 1 2 3 ... 96 97 next>
Ce n’est pas indispensable non plus…

1 Like

Si en parcourant toutes les données en base, mais c’est dramatiquement inefficient, ça revient à ne pas faire de pagination du tout coté serveur, à prendre toutes les données à chaque requête, même s’il y en a des millions et que le client n’en veut que quelques dizaines, c’est clairement hors de question.

Une autre possibilité serait de créer des nouvelles collections pour stocker et mettre à jour des compteurs, mais ça complique le code d’écriture des données, et ça ne peut se faire que sur les filtres simples (par exemple le script d’un compte).

Je pense aussi. Il faudra réfléchir à quels sont les cas qui aurait plus besoin que d’autres d’une numérotation des pages, afin de ne complexifier que là où c’est dûment justifié, je chasse toujours la complexité inutile, car c’est l’une des premières cause de bug et d’imaintenabilité.

Oui, la solution est de créer une nouvelle table pour stocker les compteurs. Mais ça complique beaucoup les choses. Et comme tu dis, ce n’est pas forcément utile. A voir au cas par cas…

Autre chose, si tu veux respecter les specs de relay, ce n’est pas les arguments pageSize et ord qu’il faut utiliser, mais first/after ou last/before.
Les specs relay sur la pagination.
Apollo (la plus complète lib front et serveur du monde javascript) utilise aussi cette spec.

Pour info, hasura en mode relay-api utilise les arguments :

after:
before:
distinct_on:
first:
last:
order_by:
where:
1 Like

C’est inexact. Si le ou les éléments qui suivent le curseur ou l’offset ont changé alors le contenu changera. Seul l’élément sur lequel est le curseur reste le même en mode curseur. Mais je comprends l’idée. On a un point d’encrage avec le curseur, pas avec l’ offset. :wink:

Le défaut que je vois avec le curseur par rapport à l’offset est si l’élément sur lequel pointe le curseur a été supprimé. Dans ce cas que se passe–t-il ? :sweat_smile: