Vos premiers développements sur la blockchain Ethereum - Partie 2

9.    Miner

Vient le moment de gagner un peu de monnaie, pour pouvoir faire vivre notre blockchain privée.

Pour cela, il faut demander à notre client de démarrer le minage. Un bloc contient un nombre quelconque de transactions. Le mineur récupère les transactions en attente sur le réseau, et commence à chercher la preuve de validité du bloc. Pour simplifier, ce travail consiste à trouver une valeur qui respecte une condition particulière parmi un très grand ensemble de possibilités. Pour cela il réalise plusieurs tentatives, jusqu’à trouver la solution. Une fois qu’il pense avoir trouvé une solution, il la soumet au vote des autres nœuds du réseau. Par consensus, si le réseau affirme qu’il s’agit bien de la solution, alors le bloc est ajouté à la blockchain.

Il est important de préciser qu’il est très difficile de trouver une solution, mais que la validation de la solution est immédiate.

Pour démarrer le mineur, on exécute donc la méthode

> miner.start()
I0513 13:52:57.431007 miner/miner.go:119] Starting mining operation (CPU=8 TOT=9)
 I0513 13:52:57.431108 eth/backend.go:450] Automatic pregeneration of ethash DAG ON (ethash dir: /root/.ethash)
 I0513 13:52:57.431131 eth/backend.go:457] checking DAG (ethash dir: /root/.ethash)
 I0513 13:52:57.431141 miner/worker.go:559] commit new work on block 1 with 0 txs & 0 uncles. Took 112.309µs
 I0513 13:52:57.431171 ethash.go:259] Generating DAG for epoch 0 (size 1073739904) (0000000000000000000000000000000000000000000000000000000000000000)I0513 13:52:58.091417 ethash.go:291] Generating DAG: 0%
 I0513 13:53:00.671188 ethash.go:291] Generating DAG: 1%

I0513 13:57:41.525050 ethash.go:291] Generating DAG: 100%
 I0513 13:57:41.526461 ethash.go:276] Done generating DAG for epoch 0, it took 4m44.095291362s



I0513 13:58:18.849764 miner/miner.go:119] Starting mining operation (CPU=8 TOT=9)
 I0513 13:58:18.849875 miner/worker.go:559] commit new work on block 1 with 0 txs & 0 uncles. Took 85.301µs
 I0513 13:58:19.926509 miner/worker.go:341] 🔨  Mined block (#1 / 901cea76). Wait 5 blocks for confirmation

> miner.stop()

Au premier démarrage du mineur, un fichier d’environ 1Go est généré par le client (le DAG), il sert au mineur dans sa recherche de solution. Cette phase est relativement longue. Pendant ce temps, le mineur ne travaille pas.

Vous remarquerez que le temps pour construire un bloc est vraiment très court (quelques micro-secondes). Cela vient du fait que notre bloc genèse possède une difficulté extrêmement faible. Cela nous permet de réaliser n’importe quelle action sur notre réseau de test, très rapidement.

L’argent gagné par le mineur est versé par défaut sur le premier compte créé sur le nœud. Comme nous n’avons créé qu’un seul compte sur chaque nœud, ce dernier recevra automatiquement la rémunération du mineur.

Nous pouvons consulter le solde du premier compte du nœud.

 > web3.fromWei(eth.getBalance(eth.accounts[0]),'ether')
 5

Comme affiché dans la console, notre compte possède 5 unités de la monnaie (ether). Il s’agit d’une grosse somme sachant qu’une transaction coûte une très petite fraction d’ether.

Vous pouvez à présent faire miner vos autres nœuds.

10.    Echanger de la monnaie

De la même manière que pour la plateforme Bitcoin, la plateforme Ethereum est capable d’inscrire sur la blockchain des transactions financières.

Nous allons donc réaliser notre première transaction. Je vais envoyer 1 unité d’ether du compte sur le nœud 1 au compte sur nœud 2.

Afin de pouvoir envoyer une transaction, il est nécessaire de s’authentifier auprès du client. Pour cela, il faut saisir :

> personal.unlockAccount(eth.accounts[0],"monmdp",60)

Le dernier paramètre est facultatif et indique la durée de l’authentification en secondes.

Ensuite, je peux créer ma transaction et la soumettre au réseau.

> eth.sendTransaction({from:eth.accounts[0],to:"0x8baf922d903d3e3ef49082fb07d973116effdbe0",value: web3.toWei(1,"ether"),data:"cadeau"})

0x9bf31717a6e78ae4b7b58ab25ab6fb60105a0b27c02c053e1dcfd2545c0c899c

À la création de la transaction, j’indique le compte débité (from), le compte crédité (to) et le montant (value). J’ai également ajouté une donnée personnelle (data, facultatif), qui sera inscrite dans la blockchain et que le compte crédité pourra lire.

La valeur retournée n’est autre que l’identifiant de la transaction. À partir de cet identifiant il est possible de récupérer toutes les informations de la transaction, ainsi que son état (présente sur la blockchain ou en attente).

Si un mineur était actif sur le réseau, la transaction a été ajoutée à la blockchain. Si le nœud 2 synchronise sa version locale, il verra son solde passer de 0 à 1.

11.    Créer notre premier contrat

L’intérêt de la plateforme Ethereum est de pouvoir créer du code exécutable sur la blockchain, appelé smart-contract. Pour cela, je vous propose un exemple de contrat très simple : la satisfaction client. Qui n’a jamais suspecté que l’indice satisfaction client affiché sur un produit ou un service n’était pas exactement celui exprimé par les clients ? Grâce à la blockchain, et au contrat que nous allons rédiger, nous nous assurerons que le taux affiché est bien réel.

Pour commencer, rédigeons les lignes de notre contrat à l’aide de Solidity, le langage de programmation dédié.

contract SatisfactionClient {
     
     /* Hash de "service après ventes " */
     bytes32 constant SUJET = 0x428dddc2ccc007e2a6251a8e2d9ff928061f5fc5f475a9a4e3a6d9ba8ddf4807;
     
     uint constant SEUILFAIBLE = 40;
     uint constant SEUILFORT = 80;
     
     string constant FAIBLE = "FAIBLE";
     string constant MOYEN = "MOYEN";
     string constant FORT = "FORT";
     
     uint nbClients = 0;
     uint satisfaction = 0;
     
     mapping(address => bool) aSoumis;
     
     function soumettreSatisfaction(uint s){
         if( aSoumis[msg.sender] || s<0 || s>100 ) throw;
         aSoumis[msg.sender] = true;
         satisfaction += s;
         nbClients += 1;
     }
     
     function satisfactionProduit() returns (uint256,string){
         uint256 taux = satisfaction/nbClients;
         if( taux < SEUILFAIBLE ) return (taux,FAIBLE);
         if( taux >= SEUILFORT ) return (taux,FORT);
         else return (taux,MOYEN);
     }
     
 }

 

Solidity représente un contrat comme un objet comportant des variables d’état et des fonctions. La syntaxe est relativement simple, et similaire à un langage de programmation orienté objet. Chaque variable est statiquement typée.

La première variable du contrat est le hash du sujet de satisfaction client. Sa valeur est déterminée au préalable en passant la fonction web3.sha3 (« service après-vente ») dans la console du client Geth. La valeur retournée fait 32 octets et est au format hexadécimal. De cette manière, on ne stocke pas la chaîne « service après-vente », mais son hash pour éviter le problème d’encodage et figer le sujet.

Deux autres constantes sont utilisées pour faciliter la compréhension. Les seuils (SEUILFAIBLE et SEUILFORT) permettent de retourner l’état de la satisfaction, sous la forme d’un des mots clés. Les 3 constantes suivantes sont les chaînes de caractères (string), associées à chaque état du taux de satisfaction (FAIBLE/MOYEN/FORT).

Nous stockons également le nombre de clients qui ont soumis une valeur et la somme des satisfactions. Ces variables sont de type unsigned int (uint) et initialisées à 0.

La dernière variable d’état est un dictionnaire, c’est-à-dire un tableau qui pour n’importe quelle clé, retourne une valeur. La particularité est que le mapping ne possède pas de taille. Il initialise virtuellement pour chaque clé possible (en fonction du type), une valeur initiale correspondante à 0. Il n’est donc pas possible d’ajouter ou de supprimer des clés. Il est simplement possible de modifier la valeur associée à une clé. Ici, notre mapping est de type (address => bool). Elle stocke donc pour chaque adresse (type spécial qui représente une adresse Ethereum), un booléen à vrai si l’adresse a déjà soumis sa satisfaction, ou faux dans le cas contraire. Ce système permet d’ôter la possibilité de soumettre plusieurs fois son avis (du moins avec la même adresse).

Nous n’avons pas défini de constructeur car aucune action particulière n’est nécessaire à l’initialisation du contrat. Le constructeur, comme dans beaucoup d’autres langages, est une fonction (Cf. paragraphe suivant), qui possède le même nom que celui défini après le mot clé « contract ».

La première fonction soumettreSatisfaction permet à l’appelant de soumettre une valeur de satisfaction entre 0 et 100 qui sera enregistrée sur la blockchain et servira au calcul de la moyenne de satisfaction. Nous pouvons remarquer le mot clé throw qui est (au moment ou j’écris cet article), la seule manière pour faire remonter une erreur à l’émetteur. Appelé, il engendre le remboursement des frais associés à la transaction et annule toutes les actions sur les données. Dans notre cas, si l’appelant fournit un paramètre erroné ou a déjà soumis son avis, alors il ne paiera pas les frais.

La seconde et dernière fonction satisfactionProduit permet de récupérer le taux de satisfaction ainsi que le mot clé associé. Le taux est donc calculé par la machine virtuelle Ethereum. Il est intéressant de remarquer qu’une fonction peut retourner plusieurs paramètres.

Le contrat étant à présent défini, il est temps de le partager avec l’un de nos nœuds. Pour cela commençons par ajouter notre code dans la console Javascript de l’un de nos nœuds. Nous définissons la variable « source » contenant le code source sous la forme d’une string. Attention, le code doit être sur une seule ligne.

> sources = 'contract SatisfactionClient {bytes32…'

Ensuite nous devons compiler le code Solidity avec un compilateur adapté. J’ai installé solc en tout début d’article. Je peux donc utiliser les outils prévus par le client et stocker le tout dans une autre variable « compiled ».

> compiled = eth.compile.solidity(sources)
I0524 11:25:33.542102 common/compiler/solidity.go:114] solc, the solidity compiler commandline interface
 Version: 0.3.2-0/RelWithDebInfo-Linux/g++/Interpreter
 
 path: /usr/bin/solc
 {
   SatisfactionClient: {
     code: "0x60[..]256",
     info: {
       abiDefinition: [{...}, {...}],
       compilerOptions: "--bin --abi --userdoc --devdoc --add-std --optimize -o /tmp/solc730488666",
       compilerVersion: "0.3.2",
       developerDoc: {
         methods: {}
       },
       language: "Solidity",
       languageVersion: "0.3.2",
       source: "contract SatisfactionClient{ [...] }",
       userDoc: {
         methods: {}
       }
     }
   }
 }

Nous pouvons voir que l’objet Javascript retourné est composé entre autres du code hexadécimal, d’informations sur le compilateur et des sources.

A présent, nous pouvons instancier notre contrat de cette manière :

monContrat = eth.contract(compiled.SatisfactionClient.info.abiDefinition).new({from:eth.accounts[0], data: compiled.SatisfactionClient.code,gas:250000})

Cette opération est découpée en 2 parties. Dans un premier temps on créé un objet javascript contrat (eth.contract), comportant toutes les spécificités d’un objet contrat mais également l’interface du contrat que nous avons écrit (abiDefinition). L’interface correspond à l’ensemble des éléments pouvant êtres appelés depuis l’extérieur du contrat (les 2 fonctions expliquées précédemment). Dans un second temps, il crée une nouvelle instance du contrat (mot clé new, similaire au Java). Nous passons en paramètre le compte permettant d’envoyer le contrat sur la blockchain (from), le code compilé du contrat (data) ainsi que le montant de gas à fournir pour faire fonctionner le contrat (gas). La valeur de gas donnée est relative à celle estimée par la fonction eth.estimateGas(). Le gas est une notion ajoutée à Ethereum pour éviter l’exécution de boucles infinies dans la machine virtuelle. Si une personne souhaite rendre les nœuds du réseau indisponible pendant un long moment, il doit en payer le prix fort. Car une unité de gas possède un prix (variable) en ether, que le compte doit payer à l’appel d’une fonction d’un contrat.

Pour pouvoir envoyer la transaction, et donc exécuter la commande ci-dessus, il ne faut pas oublier de débloquer le compte spécifié par le paramètre « from » grâce à la commande « personal.unlockAccount() ».

Comme il s’agit d’une transaction, pour appeler le contrat, il faut l’ajouter à la blockchain et donc le miner avec miner.start() et que les autres nœuds se synchronisent (Cf. 9).

Pour appeler le contrat depuis un autre nœud, il faut récupérer l’instance, grâce à 2 informations : l’interface (monContrat.abi) et l’adresse du contrat (monContrat.address), disponibles sur le nœud qui l’a créé.

abi = [{constant: false, inputs: [{name: "s",  ...

eth.contract(abi).at("0xb53fba34ddcfdbd642d52ac9dcc0ab8bce36a79e")

Nous pouvons à présent appeler l’une des fonctions disponibles, comme par exemple, soumettre notre avis à partir de n’importe quel noeud.

monContrat.soumettreSatisfaction.sendTransaction(70,{from:eth.accounts[0]})

Le premier paramètre de sendTransaction est la valeur du paramètre de soumettreSatisfaction, et le second est l’objet javascript qui comporte seulement l’adresse du compte avec lequel on souhaite nous exprimer. Ensuite, comme à chaque fois, il faut miner la transaction et la synchroniser sur nos autres nœuds pour pouvoir vérifier qu’elle s’est bien propagée.

Si maintenant nous appelons dans un autre nœud (qui n’a pas encore soumis son avis), qui a synchronisé sa blockchain et qui possède l’objet instance du contrat :

> monContrat.satisfactionProduit.call()

[70, "MOYEN"]

Nous faisons appel à notre contrat via un fonction différente cette fois ci : call. Elle permet de ne pas créer une transaction et d’exécuter la fonction sur notre blockchain locale sans payer de frais. À la fin de la fonction, les données qu’elle modifie seront remises à leur état avant exécution, comme si l’appel n’avait jamais eu lieu. Elle nous retourne un tableau contenant les 2 variables de retour. Nous pouvons voir que la satisfaction est moyenne. Répétons l’opération sur un autre nœud et revérifions.

> monContrat.soumettreSatisfaction.sendTransaction(10,{from:eth.coinbase})       

> monContrat.satisfactionProduit.call()
 [40, "MOYEN"]

Félicitations, vous êtes à présent prêt pour développer et déployer vos propres contrats sur la blockchain Ethereum !

2 commentaires
  1. DAVAL_Loic dit :

    Mon commentaire ne semble pas fonctionner, peut-être à cause du code que j’avais inclus dedans.
    J’ai posté mon commentaire là : http://pastebin.com/b9BHxfmv
    Mon commentaire concerne la phase de compilation et l’apparition d’un « stdin » qui fait foirer les commandes suivantes.

    Répondre
  2. NICOLAS Guillaume dit :

    Ce problème vient du fait d’une monté de version du compilateur solc (https://github.com/ethereum/solidity/releases/tag/v0.4.9).
    Le compilateur utilisé lors de la rédaction de cet article était antérieur à la version v0.4.9.
    Depuis v0.4.9, ils ont préfixés les objets en sortie du compilateur par leur path dans le répertoire de données.
    « To disambiguate contracts and libraries of the same name in different files, everything is now prefixed by « filename: ». This applies to the compiler output, the linker input and other things. »

    Pour résoudre ce problème, je préconise de compiler le contrat via la commande solc, puis d’injecter le code compilé dans un fichier javascript, et de le charger dans le client Geth (loadScript(‘monfichierjs.js’).

    Répondre

votre commentaire

Se joindre à la discussion ?
Vous êtes libre de contribuer !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Inscription newsletter

Ne manquez plus nos derniers articles !