Il y a un peu plus d’un an à présent, nous avons développé une solution d’horodatage basée sur une blockchain. Elle permet de prouver l’antériorité d’un contenu (une idée ou une création par exemple) via l’utilisation d’une blockchain publique Ethereum directement depuis son navigateur. Donc sans organe central de contrôle. Il s’agit d’un cas d’usage simple mais parfaitement applicable aux besoins métiers des applications de nos clients : sceller un contenu et ses métadonnées de manière sûre et pérenne. Le code source est publié sous la licence Apache 2.
Pour faciliter la lecture, nous vous proposons une série de cinq articles qui présentent trois points de vue complémentaires de l’outil :
- Point de vue conceptuel : 🎨 Comprendre les notions de base d’une blockchain publique
- Point de vue utilisateur : 🕺 Interagir avec l’application décentralisée
- Point de vue développeur : 🏠Configurer son environnement de développement
- Point de vue développeur : 🤝 Comprendre le développement du smart contract
- Point de vue développeur : 💻 Comprendre le développement du client
Ce quatrième article fournit des explications sur l’écriture des smart contracts et sur la manière de les déployer dans une blockchain de développements ou une blockchain publique.
Le code source des smarts contracts est dans le sous projet smart-contracts. Les smart contracts sont écrits dans le langage dédié solidity et compilés pour être exécutés dans une machine virtuelle Ethereum (EVM). Nous vous invitons à lire la documentation de référence du langage : https://solidity.readthedocs.io.
Commençons par expliciter le code source du smart contract métier.
# Smart contract métier AntProver
Il s’agit du smart contract principal qui réalise la preuve d’antériorité. Il permet de stocker les informations de manière structurée et de les requêter via une sorte d’API de services. Pour faciliter la lecture de cet article, nous explicitons le coeur de ce smart contract : une fonction d’écriture des informations et une fonction de consultation.
Le smart contract est défini dans le fichier smart-contracts/contracts/AntProver.sol.
# Squelette du contrat
Afin d’éviter un changement bloquant potentiel d’une version future (relativement au moment du développement), nous bornons les versions acceptées du compilateur. La directive suivante indique que le compilateur doit être en version 0.5.X :
1 |
pragma solidity ^0.5.0; |
Nous commençons par définir le contrat AntProver. Ici, nous n’avons pas de comportement particulier à la création :
1 2 3 4 5 |
contract AntProver { Â constructor() public {} } |
# Structure et variables
Afin de porter les données d’un enregistrement, nous définissons la structure de données Record. Les types que nous utilisons ici sont tous simples. Nous avons des entiers non signés pour l’horodatage et le numéro de bloc, une chaîne de caractères pour le commentaire et une adresse pour l’émetteur :
1 2 3 4 5 6 7 8 9 10 11 |
struct Record { Â uint mineTime; Â Â uint blockNumber; Â Â address sender; Â Â string comment; } |
Nous créons la variable hashesMetadata dans laquelle nous faisons correspondre un Record à chaque empreinte de fichier enregistrée. Dans la mesure où nous souhaitons que cette variable ne soit visible que par le contrat, nous utilisons l’accesseur private :
1 |
mapping (bytes32 => Record) private hashesMetadata; |
# Fonction d’écriture
Pour référencer un contenu à partir de son empreinte hash et du commentaire associé comment, nous définissons la fonction d’écriture addDocHash, accessible depuis l’extérieur, grâce à l’accesseur public :
1 2 3 4 5 6 7 |
function addDocHash (bytes32 hash, string memory comment) public { Â Â Record memory newRecord = Record(now, block.number, msg.sender, comment); Â Â hashesMetadata[hash] = newRecord; } |
Nous aurions pu omettre le mot clé memory pour le paramètre comment, qui est le mode de passage d’arguments activé par défaut. Avec ce mode, les arguments sont passés par valeur, contrairement au passage par référence activé avec le mot clé storage.
Notons que pour créer l’instance de Record, nous utilisons des informations récupérées à partir des paramètres et de propriétés :
- Les informations du bloc sont disponibles dans la propriété block.
- now est un alias de block.timestamp. Il s’agit de l’horodatage du bloc courant.
- bloc.number permet de connaître le numéro de bloc.
- Les informations du message sont disponibles dans la propriété msg
- msg.sender permet de récupérer l’adresse de l’émetteur
D’autres propriétés sont disponibles.
L’enregistrement fraîchement créé newRecord est stocké en face de son hash dans dans variable d’association hashesMetadata.
# Fonction de lecture
La recherche d’un contenu est rĂ©alisĂ©e avec la fonction de lecture findDocHash. Elle permet de retrouver les informations Ă©ventuellement associĂ©es Ă une empreinte. On la dĂ©clare comme Ă©tant une view function de manière Ă s’assurer qu’elle ne modifiera pas l’état : variable, production d’Ă©vĂ©nement, crĂ©ation d’un contrat, etc. De ce fait, un appel Ă cette fonction ne gĂ©nĂ©rera aucun frais.
La fonction findDocHash prend une empreinte en paramètre et retourne un quadruplet correspondant aux valeurs de l’instance de Record associée. Grâce à la variable d’association, l’accès est direct :
1 2 3 4 5 6 7 |
function findDocHash (bytes32 hash) public view returns(uint, uint, address, string memory) { Â Â Record memory rec = hashesMetadata[hash]; Â Â return (rec.mineTime, rec.blockNumber, rec.sender, rec.comment); } |
# Prérequis d’un smart contract
Pour sécuriser le référencement d’un contenu, on souhaite s’assurer que l’empreinte du fichier n’existe pas déjà . Au début de la fonction addDocHash, on ajoute un prérequis de manière à provoquer une erreur en cas de besoin :
1 |
require(!exists(hash), 'hash exists'); |
Puis on crée la fonction exists qui teste si l’empreinte a déjà été enregistrée :
1 2 3 4 5 |
function exists (bytes32 hash) public view returns(bool) { Â Â return hashesMetadata[hash].blockNumber!=0; } |
Lorsqu’une empreinte n’existe pas dans hashesMetadata, la propriété blockNumber a la valeur 0. En effet, il n’existe pas de valeur nulle dans le langage solidity et toute variable a la valeur par défaut.
# Gestion d’événements
Un smart contract peut générer des événements que des applications peuvent écouter via une interface RPC (Remote Procedure Call). Dans le cas présent, nous allons ajouter le type d’événement HashAdded :
1 2 3 4 5 6 7 8 9 |
event HashAdded( Â Â address indexed from, Â Â bytes32 indexed hash, Â Â string comment ); |
Après l’enregistrement d’une empreinte à la fin de addDocHash, on émet une occurrence de l’événement avec l’appel suivant :
1 |
emit HashAdded(msg.sender, hash, comment); |
Nous verrons dans l’article suivant que nous pouvons écouter ce type d’événement depuis le client. Dans l’exemple présent, le frontal utilisateur affichera l’événement lorsque la transaction aura été validée de manière à ce que l’utilisateur soit en mesure de récupérer le certificat final au format pdf à conserver avec le document initial.
# Gestion des migrations / déploiement
Maintenant que nous avons expliqué le fonctionnement interne du smart contract métier, revenons à l’initialisation du sous projet. Ceci a été fait avec l’outil truffle et sa commande unbox. Le projet a été initialisé avec l’arborescence suivante :
- contracts : source des smart contracts solidity
- migrations : scripts de déploiement
- test : fichiers de tests
- truffle-config.js : configuration du projet
Pour la gestion des migrations, les fichiers suivants sont utilisés :
- smart-contracts/contracts/Migrations.sol
- smart-contracts/migrations/1_initial_migration.js
En complément de ceux-ci et du smart contract AntProver.sol, nous avons écrit le script migrations/1567466298_ant_prover.js qui permet de déployer le smart contract AntProver :
1 2 3 4 |
var AntProver = artifacts.require("AntProver") module.exports = function (deployer) { deployer.deploy(AntProver) } |
La configuration truffle-config.js suivante permet de déployer dans la blockchain Ganache mise en place telle que décrite dans l’article précédent :
1 2 3 4 5 6 7 8 |
module.exports = { networks: { development: { host: "127.0.0.1", port: 7545, network_id: "5777" } } |
Pour rappel, nous avons déployé dans la blockchain de développement avec la commande :
1 |
truffle migrate --network development |
# DĂ©ploiement dans une blockchain publique
Pour déployer le smart contract dans une blockchain publique, nous allons utiliser le wallet Infura. Dans la suite, nous déploierons le smart contract sur l’instance testnet (réseau de tests) Ropsten.
Infura est une API qui permet d’accĂ©der au rĂ©seau Ethereum. Avant de pouvoir dĂ©ployer, il faut se crĂ©er un compte, un projet puis rĂ©cupĂ©rer le PROJECT ID.
La définition des variables est nécessaire pour le déploiement final (exemple) :
1 2 |
export METAMASK_MNEMONIC="Le mnemonic Metamask (Paramètres, Sécurité et confidentialité, Révéler les mots Seed)" export INFURA_PROJECT_ID="Le PROJECT ID Infura" |
En effet, nous les exploitons dans la configuration truffle-config.js qui est complétée avec la configuration ropsten. Pour celle-ci, nous profitons du provider web3 truffle-hdwallet-provider (renommé @truffle/hdwallet-provider depuis) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
var HDWalletProvider = require("truffle-hdwallet-provider"); // Metamask mnemonic and project ID du projet infura "content-registry" const METAMASK_MNEMONIC = process.env.METAMASK_MNEMONIC; const INFURA_PROJECT_ID = process.env.INFURA_PROJECT_ID; module.exports = { networks: { development: { host: "127.0.0.1", port: 7545, network_id: "5777" }, ropsten: { provider: function () { return new HDWalletProvider(METAMASK_MNEMONIC, "https://ropsten.infura.io/v3/" + INFURA_PROJECT_ID) }, network_id: 3, gas: 4000000 // make sure this gas allocation isn't over 4M, which is the max } } } |
Puis nous précisons le réseau “ropsten” plutôt que “development” lors de la migration :
1 |
truffle migrate --network ropsten |
# Métadonnées du smart contract
Lorsque le smart contract écrit en solidity est compilé, un fichier de métadonnées est généré. Par exemple, nous avons versionné le fichier smart-contracts/build/contracts/AntProver.json produit car il présente un intérêt particulier.
Ce fichier est riche en informations. Par exemple, nous retrouvons la version du compilateur utilisée (ex : 0.5.8+commit.23d335f2), les sources ou encore le contrat ABI (Application Binary Interface). Ce dernier définit l’interface pour une utilisation en dehors de la blockchain ou au sein de la blockchain dans une communication entre smart contracts.
Nous retrouvons également les informations sur le contrat déployé dans les réseaux dans “networks”. Par exemple on retrouve l’adresse de déploiement du smart contract 0xA22fe2812085cf1b755796F5dA60EdFDc6E8dEe6 dans le réseau Ropsten qui porte l’identifiant 3 :
1 2 3 4 5 6 7 8 |
"networks": { "3": { //... "address": "0xA22fe2812085cf1b755796F5dA60EdFDc6E8dEe6", "transactionHash": "0x37ab6efb0c9bd3cc1e74c105c3fa023a8b74e5109579193b5d99088d388969fe" }, //… } |
# Pour continuer
Dans cet article, nous avons couvert le développement côté blockchain du projet. Nous avons commencé par expliciter la manière dont nous avons conçu le smart contract en partant du squelette et en l’enrichissant avec la définition des structures de données, des données et des fonctions d’écriture et de lecture. Puis nous avons vu comment l’enrichir en le sécurisant et ajoutant une gestion d’événements.
Enfin, nous avons expliqué comment déployer le smart contract dans une blockchain de développements puis dans une blockchain publique.
Le prochain article et dernier de la série sera dédié au développement du client web qui permet d’interagir avec le smart contract écrit et déployé.
20 novembre 2020 at 18 h 39 min
Je vous remets l’ensemble des liens de la sĂ©rie :
• 🎨 Comprendre les notions de base d’une blockchain publique
 ↳ https://blog.atolcd.com/solution-blockchain-horodatage-1-concepts/
• 🕺 Interagir avec l’application décentralisée
 ↳ https://blog.atolcd.com/solution-blockchain-horodatage-2-utilisation/
• 🏠Configurer son environnement de développement
 ↳ https://blog.atolcd.com/solution-blockchain-horodatage-3-dev-environnement/
• 🤝 Comprendre le développement du smart contract
 ↳ https://blog.atolcd.com/solution-blockchain-horodatage-4-dev-smart-contract/
• 💻 Comprendre le développement du client
 ↳ https://blog.atolcd.com/solution-blockchain-horodatage-5-dev-client-web/
• 🚄 Synthèse globale
 ↳ https://blog.atolcd.com/solution-blockchain-horodatage-5-dev-client-web/#synthese-de-la-serie-darticles
7 avril 2022 at 20 h 04 min
Merci pour votre article, c’est très intĂ©ressant et relativement accessible đź‘Ť