Implémentation de Duniter en Rust?


#61

Du coup tu pourras aisément reprendre le fichier tests.js de wotb, même si ça fait redondant ça permet d’avoir notre invariant relativiste !


#62

Exactement c’est ce que je prévois de faire, et comme ça je n’ai pas à traduire le test sur la grande WoT. Pour l’histoire de la WoT par fichier, que pense tu de la charger en mémoire, et de mettre à jour le fichier uniquement quand il y a des modifications (add_link, remove_link, add_node, etc) ?

Sinon vu que c’est le code appellant qui donne le chemin du fichier, je suis bien obligé d’utiliser le même format que vous, non ?


#63

Moi je n’ai aucun problème avec ça, c’est bien plus efficace que l’algorithme actuel de wotb. Sur modification : impacter la zone mémoire et la zone fichier. Très bien.

Atomicité : ne retourner le résultat qu’une fois les 2 zones effectivement modifiées.

Pas vraiment : tu peux utiliser une transformation :slight_smile:


#64

Yeap, à ce moment là il faudra que le code appelant puisse récupérer les cas d’erreurs (via un tuple (result, error) retournées par les fonctions).

Je vois, rajoutant ma propre extension derrière par exemple. Comment faire par contre quand le fichier .bin du module C++ existe déjà ? Je dois proposer un code qui convertisse de l’un à l’autre ? Et si on veut repasser au module C++ ensuite ? Ou alors quand on change de module, on force la régénération manuelle de la WoT ?


#65

Si l’on veut assurer une continuité de service, alors nous faut pouvoir migrer l’ancien wotb.bin vers ton propre fichier. Et bien sûr ce ne peut pas être wotb qui le fait, car sinon on conserve encore la dépendance dessus.

Toutefois la BDD contient toutes les données nécessaires pour re-générer la toile via duniter-rs-wotb-js que développes, donc tu peux faire ça dans Duniter dans la phase de migration en ajoutant la tienne.

Voilà, donc on peut rapidement s’en sortir.

Si jamais tu arrives à faire tout cela, et qu’on arrive effectivement à avoir duniter-rs-wotb-js avec une ABI Nw.js (l’idéal c’est que tu puisses générer les 2 ABI NodeJS et Nw.js), alors il ne restera plus qu’une étape pour que ce module soit acceptable : avoir un tuto montrant comment déboguer un programme Rust :slight_smile:

Car personnellement, j’ai échoué. J’ai pas cherché trop longtemps non plus. Mais il nous faut absolument cette feature pour coder sereinement !


#66

Si je comprends bien l’ancien wotb sera complètement remplacé par mon module de coup ?

Du coup dans le “processus de mise à jour” du logiciel il y a moyen d’appeler une méthode pour migrer de format ?

Je vais faire des recherches, car j’ai un peu de mal à voir où se situe le problème.

Je vais regarder pour ça aussi, mais pour l’instant le debugging Rust est assez expérimental, car on utilise un débogueur qui n’a pas été fait pour. De mon côté je n’ai pas debug avec LLDB mais avec GDB. Je n’ai pas utilisé les “espions” dont tu parlais la dernière fois, mais les variables s’affichent bien :

EDIT : Ma config de debug :

{
    // Utilisez IntelliSense pour en savoir plus sur les attributs possibles.
    // Pointez pour afficher la description des attributs existants.
    // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug",
            "type": "cppvsdbg",
            "request": "launch",
            "program": "${workspaceRoot}/target/debug/duniter_rs_wotb-d225c351eff715d1.exe",
            "cwd": "${workspaceRoot}"
        }
    ]
}

#67

Oui.

Oui complètement. Tu peux notamment, dans ce processus, aller chercher toutes les données dont tu as besoin en BDD puis les envoyer à la méthode de migration qui se trouve dans ton module.

Ou encore, dans ce processus tu vas chercher les données en BDD puis tu pilotes le module Rust pour reconstruire la WoT complète (à coup de add_node, add_link, disable_node, …).

Peu importe au final, ça dépend où tu veux que soit le contrôle.

En tout cas le point de départ c’est lien que j’ai donné plus haut : https://github.com/nwjs/nw.js/wiki/using-node-modules#3rd-party-modules-with-cc-addons

C’est déjà largement suffisant, super :slight_smile: il manque plus qu’un petit tutoriel d’installation.


#68

Sympa le logo !


#69

Merci, j’ai mis 30 mins à galérer sur comment combiner les calques pour avoir la multiplication que je voulais xD


#70

@cgeek J’ai trouvé ça sur l’utilisation de Rust avec nw.js : https://github.com/iamdanfox/frankenrust

Après je compile directement avec neon build, tu penses qu’il y a moyen qu’il utilise nw-gyp à la place de node-gyp quand on compile l’application pour desktop (avec un alias peut-être) ?

Tu veux que je fasse ça où ?


#71

Oui on fait ce qu’on veut dans les builds de VM, après il y a tellement d’éléments en jeu que je préfère ne rien affirmer : le meilleur moyen de s’assurer que ça fonctionne est de tester.

Tu peux simplement faire un nouveau sujet en section Dev, en plus c’est facile de faire un tuto ici avec des impressions d’écran (le copier/coller d’image fonctionne).


#72

Lecture/écriture de la WoT dans un fichier fonctionnelle ! (sérialisation avec la crate serde)
Il ne manquera plus qu’à régénérer la WoT pendant la transition, je verrais ça avec vous quand le module JS sera bon.

EDIT : Ajout publié sur master et sur crates.io. (la doc devrait être générée d’ici peu)


#73

Je n’ai pas implémenté les fonctions memCopy, clear, showWoT, dumpWoT, showGraph, resetWoT dans la partie en Rust, est-ce qu’elles sont vraiment utiles ?

  • clear et resetWoT revient à créer une nouvelle instance qui remplace l’originale, non ?
  • showWot, dumpWot et showGraph représentent la WoT sous forme de texte, est-ce vraiment utile (et utilisé) ?
  • memCopy : non implémenté pour le moment, mais je prévois de le faire plus tard, je ne pouvais juste pas le faire automagiquement à cause du HashSet<NodeId> que j’utilise dans Node. Après il faudrait voir si un HashSet est vraiment utile comparé à un Vec. L’avantage du HashSet est qu’il permet de savoir rapidement s’il contient un élément sans devoir le parcourir en entier, et il propose quelque chose de similaire pour la suppression d’un élément. Mais vu qu’il n’y en à pas beaucoups, un Vec pourrait fournir des performances sensiblement similaire, une empreinte mémoire plus faible et qui implémente le trait Copy (rendant de ce fait Node éligible au Copy aussi).

Sinon je ne comprends pas trop ce code : est-ce que les fonctions définies dans const WotB sont des méthodes de classes ? Qu’est-ce qu’est un instanceId ? D’où il sort ?
Je suppose que ça parti du système de wrapper autour d’un code C++.

Avec Neon, je peux directement déclarer des classes Javascript dans mon code en Rust (voir ce billet). Est-ce que ça pourrait convenir et éviter de les déclarer en Javascript ?

Pour la partie “WoT fichier ou mémoire”, je pense plutôt proposer ça du côté du wrapper JS, avec une classe qui encapsule une “WoT mémoire” simple, et une autre pour une “WoT mémoire” chargée depuis un fichier et qui écrit à chaque modification. Est-ce qu’il y a moyen d’écrire le code d’une interface que ces 2 classes peuvent implémenter, ou du duck-typing suffi ?

De même, vu que la WoT avec fichier peut échouer (problème d’écriture dans le fichier principalement), je pensais retourner un tuple (result, err) assez classique en JS, mais qui n’est pas utilisée dans l’implémentation courante. Comment faire pour l’intégrer à duniter-ts en évitant de modifier le code appelant ? Une idée qui me vient serait de ne pas retourner le tuple mais de lancer une exception en cas d’erreur. Du coup, est-ce que le code appellant fait un try ... catch autour des appels au module ? Si oui, sont-ils adaptés à cette façon de faire ? Si non, quelle solution pourrions-nous utiliser pour vous notifier d’un éventuel problème d’écriture ?

Pour finir, merci beaucoup pour le soutient, ça me fait plaisir de voir que mon projet d’implémentation en Rust vous intéresse autant.


#74
  • memcopy est indispensable, utilisée lors de la génération d’un bloc pour vérifier que les identités ajoutées respectent bien la règle de distance (code).
  • clear aussi, complément de memCopy pour éviter les fuites mémoire (sauf à ce qu’un garbage collector fasse cela tout seul)
  • showWoT, dumpWoT, showGraph sont inutilisées
  • resetWoT n’est plus utilisée aujourd’hui, car Duniter sait quel fichier supprimer pour wotb. Mais si c’est le module Rust qui garde secrète cette info (le chemin du fichier de stockage, si c’est un fichier), il faut qu’il propose une méthode pour supprimer les données et remettre la toile à zéro.

Oui ce sont des méthodes de classe. C’est de l’ES5 préconisé par David Walsh et Kyle Simpson pour les objets JavaScript. Ceci dit je ne l’ai fait que là, et je ne suis pas convaincu du truc.

Le module wotb permet de gérer plusieurs WoT à la fois. Quand tu fais un memCopy(), tu obtiens une seconde WoT gérée par wotb. C’est un identifiant interne qu’il attribue lui-même (en gros c’est un index de tableau) (code).

Mais c’est une logique interne à wotb, du côté Duniter je demande une instance (fichier ou mémoire) sur lequel je peux faire des memCopy() au besoin, et de toucher à la WoT que je veux.

Comme ça je ne vois pas d’objection.

Le must c’est d’utiliser TypeScript, tu peux alors déclarer une interface que tu implémentes. Ce qui est appréciable pour ceux qui consomment ton module, c’est que le typage leur est transmis. Et donc même sans connaître ta bibliothèque j’ai accès à la description des objets, classes, méthodes et types retournés.

Tu peux ajouter la transpilation TypeScript facilement à coups de :

yarn add --dev typescript @types/node

Puis dans la partie scripts de ton package.json :

 "scripts": {
   "prepublish": "tsc",
   "tsc": "tsc"
}

Alors à chaque commande yarn tu transpiles tout le code TypeScript présent. Éventuellement tu ajoutes un fichier tsconfig.json à la racine du projet pour piloter le comportement du transpiler.

Lance une exception. De toute façon s’il y a une erreur d’écriture dans le module Rust (ou wotb) on est foutus, ce module est au cœur de la blockchain donc en cas d’échec c’est une erreur critique. Je considère que si ce module faillit, on peut dire que le nœud est HS.

On a besoin de sécuriser un maximum Duniter, ce qui implique qu’on soit plusieurs à avoir un regard et une maîtrise dessus. Vu les efforts et la volonté que tu manifestes, si au moins tu pouvais être le garant de la partie calcul de WoT, ce sera déjà une grande aide pour nous. Et le code Rust est plus concis que la version C++, alors c’est une avancée.

Donc dans ces cas-là, ne pas considérer tes développements serait pure perte pour nous. Merci à toi au contraire !


#75
  • memcopy : très bien, je vais la faire, c’est plutôt simple. (je l’appellerai clone(...) pour être plus idiomatique mais je l’exposerai en JS sous le nom de memcopy.
  • clear : pas de garbage collector en Rust, mais tous mes objets alloués sont libérés. (garanti par le borrow-checker de Rust, c’est là un des grands points forts de Rust)
  • resetWoT : j’ai juste changé l’extension en .wot quand je l’appelle, mais tu auras juste a remplacer .bin en .wot, ou laisser .bin (en risquant de taper sur un fichier avec l’ancien format).

Du coup je ne vais exporter que mon type WebOfTrust dans le code Rust, et faire l’interface et les implémentations en TypeScript.

Okep, je le ferais donc dans l’instance “fichier”. Normalement aucune raison que ça arrive dans la version “uniquement en mémoire”.


#76

Oui mais quand ?


#77

Chaque variable allouée est libérée quand elle sort de sa portée. Je ne fais aucun équivalent de new ou new[]. Quand à la durée de vie de l’objet WebOfTrust en lui même, elle sera associée à celle de l’objet Javascript que gérera Neon. Quand l’objet Javascript que tu utilisera sera libéré, Neon s’assurera de détruire mon objet Rust, et ça libérera tout le contenu de ma struct.

C’est justement ce qui fait la puissance de Rust : toute variable (binding en Rust) à un possesseur (contexte d’un bloc, fonction, ou une struct), et quand se possesseur est hors portée, la mémoire est libérée. Les références de Rust empreinte cette possession de variable, et ne peuvent pas référencer une adresse invalide (pas de null non plus).

EDIT : Je vais surement avoir besoin d’un peu d’aide, je n’ai connais rien en TypeScript :confused:
EDIT 2 : J’ai quelques problèmes avec Neon, les guides sont obsolètes et impossible de trouver comment faire dans la doc pour le moment. Je vais continuer à chercher encore un peu. J’ai regardé du côté de rust-in-node pour voir si l’herbe est plus verte, mais c’est du simple FFI et ça ne propose pas d’encapsulation des structs Rust dans un conteneur Javascript.
EDIT 3 : C’est bon le module Rust veut bien compiler.


#78

C’est bon, les appels depuis Node.JS fonctionnent :
index.js :

let WebOfTrust = require('../native').WebOfTrust;

{
    let wot = new WebOfTrust(3);
    console.log(wot.getMaxCert())
    wot.setMaxCert(4);
    console.log(wot.getMaxCert())
    console.log(wot.addNode());
    console.log(wot.getWoTSize())
}

console.log("-----")

{
    let wot = new WebOfTrust("hey.wot");
    console.log(wot.getMaxCert())
    console.log(wot.addNode());
    console.log(wot.getWoTSize());
    console.log(wot.toFile("hey.wot"));
}

Sortie :

PS .\duniter-rs\duniter-rs-wotb-js\lib> node .\index.js
3
4
0
1
-----
4
16
17
true
PS.\duniter-rs\duniter-rs-wotb-js\lib> node .\index.js
3
4
0
1
-----
4
17
18
true

Il ne reste plus qu’a faire l’interface et les wrappers mémoire/fichier, mais là @cgeek j’ai besoin de tes directives :stuck_out_tongue:

EDIT : J’ai setup yarn et typescript comme tu m’avais indiqué, j’ai rajouté un tsconfig.json, mais l’inclusion du native ne marche pas :confused:

lib/index.ts :

import native = require('../native');

tsconfig/json:

{
  "compilerOptions": {
    "target": "es6",
    "outDir": "build",
    "declaration": true,
    "rootDir": "lib",
    "module": "commonjs",
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noImplicitReturns": true,
    "noImplicitThis": true
  },
  "include": [
    "lib/*",
    "lib/**/*"
  ],
  "exclude": [
    "dist",
    "node_modules",
    ".vscode"
  ]
}

EDIT 2 : Trouvé. Vu que c’est un module Javascript sans typage, je suis obligé de l’inclure comme ceci :

let native: any = require('../native');

EDIT 3 : Wrapper fonctionnels, ya plus qu’a tester !


#79

Heureux d’avoir été utile :grin:


#80

Je vais avoir besoin d’aide maintenant ! @cgeek

Il faudrait que tu clones le dépôt (branche feature/typescript-wrappers) et que tu lances les tests. J’ai des tonnes d’erreurs alors que les tests en Rust passent sans problème.

Pour lancer les tests du code en Rust, tu peux cloner ce dépôt, puis faire un cargo test.