[ĞDev proposal]: batch pour corriger les incohérences du storage causées par les bug #46 #57 #60 et #62

Pas moins de quatre bugs différents ont causés des incohérences dans le storage de la ĞDev, je propose de les corriger via des batchs manuels, l’objet de ce sujet est de détailler ces batchs qui vont être soumis au vote.

D’abord deux notions importantes générales à substrate que vous devez avoir en tête :

1. Sudo n’est pas Root, et Root n’est pas Sudo

Sudo est optionnel, mais Root est obligatoire et nécessairement présent dans toute blockchain substrate.

Sudo est juste un moyen, parmi une infinité imaginable, d’agir en tant que Root. Sudo c’est juste une pallet qui permet à un compte défini (la sudo key) d’exécuter un call en tant que Root.

En l’absence de Sudo, il faut que soit défini au moins un autre moyen d’agir en tant que Root, ce “moyen” étant généralement un processus de gouvernance on-chain.

Mais dans tous les cas, Root est toujours présent et peut toujours tout faire, la seule chose qu’on peut choisir, c’est par quel processus la communauté peut agir en tant que Root.

Root peut modifier n’importe qu’elle entrée dans le storage, via les call system.setStorage et system.killStorage.

2. Il y a plusieurs moyens différents de corriger le storage, plus ou moins adaptés selon les cas

J’identifie au moins quatre moyens différents, et dans mon travail pour moonbeam on a déjà utilisé les trois premiers :

  1. Exécuter un ou plusieurs batchs manuels en tant que Root, c’est ce que je propose dans ce sujet.
  2. Créer un ou plusieurs call de hotfix dans le storage, qui ne peuvent généralement être appelés que par root. Cette méthode est plus lourde, puisqu’elle nécessite de coder et tester un ou des nouveau(x) extrinsic(s) temporaires, et de déployer un nouveau runtime avec ces extrinsics, mais elle permet de gérer des incohérences plus complexes, que les batchs manuels seuls ne peuvent pas gérer.
  3. Écrire une migration dans un nouveau runtime, et déployer ce nouveau runtime, ce qui exécutera la migration : c’est plus délicat, à n’utiliser que si les méthodes précédentes ne sont pas utilisables.
  4. Passer la blockchain en lecture seule pour les utilisateurs, et effectuer plusieurs fois l’une des trois méthodes précédentes le temps de corriger ce qui doit l’être, puis repasser la blockchain en mode “normal”. Chez moonbeam on a testé le passage en lecture seule pour s’assurer qu’on pourrait le faire si on en avait besoin un jour, mais on n’en a jamais eu besoin.

On préférera toujours 1. si c’est possible, sinon 2., sinon 3.


Dans le cas présent, la méthode des batchs manuels est parfaitement adaptée au bug #57, elle est en revanche limite pour les bugs #46 #60 et #62. Ça va fonctionner uniquement parce que nous avons encore très peu de données et très peu d’actions utilisateur, donc l’état change très lentement, mais en prod sous forte charge on aurait été obligés de créer un extrinsic de hotfix, puis, après l’upgrade d’utiliser cet extrinsic de hotfix dans un (ou plusieurs) batch manuel.

Détails du 1ᵉʳ batch soumis au vote

Je viens de proposer en blockchain un 1ᵉʳ batch, le hash du proposal est 0xd9667b15ff7ffe6e364c0aff31e117e4dfef81cc37b41727f7c18ef9e9962287

Ce 1er batch ne peut pas tout corriger, car tout les calls dans le batch doivent réussir, et pour tout corriger il aurait fallu proposer certains call qui pourront échouer selon l’évolution de l’état d’ici que le batch soit exécuté.

Ce 1er batch vide les huit comptes invalides, en effectuant un transfer_all en leur nom vers la trésorerie. Il y a malheureusement deux cas ou cela ne va pas supprimer le compte :

  1. Si le compte à une identité associée, transfer_all va laisser 2 ĞD, et le compte ne sera donc pas supprimé. C’est le cas pour le compte 5G27hLdxFgtaZtwFDhs6iMp5fBgwCaoEXaKFJzk16wV6MhnG, c’est pourquoi le batch contient un call pour supprimer l’identité associée (index 23). Mais d’autres comptes peuvent créer une identité entre-temps, et on ne peut pas remove leur identité préventivement, car on ne sait pas quel IdtyIndex leur sera attribué.
  2. Si le compte a déjà un solde de zéro, c’est le cas pour deux comptes sur les huit (du moins au moment où j’ai conçu le batch). Pour contourner ce point, je vais verser 2 ĞD sur ces deux comptes, mais ils peuvent être re-vidés entre-temps (et les autres comptes aussi).

Pour voir le détail du batch, dans la vue “Chain State”: smithsCollective.proposalOf() puis indiquez en paramètre le hash du proposal, vous obtenez ceci :

Je vous recommande de copier-coller le détail du batch dans un éditeur de texte avancé style vscode :

image

On voit alors plus clairement que le call proposé est upgradeOrigin.dispatchAsRoot(utility.batch(calls)).

Le 1ᵉʳ call du batch :

image

C’est un utility.dispatchAs(origin, call) avec pour origin l’un des comptes invalide, et pour call un transfer_all vers la trésorerie.

Tout les call du batch sont du même style sauf deux, celui qui supprime l’identité 23 et celui qui supprime une entrée dans UdAccountsStorage. Regardons ce dernier :

          {
            args: {
              keys: [
                0xd97d5a9fdd130e4394796760df433ce0a3309dc5910075f2fe7bcc37353af7ea83d0d534df964266fca6837ccfc60172b8f7ab8cd50e22901a2703a238897bb2cd9cb1b13820ae4971833023b9f33534
              ]
            }
            method: killStorage
            section: system
          }

La clé correspond à twox128("UdAccountsStorage") ++twox128("UdAccounts") ++ blake2_128(0x5GFEEx7kqvP4QEPCAXkALeYCG7m8DiA5LQ4YzW62j7FytQyg) ++ 0x5GFEEx7kqvP4QEPCAXkALeYCG7m8DiA5LQ4YzW62j7FytQyg.

Vous pouvez la vérifier dans Polkadot{.js} (il vous faut ajouter la clé publique dans vos contacts) :

En bas à gauche, “encoded storage key” est la raw key complete. En bas à droite vous avez la décomposition. Pour module et method la fonction de hashage n’est pas précisée, car c’est toujours twox128 dans tous les cas.

1 Like

Je réponds à chaud (et il est minuit passé). Est-ce vraiment important de corriger ces incohérences ? Avantages que j’y vois :

  • on s’entraîne à faire ce genre de manips qui seront peut-être utiles un jour en prod
  • on élimine des sources de bugs potentielles

Inconvénients :

  • ça représente une surcharge de travail importante assumée par la seule personne actuellement opérationnelle sur le développement du cœur (à en juger par la longueur du post)
  • si on résout ce problème rapidement, on perd une occasion de former quelqu’un d’autre à gérer ce genre de situation, y compris en cas de charge plus forte

Je m’arrête là, je veux juste exprimer le fait que je ne comprends pas bien les raison de ce choix de priorisation d’une tâche « de maintenance » sur une monnaie de développement.

2 Likes

J’ai besoin de tout comprendre sur ce qu’il se passe sur cette monnaie, et si on laisse trainer des états incohérents, ça va devenir dur de suivre toutes les conséquences de ces états incohérents.

Ça peut aussi masquer d’autres bugs. À l’avenir je déploierai des tests de contrôle de cohérence du storage, car ils sont utiles pour détecter des bugs, c’est ce qu’on fait au boulot. Mais si les réseaux de tests ont des états déjà incohérents, alors ces tests ne marchent pas, ou remontent des faux positifs.

Mon expérience chez Moonbeam m’a appris que c’est super important de ne pas laisser trainer des incohérences sur les réseaux de tests, les réseaux de tests sont des thermomètres, on peut les casser, mais il faut les réparer dès que possible ensuite, sinon le thermomètre ne mesure plus rien, et on avance à l’aveugle.

De plus, il faut s’entraîner aux taches de maintenance sur les monnaies de développement, sinon on ne sera pas prêt quand ça arrivera sur la monnaie de prod.

Enfin, je ne serais de toute façon pas serein pour avancer dans le développement tant que la ĞDev ne sera pas clean.
Il faut y aller étape par étape, on ne peut pas poser le plancher ni les murs tant que les fondations ne sont pas saines :slight_smile:

3 Likes

T’inquiète des occasions il y en aura d’autre, je peux aussi les créer au besoin. Dans le cadre d’un exercice pratique, je peux très bien créer volontairement un état inconsistant en modifiant le storage avec Sudo. On peut même faire ça dans un fork de la ĞDev pour ne pas impacter ceux qui ne participent pas à l’exercice :slight_smile:

La charge plus forte, ça se crée aussi, c’est ce qu’on fait au boulot, et je compte bien le faire sur la ĞDev quand elle sera plus stable (et quand on aura fait les benchmarks des poids surtout) :slight_smile:

1 Like

Merci pour tous ces détails @elois, par contre de mon côté les sollicitations pour voter son trop nombreuses en trop peu de temps, je ne vais pas pouvoir suivre. Cela ne me dérange pas que tu passes en force avec un sudo à la place pour ce genre de correctifs très précis et dont tu es le seul à avoir la connaissance suffisante. Je vais me contenter de lire les rapports si tu en produis, comme ici.

2 Likes

Ok, je vais corriger le storage avec sudo.

EDIT: c’est fait, le batch s’est appliqué intégralement avec succès au bloc #151277 :partying_face:

Suite à l’application du batch, il restait 2 comptes qui persistait dans le storage alors que leur solde était de zéro, je les aie supprimés avec sudo au bloc #151326:

Maintenant, il semble qu’il ne reste enfin plus d’états incohérents liés aux bugs corrigés.

Il faudra que je mette en place des tests de cohérence du storage pour m’en assurer :slight_smile:

6 Likes

Voilà qui est fait:

Pour le moment ces tests de cohérente du storage (que j’ai nommé “sanity tests”) ne vérifient que 8 règles liées aux comptes et aux identités, mais ça couvre déjà toutes les incohérences induites par les bugs #46 #57 #60 et #62.

Ces tests peuvent être exécutés au bloc de votre choix (voir comment dans le README).
Je les ai notamment exécutés aux blocs 151325 et 151326, et comme attendu ils échouent au bloc 151325 et réussissent au bloc 151326.

5 Likes